package i6502

import "fmt"

/*
The Cpu only contains the AddressBus, through which 8-bit values can be read and written
at 16-bit addresses.

The Cpu has an 8-bit accumulator (A) and two 8-bit index registers (X,Y). There is a 16-bit
Program Counter (PC) and an 8-bit Stack Pointer (SP), pointing to addresses in 0x0100-01FF.

The status register (P) contains flags for Zero, Negative, Break, Decimal, IrqDisable,
Carry and Overflow flags.
*/
type Cpu struct {
	A byte // Accumulator
	X byte // Index register X
	Y byte // Index register Y

	PC uint16 // 16-bit program counter
	P  byte   // Status Register
	SP byte   // Stack Pointer

	Bus *AddressBus // The address bus
}

const (
	ZeropageBase = 0x0000 // 0x0000-00FF Reserved for zeropage instructions
	StackBase    = 0x0100 // 0x0100-01FF Reserved for stack

	ResetVector = 0xFFFC // 0xFFFC-FFFD
	IrqVector   = 0xFFFE // 0xFFFE-FFFF

)

// Create an new Cpu, using the AddressBus for accessing memory.
func NewCpu(bus *AddressBus) (*Cpu, error) {
	return &Cpu{Bus: bus}, nil
}

// Returns a string containing the current state of the CPU.
func (c *Cpu) String() string {
	str := ">>> CPU [  A ] [  X ] [  Y ] [ SP ] [  PC  ] NVxBDIZC\n>>>      0x%02X   0x%02X   0x%02X   0x%02X   0x%04X  %08b\n"
	return fmt.Sprintf(str, c.A, c.X, c.Y, c.SP, c.PC, c.P)
}

/*
Reset the CPU, emulating the RESB pin.

The status register is reset to a know state (0x34, IrqDisabled set, Decimal unset, Break set).

Then the Program Counter is set to the value read from `ResetVector` (0xFFFC-FFFD).

Normally, no assumptions can be made about registers (A, X, Y) and the
Stack Pointer. For convenience, these are reset to 0x00 (A,X,Y) and 0xFF (SP).
*/
func (c *Cpu) Reset() {
	c.PC = c.Bus.Read16(ResetVector)
	c.P = 0x34

	// Not specified, but let's clean up
	c.A = 0x00
	c.X = 0x00
	c.Y = 0x00
	c.SP = 0xFF
}

/*
Simulate the IRQ pin.

This will push the current Cpu state to the stack (P + PC) and set the PC
to the address read from the `IrqVector` (0xFFFE-FFFF)
*/
func (c *Cpu) Interrupt() {
	c.handleIrq(c.PC)
}

// Handles an interrupt or BRK.
func (c *Cpu) handleIrq(PC uint16) {
	c.stackPush(byte(PC >> 8))
	c.stackPush(byte(PC))
	c.stackPush(c.P)

	c.setIrqDisable(true)

	c.PC = c.Bus.Read16(IrqVector)
}

// Load the specified program data at the given memory location
// and point the Program Counter to the beginning of the program.
func (c *Cpu) LoadProgram(data []byte, location uint16) {
	for i, b := range data {
		c.Bus.WriteByte(location+uint16(i), b)
	}

	c.PC = location
}

func (c *Cpu) Steps(steps int) {
	for i := 0; i < steps; i++ {
		c.Step()
	}
}

// Read and execute the instruction pointed to by the Program Counter (PC)
func (c *Cpu) Step() {
	instruction := c.readNextInstruction()
	c.PC += uint16(instruction.Size)
	c.execute(instruction)
}

// Handle the execution of an instruction
func (c *Cpu) execute(instruction Instruction) {
	switch instruction.opcodeId {
	case nop:
		break
	case adc:
		c.adc(instruction)
	case sbc:
		c.sbc(instruction)
	case sec:
		c.setCarry(true)
	case sed:
		c.setDecimal(true)
	case sei:
		c.setIrqDisable(true)
	case clc:
		c.setCarry(false)
	case cld:
		c.setDecimal(false)
	case cli:
		c.setIrqDisable(false)
	case clv:
		c.setOverflow(false)
	case inx:
		c.setX(c.X + 1)
	case iny:
		c.setY(c.Y + 1)
	case inc:
		c.inc(instruction)
	case dex:
		c.setX(c.X - 1)
	case dey:
		c.setY(c.Y - 1)
	case dec:
		c.dec(instruction)
	case lda:
		value := c.resolveOperand(instruction)
		c.setA(value)
	case ldx:
		value := c.resolveOperand(instruction)
		c.setX(value)
	case ldy:
		value := c.resolveOperand(instruction)
		c.setY(value)
	case ora:
		value := c.resolveOperand(instruction)
		c.setA(c.A | value)
	case and:
		value := c.resolveOperand(instruction)
		c.setA(c.A & value)
	case eor:
		value := c.resolveOperand(instruction)
		c.setA(c.A ^ value)
	case sta:
		address := c.memoryAddress(instruction)
		c.Bus.WriteByte(address, c.A)
	case stx:
		address := c.memoryAddress(instruction)
		c.Bus.WriteByte(address, c.X)
	case sty:
		address := c.memoryAddress(instruction)
		c.Bus.WriteByte(address, c.Y)
	case tax:
		c.setX(c.A)
	case tay:
		c.setY(c.A)
	case txa:
		c.setA(c.X)
	case tya:
		c.setA(c.Y)
	case tsx:
		c.setX(c.SP)
	case txs:
		c.SP = c.X
	case asl:
		c.asl(instruction)
	case lsr:
		c.lsr(instruction)
	case rol:
		c.rol(instruction)
	case ror:
		c.ror(instruction)
	case cmp:
		value := c.resolveOperand(instruction)
		c.setCarry(c.A >= value)
		c.setArithmeticFlags(c.A - value)
	case cpx:
		value := c.resolveOperand(instruction)
		c.setCarry(c.X >= value)
		c.setArithmeticFlags(c.X - value)
	case cpy:
		value := c.resolveOperand(instruction)
		c.setCarry(c.Y >= value)
		c.setArithmeticFlags(c.Y - value)
	case brk:
		c.setBreak(true)
		c.handleIrq(c.PC + 1)
	case bcc:
		if !c.getCarry() {
			c.branch(instruction)
		}
	case bcs:
		if c.getCarry() {
			c.branch(instruction)
		}
	case bne:
		if !c.getZero() {
			c.branch(instruction)
		}
	case beq:
		if c.getZero() {
			c.branch(instruction)
		}
	case bpl:
		if !c.getNegative() {
			c.branch(instruction)
		}
	case bmi:
		if c.getNegative() {
			c.branch(instruction)
		}
	case bvc:
		if !c.getOverflow() {
			c.branch(instruction)
		}
	case bvs:
		if c.getOverflow() {
			c.branch(instruction)
		}
	case bit:
		value := c.resolveOperand(instruction)
		c.setNegative((value & 0x80) != 0)
		c.setOverflow((value & 0x40) != 0)
		c.setZero((c.A & value) == 0)
	case php:
		c.stackPush(c.P | 0x30)
	case plp:
		c.setP(c.stackPop())
	case pha:
		c.stackPush(c.A)
	case pla:
		value := c.stackPop()
		c.setA(value)
	case jmp:
		c.PC = c.memoryAddress(instruction)
	case jsr:
		c.stackPush(byte((c.PC - 1) >> 8))
		c.stackPush(byte(c.PC - 1))
		c.PC = c.memoryAddress(instruction)
	case rts:
		c.PC = (uint16(c.stackPop()) | uint16(c.stackPop())<<8) + 1
	case rti:
		c.setP(c.stackPop())
		c.PC = uint16(c.stackPop()) | uint16(c.stackPop())<<8
	default:
		panic(fmt.Errorf("Unimplemented instruction: %s", instruction))
	}
}

func (c *Cpu) readNextInstruction() Instruction {
	// Read the opcode
	opcode := c.Bus.ReadByte(c.PC)

	optype, ok := opTypes[opcode]
	if !ok {
		panic(fmt.Sprintf("Unknown or unimplemented opcode 0x%02X\n%s", opcode, c.String()))
	}

	instruction := Instruction{OpType: optype, Address: c.PC}
	switch instruction.Size {
	case 1: // Zero operand instruction
	case 2: // 8-bit operand
		instruction.Op8 = c.Bus.ReadByte(c.PC + 1)
	case 3: // 16-bit operand
		instruction.Op16 = c.Bus.Read16(c.PC + 1)
	}

	return instruction
}

func (c *Cpu) branch(in Instruction) {
	relative := int8(in.Op8) // Signed!
	if relative >= 0 {
		c.PC += uint16(relative)
	} else {
		c.PC -= -uint16(relative)
	}
}

func (c *Cpu) resolveOperand(in Instruction) uint8 {
	switch in.addressingId {
	case immediate:
		return in.Op8
	default:
		return c.Bus.ReadByte(c.memoryAddress(in))
	}
}

func (c *Cpu) memoryAddress(in Instruction) uint16 {
	switch in.addressingId {
	case absolute:
		return in.Op16
	case absoluteX:
		return in.Op16 + uint16(c.X)
	case absoluteY:
		return in.Op16 + uint16(c.Y)
	case indirect:
		return c.Bus.Read16(in.Op16)
	case indirectX:
		return c.Bus.Read16(uint16(in.Op8 + c.X))
	case indirectY:
		return c.Bus.Read16(uint16(in.Op8)) + uint16(c.Y)
	case relative:
		panic("Relative addressing not yet implemented.")
	case zeropage:
		return uint16(in.Op8)
	case zeropageX:
		return uint16(in.Op8 + c.X)
	case zeropageY:
		return uint16(in.Op8 + c.Y)
	default:
		panic(fmt.Errorf("Unhandled addressing mode. Are you sure you are running a 6502 ROM?"))
	}
}

// Add Memory to Accumulator with Carry
func (c *Cpu) adc(in Instruction) {
	operand := c.resolveOperand(in)
	carryIn := c.getCarryInt()

	if c.getDecimal() {
		c.adcDecimal(c.A, operand, carryIn)
	} else {
		c.adcNormal(c.A, operand, carryIn)
	}
}

// Substract memory from Accummulator with carry
func (c *Cpu) sbc(in Instruction) {
	operand := c.resolveOperand(in)
	carryIn := c.getCarryInt()

	// fmt.Printf("SBC: A: 0x%02X V: 0x%02X C: %b D: %v\n", c.A, operand, carryIn, c.getDecimal())

	if c.getDecimal() {
		c.sbcDecimal(c.A, operand, carryIn)
	} else {
		c.adcNormal(c.A, ^operand, carryIn)
	}
}

func (c *Cpu) inc(in Instruction) {
	address := c.memoryAddress(in)
	value := c.Bus.ReadByte(address) + 1

	c.Bus.WriteByte(address, value)
	c.setArithmeticFlags(value)
}

func (c *Cpu) dec(in Instruction) {
	address := c.memoryAddress(in)
	value := c.Bus.ReadByte(address) - 1

	c.Bus.WriteByte(address, value)
	c.setArithmeticFlags(value)
}

func (c *Cpu) asl(in Instruction) {
	switch in.addressingId {
	case accumulator:
		c.setCarry((c.A >> 7) == 1)
		c.A <<= 1
		c.setArithmeticFlags(c.A)
	default:
		address := c.memoryAddress(in)
		value := c.Bus.ReadByte(address)
		c.setCarry((value >> 7) == 1)
		value <<= 1
		c.Bus.WriteByte(address, value)
		c.setArithmeticFlags(value)
	}
}

func (c *Cpu) lsr(in Instruction) {
	switch in.addressingId {
	case accumulator:
		c.setCarry((c.A & 0x01) == 1)
		c.A >>= 1
		c.setArithmeticFlags(c.A)
	default:
		address := c.memoryAddress(in)
		value := c.Bus.ReadByte(address)
		c.setCarry((value & 0x01) == 1)
		value >>= 1
		c.Bus.WriteByte(address, value)
		c.setArithmeticFlags(value)
	}
}

func (c *Cpu) rol(in Instruction) {
	carry := c.getCarryInt()

	switch in.addressingId {
	case accumulator:
		c.setCarry((c.A & 0x80) != 0)
		c.A = c.A<<1 | carry
		c.setArithmeticFlags(c.A)
	default:
		address := c.memoryAddress(in)
		value := c.Bus.ReadByte(address)
		c.setCarry((value & 0x80) != 0)
		value = value<<1 | carry
		c.Bus.WriteByte(address, value)
		c.setArithmeticFlags(value)
	}
}

func (c *Cpu) ror(in Instruction) {
	carry := c.getCarryInt()

	switch in.addressingId {
	case accumulator:
		c.setCarry(c.A&0x01 == 1)
		c.A = c.A>>1 | carry<<7
		c.setArithmeticFlags(c.A)
	default:
		address := c.memoryAddress(in)
		value := c.Bus.ReadByte(address)
		c.setCarry(value&0x01 == 1)
		value = value>>1 | carry<<7
		c.Bus.WriteByte(address, value)
		c.setArithmeticFlags(value)
	}
}

// Performs regular, 8-bit addition
func (c *Cpu) adcNormal(a uint8, b uint8, carryIn uint8) {
	result16 := uint16(a) + uint16(b) + uint16(carryIn)
	result := uint8(result16)
	carryOut := (result16 & 0x100) != 0
	overflow := (a^result)&(b^result)&0x80 != 0

	// Set the carry flag if we exceed 8-bits
	c.setCarry(carryOut)
	// Set the overflow bit
	c.setOverflow(overflow)
	// Store the resulting value (8-bits)
	c.setA(result)
}

// Performs addition in decimal mode
func (c *Cpu) adcDecimal(a uint8, b uint8, carryIn uint8) {
	var carryB uint8 = 0

	low := (a & 0x0F) + (b & 0x0F) + carryIn
	if (low & 0xFF) > 9 {
		low += 6
	}
	if low > 15 {
		carryB = 1
	}

	high := (a >> 4) + (b >> 4) + carryB
	if (high & 0xFF) > 9 {
		high += 6
	}

	result := (low & 0x0F) | (high<<4)&0xF0

	c.setCarry(high > 15)
	c.setZero(result == 0)
	c.setNegative(false) // BCD never sets negative
	c.setOverflow(false) // BCD never sets overflow

	c.A = result
}

func (c *Cpu) sbcDecimal(a uint8, b uint8, carryIn uint8) {
	var carryB uint8 = 0

	if carryIn == 0 {
		carryIn = 1
	} else {
		carryIn = 0
	}

	low := (a & 0x0F) - (b & 0x0F) - carryIn
	if (low & 0x10) != 0 {
		low -= 6
	}
	if (low & 0x10) != 0 {
		carryB = 1
	}

	high := (a >> 4) - (b >> 4) - carryB
	if (high & 0x10) != 0 {
		high -= 6
	}

	result := (low & 0x0F) | (high << 4)

	c.setCarry((high & 0xFF) < 15)
	c.setZero(result == 0)
	c.setNegative(false) // BCD never sets negative
	c.setOverflow(false) // BCD never sets overflow

	c.A = result
}

func (c *Cpu) stackPush(data byte) {
	c.Bus.WriteByte(StackBase+uint16(c.SP), data)
	c.SP -= 1
}

func (c *Cpu) stackPeek() byte {
	return c.Bus.ReadByte(StackBase + uint16(c.SP+1))
}

func (c *Cpu) stackPop() byte {
	c.SP += 1
	return c.Bus.ReadByte(StackBase + uint16(c.SP))
}
