package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
	"time"

	intcode "git.bullercodeworks.com/brian/adventofcode/2019/intcode-processor"
	helpers "git.bullercodeworks.com/brian/adventofcode/helpers"
)

var auto bool
var maze *Maze

func main() {
	progFileName := "input"
	auto = true

	if len(os.Args) > 1 && os.Args[1] == "-manual" {
		auto = false
	}
	prog := intcode.ReadIntCodeFile(progFileName)
	maze = NewMaze()

	part1(prog)
	part2()
}

var all map[string]*MazeCoord

func part1(prog []int) {
	p := intcode.NewProgram(prog)
	go func() {
		var roadTaken []helpers.Coordinate
		for {
			//time.Sleep(500)
			//fmt.Println(helpers.CLEAR_SCREEN)
			//maze.Print()
			for !p.NeedsInput() {
				time.Sleep(1)
			}
			var movingDir int
			var moveToCoord *helpers.Coordinate
			if auto {
				var picked bool
				directions := []int{DIR_N, DIR_E, DIR_S, DIR_W}
				// If we have an unexplored location, try it
				for _, tryDir := range directions {
					v := maze.GetCoord(maze.GetDirFromBot(tryDir))
					if v == MAZE_UNKNOWN {
						movingDir = tryDir
						picked = true
						break
					}
				}

				if !picked {
					movingDir = maze.GetDirectionToLast()
					if movingDir == -1 {
						fmt.Println("Maze Created")
						p.ForceQuit()
						return
					}
					picked = true
				}
				moveToCoord = maze.GetDirFromBot(movingDir)
			} else {
				for movingDir == 0 {
					fmt.Print("Input (vimlike): ")
					reader := bufio.NewReader(os.Stdin)
					inp, err := reader.ReadString('\n')
					if err != nil {
						panic(err)
					}
					inp = strings.TrimSpace(inp)
					switch inp {
					case "h": // West
						movingDir = DIR_W
						moveToCoord = maze.bot.GetWestCoord()
					case "j": // South
						movingDir = DIR_S
						moveToCoord = maze.bot.GetSouthCoord()
					case "k": // North
						movingDir = DIR_N
						moveToCoord = maze.bot.GetNorthCoord()
					case "l": // East
						movingDir = DIR_E
						moveToCoord = maze.bot.GetEastCoord()
					}
				}
			}
			p.Input(movingDir)
			for !p.NeedsOutput() {
				time.Sleep(1)
			}
			moveRes := p.Output()
			maze.SetCoord(moveToCoord, moveRes)
			switch moveRes {
			case 1, 2: // Moved
				roadTaken = append(roadTaken, *maze.bot)
				maze.MoveBot(movingDir)
			}
			if p.State() == intcode.RET_DONE || p.State() == intcode.RET_ERR {
				break
			}
		}
	}()
	p.Run()
	//We force quit the program when the maze is built.
	// Now we find the quickest path through the maze.
	fmt.Println(helpers.CLEAR_SCREEN)
	maze.Print()
	fmt.Println("Now find shortest path")
	all = make(map[string]*MazeCoord)
	for k, v := range maze.maze {
		if v == MAZE_EMPTY || v == MAZE_O2SYS {
			c := helpers.CoordinateFromString(k)
			m := &MazeCoord{coord: c}
			m.Distance = helpers.MAX_INT
			all[c.String()] = m
		}
	}
	for _, v := range all {
		n, ok := all[v.coord.GetNorthCoord().String()]
		if ok {
			v.N = n
		}
		e, ok := all[v.coord.GetEastCoord().String()]
		if ok {
			v.E = e
		}
		s, ok := all[v.coord.GetSouthCoord().String()]
		if ok {
			v.S = s
		}
		w, ok := all[v.coord.GetWestCoord().String()]
		if ok {
			v.W = w
		}
	}
	start := all[maze.startCoord.String()]
	start.Distance = 0
	fmt.Println("Processing. . .")
	ProcessNode(start, 0)
	fmt.Println("Distance to O2:", all[maze.o2Coord.String()].Distance)
}

func ProcessNode(m *MazeCoord, steps int) {
	if m.coord.String() == maze.o2Coord.String() {
		return
	}
	for _, neighbor := range []*MazeCoord{m.N, m.E, m.S, m.W} {
		if neighbor == nil {
			continue
		}
		wrk, ok := all[neighbor.coord.String()]
		if ok {
			if m.Distance+1 < wrk.Distance {
				wrk.Distance = m.Distance + 1
				if !wrk.Visited {
					ProcessNode(wrk, m.Distance+1)
				}
			}
		}
	}
	m.Visited = true
}

func part2() {
	// We're going to reuse the visited flag for oxygen
	for _, v := range all {
		v.Visited = false
	}
	fmt.Println("Unfilled", countUnfilledSpaces())
	all[maze.o2Coord.String()].Visited = true
	var cnt int
	for countUnfilledSpaces() > 0 {
		tick()
		fmt.Println("Unfilled", countUnfilledSpaces())
		cnt++
	}
	fmt.Println("Minutes:", cnt)
}

func countUnfilledSpaces() int {
	var count int
	for _, v := range all {
		if !v.Visited {
			count++
		}
	}
	return count
}

func tick() bool {
	// Start with the o2 coord
	var toFill []*MazeCoord
	for _, v := range all {
		if v.N != nil && v.N.Visited {
			toFill = append(toFill, v)
			continue
		}
		if v.E != nil && v.E.Visited {
			toFill = append(toFill, v)
			continue
		}
		if v.S != nil && v.S.Visited {
			toFill = append(toFill, v)
			continue
		}
		if v.W != nil && v.W.Visited {
			toFill = append(toFill, v)
			continue
		}
	}
	for k := range toFill {
		all[toFill[k].coord.String()].Visited = true
	}
	fmt.Println("Filled", len(toFill), "spaces")
	return len(toFill) > 0
}

type MazeCoord struct {
	coord      *helpers.Coordinate
	N, S, E, W *MazeCoord
	Distance   int
	Visited    bool
}
