package intcodeprocessor

import (
	"fmt"
	"math"
	"strconv"
)

type OptProc struct {
	OriginalCode []int
	Code         []int
	Ptr          int
	RelBase      int

	State int
	Error error

	Pause            bool
	Bail             bool
	WaitingForInput  bool
	WaitingForOutput bool

	Debug       bool
	DebugToFile bool
}

func NewOptProc(prog []int) *OptProc {
	p := new(OptProc)
	p.State = RET_OK
	p.OriginalCode = make([]int, len(prog))
	max := math.MaxInt16
	if 2*len(prog) > max {
		max = 2 * len(prog)
	}
	p.Code = make([]int, max)
	copy(p.OriginalCode, prog)
	return p
}

func (p *OptProc) Run(in <-chan int, out chan<- int) int {
	p.Ptr = 0
	p.RelBase = 0
	copy(p.Code, p.OriginalCode)

	for p.State == RET_OK {
		ins := fmt.Sprintf("%05d", p.Code[p.Ptr])
		opcode, _ := strconv.Atoi(ins[3:])
		arg := func(i int) int {
			switch ins[3-i] {
			case '1': // Immediate mode
				return p.Ptr + i
			case '2': // Relative mode
				return p.RelBase + p.Code[p.Ptr+i]
			default: // 1, position mode
				return p.Code[p.Ptr+i]
			}
		}
		switch opcode {
		case OP_ADD:
			p.Code[arg(3)] = p.Code[arg(1)] + p.Code[arg(2)]
			p.Ptr = p.Ptr + 4
		case OP_MLT:
			p.Code[arg(3)] = p.Code[arg(1)] * p.Code[arg(2)]
			p.Ptr += 4
		case OP_INP:
			p.WaitingForInput = true
			v := <-in
			p.WaitingForInput = false
			p.Code[arg(1)] = v
			p.Ptr += 2

		case OP_OUT:
			p.WaitingForOutput = true
			out <- p.Code[arg(1)]
			p.WaitingForOutput = false
			p.Ptr += 2

		case OP_JIT:
			if p.Code[arg(1)] != 0 {
				p.Ptr = p.Code[arg(2)]
			} else {
				p.Ptr += 3
			}
		case OP_JIF:
			if p.Code[arg(1)] == 0 {
				p.Ptr = p.Code[arg(2)]
			} else {
				p.Ptr += 3
			}
		case OP_ILT:
			if p.Code[arg(1)] < p.Code[arg(2)] {
				p.Code[arg(3)] = 1
			} else {
				p.Code[arg(3)] = 0
			}
			p.Ptr += 4
		case OP_IEQ:
			if p.Code[arg(1)] == p.Code[arg(2)] {
				p.Code[arg(3)] = 1
			} else {
				p.Code[arg(3)] = 0
			}
			p.Ptr += 4
		case OP_RBS:
			p.RelBase += p.Code[arg(1)]
			p.Ptr += 2
		case OP_EXT:
			p.State = RET_DONE
		default:
			p.State = RET_ERR
		}
	}
	return p.State
}

func (p *OptProc) IsStopped() bool {
	return p.State != RET_OK
}
