package main

import (
	"crypto/md5"
	"fmt"
	"os"

	"../../"
)

var gridWidth, gridHeight int

func main() {
	var passcode string
	gridWidth, gridHeight = 4, 4

	if len(os.Args) < 2 {
		printUsageAndExit()
	}
	passcode = os.Args[1]
	if len(os.Args) >= 4 {
		gridWidth = aoc.Atoi(os.Args[2])
		gridHeight = aoc.Atoi(os.Args[3])
	}
	foundPaths := findPaths("", passcode)
	if len(foundPaths) == 0 {
		fmt.Println("Couldn't find a valid path!")
		os.Exit(1)
	}
	shortestPath := foundPaths[0]
	longestPath := foundPaths[0]
	for i := range foundPaths {
		if len(foundPaths[i]) < len(shortestPath) {
			shortestPath = foundPaths[i]
		}
		if len(foundPaths[i]) > len(longestPath) {
			longestPath = foundPaths[i]
		}
	}
	fmt.Println("Shortest Path:", shortestPath)
	fmt.Println("Longest Path Length:", len(longestPath))
}

func findPaths(path, passcode string) []string {
	var ret []string
	cX, cY := currPos(path)
	if cY == gridHeight-1 && cX == gridWidth-1 {
		return []string{path}
	}
	if cY > 0 && doorIsOpen(path, passcode, 'U') {
		ret = append(ret, findPaths(path+"U", passcode)...)
	}
	if cY < (gridHeight-1) && doorIsOpen(path, passcode, 'D') {
		ret = append(ret, findPaths(path+"D", passcode)...)
	}
	if cX > 0 && doorIsOpen(path, passcode, 'L') {
		ret = append(ret, findPaths(path+"L", passcode)...)
	}
	if cX < (gridWidth-1) && doorIsOpen(path, passcode, 'R') {
		ret = append(ret, findPaths(path+"R", passcode)...)
	}
	return ret
}

// We cache the last hash calculated
// It should speed things up a little
var lastHashInput string
var lastHash string

func doorIsOpen(path, passcode string, dir rune) bool {
	if lastHashInput != passcode+path {
		lastHashInput = passcode + path
		lastHash = fmt.Sprintf("%x", md5.Sum([]byte(lastHashInput)))
	}
	switch dir {
	case 'U':
		return lastHash[0] >= 'b' && lastHash[0] <= 'f'
	case 'D':
		return lastHash[1] >= 'b' && lastHash[1] <= 'f'
	case 'L':
		return lastHash[2] >= 'b' && lastHash[2] <= 'f'
	case 'R':
		return lastHash[3] >= 'b' && lastHash[3] <= 'f'
	}
	return false
}

func currPos(path string) (int, int) {
	var cX, cY int
	for i := range path {
		switch path[i] {
		case 'U':
			cY--
		case 'D':
			cY++
		case 'L':
			cX--
		case 'R':
			cX++
		}
	}
	return cX, cY
}

func printUsageAndExit() {
	fmt.Println("Usage: ./day17 <passcode> <gridWidth> <gridHeight>")
	os.Exit(0)
}
