package main

import (
	"fmt"
	"strings"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

const (
	NOP = iota
	ACC
	JMP
)

type Instruction struct {
	OpCode  int
	Operand int
}

func CompileLine(inp string) Instruction {
	var ret Instruction
	pts := strings.Split(inp, " ")

	switch pts[0] {
	case "nop":
		ret.OpCode = NOP
	case "acc":
		ret.OpCode = ACC
	case "jmp":
		ret.OpCode = JMP
	}
	ret.Operand = h.Atoi(pts[1])
	return ret
}

type Program struct {
	Pointer      int
	Accumulator  int
	Instructions []Instruction
	Code         []string
	History      map[int]int
	BreakOnLoop  bool
	Verbose      bool
}

func NewProgram(inp []string) *Program {
	p := &Program{
		Pointer:     0,
		Accumulator: 0,
		Code:        inp,
		History:     make(map[int]int),
		BreakOnLoop: true,
		Verbose:     true,
	}
	p.Compile()
	return p
}

func (p *Program) Copy() *Program {
	cp := Program{
		Pointer:     p.Pointer,
		Accumulator: p.Accumulator,
		Code:        p.Code,
		History:     make(map[int]int),
		BreakOnLoop: p.BreakOnLoop,
		Verbose:     p.Verbose,
	}
	for k, v := range p.History {
		cp.History[k] = v
	}
	cp.Compile()
	return &cp
}

func (p *Program) Compile() {
	for k := range p.Code {
		p.Instructions = append(p.Instructions, CompileLine(p.Code[k]))
	}
}

func (p *Program) Run() int {
	for p.Pointer < len(p.Instructions) {
		if p.BreakOnLoop {
			if _, ok := p.History[p.Pointer]; ok {
				if p.Verbose {
					fmt.Printf("Instruction %d repeated. Acc: %d\n", p.Pointer, p.Accumulator)
				}
				return 1
			}
		}
		p.History[p.Pointer] = p.Accumulator
		switch p.Instructions[p.Pointer].OpCode {
		case NOP:
			p.Pointer++
		case ACC:
			p.Accumulator = p.Accumulator + p.Instructions[p.Pointer].Operand
			p.Pointer++
		case JMP:
			p.Pointer = p.Pointer + p.Instructions[p.Pointer].Operand
		}
	}
	if p.Verbose {
		fmt.Println("Done")
	}
	return 0
}

func (p *Program) CountOpCodes(tp ...int) int {
	var ret int
	for tpk := range tp {
		for k := range p.Instructions {
			if p.Instructions[k].OpCode == tp[tpk] {
				ret++
			}
		}
	}
	return ret
}

func (p *Program) FindNthOp(n int, tp ...int) int {
	for i := 0; i < len(p.Instructions); i++ {
		for _, tpv := range tp {
			if p.Instructions[i].OpCode == tpv {
				n--
			}
			if n == 0 {
				return i
			}
		}
	}
	return -1
}
