package main

import (
	"bufio"
	"fmt"
	"os"
	"time"
)

type ArgumentType int

const (
	ArgumentTypeNone ArgumentType = iota
	ArgumentTypeRegister
	ArgumentTypeValue
)

type Opcode struct {
	Name string
	A    ArgumentType
	B    ArgumentType
	Exe  func(a, b int) int
}

var opcodes = []Opcode{
	{
		Name: "banr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeRegister,
		Exe:  ban,
	},
	{
		Name: "eqrr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeRegister,
		Exe:  eq,
	},
	{
		Name: "setr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeNone,
		Exe:  set,
	},
	{
		Name: "eqir",
		A:    ArgumentTypeValue,
		B:    ArgumentTypeRegister,
		Exe:  eq,
	},
	{
		Name: "bori",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeValue,
		Exe:  bor,
	},
	{
		Name: "muli",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeValue,
		Exe:  mul,
	},
	{
		Name: "bani",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeValue,
		Exe:  ban,
	},
	{
		Name: "borr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeRegister,
		Exe:  bor,
	},
	{
		Name: "gtir",
		A:    ArgumentTypeValue,
		B:    ArgumentTypeRegister,
		Exe:  gt,
	},
	{
		Name: "gtrr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeRegister,
		Exe:  gt,
	},
	{
		Name: "addi",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeValue,
		Exe:  add,
	},
	{
		Name: "gtri",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeValue,
		Exe:  gt,
	},
	{
		Name: "eqri",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeValue,
		Exe:  eq,
	},
	{
		Name: "addr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeRegister,
		Exe:  add,
	},
	{
		Name: "mulr",
		A:    ArgumentTypeRegister,
		B:    ArgumentTypeRegister,
		Exe:  mul,
	},
	{
		Name: "seti",
		A:    ArgumentTypeValue,
		B:    ArgumentTypeNone,
		Exe:  set,
	},
}

func add(a, b int) int {
	return a + b
}

func mul(a, b int) int {
	return a * b
}

func ban(a, b int) int {
	return a & b
}

func bor(a, b int) int {
	return a | b
}

func set(a, b int) int {
	return a
}

func gt(a, b int) int {
	if a > b {
		return 1
	}
	return 0
}

func eq(a, b int) int {
	if a == b {
		return 1
	}
	return 0
}

func waitEnter() {
	bufio.NewReader(os.Stdin).ReadBytes('\n')
}

type Test struct {
	Before [4]int
	Op     [4]int
	After  [4]int
}

func sliceEqual(a, b []int) bool {
	if len(a) != len(b) {
		return false
	}
	for t := range a {
		if a[t] != b[t] {
			return false
		}
	}
	return true
}

type Puzzle struct {
	Regs  [6]int
	ROM   []int
	IP    int
	IPreg int
}

func (p *Puzzle) RunProgram() {
	oldRegs := [6]int{}
	regs := [6]rune{}
	ticks := 0
	start := time.Now()
	for {
		if p.IP < 0 || p.IP*4 >= len(p.ROM) {
			fmt.Println("PANIC! Access OOB @", p.IP, "after", ticks, "iterations")
			return
		}

		copy(oldRegs[:], p.Regs[:])

		p.Regs[p.IPreg] = p.IP
		op := p.ROM[p.IP*4 : (p.IP*4)+4]
		o := opcodes[op[0]]
		a := op[1]
		b := op[2]
		c := op[3]
		if o.A == ArgumentTypeRegister {
			a = p.Regs[a]
		}
		if o.B == ArgumentTypeRegister {
			b = p.Regs[b]
		}

		p.Regs[c] = o.Exe(a, b)
		p.IP = p.Regs[p.IPreg]

		p.IP++
		ticks++

		for t := range regs {
			if oldRegs[t] == p.Regs[t] {
				regs[t] = '.'
				continue
			}
			regs[t] = '*'
		}
		if ticks%10000000 == 0 {
			fmt.Println(time.Now().Sub(start), ticks)
		}
		//p.PrintRegs()
		fmt.Printf("%3c %3c %3c %3c %3c %3c <- %d\n", regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], ticks)
	}
}

func (p *Puzzle) PrintRegs() {
	fmt.Println("Regs:", p.Regs)
}

func main() {
	// Program 1
	if true {
		fmt.Println("Program 1\n---")
		p := Puzzle{}

		input, err := os.Open("input.txt")
		if err != nil {
			panic(err.Error())
		}
		defer input.Close()
		scanner := bufio.NewScanner(input)

		for scanner.Scan() {
			var op string
			var a, b, c int
			fmt.Sscanf(scanner.Text(), "%s %d %d %d", &op, &a, &b, &c)

			if op == "#ip" {
				p.IPreg = a
				continue
			}

			for k, o := range opcodes {
				if o.Name == op {
					p.ROM = append(p.ROM, k, a, b, c)
					break
				}
			}

			fmt.Println(op, a, b, c)
		}
		if err := scanner.Err(); err != nil {
			panic(err.Error())
		}

		fmt.Printf("\n")
		p.RunProgram()
		p.PrintRegs()
	}

	// Program 2
	// NOTE: This runs for a looooong time, time to reverseengineer
	if false {
		fmt.Println("\nProgram 2\n---")
		p := Puzzle{}
		p.Regs[0] = 1

		// Bruteforcing after lengthy debugging and trying to understanding what the assembly is doing
		//p.Regs = [6]int{0, 0, 1, 9, 10551326, 10551325}
		//p.Regs = [6]int{1, 0, 10551326, 9, 10551326, 10551326}
		//p.Regs = [6]int{1, 0, 10551326, 9, 10551326, 10551325}
		//p.IP = 9

		input, err := os.Open("input.txt")
		if err != nil {
			panic(err.Error())
		}
		defer input.Close()
		scanner := bufio.NewScanner(input)

		for scanner.Scan() {
			var op string
			var a, b, c int
			fmt.Sscanf(scanner.Text(), "%s %d %d %d", &op, &a, &b, &c)

			if op == "#ip" {
				p.IPreg = a
				continue
			}

			for k, o := range opcodes {
				if o.Name == op {
					p.ROM = append(p.ROM, k, a, b, c)
					break
				}
			}

			fmt.Println(op, a, b, c)
		}
		if err := scanner.Err(); err != nil {
			panic(err.Error())
		}

		fmt.Printf("\n")
		p.RunProgram()
		p.PrintRegs()
	}
	return

	// Attempt to re-impl algo
	var r0, r1, r2, r3, r4, r5 int

	r0 = 1
	r4 += 2
	r4 *= r4
	r4 *= 19
	r4 *= 11
	r1 += 4
	r1 += 22
	r1 += 2
	r4 += r1

	r1 = 27
	r1 *= 28
	r1 += 29
	r1 *= 30
	r1 *= 14
	r1 *= 32
	r4 += r1
	r0 = 0

	//start
	r2 = 1
	//start1
	r5 = 1
	//start3
	tick := 0
	for {
		r1 = r2 * r5
		if r1 == r4 {
			//check
			if r5 > r4 {
				//skip
				r2++
				if r2 > r4 {
					fmt.Println("DONE", r0, r1, r2, r3, r4, r5)
					return
				} else {
					r5 = 1
				}
			}
		} else {
			r5++
		}

		if tick%10000 == 0 {
			fmt.Println(tick, "-", r0, r1, r2, r3, r4, r5)
		}
		tick++
	}
}