package main

import (
	"fmt"
	"math"
	"sort"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

func main() {
	inp := h.StdinToStringSlice()
	part1(inp)
	part2(inp)
}

func part1(inp []string) {
	field := plotElves(inp)
	for i := 0; i < 10; i++ {
		proposals := buildProposals(field, i)
		for k, v := range proposals {
			delete(field, v)
			field[k] = true
		}
	}
	fmt.Println("# Part 1")
	fmt.Println("Empty Tiles:", countEmpties(field))
}
func part2(inp []string) {
	field := plotElves(inp)
	var lastHash string
	for i := 0; i < 100000000; i++ {
		proposals := buildProposals(field, i)
		for k, v := range proposals {
			delete(field, v)
			field[k] = true
		}
		currHash := fieldHash(field)
		if currHash == lastHash {
			fmt.Println("# Part 2")
			fmt.Println("Stable on round", i+1)
			return
		}
		lastHash = currHash
	}
	fmt.Println("# Part 2")
	fmt.Println("No solution found.")
}
func fieldHash(field map[h.Coordinate]bool) string {
	var sorted []h.Coordinate
	for k := range field {
		sorted = append(sorted, k)
	}
	sort.Slice(sorted, func(i, j int) bool {
		if sorted[i].X == sorted[j].X {
			return sorted[i].Y > sorted[j].Y
		}
		return sorted[i].X > sorted[j].X
	})
	return fmt.Sprintf("%v", sorted)
}

func countEmpties(field map[h.Coordinate]bool) int {
	maxX, minX := math.MinInt, math.MaxInt
	maxY, minY := math.MinInt, math.MaxInt
	for k := range field {
		maxX = h.Max(maxX, k.X)
		minX = h.Min(minX, k.X)
		maxY = h.Max(maxY, k.Y)
		minY = h.Min(minY, k.Y)
	}
	var ret int
	for y := minY; y <= maxY; y++ {
		for x := minX; x <= maxX; x++ {
			if _, ok := field[h.Coordinate{X: x, Y: y}]; !ok {
				ret++
			}
		}
	}
	return ret
}
func printField(field map[h.Coordinate]bool) {
	maxX, minX := math.MinInt, 0
	maxY, minY := math.MinInt, 0
	for k := range field {
		maxX = h.Max(maxX, k.X)
		minX = h.Min(minX, k.X)
		maxY = h.Max(maxY, k.Y)
		minY = h.Min(minY, k.Y)
	}
	maxX = maxX + 2
	maxY = maxY + 2
	for y := minY; y <= maxY; y++ {
		for x := minX; x <= maxX; x++ {
			if _, ok := field[h.Coordinate{X: x, Y: y}]; ok {
				fmt.Print("#")
			} else {
				fmt.Print(".")
			}
		}
		fmt.Println()
	}
}
func plotElves(inp []string) map[h.Coordinate]bool {
	field := make(map[h.Coordinate]bool)
	for y := range inp {
		for x := range inp[y] {
			if inp[y][x] == '#' {
				field[h.Coordinate{X: x, Y: y}] = true
			}
		}
	}
	return field
}
func buildProposals(field map[h.Coordinate]bool, round int) map[h.Coordinate]h.Coordinate {
	props := make(map[h.Coordinate]h.Coordinate)
	var errProps []h.Coordinate
	for k := range field {
		var neighbors []h.Coordinate
		for _, n := range k.GetAllNeighbors() {
			if _, ok := field[n]; ok {
				neighbors = append(neighbors, n)
			}
		}
		if len(neighbors) == 0 {
			continue
		}
		var hasNorth, hasEast, hasSouth, hasWest bool
		for _, n := range neighbors {
			if n.Y < k.Y {
				hasNorth = true
			}
			if n.Y > k.Y {
				hasSouth = true
			}
			if n.X < k.X {
				hasWest = true
			}
			if n.X > k.X {
				hasEast = true
			}
		}
		addProp := func(c h.Coordinate) {
			if _, ok := props[c]; ok {
				errProps = append(errProps, c)
			} else {
				props[c] = k
			}
		}
		testNorth := func(c h.Coordinate) bool {
			if !hasNorth {
				addProp(k.North())
				return true
			}
			return false
		}
		testSouth := func(c h.Coordinate) bool {
			if !hasSouth {
				addProp(k.South())
				return true
			}
			return false
		}
		testWest := func(c h.Coordinate) bool {
			if !hasWest {
				addProp(k.West())
				return true
			}
			return false
		}
		testEast := func(c h.Coordinate) bool {
			if !hasEast {
				addProp(k.East())
				return true
			}
			return false
		}
		findProposal := func(c h.Coordinate, tests ...func(h.Coordinate) bool) {
			for i := range tests {
				if tests[i](c) == true {
					return
				}
			}
		}
		switch round % 4 {
		case 0: // North First
			findProposal(k, testNorth, testSouth, testWest, testEast)
		case 1: // South First
			findProposal(k, testSouth, testWest, testEast, testNorth)
		case 2: // West First
			findProposal(k, testWest, testEast, testNorth, testSouth)
		case 3: // East First
			findProposal(k, testEast, testNorth, testSouth, testWest)
		}
	}
	// Now remove all conflicts
	for i := range errProps {
		delete(props, errProps[i])
	}
	return props
}