package main

import (
	"flag"
	"fmt"
	"math"
	"strings"

	"github.com/alexchao26/advent-of-code-go/cast"
	"github.com/alexchao26/advent-of-code-go/mathy"
	"github.com/alexchao26/advent-of-code-go/util"
)

func main() {
	var part int
	flag.IntVar(&part, "part", 1, "part 1 or 2")
	flag.Parse()
	fmt.Println("Running part", part)

	if part == 1 {
		ans := part1(util.ReadFile("./input.txt"))
		fmt.Println("Output:", ans)
	} else {
		ans := part2(util.ReadFile("./input.txt"), 10000)
		fmt.Println("Output:", ans)
	}
}

func part1(input string) int {
	coords := parseInputCoords(input)

	boundLeft, boundRight, boundTop, boundBottom := getBounds(coords)

	// maps coordinates to the number (count) of cells that they are closest too
	coordsToMinDistanceCounts := map[[2]int]int{}

	for r := boundTop; r <= boundBottom; r++ {
		for c := boundLeft; c <= boundRight; c++ {
			bestManhattan := math.MaxInt16
			var coordsToBest [2]int
			distCounts := map[int]int{} // dedeupe equidistant cells
			for _, coord := range coords {
				man := mathy.ManhattanDistance(r, c, coord[0], coord[1])
				if man <= bestManhattan {
					bestManhattan = man
					coordsToBest = coord
					distCounts[bestManhattan]++
				}
			}

			// do not increment anything if there were two equidistant coords
			if distCounts[bestManhattan] == 1 {
				coordsToMinDistanceCounts[coordsToBest]++
			}
		}
	}

	var largest int
	for coord, val := range coordsToMinDistanceCounts {
		// exclude edges
		if coord[0] == boundTop || coord[0] == boundBottom {
			continue
		}
		if coord[1] == boundLeft || coord[1] == boundRight {
			continue
		}

		if val > largest {
			largest = val
		}
	}
	return largest
}

func part2(input string, dist int) int {
	coords := parseInputCoords(input)

	boundLeft, boundRight, boundTop, boundBottom := getBounds(coords)

	coordsToTotalDist := map[[2]int]int{}
	var area int
	for r := boundTop; r <= boundBottom; r++ {
		for c := boundLeft; c <= boundRight; c++ {
			point := [2]int{r, c}
			for _, coord := range coords {
				coordsToTotalDist[point] += mathy.ManhattanDistance(point[0], point[1], coord[0], coord[1])
			}
			if coordsToTotalDist[point] < dist {
				area++
			}
		}
	}

	return area
}

func parseInputCoords(input string) [][2]int {
	lines := strings.Split(input, "\n")
	coords := [][2]int{} // [row, col]
	for _, l := range lines {
		c := strings.Split(l, ", ")
		if len(c) == 2 {
			coords = append(coords, [2]int{
				cast.ToInt(c[0]),
				cast.ToInt(c[1]),
			})
		}
	}
	return coords
}

func getBounds(coords [][2]int) (left int, right int, top int, bottom int) {
	var (
		boundLeft   = math.MaxInt16
		boundRight  = -math.MaxInt16
		boundTop    = math.MaxInt16
		boundBottom = -math.MaxInt16
	)
	for _, c := range coords {
		if c[0] < boundTop {
			boundTop = c[0]
		}
		if c[0] > boundBottom {
			boundBottom = c[0]
		}
		if c[1] < boundLeft {
			boundLeft = c[1]
		}
		if c[1] > boundRight {
			boundRight = c[1]
		}
	}
	return boundLeft, boundRight, boundTop, boundBottom
}
