#pragma once

#include <span>

#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A32/config.h"
#include "dynarmic/interface/exclusive_monitor.h"
#include "dynarmic_cp15.hpp"
#include "helpers.hpp"
#include "kernel.hpp"
#include "memory.hpp"

class CPU;

class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
public:
    u64 ticksLeft = 0;
    u64 totalTicks = 0;
    Memory& mem;
    Kernel& kernel;

    u64 getCyclesForInstruction(bool isThumb, u32 instruction);

    u8 MemoryRead8(u32 vaddr) override {
        return mem.read8(vaddr);
    }

    u16 MemoryRead16(u32 vaddr) override {
        return mem.read16(vaddr);
    }

    u32 MemoryRead32(u32 vaddr) override {
        return mem.read32(vaddr);
    }

    u64 MemoryRead64(u32 vaddr) override {
        return mem.read64(vaddr);
    }

    void MemoryWrite8(u32 vaddr, u8 value) override {
        mem.write8(vaddr, value);
    }

    void MemoryWrite16(u32 vaddr, u16 value) override {
        mem.write16(vaddr, value);
    }

    void MemoryWrite32(u32 vaddr, u32 value) override {
        mem.write32(vaddr, value);
    }

    void MemoryWrite64(u32 vaddr, u64 value) override {
        mem.write64(vaddr, value);
    }

    #define makeExclusiveWriteHandler(size) \
    bool MemoryWriteExclusive##size(u32 vaddr, u##size value, u##size expected) override { \
        u##size current = mem.read##size(vaddr); /* Get current value */                   \
        if (current == expected) {   /* Perform the write if current == expected */        \
            mem.write##size(vaddr, value);                                                 \
            return true; /* Exclusive write succeeded */                                   \
        }                                                                                  \
                                                                                           \
        return false; /* Exclusive write failed */                                         \
    }

    makeExclusiveWriteHandler(8)
    makeExclusiveWriteHandler(16)
    makeExclusiveWriteHandler(32)
    makeExclusiveWriteHandler(64)

    #undef makeExclusiveWriteHandler

    void InterpreterFallback(u32 pc, size_t num_instructions) override {
        // This is never called in practice.
        std::terminate();
    }

    void CallSVC(u32 swi) override {
        kernel.serviceSVC(swi);
    }

    void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
        switch (exception) {
            case Dynarmic::A32::Exception::UnpredictableInstruction:
                Helpers::panic("Unpredictable instruction at pc = %08X", pc);
                break;

            default: Helpers::panic("Fired exception oops");
        }
    }

    void AddTicks(u64 ticks) override {
        totalTicks += ticks;

        if (ticks > ticksLeft) {
            ticksLeft = 0;
            return;
        }
        ticksLeft -= ticks;
    }

    u64 GetTicksRemaining() override {
        return ticksLeft;
    }

    u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override {
        return getCyclesForInstruction(isThumb, instruction);
    }

    MyEnvironment(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
};

class CPU {
    std::unique_ptr<Dynarmic::A32::Jit> jit;
    std::shared_ptr<CP15> cp15;

    // Make exclusive monitor with only 1 CPU core
    Dynarmic::ExclusiveMonitor exclusiveMonitor{1};
    MyEnvironment env;
    Memory& mem;

public:
    static constexpr u64 ticksPerSec = 268111856;

    CPU(Memory& mem, Kernel& kernel);
    void reset();

    void setReg(int index, u32 value) {
        jit->Regs()[index] = value;
    }

    u32 getReg(int index) {
        return jit->Regs()[index];
    }

	std::span<u32, 16> regs() { return jit->Regs(); }

	// Get reference to array of FPRs. This array consists of the FPRs as single precision values
	// Hence why its base type is u32
	std::span<u32, 32> fprs() { return std::span(jit->ExtRegs()).first<32>(); }

    void setCPSR(u32 value) {
        jit->SetCpsr(value);
    }

    u32 getCPSR() {
        return jit->Cpsr();
    }

    void setFPSCR(u32 value) {
        jit->SetFpscr(value);
    }

    u32 getFPSCR() {
        return jit->Fpscr();
    }

    // Set the base pointer to thread-local storage, stored in a CP15 register on the 3DS
    void setTLSBase(u32 value) {
        cp15->setTLSBase(value);
    }

    u64 getTicks() {
        return env.totalTicks;
    }

    // Get reference to tick count. Memory needs access to this
    u64& getTicksRef() {
        return env.totalTicks;
    }

    void clearCache() { jit->ClearCache(); }

    void runFrame() {
		env.ticksLeft = ticksPerSec / 60;
	execute:
		const auto exitReason = jit->Run();

		if (static_cast<u32>(exitReason) != 0) [[unlikely]] {
			// Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing
            // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump
			if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) {
				goto execute;
			} else {
				Helpers::panic("Exit reason: %d\nPC: %08X", static_cast<u32>(exitReason), getReg(15));
			}
		}
	}
};