package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage:	day03 <target>")
		os.Exit(0)
	}
	target := Atoi(os.Args[1])
	fmt.Println("== Part 1 ==")
	part1(target)
	fmt.Println("== Part 2 ==")
	part2(target)
}

func part1(target int) {
	idx := 1
	lyrSz := 1
	sizeSquare := int(math.Pow(float64(lyrSz), 2))
	for sizeSquare < target {
		lyrSz += 2
		idx += 1
		sizeSquare = int(math.Pow(float64(lyrSz), 2))
	}
	diff := sizeSquare - target
	lyrJmp := lyrSz - 1
	// Find the side it's on
	side := int(diff / lyrJmp)
	perps := make([]int, 4)
	perps[0] = sizeSquare - (lyrJmp / 2)
	perps[1] = perps[0] - lyrJmp
	perps[2] = perps[1] - lyrJmp
	perps[3] = perps[2] - lyrJmp
	fmt.Println("Distance: ", int(math.Abs(float64(perps[side]-target)))+(idx-1))
}

const (
	DIR_E = iota
	DIR_N
	DIR_W
	DIR_S
	DIR_ERROR
)

var memMap map[string]int
var lastX, lastY int
var lastDir int

func part2(target int) {
	lastDir = DIR_S
	lastX, lastY = 0, 0

	val := 1
	memMap = make(map[string]int)
	memMap[getMapKey(lastX, lastY)] = val
	for val <= target {
		// Find the coordinate for the next one
		lastX, lastY, lastDir = findNextMapPos()
		val = getAdjacentSum(lastX, lastY)
		memMap[getMapKey(lastX, lastY)] = val
	}
	fmt.Println("Result:", val, "at", getMapKey(lastX, lastY))
}

func getAdjacentSum(posX, posY int) int {
	res := 0
	for _, x := range []int{-1, 0, 1} {
		for _, y := range []int{-1, 0, 1} {
			if x == 0 && y == 0 {
				continue
			}
			if i, ok := memMap[getMapKey((posX+x), (posY+y))]; ok {
				res += i
			}
		}
	}
	return res
}

func findNextMapPos() (int, int, int) {
	// First check if the current pos is empty
	if _, ok := memMap[getMapKey(lastX, lastY)]; !ok {
		return lastX, lastY, lastDir
	}
	// Check if the wrapping direction is free
	nextDir := (lastDir + 1) % DIR_ERROR
	testX, testY := getXYInDir(nextDir)
	if _, ok := memMap[getMapKey(testX, testY)]; ok {
		testX, testY = getXYInDir(lastDir)
		nextDir = lastDir
	}
	// Otherwise continue in lastDir
	return testX, testY, nextDir
}

func getXYInDir(dir int) (int, int) {
	testX, testY := lastX, lastY
	switch dir {
	case DIR_E:
		testX++
	case DIR_N:
		testY--
	case DIR_W:
		testX--
	case DIR_S:
		testY++
	}
	return testX, testY
}

func getMapKey(xPos, yPos int) string {
	return fmt.Sprintf("%d;%d", xPos, yPos)
}

func Atoi(i string) int {
	var ret int
	var err error
	if ret, err = strconv.Atoi(i); err != nil {
		log.Fatal("Invalid Atoi")
	}
	return ret
}
