package day03

import (
	"adventofcode2023/utils"
	_ "adventofcode2023/utils"
	_ "adventofcode2023/utils/grid2d"
	"adventofcode2023/utils/inputs"
	"errors"
	"fmt"
	"unicode"
)

type Coord struct {
	x int
	y int
}
type Part struct {
	number string
	location Coord
}

func Part1(input string) int {
	grid := inputs.ToGrid2D(input, "\n", "", ".", func(c string) string { return c})
	partLocs := []Coord{}
	for j := 0; j < grid.SizeY(); j++ {
		for i := 0; i < grid.SizeX(); i++ {
			if  !unicode.IsDigit(rune(grid.Get(i, j)[0])) { continue}
			directions := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0},{-1,1},{1,1},{-1,-1},{1,-1}}
			for _, dir := range directions {
				x := i + dir[0]
				y := j + dir[1]
				if x < 0 || x >= grid.SizeX() || y < 0 || y >= grid.SizeY() {
					continue
				}
				if !unicode.IsDigit(rune(grid.Get(x, y)[0])) && grid.Get(x, y) != "." {
					fmt.Printf("partLoc x:%v y:%v %v\n", i, j, string(grid.Get(i, j)))
					partLocs = append(partLocs, Coord{i, j})
					break
				}
			}
		}
	}
	fmt.Println(partLocs)
	sum := 0
	for _, partLoc := range partLocs {
		if grid.Get(partLoc.x, partLoc.y) == "." {
			continue
		}
		partNum := grid.Get(partLoc.x, partLoc.y)
		for i:=1;unicode.IsDigit(rune(grid.Get(partLoc.x+i, partLoc.y)[0]));i++{
			partNum += grid.Get(partLoc.x+i, partLoc.y)
			grid.Set(partLoc.x+i, partLoc.y, ".")
		}
		for i:=-1;unicode.IsDigit(rune(grid.Get(partLoc.x+i, partLoc.y)[0]));i--{
			partNum = grid.Get(partLoc.x+i, partLoc.y) + partNum
			grid.Set(partLoc.x+i, partLoc.y, ".")
		}
		fmt.Println(partNum)
		sum += utils.MustAtoi(partNum)
	}
	return sum
}

func Part2(input string) int {
	grid := inputs.ToGrid2D(input, "\n", "", ".", func(c string) string { return c})
	partLocs := []Coord{}
	gearLocs := []Coord{}
	for j := 0; j < grid.SizeY(); j++ {
		for i := 0; i < grid.SizeX(); i++ {
			if grid.Get(i, j) == "*" {
				gearLocs = append(gearLocs, Coord{i, j})
			}
			if  !unicode.IsDigit(rune(grid.Get(i, j)[0])) { continue}
			directions := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0},{-1,1},{1,1},{-1,-1},{1,-1}}
			for _, dir := range directions {
				x := i + dir[0]
				y := j + dir[1]
				if x < 0 || x >= grid.SizeX() || y < 0 || y >= grid.SizeY() {
					continue
				}
				if !unicode.IsDigit(rune(grid.Get(x, y)[0])) && grid.Get(x, y) != "." {
					partLocs = append(partLocs, Coord{i, j})
					break
				}
			}
		}
	}
	fmt.Println(partLocs)
	fmt.Println(gearLocs)

	parts := []Part{}
	for _, partLoc := range partLocs {
		l := 0
		if grid.Get(partLoc.x, partLoc.y) == "." {
			continue
		}
		partNum := grid.Get(partLoc.x, partLoc.y)
		for i:=1;unicode.IsDigit(rune(grid.Get(partLoc.x+i, partLoc.y)[0]));i++{
			partNum += grid.Get(partLoc.x+i, partLoc.y)
			grid.Set(partLoc.x+i, partLoc.y, ".")
		}
		l = partLoc.x
		for i:=-1;unicode.IsDigit(rune(grid.Get(partLoc.x+i, partLoc.y)[0]));i--{
			partNum = grid.Get(partLoc.x+i, partLoc.y) + partNum
			grid.Set(partLoc.x+i, partLoc.y, ".")
			l = partLoc.x+i
		}
		fmt.Println(partNum)
		parts = append(parts, Part{number: partNum, location: Coord{x: l, y: partLoc.y}})
	}
	sum := 0
	for _, gearLoc := range gearLocs {
		var gearedParts [2]Part

		directions := []Coord{{0,1},{0,-1},{1,0},{-1,0},{-1,1},{1,1},{-1,-1},{1,-1}}
		for _, dir := range directions {
			part, err := getPart(gearLoc, dir, parts)
			if err != nil {continue}
			if gearedParts[0] == (Part{}) {
				gearedParts[0] = part
			} else if gearedParts[0] == part {
				fmt.Println("duplicate part found")
			} else {
				gearedParts[1] = part
			}
		}
		fmt.Println(gearedParts)
		if gearedParts[0] != (Part{}) && gearedParts[1] != (Part{}) {
			sum += utils.MustAtoi(gearedParts[0].number) * utils.MustAtoi(gearedParts[1].number)
		}
	}
//	fmt.Println(parts)
	return sum
}

func getPart(gearloc Coord, offset Coord, parts []Part) (Part, error) {
	coord := Coord{x: gearloc.x + offset.x, y: gearloc.y + offset.y}
	for _, part := range parts {
		if coord.x >= part.location.x && 
			coord.x <= part.location.x + len(part.number) -1 &&
			coord.y == part.location.y {
				return part, nil
			}
	}
	return Part{}, errors.New("Not Found")
}