package main

import (
	"fmt"
	"math"
	"strings"

	"github.com/alexchao26/advent-of-code-go/2019/day10/part2/trig"
	"github.com/alexchao26/advent-of-code-go/util"
)

/*
	Overall approach:
	- need to make a map of some kind
		make it a slice where each element is a struct
			- each struct will contain:
				x
				y
				degOffVert float64 (degrees 0 -> 360)
				distance float64
	- iterate through the slice of structs
		- store the index of the minimum distance

		- remove it from the slice of structs
			- if this is the 200th iteration, store the x and y to return at the end
		- NOTE there are a limited number of asteroids because of the fixed size of the input, so having a O(200 * n) where n is the number of Asteroids, is not a _terrible_ time complexity
*/

// Asteroid data
type Asteroid struct {
	x          int
	y          int
	degOffVert float64
	distance   float64
}

func main() {
	// read input.txt file, split it into a slice of lines
	input := util.ReadFile("../input.txt")
	stringSlice := strings.Split(input, "\n")

	// generate 2D grid of each character from stringSlice
	inputGrid := make([][]string, len(stringSlice))
	for i, str := range stringSlice {
		inputGrid[i] = strings.Split(str, "")
	}

	allAsteroids := makeAsteroidsSlice(inputGrid)

	// need to start this just to the left of zero to get that as the first input
	lastDegreeUsed := 359.999999
	// to store the last vaporized asteroid to output its coordinates
	var lastAsteroid Asteroid

	for i := 0; i < 200; i++ {
		// iterate through all of allAsteroids and find the next closest degree
		var indexOfAsteroidToDelete int // will be updated by iMin

		// reset the minDegDiff and minDist for each run of the outer loop
		minDegDiff, minDist := math.Inf(1), math.Inf(1) // I can use inf now b/c I'm using float64's!

		// iterate over the entire slice of asteroids
		for iMin, eAsteroid := range allAsteroids {
			// calculate the degrees difference
			degDiff := eAsteroid.degOffVert - lastDegreeUsed
			if degDiff <= 0 { // account for the diff passing over zero
				degDiff += 360
			}
			// if this asteroid has a smaller degrees difference, update all min values
			if degDiff < minDegDiff {
				minDist = eAsteroid.distance
				minDegDiff = degDiff
				indexOfAsteroidToDelete = iMin
			} else if degDiff == minDegDiff && minDist > eAsteroid.distance {
				// OR if the degDiff is the same but the distance to 13,11 is smaller
				// update just the minDistance and index for this asteroid
				minDist = eAsteroid.distance
				indexOfAsteroidToDelete = iMin
			}
		}

		// remove the element at index indexOfAst
		// this doesn't maintain order, but I don't care about order right now
		lastAsteroid = allAsteroids[indexOfAsteroidToDelete]

		// swap last element to indexToDelete
		allAsteroids[indexOfAsteroidToDelete] = allAsteroids[len(allAsteroids)-1]
		// re-size slice to effectively pop last element off of slice
		allAsteroids = allAsteroids[:len(allAsteroids)-1]

		// update last deg used by adding the diff to it
		lastDegreeUsed += minDegDiff
		if lastDegreeUsed >= 360 {
			// if we pass over 360, subtract 360
			lastDegreeUsed -= 360
		}
	}

	// print the last used asteroid
	fmt.Println("Last asteroid", lastAsteroid)
	// print the github.com/alexchao26/advent-of-code-go-formatted answer
	fmt.Println("Advent of code answer: ", lastAsteroid.y*100+lastAsteroid.x)
}

func makeAsteroidsSlice(grid [][]string) []Asteroid {
	result := make([]Asteroid, 0)

	// iterate through the entire grid
	for rowIndex, rowSlice := range grid {
		for colIndex, element := range rowSlice {
			// if an asteroid is found...
			if element == "#" && !(rowIndex == 13 && colIndex == 11) {
				// calculate the degree and dist
				// degree, dist := trig.TangentAndDistance(13, 11, rowIndex, colIndex)
				// create an instance of an Asteroid struct and append it to the result slice
				ast := Asteroid{
					x:          rowIndex,
					y:          colIndex,
					degOffVert: trig.AngleOffVertical(13, 11, rowIndex, colIndex),
					distance:   trig.Distance(13, 11, rowIndex, colIndex),
				}
				result = append(result, ast)
			}
		}
	}

	return result
}
