package day12

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

)

type Plot struct {
	crop    string
	visited bool
	region  int
	x		int
	y 		int
}

type Region struct {
	plots      []Plot
	crop       string
	id         int
	area       int
	perimteter int
	sides      int
}

func Part1(input string) int {
	var val int
	region_id := 0
	regions := make(map[int]Region)
	grid := inputs.ToGrid2D(input, "\n", "", Plot{}, func(c string) Plot { return Plot{crop: c, region: -1} })
	fmt.Println(grid.StringWithFormatter(formatter))
	for y := 0; y < grid.SizeY(); y++ {
		for x := 0; x < grid.SizeX(); x++ {
			var region Region
			plot := grid.Get(x, y)
			if plot.visited {
				continue
			}

			region_id++
			region = Region{id: region_id, area: 1, crop: plot.crop, sides: 4}
			plot.region = region_id
			plot.visited = true
			plot.x = x
			plot.y = y
			grid.Set(x, y, plot)
			region.plots = append(region.plots, plot)
			regions[region_id] = region

			try_neighours(grid, &region, x, y)
			regions[region.id] = region
		}
	}
	for _, region := range regions {
		val += region.area * region.perimteter
	}
	return val
}

func Part2(input string) int {
	var val int
	region_id := 0
	regions := make(map[int]Region)
	grid := inputs.ToGrid2D(input, "\n", "", Plot{}, func(c string) Plot { return Plot{crop: c, region: -1} })
	fmt.Println(grid.StringWithFormatter(formatter))
	for y := 0; y < grid.SizeY(); y++ {
		for x := 0; x < grid.SizeX(); x++ {
			var region Region
			plot := grid.Get(x, y)
			if plot.visited {
				continue
			}

			region_id++
			region = Region{id: region_id, area: 1, crop: plot.crop}
			plot.region = region_id
			plot.visited = true
			plot.x = x
			plot.y = y
			grid.Set(x, y, plot)
			region.plots = append(region.plots, plot)
			regions[region_id] = region

			try_neighours(grid, &region, x, y)
			regions[region.id] = region
		}
	}
	for _, region := range regions {
		val += region.area * get_sides(grid, region)
	}
	return val
}

func formatter(p Plot, x int, y int) string {
	return fmt.Sprintf("%v", p.crop)
}

func try_neighours(grid *grid2d.Grid[Plot], region *Region, x int, y int) {

	directions := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
	plot := grid.Get(x, y)
	perimeters := 0

	for _, dir := range directions {
		x1 := x + dir[0]
		y1 := y + dir[1]
		nplot := grid.Get(x1, y1)

		if nplot.crop == plot.crop {
			if !nplot.visited {
				region.area++
				nplot.visited = true
				nplot.region = plot.region
				nplot.x = x1
				nplot.y = y1
				grid.Set(x1, y1, nplot)
				region.plots = append(region.plots, nplot)
				try_neighours(grid, region, x1, y1)
			}
		} else {
			perimeters++
		}
	}
}

func get_sides(grid *grid2d.Grid[Plot], region Region) int {
	sides := 0	
	corners := [][][]int{{{-1, 0}, {0, -1}, {-1, -1}},
					     {{+1, 0}, {0, -1}, {+1, -1}},
					     {{-1, 0}, {0, +1}, {-1, +1}},
					     {{+1, 0}, {0, +1}, {+1, +1}}}
	for _, plot := range region.plots {
		for _, o_corners := range corners {
				c1 := grid.Get(plot.x + o_corners[0][0], plot.y + o_corners[0][1])
				c2 := grid.Get(plot.x + o_corners[1][0], plot.y + o_corners[1][1])
				c3 := grid.Get(plot.x + o_corners[2][0], plot.y + o_corners[2][1])
				if c1.crop != region.crop && c2.crop != region.crop {
					sides++
				} else if c1.crop == region.crop && c2.crop == region.crop && c3.crop != region.crop {
					sides++
				}
		}
	}
	return sides
}