package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

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

var auto bool

func main() {
	progFileName := "input"

	if len(os.Args) > 1 && os.Args[1] == "-auto" {
		auto = true
	}
	prog := readIntCodeFile(progFileName)
	play(prog)
}

func play(prog []int) {
	p := intcode.NewProgram(prog)
	// For part 1, comment out this line
	p.SetProgramValueAt(0, 2)
	//p.EnableDebug()
	maxX, maxY := 0, 0
	screen := make(map[string]int)
	var score int
	go func() {
		for {
			for !p.NeedsOutput() && !p.NeedsInput() {
				time.Sleep(1)
			}
			if p.NeedsOutput() {
				// There should be three outputs
				x := p.Output()
				y := waitForOutput(p)
				b := waitForOutput(p)
				if x > maxX {
					maxX = x
				}
				if y > maxY {
					maxY = y
				}
				if x == -1 && y == 0 {
					score = b
				} else {
					screen[coord(x, y)] = b
				}
			}
			if p.NeedsInput() {
				printScreen(screen, maxX, maxY, score)
				if auto {
					ballX, _ := findFirst(4, screen)
					pddlX, _ := findFirst(3, screen)
					if ballX < pddlX {
						p.Input(-1)
					} else if ballX > pddlX {
						p.Input(1)
					} else {
						p.Input(0)
					}
				} else {
					var gotInput bool
					for !gotInput {
						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":
							p.Input(-1)
							gotInput = true
						case "l":
							p.Input(1)
							gotInput = true
						case "j", "k":
							p.Input(0)
							gotInput = true
						}
					}
				}
			}
			switch p.State() {
			case intcode.RET_ERR:
				panic(p.Error())
			case intcode.RET_DONE:
				break
			}
		}
	}()
	ret := p.Run()
	if ret == intcode.RET_DONE {
		fmt.Println("DONE:", score)
	} else if ret == intcode.RET_ERR {
		fmt.Println("ERROR")
	}
	//part1(screen)
}

func lookForScore(p *intcode.Program, score int) {
	var res []int
	code := p.GetCode()
	for k, v := range code {
		if v == score {
			res = append(res, k)
		}
	}
	if len(res) == 1 {
		fmt.Println("Score Position:", res[0])
		os.Exit(0)
	}
}

func part1(s map[string]int) {
	var cnt int
	for _, v := range s {
		if v == 2 {
			cnt++
		}
	}
	fmt.Println(cnt, "blocks")
}

func printScreen(screen map[string]int, maxX, maxY, score int) {
	//fmt.Print(helpers.CLEAR_SCREEN)
	fmt.Println("Score:", score)
	for y := 0; y <= maxY; y++ {
		for x := 0; x <= maxX; x++ {
			v, ok := screen[coord(x, y)]
			if !ok {
				v = 0
			}
			switch v {
			case 0:
				fmt.Print(" ")
			case 1:
				fmt.Print("▉")
			case 2:
				fmt.Print("░")
			case 3:
				fmt.Print("═")
			case 4:
				fmt.Print("o")
			}
		}
		fmt.Println()
	}
}

func findFirst(val int, screen map[string]int) (int, int) {
	for k := range screen {
		if screen[k] == val {
			return revCoord(k)
		}
	}
	return -1, -1
}

// This will block waiting for output
func waitForOutput(p *intcode.Program) int {
	for !p.NeedsOutput() {
		time.Sleep(1)
	}
	return p.Output()
}

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

func revCoord(c string) (int, int) {
	var x, y int
	fmt.Sscanf(c, "[%d, %d]", &x, &y)
	return x, y
}

func readIntCodeFile(fn string) []int {
	dat, err := ioutil.ReadFile(fn)
	if err != nil {
		fmt.Println("Error reading program file:", err.Error())
		os.Exit(1)
	}
	var prog []int
	stringDat := strings.TrimSpace(string(dat))
	for _, v := range strings.Split(stringDat, ",") {
		prog = append(prog, helpers.Atoi(v))
	}
	return prog
}
