package okean240

import (
	_ "embed"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"image/color"
	"okemu/config"
	"okemu/debug"
	"okemu/okean240/fdc"
	"okemu/okean240/pic"
	"okemu/okean240/pit"
	"okemu/okean240/usart"
	"okemu/z80"
	"os"
	"strconv"
	"sync/atomic"

	//"okemu/z80"
	"okemu/z80/c99"
	//"okemu/z80/js"

	log "github.com/sirupsen/logrus"
)

const DefaultCPUFrequency = 2_500_000
const CPUFrequencyLow float64 = 2.47
const CPUFrequencyHi float64 = 2.53
const TimerFrequencyLow float64 = 1.47
const TimerFrequencyHi float64 = 1.53

type ComputerType struct {
	cpu            *c99.Z80
	memory         Memory
	ioPorts        [256]byte
	cycles         uint64
	tstatesPartial uint64
	dd17EnableOut  bool
	colorMode      bool
	screenWidth    int
	screenHeight   int
	vRAM           *RamBlock
	palette        byte
	bgColor        byte
	pit            *pit.I8253
	usart          *usart.I8251
	pic            *pic.I8259
	fdc            *fdc.FloppyDriveController
	kbdBuffer      []byte
	vShift         byte
	hShift         byte
	cpuFrequency   uint32
	//
	debugger *debug.Debugger
	config   *config.OkEmuConfig
	kbAck    atomic.Bool
}

type Snapshot struct {
	CPU    *z80.CPU `json:"cpu,omitempty"`
	Memory string   `json:"memory,omitempty"`
}

const VRAMBlock0 = 3
const VRAMBlock1 = 7
const VidVsuBit = 0x80
const VidColorBit = 0x40

//type ComputerInterface interface {
//	Run()
//	Reset()
//	GetPixel(x uint16, y uint16) color.RGBA
//	Do() uint64
//	TimerClk()
//	PutKey(key *fyne.KeyEvent)
//	PutRune(key rune)
//	PutCtrlKey(shortcut fyne.Shortcut)
//	SaveFloppy()
//	LoadFloppy()
//	CPUState() *z80.CPU
//	SetCPUState(state *z80.CPU)
//}

func (c *ComputerType) CPUState() *z80.CPU {
	return c.cpu.GetState()
}

func (c *ComputerType) SetCPUState(state *z80.CPU) {
	c.cpu.SetState(state)
}

func (c *ComputerType) M1MemRead(addr uint16) byte {
	return c.memory.M1MemRead(addr)
}

func (c *ComputerType) MemRead(addr uint16) byte {
	return c.memory.MemRead(addr)
}

func (c *ComputerType) MemWrite(addr uint16, val byte) {
	c.memory.MemWrite(addr, val)
}

// NewComputer Builds new computer
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
	c := ComputerType{}
	c.config = cfg
	c.memory = Memory{}
	c.memory.Init(cfg.MonitorFile, cfg.CPMFile)

	c.cpu = c99.New(&c)

	c.cycles = 0
	c.tstatesPartial = 0
	c.dd17EnableOut = false
	c.screenWidth = 512
	c.screenHeight = 256
	c.vRAM = c.memory.allMemory[3]
	c.palette = 0
	c.bgColor = 0

	c.vShift = 0
	c.hShift = 0
	//c.aOffset = 0x100

	c.pit = pit.New()
	c.kbAck.Store(false)
	c.usart = usart.New()
	c.pic = pic.NewI8259()
	c.fdc = fdc.NewFDC(cfg)
	c.cpuFrequency = DefaultCPUFrequency
	c.debugger = deb
	return &c
}

func (c *ComputerType) Reset() {
	c.cpu.Reset()
	c.cycles = 0
	c.tstatesPartial = 0
}

func (c *ComputerType) getContext() map[string]interface{} {
	context := make(map[string]interface{})
	s := c.cpu.GetState()
	context["A"] = s.A
	context["B"] = s.B
	context["C"] = s.C
	context["D"] = s.D
	context["E"] = s.E
	context["H"] = s.H
	context["L"] = s.L
	context["A'"] = s.AAlt
	context["B'"] = s.BAlt
	context["C'"] = s.CAlt
	context["D'"] = s.DAlt
	context["E'"] = s.EAlt
	context["H'"] = s.HAlt
	context["L'"] = s.LAlt
	context["PC"] = s.PC
	context["SP"] = s.SP
	context["IX"] = s.IX
	context["IY"] = s.IY
	context["ZF"] = s.Flags.Z
	context["SF"] = s.Flags.S
	context["NF"] = s.Flags.N
	context["PF"] = s.Flags.P
	context["HF"] = s.Flags.H
	context["YF"] = s.Flags.Y
	context["XF"] = s.Flags.X
	context["CF"] = s.Flags.C
	context["BC"] = uint16(s.B)<<8 | uint16(s.C)
	context["DE"] = uint16(s.D)<<8 | uint16(s.E)
	context["HL"] = uint16(s.H)<<8 | uint16(s.L)
	context["AF"] = uint16(s.A)<<8 | uint16(s.Flags.GetFlags())
	return context
}

func (c *ComputerType) Do() (uint32, uint16, byte) {
	ticks := uint32(0)
	var memAccess *map[uint16]byte
	if c.debugger.StepMode() {
		if c.debugger.RunMode() || c.debugger.DoStep() {
			if c.debugger.RunInst() > 0 {
				// skip first instruction after run-mode activated
				bpHit, bp := c.debugger.CheckBreakpoints(c.getContext())
				if bpHit {
					//c.debugger.SetRunMode(false)
					return 0, bp, 0
				}
			}
			c.debugger.SaveHistory(c.cpu.GetState())
			ticks, memAccess = c.cpu.RunInstruction()
			mHit, mAddr, mTyp := c.debugger.CheckMemBreakpoints(memAccess)
			if mHit {
				return ticks, mAddr, mTyp
			}
		}
	} else {
		ticks, memAccess = c.cpu.RunInstruction()
	}
	c.cycles += uint64(ticks)
	c.tstatesPartial += uint64(ticks)
	return ticks, 0, 0
}

func (c *ComputerType) GetPixel(x uint16, y uint16) color.RGBA {
	if y > 255 {
		return CWhite
	}

	var addr uint16
	var resColor color.RGBA

	if c.colorMode {
		if x > 255 {
			return CWhite
		}

		var offset uint16
		if (c.vShift != 0) && (y > 255-uint16(c.vShift)) {
			offset = 0x100
		} else {
			offset = 0
		}
		y += uint16(c.vShift) & 0x00ff
		x += uint16(c.hShift-7) & 0x00ff

		// Color 256x256 mode
		addr = ((x & 0xf8) << 6) | y

		a1 := (addr - offset) & 0x3fff
		a2 := (a1 + 0x100) & 0x3fff

		cl := (c.vRAM.memory[a1] >> (x & 0x07)) & 1
		cl |= ((c.vRAM.memory[a2] >> (x & 0x07)) & 1) << 1
		if cl == 0 {
			//resColor = BgColorPalette[c.bgColor]
			resColor = ColorPalette[c.palette][cl]
		} else {
			resColor = ColorPalette[c.palette][cl]
		}
	} else {
		if x > 511 {
			return CWhite
		}

		var offset uint16
		if (c.vShift != 0) && (y > 255-uint16(c.vShift)) {
			offset = 0x100
		} else {
			offset = 0
		}

		// Shifts
		y += uint16(c.vShift) & 0x00ff
		x += uint16(c.hShift-7) & 0x001ff

		// Mono 512x256 mode
		addr = (((x & 0x1f8) << 5) + y) - offset
		pix := c.vRAM.memory[addr&0x3fff] >> (x & 0x07) & 1
		if c.palette == 6 {
			if pix == 0 {
				resColor = CBlack
			} else {
				resColor = CLGreen
			}
		} else {
			if pix == 0 {
				resColor = BgColorPalette[c.bgColor]
			} else {
				resColor = MonoPalette[c.palette]
			}
		}
	}
	return resColor
}

func (c *ComputerType) ScreenWidth() int {
	return c.screenWidth
}

func (c *ComputerType) ScreenHeight() int {
	return c.screenHeight
}

func (c *ComputerType) Cycles() uint64 {
	return c.cycles
}

func (c *ComputerType) ResetTStatesPartial() {
	c.tstatesPartial = 0
}

func (c *ComputerType) TStatesPartial() uint64 {
	return c.tstatesPartial
}

func (c *ComputerType) TimerClk() {
	// DD70 KR580VI53 CLK0, CKL1 @ 1.5MHz
	c.pit.Tick(0)
	c.pit.Tick(1)

	// IRQ from timer
	if c.pit.Fired(0) {
		c.pic.SetIRQ(RstTimerNo)
	}
	// clock for SIO KR580VV51
	if c.pit.Fired(1) {
		c.usart.Tick()
	}
}

func (c *ComputerType) LoadFloppy(drive byte) error {
	return c.fdc.LoadFloppy(drive)
}

func (c *ComputerType) SaveFloppy(drive byte) error {
	return c.fdc.SaveFloppy(drive)
}

func (c *ComputerType) SetSerialBytes(bytes []byte) {
	c.usart.SetRxBytes(bytes)
}

func (c *ComputerType) SetRamBytes(bytes []byte) {
	addr := 0x100
	for i := 0; i < len(bytes); i++ {
		c.memory.MemWrite(uint16(addr), bytes[i])
		addr++
	}
	pages := len(bytes) / 256
	if len(bytes)%256 != 0 {
		pages++
	}
	log.Debugf("Loaded bytes: %d; blocks: %d", len(bytes), pages)
	//c.cpu.SP = 0x100
	//c.cpu.PC = 0x100
}

func (c *ComputerType) Dump(start uint16, length uint16) {
	file, err := os.Create("dump.dat")
	if err != nil {
		log.Error(err)
		return
	}
	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
			log.Error(err)
		}
	}(file)

	var buffer []byte
	for addr := start; addr < start+length; addr++ {
		buffer = append(buffer, c.memory.MemRead(addr))
	}
	_, err = file.Write(buffer)
	err = binary.Write(file, binary.LittleEndian, buffer)
	if err != nil {
		log.Error("Save memory dump failed:", err)
	} else {
		log.Debug("Memory dump saved successfully")
	}
}

func (c *ComputerType) CPUFrequency() uint32 {
	return c.cpuFrequency
}

func (c *ComputerType) SetCPUFrequency(frequency uint32) {
	c.cpuFrequency = frequency
}

func (c *ComputerType) DebuggerState() string {
	if c.debugger.StepMode() {
		if c.debugger.RunMode() {
			return "Run"
		}
		return "Step"
	}
	return "Off"
}

func (c *ComputerType) memoryAsHexStr() string {
	res := ""
	for addr := 0; addr <= 65535; addr++ {
		res += fmt.Sprintf("%02X", c.memory.MemRead(uint16(addr)))
	}
	return res
}

func (c *ComputerType) SaveSnapshot(fn string) error {
	// take snapshot
	s := Snapshot{
		CPU:    c.cpu.GetState(),
		Memory: c.memoryAsHexStr(),
	}
	// convert to JSON
	b, err := json.Marshal(s)
	if err != nil {
		return err
	}
	// and save
	err = os.WriteFile(fn, b, 0644)
	if err != nil {
		return err
	}
	return nil
}

func (c *ComputerType) LoadSnapshot(fn string) error {
	// read snapshot file
	b, err := os.ReadFile(fn)
	if err != nil {
		return err
	}
	// unmarshal from JSON
	var result Snapshot
	err = json.Unmarshal(b, &result)
	if err != nil {
		return err
	}
	c.cpu.SetState(result.CPU)
	return c.restoreMemoryFromHex(result.Memory)
}

func (c *ComputerType) restoreMemoryFromHex(memory string) error {
	for addr := 0; addr <= 65535; addr++ {
		b, e := strconv.ParseUint(memory[addr*2:addr*2+2], 16, 8)
		if e != nil {
			log.Error(e)
			return e
		}
		c.memory.MemWrite(uint16(addr), byte(b))
	}
	return nil
}

func (c *ComputerType) AutoSaveFloppy() {
	for drv := byte(0); drv < fdc.TotalDrives; drv++ {
		if c.config.FDC[drv].AutoSave {
			e := c.fdc.SaveFloppy(drv)
			if e != nil {
				log.Error(e)
			}
		}
	}
}

func (c *ComputerType) AutoLoadFloppy() {
	for drv := byte(0); drv < fdc.TotalDrives; drv++ {
		if c.config.FDC[drv].AutoLoad {
			e := c.fdc.LoadFloppy(drv)
			if e != nil {
				log.Error(e)
			}
		}
	}
}
