package day06

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

type Direction int

const (
	_ Direction = iota
	Up
	Down
	Left
	Right
)

type Pos struct {
	x int
	y int
}
type Loc struct {
	dir   Direction
	pos   Pos
	count int
}

type Point struct {
	dirs    []Direction
	display string
}

func (d Direction) String() string {
	return [...]string{"-", "Up", "Down", "Left", "Right"}[d]
}

func (l Loc) String() string {
	return fmt.Sprintf("Direction: %s, Position: %d %d", l.dir, l.pos.x, l.pos.y)
}

func Part1(input string) int {
	var loc Loc
	grid := inputs.ToGrid2D(input, "\n", "", "", func(c string) string { return c })
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			if grid.Get(x, y) == "^" {
				loc = Loc{dir: Up, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y) == "v" {
				loc = Loc{dir: Down, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y) == ">" {
				loc = Loc{dir: Right, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y) == "<" {
				loc = Loc{dir: Left, pos: Pos{x: x, y: y}}
			}
		}
	}
	fmt.Println(grid.Matrix())
	fmt.Printf("%v\n", loc)
	grid.Set(loc.pos.x, loc.pos.y, "X")
	loc.count = 1
	move1(&loc, grid)
	return loc.count
}

func Part2(input string) int {
	var loc Loc
	grid := inputs.ToGrid2D(input, "\n", "", Point{}, func(c string) Point { return Point{dirs: make([]Direction, 1), display: c} })
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			if grid.Get(x, y).display == "^" {
				loc = Loc{dir: Up, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y).display == "v" {
				loc = Loc{dir: Down, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y).display == ">" {
				loc = Loc{dir: Right, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y).display == "<" {
				loc = Loc{dir: Left, pos: Pos{x: x, y: y}}
			}
		}
	}
	fmt.Println(grid.Matrix())
	fmt.Printf("%v\n", loc)
	point := grid.Get(loc.pos.x, loc.pos.y)
	grid.Set(loc.pos.x, loc.pos.y, Point{dirs: append(point.dirs, Up), display: "X"})
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			grid = inputs.ToGrid2D(input, "\n", "", Point{}, func(c string) Point { return Point{dirs: make([]Direction, 1), display: c} })
			p := grid.Get(x, y)
			if p.display == "." {
				grid.Set(x, y, Point{dirs: p.dirs, display: "#"})
				l := loc
				if isLoop(&l, grid) {
					loc.count++
				}
				grid.Set(x, y, Point{dirs: p.dirs, display: "."})
			}
		}
	}
	return loc.count
}

func isLoop(loc *Loc, grid *grid2d.Grid[Point]) bool {
	for {
		_, _, err := move2(loc, grid)
		if err != nil { return false }
		if contains(loc.dir, grid.Get(loc.pos.x, loc.pos.y)) {
			return true
		} else {
			p := grid.Get(loc.pos.x, loc.pos.y)
			grid.Set(loc.pos.x, loc.pos.y, Point{dirs: append(p.dirs, loc.dir), display: "X"})
		}
	}
}

func Part2b(input string) int {
	var loc Loc
	grid := inputs.ToGrid2D(input, "\n", "", Point{}, func(c string) Point { return Point{dirs: make([]Direction, 1), display: c} })
	for x := 0; x < grid.SizeX(); x++ {
		for y := 0; y < grid.SizeY(); y++ {
			if grid.Get(x, y).display == "^" {
				loc = Loc{dir: Up, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y).display == "v" {
				loc = Loc{dir: Down, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y).display == ">" {
				loc = Loc{dir: Right, pos: Pos{x: x, y: y}}
			}
			if grid.Get(x, y).display == "<" {
				loc = Loc{dir: Left, pos: Pos{x: x, y: y}}
			}
		}
	}
	fmt.Println(grid.Matrix())
	fmt.Printf("%v\n", loc)
	point := grid.Get(loc.pos.x, loc.pos.y)
	grid.Set(loc.pos.x, loc.pos.y, Point{dirs: append(point.dirs, Up), display: "X"})
	loc.count = 0
	for {
		_, _, err := move2(&loc, grid)
		if err != nil {
			break
		}
		if contains(loc.dir, grid.Get(loc.pos.x, loc.pos.y)) {
			loc.count++
			break
		}
	}
	return loc.count
}

func move1(loc *Loc, grid *grid2d.Grid[string]) int {
	return -1
}

func move2(loc *Loc, grid *grid2d.Grid[Point]) (int, int, error) {
	x, y, err := doMove(loc, grid)
	if err != nil {
		return x, y, err
	}
	if x == loc.pos.x && y == loc.pos.y {
		move2(loc, grid)
	}
	return x, y, nil
}

func canBlock(loc *Loc, point Point) bool {
	val := false
	switch loc.dir {
	case Up:
		if contains(Right, point) {
			val = true
		}
	case Down:
		if contains(Left, point) {
			val = true
		}
	case Left:
		if contains(Up, point) {
			val = true
		}
	case Right:
		if contains(Down, point) {
			val = true
		}
	}
	return val
}

func contains(v Direction, point Point) bool {
	for _, d := range point.dirs {
		if d == v {
			return true
		}
	}
	return false
}

func checkMove(loc *Loc, grid *grid2d.Grid[string]) bool {
	switch loc.dir {
	case Down:
		if loc.pos.y+1 >= grid.SizeY() {
			return false
		}
		if grid.Get(loc.pos.x, loc.pos.y+1) == "#" {
			loc.dir = Left
		} else {
			loc.pos.y++
		}
	case Up:
		if loc.pos.y-1 < 0 {
			return false
		}
		if grid.Get(loc.pos.x, loc.pos.y-1) == "#" {
			loc.dir = Right
		} else {
			loc.pos.y--
		}
	case Left:
		if loc.pos.x-1 < 0 {
			return false
		}
		if grid.Get(loc.pos.x-1, loc.pos.y) == "#" {
			loc.dir = Up
		} else {
			loc.pos.x--
		}
	case Right:
		if loc.pos.x+1 >= grid.SizeX() {
			return false
		}
		if grid.Get(loc.pos.x+1, loc.pos.y) == "#" {
			loc.dir = Down
		} else {
			loc.pos.x++
		}
	}
	return true
}

func doMove(loc *Loc, grid *grid2d.Grid[Point]) (int, int, error) {
	x := loc.pos.x
	y := loc.pos.y
	switch loc.dir {
	case Down:
		if loc.pos.y+1 >= grid.SizeY() {
			return x, y, fmt.Errorf("outside")
		}
		if grid.Get(loc.pos.x, loc.pos.y+1).display == "#" {
			loc.dir = Left
		} else {
			loc.pos.y++
		}
	case Up:
		if loc.pos.y-1 < 0 {
			return x, y, fmt.Errorf("outside")
		}
		if grid.Get(loc.pos.x, loc.pos.y-1).display == "#" {
			loc.dir = Right
		} else {
			loc.pos.y--
		}
	case Left:
		if loc.pos.x-1 < 0 {
			return x, y, fmt.Errorf("outside")
		}
		if grid.Get(loc.pos.x-1, loc.pos.y).display == "#" {
			loc.dir = Up
		} else {
			loc.pos.x--
		}
	case Right:
		if loc.pos.x+1 >= grid.SizeX() {
			return x, y, fmt.Errorf("outside")
		}
		if grid.Get(loc.pos.x+1, loc.pos.y).display == "#" {
			loc.dir = Down
		} else {
			loc.pos.x++
		}
	}
	return x, y, nil
}
