package main

import (
	"fmt"
	"strings"
	"time"

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

var beam map[string]int
var maxY int

func main() {
	pt := helpers.GetArgNumber(1)
	prog := intcode.ReadIntCodeFile("input")
	beam = make(map[string]int)
	if pt == "1" {
		part1(prog)
	} else {
		part2(prog)
	}
}

func part1(prog []int) {
	var count int
	var lastRowStart int
	for y := 0; y < 50; y++ {
		var foundFirst bool
		fmt.Print(strings.Repeat(" ", lastRowStart))
		for x := lastRowStart; x < 50; x++ {
			out := runCodeAt(prog, x, y)
			fmt.Print(out)
			beam[c(x, y)] = out
			if out == 1 {
				if !foundFirst {
					lastRowStart = x
				}
				foundFirst = true
				count++
			} else if foundFirst {
				break
			}
		}
		fmt.Println()
		maxY = y
	}
	fmt.Println("Total:", count)
}

func part2(prog []int) {
	shipSize := 100
	// Guesses
	//x, y := 0, 1000
	var x, square int
	bot, _ := 1034, 1038
	for square != shipSize {
		bot++
		fmt.Print("Calculating ", bot, "... ")
		square, x = maxSquare(prog, bot, bot/2)
		fmt.Println(square)
	}
	fmt.Println(x, (bot+1)-100)
}

func part2Slow(prog []int) {
	shipSize := 100
	var x, y int
	retX, retY := -1, -1
	var lastRowStart int
	for y = maxY; true; y++ {
		var foundFirst bool
		fmt.Print(strings.Repeat(" ", lastRowStart))
		for x = lastRowStart; x < y; x++ {
			out := runCodeAt(prog, x, y)
			fmt.Print(out)
			beam[c(x, y)] = out
			if out == 1 {
				if !foundFirst {
					lastRowStart = x
				}
				foundFirst = true
			} else if foundFirst {
				break
			}
		}
		square, _ := maxSquare(prog, y, lastRowStart)
		fmt.Println(":", square)
		// Every row, check what the maximum square is
		if square >= shipSize {
			retY, retX = y, lastRowStart
			break
		}
	}
	fmt.Println(retX, ",", retY)
}

// maxSquare returns the square size and the bottom-left x position it starts at
func maxSquare(prog []int, row, firstX int) (int, int) {
	for ; firstX < row; firstX++ {
		// Find the first X
		if runCodeAt(prog, firstX, row) == 0 {
			continue
		}

		var square int
		for d := 0; true; d++ {
			if runCodeAt(prog, firstX+d, row-d) == 0 {
				return square, firstX
			}
			square++
		}
	}
	return 0, firstX
}

func runCodeAt(prog []int, x, y int) int {
	// If we've already cached this pos, just return it
	if v, ok := beam[c(x, y)]; ok {
		return v
	}

	var res int
	p := intcode.NewProgram(prog)
	go func() {
		sendInputs(p, []int{x, y})
		res = getOutput(p)
	}()
	p.Run()

	beam[c(x, y)] = res
	return res
}

func sendInputs(p *intcode.Program, inp []int) {
	for k := range inp {
		for !p.NeedsInput() {
			time.Sleep(1)
		}
		p.Input(inp[k])
	}
}

func getOutput(p *intcode.Program) int {
	for !p.NeedsOutput() {
		time.Sleep(1)
	}
	return p.Output()
}

func c(x, y int) string {
	return fmt.Sprintf("[%d, %d]", x, y)
}
