package i6502

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func AciaSubject() (*Acia6551, chan []byte) {
	output := make(chan []byte)
	acia, _ := NewAcia6551(output)
	return acia, output
}

func TestNewAcia6551(t *testing.T) {
	output := make(chan []byte)
	acia, err := NewAcia6551(output)

	assert.Nil(t, err)
	assert.EqualValues(t, 0x4, acia.Size())
}

func TestAciaAsMemory(t *testing.T) {
	assert.Implements(t, (*Memory)(nil), new(Acia6551))
}

func TestAciaReset(t *testing.T) {
	a, _ := AciaSubject()

	a.Reset()

	assert.EqualValues(t, a.tx, 0)
	assert.True(t, a.txEmpty)

	assert.EqualValues(t, a.rx, 0)
	assert.False(t, a.rxFull)

	assert.False(t, a.txIrqEnabled)
	assert.False(t, a.rxIrqEnabled)

	assert.False(t, a.overrun)
	assert.EqualValues(t, 0, a.controlData)
}

func TestAciaReaderWithTxEmpty(t *testing.T) {
	a, _ := AciaSubject()

	// Nothing to read
	assert.True(t, a.txEmpty)

	value := make([]byte, 1)
	bytesRead, _ := a.Read(value)

	assert.EqualValues(t, 0, bytesRead)
}

func TestAciaWriteByteAndReader(t *testing.T) {
	var value []byte

	a, o := AciaSubject()
	done := make(chan bool)

	go func() {
		value = <-o
		done <- true
	}()

	// CPU writes data
	a.WriteByte(aciaData, 0x42)

	<-done

	assert.EqualValues(t, 0x42, value[0])
}

func TestAciaWriterAndReadByte(t *testing.T) {
	a, _ := AciaSubject()

	// System writes a single byte
	bytesWritten, _ := a.Write([]byte{0x42})

	if assert.EqualValues(t, 1, bytesWritten) {
		assert.EqualValues(t, 0x42, a.ReadByte(aciaData))
	}

	// System writes multiple bytes
	bytesWritten, _ = a.Write([]byte{0x42, 0x32, 0xAB})

	if assert.EqualValues(t, 3, bytesWritten) {
		assert.EqualValues(t, 0xAB, a.ReadByte(aciaData))
	}
}

func TestAciaCommandRegister(t *testing.T) {
	a, _ := AciaSubject()
	assert.False(t, a.rxIrqEnabled)
	assert.False(t, a.txIrqEnabled)

	a.WriteByte(aciaCommand, 0x02) // b0000 0010 RX Irq enabled
	assert.True(t, a.rxIrqEnabled)
	assert.False(t, a.txIrqEnabled)

	a.WriteByte(aciaCommand, 0x04) // b0000 0100 TX Irq enabled
	assert.False(t, a.rxIrqEnabled)
	assert.True(t, a.txIrqEnabled)

	a.WriteByte(aciaCommand, 0x06) // b0000 0110 RX + TX Irq enabled
	assert.True(t, a.rxIrqEnabled)
	assert.True(t, a.txIrqEnabled)

	assert.EqualValues(t, 0x06, a.ReadByte(aciaCommand))
}

func TestAciaControlRegister(t *testing.T) {
	a, _ := AciaSubject()

	a.WriteByte(aciaControl, 0xB8)
	assert.EqualValues(t, 0xB8, a.ReadByte(aciaControl))
}

func TestAciaStatusRegister(t *testing.T) {
	a, _ := AciaSubject()

	a.rxFull = false
	a.txEmpty = false
	a.overrun = false
	assert.EqualValues(t, 0x00, a.ReadByte(aciaStatus))

	a.rxFull = true
	a.txEmpty = false
	a.overrun = false
	assert.EqualValues(t, 0x08, a.ReadByte(aciaStatus))

	a.rxFull = false
	a.txEmpty = true
	a.overrun = false
	assert.EqualValues(t, 0x10, a.ReadByte(aciaStatus))

	a.rxFull = false
	a.txEmpty = false
	a.overrun = true
	assert.EqualValues(t, 0x04, a.ReadByte(aciaStatus))
}

func TestAciaIntegration(t *testing.T) {
	var value []byte

	output := make(chan []byte)
	done := make(chan bool)

	// Create a system
	// * 32kB RAM at 0x0000-7FFFF
	// * ACIA at 0x8800-8803
	ram, _ := NewRam(0x8000)
	acia, _ := NewAcia6551(output)
	bus, _ := NewAddressBus()
	bus.Attach(ram, 0x0000)
	bus.Attach(acia, 0x8800)
	cpu, _ := NewCpu(bus)

	program := []byte{
		0xA9, 0x00, // LDA #$00
		0x8D, 0x01, 0x88, // STA AciaStatus (Reset)
		0xA9, 0x42, // LDA #$42
		0x8D, 0x00, 0x88, // STA AciaData (Write)
		0xAD, 0x00, 0x88, // LDA AciaData (Read)
	}

	cpu.LoadProgram(program, 0x0200)
	cpu.Steps(2)

	go func() {
		value = <-output
		done <- true
	}()

	acia.Write([]byte{0xAB})

	cpu.Steps(3)

	<-done

	// value := make([]byte, 1)
	// bytesRead, _ := acia.Read(value)

	assert.EqualValues(t, 0x42, value[0])
	assert.EqualValues(t, 0xAB, cpu.A)
}
