package main

import (
	"fmt"
	"io/ioutil"
	"strings"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

type State struct {
	Position h.Coordinate3d
	Distance int
}

var filename string
var directions []h.Coordinate3d

func main() {
	filename = "input"
	if h.GetArgNumber(1) != "" {
		filename = h.GetArgNumber(1)
	}

	directions = []h.Coordinate3d{{X: 0, Y: -1, Z: 0}, {X: 1, Y: 0, Z: 0}, {X: 0, Y: 1, Z: 0}, {X: -1, Y: 0, Z: 0}}
	part := h.GetArgNumber(2)
	if part != "2" {
		part1(filename)
	} else {
		part2(filename)
	}
}

func part1(filename string) {
	maze := loadMaze(filename)
	queue, visited := []State{{Position: maze.Start}}, map[h.Coordinate3d]bool{maze.Start: true}
	var st State
	for {
		st, queue = queue[0], queue[1:]
		for _, d := range directions {
			next := h.Coordinate3d{X: st.Position.X + d.X, Y: st.Position.Y + d.Y, Z: 0}
			if next == maze.End {
				fmt.Println(st.Distance + 1)
				return
			}
			if maze.MazeMap[next] && !visited[next] {
				visited[next] = true
				p, ok := maze.Portals[next]
				if ok {
					next = p.To
				}
				queue = append(queue, State{next, st.Distance + 1})
			}
		}
	}
}

func part2(filename string) {
	maze := loadMaze(filename)
	queue, visited := []State{{Position: maze.Start}}, map[h.Coordinate3d]bool{{X: maze.Start.X, Y: maze.Start.Y, Z: 0}: true}
	var st State
	for {
		st, queue = queue[0], queue[1:]
		for _, d := range directions {
			next := h.Coordinate3d{X: st.Position.X + d.X, Y: st.Position.Y + d.Y, Z: st.Position.Z}
			if next == maze.End {
				fmt.Println(st.Distance + 1)
				return
			}
			if maze.MazeMap[h.Coordinate3d{X: next.X, Y: next.Y, Z: 0}] && !visited[next] {
				visited[next] = true
				p, ok := maze.Portals[h.Coordinate3d{X: next.X, Y: next.Y, Z: 0}]
				if ok && (st.Position.Z > 0 || !p.Outer) {
					next = h.Coordinate3d{X: p.To.X, Y: p.To.Y, Z: st.Position.Z}
					if p.Outer {
						next.Z--
					} else {
						next.Z++
					}
					visited[next] = true
				}
				queue = append(queue, State{next, st.Distance + 1})
			}
		}
	}
}

func loadMaze(filename string) Maze {
	ret := Maze{
		MazeMap: make(map[h.Coordinate3d]bool),
		Outer:   Square{},
		Inner:   Square{},
		Portals: make(map[h.Coordinate3d]Portal),
	}
	input, _ := ioutil.ReadFile(filename)
	lines := make([]string, 0)
	for _, line := range strings.Split(string(input), "\n") {
		lines = append(lines, line)
	}
	//outer, inner := Square{Tl: h.Coordinate3d{X: 2, Y: 2, Z: 0}, Br: h.Coordinate3d{X: len(lines[0]) - 3, Y: len(lines) - 3, Z: 0}}, Square{}
	// Find the outer top-left
	var done bool
	for y := 0; y < len(lines); y++ {
		for x := 0; x < len(lines[y]); x++ {
			if lines[y][x] == '#' || lines[y][x] == '.' {
				ret.Outer.Tl = h.Coordinate3d{X: x, Y: y, Z: 0}
				done = true
				break
			}
		}
		if done {
			break
		}
	}
	// Now find the outer bottom-righ
	done = false
	for y := len(lines) - 1; y > ret.Outer.Tl.Y; y-- {
		for x := len(lines[y]) - 1; x > ret.Outer.Tl.X; x-- {
			if lines[y][x] == '#' || lines[y][x] == '.' {
				ret.Outer.Br = h.Coordinate3d{X: x, Y: y, Z: 0}
				done = true
				break
			}
		}
		if done {
			break
		}
	}

	// Find the inner top-left
	done = false
	for y := ret.Outer.Tl.Y; y < ret.Outer.Br.Y; y++ {
		for x := ret.Outer.Tl.X; x < ret.Outer.Br.X; x++ {
			if lines[y][x] != '#' && lines[y][x] != '.' {
				// Found it.
				ret.Inner.Tl = h.Coordinate3d{X: x - 1, Y: y - 1, Z: 0}
				done = true
				break
			}
		}
		if done {
			break
		}
	}
	// Ok, now find the inner bottom-right
	done = false
	for y := ret.Outer.Br.Y - 1; y > ret.Outer.Tl.Y; y-- {
		for x := ret.Outer.Br.X - 1; x > ret.Outer.Tl.X; x-- {
			if lines[y][x] != '#' && lines[y][x] != '.' {
				// Found it
				ret.Inner.Br = h.Coordinate3d{X: x + 1, Y: y + 1, Z: 0}
				done = true
				break
			}
		}
		if done {
			break
		}
	}

	for y := ret.Outer.Tl.Y; y <= ret.Outer.Br.Y; y++ {
		for x := ret.Outer.Tl.X; x <= ret.Outer.Br.X; x++ {
			if lines[y][x] == '.' {
				ret.MazeMap[h.Coordinate3d{X: x, Y: y, Z: 0}] = true

				var label string
				var pos h.Coordinate3d
				var outerPortal bool
				if y == ret.Outer.Tl.Y {
					label = lines[y-2][x:x+1] + lines[y-1][x:x+1]
					pos = h.Coordinate3d{X: x, Y: y - 1, Z: 0}
					outerPortal = true
				} else if y == ret.Outer.Br.Y {
					label = lines[y+1][x:x+1] + lines[y+2][x:x+1]
					pos = h.Coordinate3d{X: x, Y: y + 1, Z: 0}
					outerPortal = true
				} else if x == ret.Outer.Tl.X {
					label = lines[y][x-2 : x]
					pos = h.Coordinate3d{X: x - 1, Y: y, Z: 0}
					outerPortal = true
				} else if x == ret.Outer.Br.X {
					label = lines[y][x+1 : x+3]
					pos = h.Coordinate3d{X: x + 1, Y: y, Z: 0}
					outerPortal = true
				} else if y == ret.Inner.Br.Y && x > ret.Inner.Tl.X && x < ret.Inner.Br.X {
					label = lines[y-2][x:x+1] + lines[y-1][x:x+1]
					pos = h.Coordinate3d{X: x, Y: y - 1, Z: 0}
				} else if y == ret.Inner.Tl.Y && x > ret.Inner.Tl.X && x < ret.Inner.Br.X {
					label = lines[y+1][x:x+1] + lines[y+2][x:x+1]
					pos = h.Coordinate3d{X: x, Y: y + 1, Z: 0}
				} else if x == ret.Inner.Br.X && y > ret.Inner.Tl.Y && y < ret.Inner.Br.Y {
					label = lines[y][x-2 : x]
					pos = h.Coordinate3d{X: x - 1, Y: y, Z: 0}
				} else if x == ret.Inner.Tl.X && y > ret.Inner.Tl.Y && y < ret.Inner.Br.Y {
					label = lines[y][x+1 : x+3]
					pos = h.Coordinate3d{X: x + 1, Y: y, Z: 0}
				}

				if label == "AA" {
					ret.Start = h.Coordinate3d{X: x, Y: y, Z: 0}
				} else if label == "ZZ" {
					ret.End = h.Coordinate3d{X: x, Y: y, Z: 0}
				} else if label != "" {
					ret.Portals[pos] = Portal{Label: label, From: h.Coordinate3d{X: x, Y: y, Z: 0}, Outer: outerPortal}
					ret.MazeMap[pos] = true
				}
			} else {
				// Make sure we don't overwrite anything
				if _, ok := ret.MazeMap[h.Coordinate3d{X: x, Y: y, Z: 0}]; !ok {
					ret.MazeMap[h.Coordinate3d{X: x, Y: y, Z: 0}] = false
				}
			}
		}
	}
	for i, p1 := range ret.Portals {
		for _, p2 := range ret.Portals {
			if p1.Label == p2.Label && p1.From != p2.From {
				p1.To = p2.From
				ret.Portals[i] = p1
			}
		}
	}
	return ret
}
