package day16

import (
	_ "adventofcode2023/utils"
	"adventofcode2023/utils/grid2d"
	_ "adventofcode2023/utils/grid2d"
	"adventofcode2023/utils/inputs"
	"fmt"
	_ "strings"
)

type Tile struct {
	object rune
	energized int
}

type Beam struct {
	dir rune
	r int
	c int
	rm bool
}

var startBeams []Beam

func Part1(input string) int {
	beams := []Beam{}
	beams = addBeam(beams, Beam{'s', 0, 3, false})
	grid := inputs.ToGrid2D[Tile](input, "\n", "", Tile{}, func(c string) Tile { return Tile{ object: rune(c[0]), energized: 0}})
	return score(grid, beams)
}

func Part2(input string) int {
	grid := inputs.ToGrid2D[Tile](input, "\n", "", Tile{}, func(c string) Tile { return Tile{ object: rune(c[0]), energized: 0}})
	G := grid.Matrix()
	R := len(G)
	C := len(G[0])
	ans := 0
	for r:=0;r<R;r++{
		beams := []Beam{}
		grid := inputs.ToGrid2D[Tile](input, "\n", "", Tile{}, func(c string) Tile { return Tile{ object: rune(c[0]), energized: 0}})
		beams = addBeam(beams, Beam{'e', r, 0, false})
		ans = max(ans, score(grid, beams))
		beams = []Beam{}
		beams = addBeam(beams, Beam{'w', r, C-1, false})
		grid = inputs.ToGrid2D[Tile](input, "\n", "", Tile{}, func(c string) Tile { return Tile{ object: rune(c[0]), energized: 0}})
		ans = max(ans, score(grid, beams))
	}
	for c:=0;c<C;c++{
		beams := []Beam{}
		grid := inputs.ToGrid2D[Tile](input, "\n", "", Tile{}, func(c string) Tile { return Tile{ object: rune(c[0]), energized: 0}})
		beams = addBeam(beams, Beam{'s', 0, c, false})
		ans = max(ans, score(grid, beams))
		beams = []Beam{}
		beams = addBeam(beams, Beam{'n', R-1, c, false})
		grid = inputs.ToGrid2D[Tile](input, "\n", "", Tile{}, func(c string) Tile { return Tile{ object: rune(c[0]), energized: 0}})
		ans = max(ans, score(grid, beams))
	}

	return ans
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func score(grid *grid2d.Grid[Tile], beams []Beam) int {
	fmt.Println(grid.StringWithFormatter(func(t Tile, x int, y int) string { return string(t.object)}))
	G := grid.Matrix()
	R := len(G)
	C := len(G[0])
	fmt.Println("r:", R, "c:", C, "tiles:", R*C )
	for i:=0;i<1000;i++ {
		beams = rmBeams(beams)
		fmt.Println(i, ":", len(beams), ":", energy(G))
		for i, _ := range beams {
			r := beams[i].r
			c := beams[i].c
			if r >= 0 && r < R && c >= 0 && c < C {
				G[r][c].energized++
				switch beams[i].dir {
				case 'n':
						tile := &G[r][c]
						switch tile.object {
						case '.':
							beams[i].r--
						case '|':
							beams[i].r--
						case '-':
							beams[i].dir = 'e'
							beams[i].c++
							beams = addBeam(beams, Beam{'w', beams[i].r, beams[i].c-1, false})
						case '\\':
							beams[i].dir = 'w'
							beams[i].c--
						case '/':
							beams[i].dir = 'e'
							beams[i].c++
						}
				case 'e':
						tile := &G[r][c]
						switch tile.object {
						case '.':
							beams[i].c++
						case '-':
							beams[i].c++
						case '|':
							beams[i].dir = 'n'
							beams[i].r--
							beams = addBeam(beams, Beam{'s', beams[i].r+1, beams[i].c, false})
						case '\\':
							beams[i].dir = 's'
							beams[i].r++
						case '/':
							beams[i].dir = 'n'
							beams[i].r--
						}
				case 's':
						tile := &G[r][c]
						switch tile.object {
						case '.':
							beams[i].r++
						case '|':
							beams[i].r++
						case '-':
							beams[i].dir = 'e'
							beams[i].c++
							beams = addBeam(beams, Beam{'w', beams[i].r, beams[i].c-1, false})
						case '\\':
							beams[i].dir = 'e'
							beams[i].c++
						case '/':
							beams[i].dir = 'w'
							beams[i].c--
						}
				case 'w':
						tile := &G[r][c]
						switch tile.object {
						case '.':
							beams[i].c--
						case '-':
							beams[i].c--
						case '|':
							beams[i].dir = 'n'
							beams[i].r--
							beams = addBeam(beams, Beam{'s', beams[i].r+1, beams[i].c, false})
						case '\\':
							beams[i].dir = 'n'
							beams[i].r--
						case '/':
							beams[i].dir = 's'
							beams[i].r++
						}
				}
			} else {
				beams[i].rm = true
			}
		}
		fmt.Println(grid.StringWithFormatter(func(t Tile, x int, y int) string { if t.energized > 0 {return "#"}; return "."}))
	}
	fmt.Println(grid.StringWithFormatter(func(t Tile, x int, y int) string { if t.energized > 0 {return "#"}; return "."}))
	return energy(G)
}

func rmBeams(slice []Beam) []Beam {
    var result []Beam

    for _, value := range slice {
        if ! value.rm {
            result = append(result, value)
        }
    }
    return result
}

func addBeam(beams []Beam, beam Beam) []Beam {
    for _, value := range startBeams {
        if value == beam { return beams }
    }
	startBeams = append(startBeams, beam)
	for _, value := range beams {
        if value == beam { return beams }
    }
    return append(beams, beam)
}

func energy(G [][]Tile) int {
	ans := 0
	R := len(G)
	C := len(G[0])
	for r:=0;r<R;r++ {
		for c:=0;c<C;c++ {
			if G[r][c].energized > 0 {
				ans++
			}
		}
	}
	return ans
}