package izapple2

import (
	"fmt"
	"time"
)

const (
	// CPUClockMhz is the actual Apple II clock speed
	CPUClockMhz     = 14.318 / 14
	cpuClockEuroMhz = 14.238 / 14
)

const (
	maxWaitDuration = 100 * time.Millisecond
	cpuSpinLoops    = 100
)

// Run starts the Apple2 emulation
func (a *Apple2) Run() {
	a.Start(false)
}

// Start the Apple2 emulation, can start paused
func (a *Apple2) Start(paused bool) {
	// Start the processor
	a.cpu.Reset()
	referenceTime := time.Now()
	speedReferenceTime := referenceTime
	speedReferenceCycles := uint64(0)

	a.paused = paused

	for {
		// Run 6502 steps
		if !a.paused {
			for i := 0; i < cpuSpinLoops; i++ {
				// Conditional tracing
				//pc, _ := a.cpu.GetPCAndSP()
				//a.cpu.SetTrace(pc >= 0xc75e && pc < 0xc800)

				// Execution
				a.cpu.ExecuteInstruction()

				// Special tracing
				a.executionTrace()
			}

			if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
				a.breakPoint = true
				a.cycleBreakpoint = 0
				a.paused = true
			}
		} else {
			time.Sleep(200 * time.Millisecond)
		}

		// Execute meta commands
		commandsPending := true
		for commandsPending {
			select {
			case command := <-a.commandChannel:
				switch command.getId() {
				case CommandKill:
					return
				case CommandPause:
					if !a.paused {
						a.paused = true
					}
				case CommandStart:
					if a.paused {
						a.paused = false
						referenceTime = time.Now()
						speedReferenceTime = referenceTime
					}
				case CommandPauseUnpause:
					a.paused = !a.paused
					referenceTime = time.Now()
					speedReferenceTime = referenceTime
				default:
					// Execute the other commands
					a.executeCommand(command)
				}
			default:
				commandsPending = false
			}
		}

		if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
			// Wait until next 6502 step has to run
			clockDuration := time.Since(referenceTime)
			simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
			waitDuration := simulatedDuration - clockDuration
			if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
				// We have to wait too long or are too much behind. Let's fast forward
				referenceTime = referenceTime.Add(-waitDuration)
				waitDuration = 0
			}
			if waitDuration > 0 {
				time.Sleep(waitDuration)
			}
		}

		if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
			// Calculate speed in MHz every million cycles
			newTime := time.Now()
			newCycles := a.cpu.GetCycles()
			elapsedCycles := float64(newCycles - speedReferenceCycles)
			freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
			fmt.Printf("Freq: %f Mhz\n", freq)
			speedReferenceTime = newTime
			speedReferenceCycles = newCycles
		}
	}
}

func (a *Apple2) reset() {
	a.cpu.Reset()
	a.mmu.reset()
	for _, c := range a.cards {
		if c != nil {
			c.reset()
		}
	}
}

func (a *Apple2) executionTrace() {
	for _, v := range a.tracers {
		v.inspect()
	}
}

func (a *Apple2) dumpDebugInfo() {
	// See "Apple II Monitors Peeled"
	pageZeroSymbols := map[int]string{
		0x36: "CSWL",
		0x37: "CSWH",
		0x38: "KSWL",
		0x39: "KSWH",
		0xe2: "ACJVAFLDL", // Apple Pascal
		0xe3: "ACJVAFLDH", // Apple Pascal
		0xec: "JVBFOLDL",  // Apple Pascal
		0xed: "JVBFOLDH",  // Apple Pascal
		0xee: "JVAFOLDL",  // Apple Pascal
		0xef: "JVAFOLDH",  // Apple Pascal
	}

	fmt.Printf("Page zero values:\n")
	for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
		d := a.mmu.physicalMainRAM.data[k]
		fmt.Printf("  %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
	}
}
