package main

import (
	"log"
	"os"
	"time"

	"github.com/koron-go/z80"
)

// evacuationController is the PH00524 board emulated in a synchronous step
// manner.
type evacuationController struct {
	cpu *z80.CPU

	m1 *motor
	m2 *motor

	u8 *i8255
	u9 *i8255

	// lastIOPC is the last PC on which an I/O operation (through U8 or U9) was
	// performed. This is used for status/debugging.
	lastIOPC uint16
}

func newEvacuationController(romPath string) *evacuationController {
	rom, err := os.ReadFile(romPath)
	if err != nil {
		log.Fatalf("Failed to read ROM: %v", err)
	}

	mem := memoryArbiter{
		memories: []memory{
			{start: 0x0000, data: rom, readonly: true},
			{start: 0x8000, data: make([]byte, 512)},
		},
	}

	u8 := i8255{}
	u9 := i8255{}
	io := ioArbiter{
		peripherals: []*ioPeripheral{
			{start: 0x40, length: 4, peripheral: &u8},
			{start: 0x80, length: 4, peripheral: &u9},
		},
	}

	m1 := motor{barrier: &barrier{npos: 4}}
	m2 := motor{barrier: &barrier{npos: 2}}

	cpu := z80.CPU{
		Memory: &mem,
		IO:     &io,
	}

	res := &evacuationController{
		cpu: &cpu,
		m1:  &m1,
		m2:  &m2,
		u8:  &u8,
		u9:  &u9,
	}

	// Make lastIOPC get updated on u8/u9 I/O.
	u8.onIn = res.onIO
	u8.onOut = res.onIO
	u9.onIn = res.onIO
	u9.onOut = res.onIO

	// Expansion bus connector, seems to be always pulled up.
	u9.pa = 0xff

	return res
}

func (e *evacuationController) onIO(_ i8255Port) {
	e.lastIOPC = e.cpu.PC
}

func (e *evacuationController) getIndcators() indicators {
	return indicators{
		indEvac:     (e.u8.pc & 1) != 0,
		indHTReadyN: (e.u8.pc & 2) != 0,
		indPumpDown: (e.u8.pc & 4) != 0,
	}
}

func (e *evacuationController) pressPumpdown(value bool) {
	if value {
		e.u8.pb |= 1
	} else {
		e.u8.pb &= (0xff ^ 1)
	}
}

func (e *evacuationController) pressVent(value bool) {
	if value {
		e.u8.pb |= 2
	} else {
		e.u8.pb &= (0xff ^ 2)
	}
}

// step through the simulation/emulation given a delta time.
func (e *evacuationController) step(dt time.Duration) bool {
	e.cpu.Step()
	if e.cpu.HALT {
		return false
	}

	// Simulate motors and barriers.
	e.m1.dir = (e.u9.pc & 16) != 0
	e.m1.en = (e.u9.pc & 4) != 0
	e.m2.dir = (e.u9.pc & 8) != 0
	e.m2.en = (e.u9.pc & 2) != 0
	e.m1.sim(dt)
	e.m2.sim(dt)

	// Feed barrier status back into CPU.
	b1p := e.m1.barrier.pins()
	b2p := e.m2.barrier.pins()
	e.u8.pa = 0
	if b1p[0] {
		e.u8.pa |= (1 << 5)
	}
	if b1p[1] {
		e.u8.pa |= (1 << 4)
	}
	if b1p[2] {
		e.u8.pa |= (1 << 3)
	}
	if b1p[3] {
		e.u8.pa |= (1 << 2)
	}
	if b2p[0] {
		e.u8.pa |= (1 << 1)
	}
	if b2p[1] {
		e.u8.pa |= (1 << 0)
	}

	// Simulate vacuum gauge / comparators given light barrier status.
	if b1p[3] {
		// half succ (PRE_EVAC)
		e.u8.pb |= 1 << 5
		// full succ (???)
		e.u8.pb |= 1 << 6
	}
	if !b1p[0] {
		// no more succ
		e.u8.pb &= 0xff ^ (1 << 5)
		e.u8.pb &= 0xff ^ (1 << 6)
	}

	return true
}
