package z80_test

import (
	"bufio"
	"bytes"
	_ "embed"
	"okemu/z80"
	"okemu/z80/c99"
	"strconv"
	"strings"
	"testing"

	log "github.com/sirupsen/logrus"
)

const (
	ScanNone int = iota
	ScanDesc
	ScanEvent
	ScanRegs
	ScanState
	ScanMem
	ScanEnd
)

type Registers struct {
	AF     uint16
	BC     uint16
	DE     uint16
	HL     uint16
	AFa    uint16
	BCa    uint16
	DEa    uint16
	HLa    uint16
	IX     uint16
	IY     uint16
	SP     uint16
	PC     uint16
	MemPtr uint16
}

type State struct {
	I        byte
	R        byte
	IFF1     bool
	IFF2     bool
	IM       byte
	isHalted bool
	tStates  uint16
}

type MemorySetup struct {
	addr   uint16
	values []byte
}

type Event struct {
	time uint16
	typ  string
	addr uint16
	data byte
}

type Z80TestIn struct {
	//	description string
	registers   Registers
	state       State
	memorySetup []MemorySetup
}

type Expect struct {
	events    []Event
	registers Registers
	state     State
	memory    []MemorySetup
}

//go:embed tests.in
var testIn []byte

//go:embed tests.expected
var testExpected []byte

type Computer struct {
	cpu    *c99.Z80
	memory [65536]byte
	ports  [256]byte
}

var z80TestsIn map[string]Z80TestIn
var z80TestsExpected map[string]Expect

var computer Computer

var testNames []string

//var z80 *c99.Z80

func init() {
	z80TestsIn = make(map[string]Z80TestIn)
	z80TestsExpected = make(map[string]Expect)
	parseTestIn()
	parseTestExpected()
	for addr := 0; addr < 65535; addr++ {
		computer.memory[addr] = 0x00
	}
	for addr := 0; addr < 255; addr++ {
		computer.ports[addr] = 0
	}
	computer.cpu = c99.New(&computer)
}

func (c *Computer) M1MemRead(addr uint16) byte {
	return c.memory[addr]
}

func (c *Computer) MemRead(addr uint16) byte {
	return c.memory[addr]
}

func (c *Computer) MemWrite(addr uint16, val byte) {
	c.memory[addr] = val
}

func (c *Computer) IOWrite(port uint16, val byte) {
	c.ports[port&0x00ff] = val
}

func (c *Computer) IORead(port uint16) byte {
	return byte(port >> 8) //c.ports[port&0x00ff]
}

func parseTestExpected() {
	exScanner := bufio.NewScanner(bytes.NewReader(testExpected))
	scanState := ScanNone

	testName := ""
	var events []Event
	registers := Registers{}
	state := State{}
	var memorySetup []MemorySetup

	for exScanner.Scan() {
		line := exScanner.Text()
		if len(strings.TrimSpace(line)) == 0 {
			if scanState == ScanMem {
				scanState = ScanEnd
			} else {
				scanState = ScanNone
				continue
			}
		}
		if ScanNone == scanState {
			scanState = ScanDesc
		} else if len(line) > 0 && line[0] == ' ' {
			scanState = ScanEvent
		}
		//else {
		//	if scanState == ScanEvent {
		//		scanState = ScanRegs
		//	}
		//}

		switch scanState {
		case ScanDesc:
			testName = line
			scanState = ScanRegs
		case ScanEvent:
			events = append([]Event{}, *parseEvent(line))
			scanState = ScanRegs
		case ScanRegs:
			registers = *parseRegisters(line)
			scanState = ScanState
		case ScanState:
			state = *parseState(line)
			scanState = ScanMem
		case ScanMem:
			memorySetup = append(memorySetup, *parseMemory(line))
			//scanState = ScanMem
		case ScanEnd:
			z80TestsExpected[testName] = Expect{
				events:    events,
				registers: registers,
				state:     state,
				memory:    memorySetup,
			}
			events = []Event{}
			memorySetup = []MemorySetup{}
			scanState = ScanNone
		default:
			panic("unhandled default case")
		}

	}
}

func parseTestIn() {
	inScanner := bufio.NewScanner(bytes.NewReader(testIn))
	scanState := ScanNone
	testName := ""
	registers := Registers{}
	state := State{}
	var memorySetup []MemorySetup
	for inScanner.Scan() {
		line := inScanner.Text()
		if len(line) == 0 || strings.TrimSpace(line) == "" {
			scanState = ScanNone
			continue
		}
		if ScanNone == scanState {
			scanState = ScanDesc
		} else if line == "-1" {
			scanState = ScanEnd
		}
		switch scanState {
		case ScanDesc:
			testName = line
			scanState = ScanRegs
		case ScanRegs:
			registers = *parseRegisters(line)
			scanState = ScanState
		case ScanState:
			state = *parseState(line)
			scanState = ScanMem
		case ScanMem:
			memorySetup = append(memorySetup, *parseMemory(line))
			//scanState = ScanMem
		case ScanEnd:
			test := Z80TestIn{
				registers:   registers,
				state:       state,
				memorySetup: memorySetup,
			}
			testNames = append(testNames, testName)
			z80TestsIn[testName] = test
			scanState = ScanNone
		default:
			panic("unhandled default case")
		}

	}
}

func parseEvent(event string) *Event {
	e := Event{}
	line := strings.TrimSpace(event)
	//4 MR 0000 00
	//012345678
	if len(line) < 9 {
		log.Errorf("Invalid event: %s", line)
		return nil
	}
	sp := strings.Index(line, " ")
	if sp == -1 {
		log.Errorf("Invalid event: %s", line)
		return nil
	}
	e.time = parseDecW(line[:sp])
	e.typ = line[sp+1 : sp+3]
	e.addr = parseHexW(line[sp+4 : sp+8])
	if len(line) > sp+9 {
		//println("Event: ", line)

		e.data = parseHexB(line[sp+9:])
	}

	return &e
}

func parseMemory(line string) *MemorySetup {
	m := MemorySetup{}
	//0000 00 -1
	//0123456789
	m.addr = parseHexW(line[0:4])
	mem := line[5:]
	for {
		st := mem[:2]
		if st == "-1" {
			break
		}
		m.values = append(m.values, parseHexB(st))
		mem = strings.TrimSpace(mem[2:])
	}
	return &m
}

func parseRegisters(line string) *Registers {
	r := Registers{}
	line = strings.TrimSpace(line)
	if len(line) != 64 {
		log.Errorf("Invalid register line: %s", line)
	} else {
		// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
		for ctr := 0; ctr < 13; ctr++ {
			hexString := line[ctr*5 : ctr*5+4]
			v, err := strconv.ParseUint(hexString, 16, 16)
			if err != nil {
				log.Errorf("Invalid register value: %s in line %s", hexString, line)
				break
			}
			switch ctr {
			case 0:
				r.AF = uint16(v)
			case 1:
				r.BC = uint16(v)
			case 2:
				r.DE = uint16(v)
			case 3:
				r.HL = uint16(v)
			case 4:
				r.AFa = uint16(v)
			case 5:
				r.BCa = uint16(v)
			case 6:
				r.DEa = uint16(v)
			case 7:
				r.HLa = uint16(v)
			case 8:
				r.IX = uint16(v)
			case 9:
				r.IY = uint16(v)
			case 10:
				r.SP = uint16(v)
			case 11:
				r.PC = uint16(v)
			case 12:
				r.MemPtr = uint16(v)
			}
		}

	}
	return &r
}

func parseHexB(line string) byte {
	v, err := strconv.ParseUint(line, 16, 8)
	if err != nil {
		log.Errorf("Invalid HexB value: %s", line)
	}
	return byte(v)
}

func parseHexW(line string) uint16 {
	v, err := strconv.ParseUint(line, 16, 16)
	if err != nil {
		log.Errorf("Invalid HexW value: %s", line)
	}
	return uint16(v)
}

func parseDecB(line string) byte {
	v, err := strconv.ParseUint(line, 10, 8)
	if err != nil {
		log.Errorf("Invalid B value: %s", line)
	}
	return byte(v)
}

func parseDecW(line string) uint16 {
	v, err := strconv.ParseUint(line, 10, 16)
	if err != nil {
		log.Errorf("Invalid W value: %s", line)
	}
	return uint16(v)
}

func parseBoolB(line string) bool {
	v, err := strconv.ParseUint(line, 10, 8)
	if err != nil {
		log.Errorf("Invalid state I value: %s", line)
	}
	return v != 0
}

func parseState(line string) *State {
	s := State{}
	line = strings.TrimSpace(line)
	if len(line) < 15 {
		log.Errorf("Invalid state line: %s", line)
	} else {
		//00 00 0 0 0 0     1
		//0123456789012345678
		s.I = parseHexB(line[0:2])
		s.R = parseHexB(line[3:5])
		s.IFF1 = parseBoolB(line[6:7])
		s.IFF2 = parseBoolB(line[8:9])
		s.IM = parseDecB(line[10:11])
		s.isHalted = parseBoolB(line[12:13])
		s.tStates = parseDecW(strings.TrimSpace(line[13:]))
	}
	return &s
}

func TestZ80Fuse(t *testing.T) {
	t.Logf("Fuse-type Z80 emulator test")
	computer.cpu.Reset()
	for _, name := range testNames {
		setComputerState(z80TestsIn[name])
		exp, exists := z80TestsExpected[name]
		if !exists {
			t.Errorf("Expected values for test %s not exists!", name)
			return
		}
		cy := uint32(0)
		for {
			cy += computer.cpu.RunInstruction()
			if cy >= uint32(exp.state.tStates) {
				break
			}
		}
		checkComputerState(t, name)
	}
}

func setComputerState(test Z80TestIn) {
	state := z80.CPU{
		A:                 byte(test.registers.AF >> 8),
		B:                 byte(test.registers.BC >> 8),
		C:                 byte(test.registers.BC),
		D:                 byte(test.registers.DE >> 8),
		E:                 byte(test.registers.DE),
		H:                 byte(test.registers.HL >> 8),
		L:                 byte(test.registers.HL),
		AAlt:              byte(test.registers.AFa >> 8),
		BAlt:              byte(test.registers.BCa >> 8),
		CAlt:              byte(test.registers.BCa),
		DAlt:              byte(test.registers.DEa >> 8),
		EAlt:              byte(test.registers.DEa),
		HAlt:              byte(test.registers.HLa >> 8),
		LAlt:              byte(test.registers.HLa),
		IX:                test.registers.IX,
		IY:                test.registers.IY,
		I:                 test.state.I,
		R:                 test.state.R,
		SP:                test.registers.SP,
		PC:                test.registers.PC,
		Flags:             z80.GetFlags(byte(test.registers.AF)),
		FlagsAlt:          z80.GetFlags(byte(test.registers.AFa)),
		IMode:             test.state.IM,
		Iff1:              test.state.IFF1,
		Iff2:              test.state.IFF2,
		Halted:            test.state.isHalted,
		DoDelayedDI:       false,
		DoDelayedEI:       false,
		CycleCount:        0,
		InterruptOccurred: false,
		MemPtr:            test.registers.MemPtr,
	}

	// Setup CPU
	computer.cpu.SetState(&state)

	// Setup Memory
	for _, ms := range test.memorySetup {
		addr := ms.addr
		for _, b := range ms.values {
			computer.memory[addr] = b
			addr++
		}
	}
}

func lo(w uint16) byte {
	return byte(w)
}

func hi(w uint16) byte {
	return byte(w >> 8)
}

func checkComputerState(t *testing.T, name string) {
	state := computer.cpu.GetState()
	exp, exists := z80TestsExpected[name]
	if !exists {
		t.Errorf("Expected values for test %s not exists!", name)
		return
	}

	// A,B,C,D,E,H,L
	if hi(exp.registers.AF) != state.A {
		t.Errorf("%s:  Expected A to be %x, got %x", name, hi(exp.registers.AF), state.A)
	}
	if hi(exp.registers.BC) != state.B {
		t.Errorf("%s:  Expected B to be %x, got %x", name, hi(exp.registers.BC), state.B)
		computer.cpu.DebugOutput()
	}

	if lo(exp.registers.BC) != state.C {
		t.Errorf("%s:  Expected C to be %x, got %x", name, lo(exp.registers.BC), state.C)
	}

	if hi(exp.registers.DE) != state.D {
		t.Errorf("%s:  Expected D to be %x, got %x", name, hi(exp.registers.DE), state.D)
	}

	if lo(exp.registers.DE) != state.E {
		t.Errorf("%s:  Expected E to be %x, got %x", name, lo(exp.registers.DE), state.E)
	}

	if hi(exp.registers.HL) != state.H {
		t.Errorf("%s:  Expected H to be %x, got %x", name, hi(exp.registers.HL), state.H)
	}

	if lo(exp.registers.HL) != state.L {
		t.Errorf("%s:  Expected L to be %x, got %x", name, lo(exp.registers.BC), state.L)
	}

	// Alt A,B,C,D,E,H,L
	if hi(exp.registers.AFa) != state.AAlt {
		t.Errorf("%s:  Expected A' to be %x, got %x", name, hi(exp.registers.AFa), state.AAlt)
	}
	if hi(exp.registers.BCa) != state.BAlt {
		t.Errorf("%s:  Expected B' to be %x, got %x", name, hi(exp.registers.BCa), state.BAlt)
	}

	if lo(exp.registers.BCa) != state.CAlt {
		t.Errorf("%s:  Expected C' to be %x, got %x", name, lo(exp.registers.BCa), state.CAlt)
	}

	if hi(exp.registers.DEa) != state.DAlt {
		t.Errorf("%s:  Expected D' to be %x, got %x", name, hi(exp.registers.DEa), state.DAlt)
	}

	if lo(exp.registers.DEa) != state.EAlt {
		t.Errorf("%s:  Expected E' to be %x, got %x", name, lo(exp.registers.DEa), state.EAlt)
	}

	if hi(exp.registers.HLa) != state.HAlt {
		t.Errorf("%s:  Expected H' to be %x, got %x", name, hi(exp.registers.HLa), state.HAlt)
	}

	if lo(exp.registers.HLa) != state.LAlt {
		t.Errorf("%s:  Expected L' to be %x, got %x", name, lo(exp.registers.BCa), state.LAlt)
	}

	// 16b regs PC, SP, Meme, R, I

	if exp.registers.IX != state.IX {
		t.Errorf("%s:  Expected IX to be %x, got %x", name, exp.registers.IX, state.IX)
	}

	if exp.registers.IY != state.IY {
		t.Errorf("%s:  Expected IX to be %x, got %x", name, exp.registers.IX, state.IX)
	}

	if exp.registers.PC != state.PC {
		t.Errorf("%s:  Expected PC to be %x, got %x", name, exp.registers.PC, state.PC)
	}

	if exp.registers.SP != state.SP {
		t.Errorf("%s:  Expected SP to be %x, got %x", name, exp.registers.SP, state.SP)
	}

	if exp.registers.MemPtr != state.MemPtr {
		t.Errorf("%s:  Expected MemPtr to be %x, got %x", name, exp.registers.MemPtr, state.MemPtr)
	}

	// State
	if exp.state.I != state.I {
		t.Errorf("%s:  Expected I to be %x, got %x", name, exp.state.I, state.I)
	}

	if exp.state.IM != state.IMode {
		t.Errorf("%s:  Expected IM to be %d, got %d", name, exp.state.IM, state.IMode)
	}

	if exp.state.IFF1 != state.Iff1 {
		t.Errorf("%s:  Expected IIF1 to be %t, got %t", name, exp.state.IFF1, state.Iff1)
	}

	if exp.state.IFF2 != state.Iff2 {
		t.Errorf("%s:  Expected IIF2 to be %t, got %t", name, exp.state.IFF2, state.Iff2)
	}

	if exp.state.isHalted != state.Halted {
		t.Errorf("%s:  Expected isHalted to be %t, got %t", name, exp.state.isHalted, state.Halted)
	}

	// FLAGS
	if lo(exp.registers.AF) != state.Flags.GetFlags() {
		t.Errorf("%s:  Expected Flags to be %08b, got %08b", name, lo(exp.registers.AF), state.Flags.GetFlags())
	}

	if lo(exp.registers.AFa) != state.FlagsAlt.GetFlags() {
		t.Errorf("%s:  Expected Flags' to be %08b, got %08b", name, lo(exp.registers.AFa), state.FlagsAlt.GetFlags())
	}

	// Check memory
	for _, mExpect := range exp.memory {
		addr := mExpect.addr
		for _, b := range mExpect.values {
			if computer.memory[addr] != b {
				t.Errorf("%s:  Expected memory[%x] to be %x, got %x", name, addr, b, computer.memory[addr])
			}
			addr++
		}
	}
}
