package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"git.annabunch.es/annabunches/adventofcode/2020/lib/util"
)

type Instruction struct {
	Operation string
	Operand   int
}

func parseInstruction(input string) Instruction {
	data := strings.Split(input, " ")
	op := data[0]
	operand, err := strconv.Atoi(strings.TrimPrefix(data[1], "+"))
	if err != nil {
		log.Panicf(err.Error())
	}
	return Instruction{
		Operation: op,
		Operand:   operand,
	}
}

func executeInstruction(ins Instruction, ip, acc *int, history map[int]bool) {
	history[*ip] = true
	switch ins.Operation {
	case "acc":
		*acc += ins.Operand
		*ip++
	case "jmp":
		*ip += ins.Operand
	case "nop":
		*ip++
	}
}

func executeProgram(instructions []Instruction) (int, bool) {
	ip := 0
	acc := 0
	history := make(map[int]bool)

	for ip < len(instructions) {
		if _, found := history[ip]; found {
			// detect infinite loop and return
			return acc, true
		}
		executeInstruction(instructions[ip], &ip, &acc, history)
	}

	return acc, false
}

func main() {
	step := os.Args[1]
	values := util.InputParserStrings(os.Args[2])

	instructions := make([]Instruction, 0)

	for _, line := range values {
		if line == "" {
			continue
		}
		ins := parseInstruction(line)
		instructions = append(instructions, ins)
	}

	switch step {
	case "1":
		ret, loop := executeProgram(instructions)
		if loop {
			fmt.Println("Loop detected. Last ACC value:", ret)
		}

	case "2":
		for i, ins := range instructions {
			if ins.Operation == "jmp" || ins.Operation == "nop" {
				newInstructions := make([]Instruction, len(instructions))
				copy(newInstructions, instructions)
				switch ins.Operation {
				case "jmp":
					newInstructions[i].Operation = "nop"
				case "nop":
					newInstructions[i].Operation = "jmp"
				}
				ret, loop := executeProgram(newInstructions)

				if !loop {
					fmt.Println(ret)
					break
				}
			}
		}
	}
}