package okean240

import (
	"os"

	log "github.com/sirupsen/logrus"
)

const RamBlockSize = 16 * 1024           // 16Kb
const RamSize = 512 * 1024               // 512kb (16xRU7) or 128k (16xRU5)
const RamBlocks = RamSize / RamBlockSize // 32 Ram blocks for 512k, 8 for 128k
const RamDefaultInitPattern = 0x3f
const RamWindows = 4

const RSTBit = 0x20
const ROMDisBit = 0x10
const AccessHiBit = 0x01
const ExtRamAddrBits = 0x0E

const (
	WindowNo0 = iota
	WindowNo1
	WindowNo2
	WindowNo3
)

type RamBlock struct {
	id     byte
	memory [RamBlockSize]byte
}

type Memory struct {
	allMemory    [RamBlocks]*RamBlock
	memoryWindow [RamWindows]*RamBlock
	rom0         RamBlock // monitor + monitor
	rom1         RamBlock // cpm + monitor
	config       byte
}

type MemoryInterface interface {
	// Init - Initialize memory at "computer started"
	Init(rom0 string, rom1 string)
	// Configure - Set memory configuration
	Configure(value byte)
	// M1MemRead Read byte from memoryWindow for specified address
	M1MemRead(addr uint16) byte
	// MemRead Read byte from memoryWindow for specified address
	MemRead(addr uint16) byte
	// MemWrite Write byte to memoryWindow to specified address
	MemWrite(addr uint16, val byte)
}

func (m *Memory) Init(monFile string, cmpFile string) {

	// empty RAM
	var id byte = 0
	for block := range m.allMemory {
		rb := RamBlock{}
		rb.id = id
		id++
		for addr := 0; addr < RamBlockSize; addr++ {
			rb.memory[addr] = RamDefaultInitPattern
		}
		m.allMemory[block] = &rb
	}

	// Command ROM files and init ROM0,1
	// Read the entire file into a byte slice
	rom0bin, err := os.ReadFile(monFile)
	if err != nil {
		log.Fatal(err)
	}
	rom1bin, err := os.ReadFile(cmpFile)
	if err != nil {
		log.Fatal(err)
	}
	m.rom0 = RamBlock{}
	m.rom0.id = 100
	m.rom1 = RamBlock{}
	m.rom0.id = 101
	half := RamBlockSize / 2
	for i := 0; i < half; i++ {
		// mon+mon
		m.rom0.memory[i] = rom0bin[i]
		m.rom0.memory[i+half] = rom0bin[i]
		// cp/m + mon
		m.rom1.memory[i] = rom1bin[i]
		m.rom1.memory[i+half] = rom0bin[i]
	}
	// Config mem with RST pin Hi
	m.Configure(RSTBit)
}

// Configure - Configure memoryWindow windows
func (m *Memory) Configure(value byte) {
	m.config = value
	if m.config&RSTBit != 0 {
		// RST bit set just after System RESET
		// All memoryWindow windows points to ROM0 (monitor)
		for i := 0; i < RamWindows; i++ {
			m.memoryWindow[i] = &m.rom0
		}
	} else {
		// Map RAM blocks to windows
		sp := (m.config & ExtRamAddrBits) << 1 // 0,4,8,12
		for i := byte(0); i < RamWindows; i++ {
			m.memoryWindow[i] = m.allMemory[sp+i]
		}
		// Map two hi windows to low windows in 32k flag set
		if m.config&AccessHiBit == 1 {
			m.memoryWindow[WindowNo0] = m.memoryWindow[WindowNo2]
			m.memoryWindow[WindowNo1] = m.memoryWindow[WindowNo3]
		}
		// If ROM enabled, map ROM to last window
		if m.config&ROMDisBit == 0 {
			// If ROM enabled, CP/M + Mon at window 3 [0xC000:0xFFFF]
			m.memoryWindow[WindowNo3] = &m.rom1
		}
	}
}

func (m *Memory) M1MemRead(addr uint16) byte {
	return m.memoryWindow[addr>>14].memory[addr&0x3fff]
}

func (m *Memory) MemRead(addr uint16) byte {
	return m.memoryWindow[addr>>14].memory[addr&0x3fff]
}

func (m *Memory) MemWrite(addr uint16, val byte) {
	window := addr >> 14
	offset := addr & 0x3fff
	if window == 1 {
		//log.Debugf("at vram [%2x][%4x]", window, offset)
	}
	m.memoryWindow[window].memory[offset] = val
}
