package day10

import (
	"fmt"

	"adventofcode2023/utils"
	"adventofcode2023/utils/grid2d"
	"adventofcode2023/utils/inputs"
)

type Coord struct {
	x int
	y int
}

type Direction struct {
	dir string
	x int
	y int
}

func Part1(input string) int {
	grid := inputs.ToGrid2D(input, "\n", "", ".", func(c string) string { return c})
	start, _ := findStart(grid)
	tunnel := findTunnel(grid, start)
	fmt.Println(start)
	return len(tunnel)/2
}

func Part2(input string) int {
	grid := inputs.ToGrid2D(input, "\n", "", ".", func(c string) string { return c})
	start, _ := findStart(grid)
	tunnel := findTunnel(grid, start)
	maskJunkPipes(grid, tunnel)
	bigGrid := expand(grid)
	floodFill(bigGrid, Coord{0,0})
	return countTiles(bigGrid, tunnel)
}

func floodFill(grid *grid2d.Grid[string], coord Coord) {
    // Base cases to stop recursion
	if coord.x < 0 || coord.x >= grid.SizeX() || coord.y < 0 || coord.y >= grid.SizeY() {
		return
	}

    if grid.Get(coord.x, coord.y) != "." || grid.Get(coord.x, coord.y) == "O" {
        return
    }

    // Change the color of the current pixel
    grid.Set(coord.x, coord.y, "O")

    // Recursively call floodFill for neighboring pixels
    floodFill(grid, Coord{coord.x+1, coord.y})
    floodFill(grid, Coord{coord.x-1, coord.y})
    floodFill(grid, Coord{coord.x, coord.y+1})
    floodFill(grid, Coord{coord.x, coord.y-1})
}

// Magnify grid

//		+-
// F => |
//
// L => |
//		+-
//
//      |
// | => |
//      |


func findTunnel(grid *grid2d.Grid[string], start Coord) []Coord {
	directions := []Direction{{"s", 0, 1}, {"n", 0, -1}, {"e", 1, 0}, {"w", -1, 0}}
	steps := 0
	current := start
	last := Direction{}
	tunnel := []Coord{}
	tunnel = append(tunnel, start)
	for {
		for _, dir := range directions {
			if dir == oposite(last) { continue }
			x := current.x + dir.x
			y := current.y + dir.y
			if x < 0 || x >= grid.SizeX() || y < 0 || y >= grid.SizeY() {
				continue
			}
			if ! possible(dir, grid.Get(current.x, current.y) ) { continue }
			pipe := grid.Get(x, y)
			if pipe == "S" { 
				return tunnel
			}	
			switch dir.dir {
			case "n":
				if pipe == "|" || pipe == "F" || pipe == "7" {
					current = Coord{x, y}
					steps++
					last = dir
					tunnel = append(tunnel, current)
					if current == start { return tunnel }
				}
			case "s":
				if pipe == "|" || pipe == "L" || pipe == "J" {
					current = Coord{x, y}
					steps++
					last = dir
					tunnel = append(tunnel, current)
					if current == start { return tunnel }
				}
			case "e":
				if pipe == "-" || pipe == "J" || pipe == "7" {
					current = Coord{x, y}
					steps++
					last = dir
					tunnel = append(tunnel, current)
					if current == start { return tunnel }
				}
			case "w":
				if pipe == "-" || pipe == "F" || pipe == "L" {
					current = Coord{x, y}
					steps++
					last = dir
					tunnel = append(tunnel, current)
					if current == start { return tunnel }
				}
			}
					
		}
	}
}

func expand(grid *grid2d.Grid[string]) *grid2d.Grid[string] {
	bigGrid := grid2d.NewGrid(grid.SizeX() * 3, grid.SizeY() * 3, ".")
	for y:=0;y<grid.SizeY();y++ {
		for x:=0;x<grid.SizeX();x++ {
			switch grid.Get(x, y) {
			case "F":
				bigGrid.Set(x*3+1,y*3+1,"+")
				bigGrid.Set(x*3+2,y*3+1,"-")
				bigGrid.Set(x*3+1,y*3+2,"|")
			case "L":
				bigGrid.Set(x*3+1,y*3,"|")
				bigGrid.Set(x*3+1,y*3+1,"+")
				bigGrid.Set(x*3+2,y*3+1,"-")
			case "|":
				bigGrid.Set(x*3+1,y*3,"|")
				bigGrid.Set(x*3+1,y*3+1,"|")
				bigGrid.Set(x*3+1,y*3+2,"|")
			case "7":
				bigGrid.Set(x*3,y*3+1,"-")
				bigGrid.Set(x*3+1,y*3+1,"+")
				bigGrid.Set(x*3+1,y*3+2,"|")
			case "-":
				bigGrid.Set(x*3,y*3+1,"-")
				bigGrid.Set(x*3+1,y*3+1,"-")
				bigGrid.Set(x*3+2,y*3+1,"-")
			case "J":
				bigGrid.Set(x*3+1,y*3,"|")
				bigGrid.Set(x*3+1,y*3+1,"+")
				bigGrid.Set(x*3,y*3+1,"-")
			case "X":
				bigGrid.Set(x*3+1,y*3+1,"X")
			case "S":
				bigGrid.Set(x*3+1,y*3+1,"*")
			default:
				bigGrid.Set(x*3+1,y*3+1,".")
			}
		}
	}
	return bigGrid
}
func maskJunkPipes(grid *grid2d.Grid[string], tunnel []Coord) {
	for y:=0;y<grid.SizeY();y++ {
		for x:=0;x<grid.SizeX();x++ {
			if ! containsCoord(Coord{x, y}, tunnel) && grid.Get(x, y) != "." {
				grid.Set(x, y, ".")
			}
		}
	}
}

func countTiles(grid *grid2d.Grid[string], tunnel []Coord) int {
	count := 0
	tiles := []Coord{}
	directions := []Coord{{0,1}, {0,-1},{1,0}, {-1, 0},{1,1},{1,-1},{-1,1},{-1,-1}}
	for y:=0;y<grid.SizeY();y=y+3 {
		for x:=0;x<grid.SizeX();x=x+3 {
			coord := Coord{x, y}
			if coord.x == 0 || coord.x == grid.SizeX() - 1 || coord.y == 0  || coord.y == grid.SizeY() - 1  {
				continue
			}
			found := true
			if grid.Get(coord.x, coord.y) == "." {
				for _, dir := range directions {
					if grid.Get(coord.x+dir.x, coord.y+dir.y) != "." {
						found = false
						continue
					}
				}
				if found {
					count++
					tiles = append(tiles, coord)
					grid.Set(coord.x, coord.y, "I")
				}
			}
		}
	}
//	fmt.Println(grid.StringWithFormatter(func(c string, x int, y int) string { return c}))
	return count
}

func containsCoord(coord Coord, tunnel []Coord) bool {
	for _, v := range tunnel {
		if v == coord {
			return true
		}
	}
	return false
}

func maxX(tunnel []Coord) int {
	max := 0
	for _, v := range tunnel {
		if v.x > max {
			max = v.x
		}
	}
	return max
}
func minX(tunnel []Coord) int {
	min := utils.MaxInt
	for _, v := range tunnel {
		if v.x < min {
			min = v.x
		}
	}
	return min
}
func maxY(tunnel []Coord) int {
	max := 0
	for _, v := range tunnel {
		if v.y > max {
			max = v.y
		}
	}
	return max
}
func minY(tunnel []Coord) int {
	min := utils.MaxInt
	for _, v := range tunnel {
		if v.y < min {
			min = v.y
		}
	}
	return min
}

func possible(dir Direction, pipe string) bool {
	v := false
	if pipe == "S" { return true }
	switch dir.dir {
	case "n":
		if pipe == "|" || pipe == "L" || pipe == "J" {
			v = true
		}
	case "s":
		if pipe == "|" || pipe == "F" || pipe == "7" {
			v = true
		}
	case "e":
		if pipe == "-" || pipe == "F" || pipe == "L" {
			v = true
		}
	case "w":
		if pipe == "-" || pipe == "J" || pipe == "7" {
			v = true
		}
	}
	return v
}

func findStart(grid *grid2d.Grid[string]) (Coord, error) {
//	for j := 0; j < grid.SizeY(); j++ {
//		for i := 0; i < grid.SizeX(); i++ {
//			if grid.Get(i, j) == "S" {
//				return Coord{i, j}, nil
//			}
//		}
//	}
//	return Coord{}, fmt.Errorf("S not found")
	return Coord{x:74, y:95}, nil
}

func oposite(dir Direction) Direction {
	switch dir.dir {
	case "n":
		return Direction{"s", 0, 1}
	case "s":
		return Direction{"n", 0, -1}
	case "e":
		return Direction{"w", -1, 0}
	case "w":
		return Direction{"e", 1, 0}
	}
	return Direction{}
}