package core6502

import (
	"encoding/binary"
	"fmt"
	"io"
)

// https://www.masswerk.at/6502/6502_instruction_set.html
// http://www.emulator101.com/reference/6502-reference.html
// https://www.csh.rit.edu/~moffitt/docs/6502.html#FLAGS
// https://ia800509.us.archive.org/18/items/Programming_the_6502/Programming_the_6502.pdf

// State represents the state of the simulated device
type State struct {
	reg     registers
	mem     Memory
	cycles  uint64
	opcodes *[256]opcode
	trace   bool
}

const (
	vectorNMI   uint16 = 0xfffa
	vectorReset uint16 = 0xfffc
	vectorBreak uint16 = 0xfffe
)

type opcode struct {
	name        string
	bytes       uint16
	cycles      int
	addressMode int
	action      opFunc
}

type opFunc func(s *State, line []uint8, opcode opcode)

func (s *State) executeLine(line []uint8) {
	opcode := s.opcodes[line[0]]
	if opcode.cycles == 0 {
		panic(fmt.Sprintf("Unknown opcode 0x%02x\n", line[0]))
	}
	opcode.action(s, line, opcode)
}

// ExecuteInstruction transforms the state given after a single instruction is executed.
func (s *State) ExecuteInstruction() {
	pc := s.reg.getPC()
	opcodeID := s.mem.Peek(pc)
	opcode := s.opcodes[opcodeID]

	if opcode.cycles == 0 {
		panic(fmt.Sprintf("Unknown opcode 0x%02x\n", opcodeID))
	}

	line := make([]uint8, opcode.bytes)
	for i := uint16(0); i < opcode.bytes; i++ {
		line[i] = s.mem.Peek(pc)
		pc++
	}
	s.reg.setPC(pc)

	if s.trace {
		//fmt.Printf("%#04x %#02x\n", pc-opcode.bytes, opcodeID)
		fmt.Printf("%#04x %-13s: ", pc-opcode.bytes, lineString(line, opcode))
	}
	opcode.action(s, line, opcode)
	s.cycles += uint64(opcode.cycles)
	if s.trace {
		fmt.Printf("%v, [%02x]\n", s.reg, line)
	}
}

// Reset resets the processor state. Moves the program counter to the vector in 0cfffc.
func (s *State) Reset() {
	startAddress := getWord(s.mem, vectorReset)
	s.cycles = 0
	s.reg.setPC(startAddress)
}

// GetCycles returns the count of CPU cycles since last reset.
func (s *State) GetCycles() uint64 {
	return s.cycles
}

// SetTrace activates tracing of the cpu execution
func (s *State) SetTrace(trace bool) {
	s.trace = trace
}

// GetTrace gets trhe tracing state of the cpu execution
func (s *State) GetTrace() bool {
	return s.trace
}

// Save saves the CPU state (registers and cycle counter)
func (s *State) Save(w io.Writer) error {
	err := binary.Write(w, binary.BigEndian, s.cycles)
	if err != nil {
		return err
	}
	binary.Write(w, binary.BigEndian, s.reg.data)
	if err != nil {
		return err
	}
	return nil
}

// Load loads the CPU state (registers and cycle counter)
func (s *State) Load(r io.Reader) error {
	err := binary.Read(r, binary.BigEndian, &s.cycles)
	if err != nil {
		return err
	}
	err = binary.Read(r, binary.BigEndian, &s.reg.data)
	if err != nil {
		return err
	}
	return nil
}
