package day08

import (
	"adventofcode2024/utils/grid2d"
	"adventofcode2024/utils/inputs"
	"fmt"
)

type loc struct {
	x int
	y int
}

type pt struct {
	display   rune
	antinodes int
	paired    map[loc]pt
	visited   map[rune]bool
}

func Part1(input string) int {
	grid := inputs.ToGrid2D(input, "\n", "", pt{display: '.'}, func(c string) pt { return pt{display: rune(c[0]), paired: make(map[loc]pt), visited: make(map[rune]bool)} })
	for x1 := 0; x1 < grid.SizeX(); x1++ {
		for y1 := 0; y1 < grid.SizeY(); y1++ {
			pt1 := grid.Get(x1, y1)
			if pt1.display != '.' {
				for x2 := 0; x2 < grid.SizeX(); x2++ {
					for y2 := 0; y2 < grid.SizeY(); y2++ {
						pt2 := grid.Get(x2, y2)
						if x1 != x2 && y1 != y2 && pt1.display == pt2.display && !isPaired(x1, y1, pt1, pt2) {
							pt1.paired[loc{x: x2, y: y2}] = pt2
							pt2.paired[loc{x: x1, y: y1}] = pt1
						}
					}
				}
			}
		}
	}
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			pt1 := grid.Get(x, y)
			if pt1.display != '.' {
				for ploc := range pt1.paired {
					addAntiNodes(x, y, ploc.x, ploc.y, grid)
				}
			}
		}
	}
	antinodes := 0
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			pt := grid.Get(x, y)
			if pt.antinodes > 0 {
				antinodes++
			}
		}
	}
	return antinodes
}

func Part2(input string) int {
	grid := inputs.ToGrid2D(input, "\n", "", pt{display: '.'}, func(c string) pt { return pt{display: rune(c[0]), paired: make(map[loc]pt), visited: make(map[rune]bool)}})
	for x1 := 0; x1 < grid.SizeX(); x1++ {
		for y1 := 0; y1 < grid.SizeY(); y1++ {
			pt1 := grid.Get(x1, y1)
			if pt1.display != '.' {
				for x2 := 0; x2 < grid.SizeX(); x2++ {
					for y2 := 0; y2 < grid.SizeY(); y2++ {
						pt2 := grid.Get(x2, y2)
						if x1 != x2 && y1 != y2 && pt1.display == pt2.display && !isPaired(x1, y1, pt1, pt2) {
							pt1.paired[loc{x: x2, y: y2}] = pt2
							pt2.paired[loc{x: x1, y: y1}] = pt1
							pt1.antinodes++
							pt2.antinodes++
							grid.Set(x1, y1, pt1)
							grid.Set(x2, y2, pt2)
						}
					}
				}
			}
		}
	}
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			pt1 := grid.Get(x, y)
			if pt1.display != '.' {
				for ploc := range pt1.paired {
					addAntiNodes_p2(pt1.display, x, y, ploc.x, ploc.y, grid)
				}
			}
		}
	}
	antinodes := 0
	fmt.Printf("%v\n", grid.StringWithFormatter(formatter))
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			pt := grid.Get(x, y)
			if pt.antinodes > 0 {
				antinodes++
			} else {
				if pt.display != '.' { antinodes++ }
			}
		}
	}
	return antinodes
}

func formatter(p pt, x int, y int) string {
	if p.antinodes > 0 { 
		return "#"
	}
	return "."
}

func isPaired(x int, y int, p1 pt, pt2 pt) bool {
	return p1.paired[loc{x: x, y: y}].display == pt2.display
}

func addAntiNodes(x1 int, y1 int, x2 int, y2 int, grid *grid2d.Grid[pt]) {
	px1, py1, px2, py2 := offsetPoints(x1, y1, x2, y2)

	if px1 < 0 || px1 >= grid.SizeX() || py1 < 0 || py1 >= grid.SizeY() {
		return
	}
	pt1 := grid.Get(px1, py1)
	pt1.antinodes++
	grid.Set(px1, py1, pt1)
	if px2 < 0 || px2 >= grid.SizeX() || py2 < 0 || py2 >= grid.SizeY() {
		return
	}
	pt2 := grid.Get(px2, py2)
	pt2.antinodes++
	grid.Set(px2, py2, pt2)
}

func addAntiNodes_p2(frequency rune, x1 int, y1 int, x2 int, y2 int, grid *grid2d.Grid[pt]) {
	px1, py1, px2, py2 := offsetPoints(x1, y1, x2, y2)
	if !(px1 < 0 || px1 >= grid.SizeX() || py1 < 0 || py1 >= grid.SizeY()) {
		pt1 := grid.Get(px1, py1)
		if !pt1.visited[frequency] {
			pt1.antinodes++
			pt1.visited[frequency] = true
			grid.Set(px1, py1, pt1)
			addAntiNodes_p2(frequency, x1, y1, px1, py1, grid)
		}
	}
	if !(px2 < 0 || px2 >= grid.SizeX() || py2 < 0 || py2 >= grid.SizeY()) {
		pt2 := grid.Get(px2, py2)
		if !pt2.visited[frequency] {
			pt2.antinodes++
			pt2.visited[frequency] = true
			grid.Set(px2, py2, pt2)
			addAntiNodes_p2(frequency, x2, y2, px2, py2, grid)
		}
	}
}

// Function to calculate two points symmetrically placed above and below the line
func offsetPoints(x1, y1, x2, y2 int) (int, int, int, int) {
	dx := x1 - x2
	dy := y1 - y2

	p1x := x1 + dx
	p1y := y1 + dy
	p2x := x2 - dx
	p2y := y2 - dy
	return p1x, p1y, p2x, p2y
}
