package main

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

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

// Pass the program name and the problem part number in parameters
// Default: "input", and part 2
func main() {
	progFileName := "input"
	part := 2
	if len(os.Args) > 1 {
		progFileName = os.Args[1]
		if len(os.Args) == 3 {
			part = helpers.Atoi(os.Args[2])
		}
	}
	dat, err := ioutil.ReadFile(progFileName)
	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))
	}
	// Pass the program and the problem part to solve:
	solve(prog, part)
}

func solve(inp []int, pt int) {
	painter := NewPainter()
	if pt == 2 {
		painter.Paint(1)
	}
	p := intcode.NewProgram(inp)
	go func() {
		for {
			for !p.NeedsInput() {
				time.Sleep(1)
			}
			p.Input(painter.GetPanelColor())
			for !p.NeedsOutput() {
				time.Sleep(1)
			}
			painter.Paint(p.Output())
			for !p.NeedsOutput() {
				time.Sleep(1)
			}
			painter.Turn(p.Output())
			switch p.State() {
			case intcode.RET_ERR:
				panic(p.Error())
			case intcode.RET_DONE:
				break
			}
		}
	}()
	res := p.Run()
	if res == intcode.RET_DONE {
		fmt.Println("DONE")
	} else if res == intcode.RET_ERR {
		fmt.Println("ERROR")
	}
	painter.Print()
	fmt.Println("Painted Panels:", len(painter.panels))
	fmt.Println("Paint Used:", painter.paintCount)
}

const (
	DIR_N = iota
	DIR_E
	DIR_S
	DIR_W
)

type Painter struct {
	dir        int
	loc        helpers.Coordinate
	panels     map[string]int
	paintCount int
	minX, minY int
	maxX, maxY int

	panelMutex *sync.Mutex
}

func NewPainter() *Painter {
	p := Painter{
		dir:        DIR_N,
		loc:        helpers.Coordinate{X: 0, Y: 0},
		panels:     make(map[string]int),
		panelMutex: &sync.Mutex{},
	}
	return &p
}

func (p *Painter) GetPanelColor() int {
	if v, ok := p.panels[p.loc.String()]; ok {
		return v
	}
	return 0
}

func (p *Painter) Paint(color int) {
	p.panelMutex.Lock()
	p.panels[p.loc.String()] = color
	p.paintCount++
	p.panelMutex.Unlock()
}

func (p *Painter) Turn(dir int) {
	if dir == 0 {
		p.dir--
	} else {
		p.dir++
	}
	if p.dir == DIR_W+1 {
		p.dir = DIR_N
	} else if p.dir == DIR_N-1 {
		p.dir = DIR_W
	}
	// After turning we move forward
	switch p.dir {
	case DIR_N:
		p.loc.Y--
	case DIR_E:
		p.loc.X++
	case DIR_S:
		p.loc.Y++
	case DIR_W:
		p.loc.X--
	}
	if p.loc.X < p.minX {
		p.minX = p.loc.X
	}
	if p.loc.X > p.maxX {
		p.maxX = p.loc.X
	}
	if p.loc.Y < p.minY {
		p.minY = p.loc.Y
	}
	if p.loc.Y > p.maxY {
		p.maxY = p.loc.Y
	}
}

func (p *Painter) Print() {
	for x := p.minX; x <= p.maxX; x++ {
		for y := p.minY; y <= p.maxY; y++ {
			c := helpers.Coordinate{x, y}
			if c.X == p.loc.X && c.Y == p.loc.Y {
				switch p.dir {
				case DIR_N:
					fmt.Print("^")
				case DIR_E:
					fmt.Print(">")
				case DIR_S:
					fmt.Print("v")
				case DIR_W:
					fmt.Print("<")
				}
			} else {
				p.panelMutex.Lock()
				if v, ok := p.panels[c.String()]; ok {
					if v == 1 {
						fmt.Print("#")
					} else {
						fmt.Print(".")
					}
				} else {
					fmt.Print(".")
				}
				p.panelMutex.Unlock()
			}
		}
		fmt.Println()
	}
}
