package main

import (
	"fmt"

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

func main() {
	inp := h.StdinToCoordMap()
	part1(inp)
	fmt.Println("")
	part2(inp)
}

func part1(inp h.CoordByteMap) {
	fmt.Println("# Part 1")
	var total int
	trackedPlots := make(map[h.Coordinate]bool)
	for y := inp.TLY; y <= inp.BRY; y++ {
		for x := inp.TLX; x <= inp.BRX; x++ {
			c := h.Coordinate{X: x, Y: y}
			if trackedPlots[c] {
				continue
			}
			plant := inp.Get(c)
			region := findRegion(inp, c, make(map[h.Coordinate]bool))
			area := len(region)
			var perimeter int
			for k := range region {
				trackedPlots[k] = true
				for _, chk := range []h.Coordinate{k.North(), k.East(), k.South(), k.West()} {
					if inp.Get(chk) != plant {
						perimeter++
					}
				}
			}
			total = total + (area * perimeter)
		}
	}
	fmt.Println("Total Price:", total)
}

func part2(inp h.CoordByteMap) {
	fmt.Println("# Part 2")
	var total int
	trackedPlots := make(map[h.Coordinate]bool)
	for y := inp.TLY; y <= inp.BRY; y++ {
		for x := inp.TLX; x <= inp.BRX; x++ {
			c := h.Coordinate{X: x, Y: y}
			if trackedPlots[c] {
				continue
			}
			region := findRegion(inp, c, make(map[h.Coordinate]bool))
			area := len(region)
			for k := range region {
				trackedPlots[k] = true
			}
			sides := findSides(region)
			total = total + (area * sides)
		}
	}
	fmt.Println("Total Price:", total)
}

func findRegion(inp h.CoordByteMap, start h.Coordinate, checked map[h.Coordinate]bool) map[h.Coordinate]bool {
	ret := make(map[h.Coordinate]bool)
	bt := inp.Get(start)
	ret[start] = true
	for _, chk := range []h.Coordinate{start.North(), start.East(), start.South(), start.West()} {
		if checked[chk] {
			continue
		}
		if inp.Get(chk) == bt {
			checked[chk] = true
			ret[chk] = true
			for k := range findRegion(inp, chk, checked) {
				ret[k] = true
			}
		}
	}
	return ret
}

var (
	N  = h.Coordinate{X: 0, Y: -1}
	NE = h.Coordinate{X: 1, Y: -1}
	E  = h.Coordinate{X: 1, Y: 0}
	SE = h.Coordinate{X: 1, Y: 1}
	S  = h.Coordinate{X: 0, Y: 1}
	SW = h.Coordinate{X: -1, Y: 1}
	W  = h.Coordinate{X: -1, Y: 0}
	NW = h.Coordinate{X: -1, Y: -1}
)

func findSides(region map[h.Coordinate]bool) int {
	var sides int
	for chk := range region {
		sides = sides + checkForExteriorCorner(chk, region)
		sides = sides + checkForInteriorCorner(chk, region)
	}
	return sides
}

// checkForExteriorCorner returns how many exterior corners 'chk' is a part of
func checkForExteriorCorner(chk h.Coordinate, region map[h.Coordinate]bool) int {
	var ret int
	// Check orthoganal neighbor pairs (N/E, E/S, S/W, W/N) for an exterior corner
	if checkDiff(chk, N, region) {
		if checkDiff(chk, E, region) {
			ret++
		}
		if checkDiff(chk, W, region) {
			ret++
		}
	}
	if checkDiff(chk, S, region) {
		if checkDiff(chk, E, region) {
			ret++
		}
		if checkDiff(chk, W, region) {
			ret++
		}
	}
	return ret
}

// checkForInteriorCorner returns how many interior corners 'chk' is a part of
func checkForInteriorCorner(chk h.Coordinate, region map[h.Coordinate]bool) int {
	var ret int
	// Check for interior corner (N/E != NE, E/S != SE, S/W != SW, W/N != NW)
	if checkDiff(chk, NE, region) {
		if checkSame(chk, N, region) && checkSame(chk, E, region) {
			ret++
		}
	}
	if checkDiff(chk, SE, region) {
		if checkSame(chk, S, region) && checkSame(chk, E, region) {
			ret++
		}
	}
	if checkDiff(chk, SW, region) {
		if checkSame(chk, S, region) && checkSame(chk, W, region) {
			ret++
		}
	}
	if checkDiff(chk, NW, region) {
		if checkSame(chk, N, region) && checkSame(chk, W, region) {
			ret++
		}
	}
	return ret
}

func checkDiff(chk, dir h.Coordinate, region map[h.Coordinate]bool) bool {
	return !checkSame(chk, dir, region)
}

func checkSame(chk, dir h.Coordinate, region map[h.Coordinate]bool) bool {
	_, ok := region[chk.Add(dir)]
	return ok
}