package main

import (
	"log"

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

// ioPeripheral is a z80.IO device at a given address.
type ioPeripheral struct {
	start      uint8
	length     uint8
	peripheral z80.IO
}

// ioArbiter is a collection of I/O devices at addresses. It implements z80.IO
// itself, dispatching to the appropriate device as needed.
type ioArbiter struct {
	peripherals []*ioPeripheral
}

func (i *ioArbiter) get(addr uint8) *ioPeripheral {
	for _, periph := range i.peripherals {
		if addr < periph.start {
			continue
		}
		if addr > periph.start+periph.length {
			continue
		}
		return periph
	}
	return nil
}

func (i *ioArbiter) In(addr uint8) uint8 {
	p := i.get(addr)
	if p == nil {
		log.Fatalf("Unhandled I/O In at %02x", addr)
		return 0
	}
	return p.peripheral.In(addr - p.start)
}
func (i *ioArbiter) Out(addr uint8, value uint8) {
	p := i.get(addr)
	if p == nil {
		log.Fatalf("Unhandled I/O Out at %02x", addr)
		return
	}
	p.peripheral.Out(addr-p.start, value)
}

// memory is a continuous memory region at a given address, optionally marked
// read only.
type memory struct {
	start    uint16
	data     []uint8
	readonly bool
}

// memoryArbiter implements Z80.Memory and dispatches accesses to subordinate
// memory instances.
type memoryArbiter struct {
	memories []memory
}

func (m *memoryArbiter) get(addr uint16) *memory {
	for _, mem := range m.memories {
		if addr < mem.start {
			continue
		}
		if addr >= mem.start+uint16(len(mem.data)) {
			continue
		}
		return &mem
	}
	return nil
}

func (m *memoryArbiter) Get(addr uint16) uint8 {
	mem := m.get(addr)
	if mem == nil {
		log.Fatalf("Unhandled memory Get at %04x", addr)
		return 0
	}
	return mem.data[addr-mem.start]
}

func (m *memoryArbiter) Set(addr uint16, value uint8) {
	mem := m.get(addr)
	if mem == nil {
		log.Fatalf("Unhandled memory Set at %04x", addr)
		return
	}
	if mem.readonly {
		log.Fatalf("Read-only memory Set at %04x", addr)
	}
	mem.data[addr-mem.start] = value
}
