package main

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

const (
	MaxInt = int(^uint(0) >> 1)
	MinInt = -MaxInt - 1
)

var timeMachine *TimeMachine

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: day11 <serial #>")
		os.Exit(1)
	}
	timeMachine = &TimeMachine{
		serial:     Atoi(os.Args[1]),
		fuelLevels: make(map[string]int),
	}
	timeMachine.fillGrid()
	part1()
	part2()
}

func part1() {
	top := MinInt
	topX, topY := 0, 0
	for i := 1; i < 300; i++ {
		for j := 1; j < 300; j++ {
			w := timeMachine.getSquareFuelLevel(i, j, 3)
			if w > top {
				top = w
				topX, topY = i, j
			}
		}
	}
	fmt.Printf("%d,%d : %d\n", topX, topY, top)
	fmt.Println("= Part 1 =")
	timeMachine.printSquare(topX, topY, 3)
}

func part2() {
	top := MinInt
	topX, topY, topS := 1, 1, 1
	for i := 1; i <= 300; i++ {
		for j := 1; j <= 300; j++ {
			maxSize := 300 - (i - 1)
			if i < j {
				maxSize = 300 - (j - 1)
			}
			for s := 1; s <= maxSize; s++ {
				wrk := timeMachine.getSquareFuelLevel(i, j, s)
				if wrk > top {
					top = wrk
					topX, topY, topS = i, j, s
				}
			}
		}
	}
	fmt.Println("= Part 2 =")
	fmt.Printf("%d,%d,%d : %d\n", topX, topY, topS, top)
}

type TimeMachine struct {
	serial     int
	fuelLevels map[string]int
}

func (t *TimeMachine) printSquare(x, y, size int) {
	for j := y; j < y+size; j++ {
		for i := x; i < x+size; i++ {
			fmt.Printf("%2d ", t.getSquareFuelLevel(i, j, 1))
		}
		fmt.Println("")
	}
}

func (t *TimeMachine) fillGrid() {
	for i := 1; i <= 300; i++ {
		for j := 1; j <= 300; j++ {
			t.fuelLevels[t.getKeyForSquare(i, j, 1)] = t.calcFuelLevel(i, j)
		}
	}
}

func (t *TimeMachine) getKeyForSquare(x, y, size int) string {
	bx := strconv.Itoa(x)
	by := strconv.Itoa(y)
	bs := strconv.Itoa(size)
	return bx + "," + by + "," + bs
}

func (t *TimeMachine) getSquareFuelLevel(x, y, size int) int {
	k := t.getKeyForSquare(x, y, size)
	if v, ok := t.fuelLevels[k]; ok {
		return v
	}
	if size == 1 {
		// We shouldn't be here if we filled the grid before running
		return 0
	}
	var ret int
	ret = t.getSquareFuelLevel(x, y, size-1)
	// Add the right-most cells
	for i := 0; i < size; i++ {
		ret += t.calcFuelLevel(x+(size-1), y+i)
	}
	// Add the bottom cells
	for i := 0; i < size-1; i++ {
		ret += t.calcFuelLevel(x+i, y+(size-1))
	}
	t.fuelLevels[k] = ret
	return ret
}

// calcFuelLevel calculates one cells fuel level
func (t *TimeMachine) calcFuelLevel(x, y int) int {
	rackId := x + 10
	startingLevel := rackId * y
	currLevel := startingLevel + t.serial
	currLevel *= rackId
	var ret int
	if currLevel < 100 {
		ret = -5
	} else {
		ret = ((currLevel / 100) % 10) - 5
	}
	return ret
}

// calcSquareFuelLevel calculates all cells from x, y -> x+size, y+size
func (t *TimeMachine) calcSquareFuelLevel(x, y, size int) int {
	var ret int
	for i := 0; i < size; i++ {
		for j := 0; j < size; j++ {
			ret += t.calcFuelLevel(x+i, y+j)
		}
	}
	return ret
}

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
}
