#include "../../internal-include/SPU/spu_mmu.hpp"
#include <PSX/System/IOPorts/spu_io.hpp>
#include <PSX/Auxiliary/math_helper.hpp>
#include <PSX/SPU/spu.hpp>
#include <PSX/jabyengine_config.hpp>
#include <stddef.hpp>
#ifdef __DEBUG_SPU_MMU__
    #include <stdio.hpp>
#endif // __DEBUG_SPU_MMU__

namespace JabyEngine {
    namespace SPU_MMU {
        namespace SPU_MemoryMap = SPU_IO_Values::MemoryMap;

        struct SPUMemory {
            const uint8_t* adr;
            size_t         size;

            static SPUMemory create(size_t size) {
                return SPUMemory{.adr = reinterpret_cast<const uint8_t*>(SPU_MemoryMap::ADPCM), .size = size};
            }

            constexpr void clear() {
                this->adr  = nullptr;
                this->size = 0;
            }

            constexpr const uint8_t* get_end_adr() const {
                return (this->adr + this->size);
            }

            constexpr bool is_free() const {
                return this->adr == nullptr;
            }

            constexpr bool intersects(const SPUMemory& other) const {
                const auto* min = max_of(this->adr,           other.adr);
                const auto* max = min_of(this->get_end_adr(), other.get_end_adr());
                return min < max;
            }
        };

        struct AllocatedVoice {
            SPUMemory       memory;
            AllocatedVoice* next_entry;
        };

        struct VoiceManager {
            struct Iterator {
                AllocatedVoice* *prev_voice;
                AllocatedVoice*  current_voice;

                void next() {
                    this->prev_voice    = &this->current_voice->next_entry;
                    this->current_voice = this->current_voice->next_entry;
                }

                bool has_next() const {
                    return this->current_voice;
                }

                operator bool() const {
                    return Iterator::has_next();
                }
            };

            AllocatedVoice  allocated_voice_buffer[SPU_IO::VoiceCount + SPU_IO::ReverbCount] = {0};
            AllocatedVoice* first_allocated_voice = nullptr;

            Iterator iterator() {
                return Iterator{.prev_voice = &this->first_allocated_voice, .current_voice = this->first_allocated_voice};
            }

            AllocatedVoice& get_voice(uint8_t id) {
                return this->allocated_voice_buffer[id];
            }
        };

        static VoiceManager   voice_mgr;
        static const uint8_t* reverb_adr = reinterpret_cast<const uint8_t*>(SPU_MemoryMap::End);
        //                    ^ change this to allocate reverb

        using MemoryFoundCallback = const uint8_t* (AllocatedVoice& new_entry, AllocatedVoice* &prev_entry, AllocatedVoice* next_entry);

        static const uint8_t* verify_and_add(AllocatedVoice& new_entry, AllocatedVoice* &prev_entry, AllocatedVoice* next_entry) {
            if(new_entry.memory.adr >= reverb_adr) {
                return nullptr;
            }

            prev_entry           = &new_entry;
            new_entry.next_entry = next_entry;
            return new_entry.memory.adr;
        }

        static const uint8_t* find_first_fit(AllocatedVoice &new_entry, MemoryFoundCallback memory_found) {
            auto iterator = voice_mgr.iterator();
            while(iterator) {
                if(!iterator.current_voice->memory.intersects(new_entry.memory)) {
                    break;
                }
                new_entry.memory.adr = iterator.current_voice->memory.get_end_adr();
                iterator.next();
            }

            return memory_found(new_entry, *iterator.prev_voice, iterator.current_voice);
        }

        const uint8_t* allocate(uint8_t voice, size_t size) {
            auto& voice_entry = voice_mgr.get_voice(voice);
            if(!voice_entry.memory.is_free()) {
                deallocate(voice);
            }

            voice_entry.memory  = SPUMemory::create(size);
            const auto* mem_adr = find_first_fit(voice_entry, verify_and_add);
            #ifdef __DEBUG_SPU_MMU__
                printf("SPU: Allocated %i @0x%p to 0x%p (%i bytes)\n", voice, mem_adr, (mem_adr + size), size);
            #endif // __DEBUG_SPU_MMU__
            return mem_adr;
        }

        void deallocate(uint8_t voice) {
            auto* voice_adr = &voice_mgr.get_voice(voice);
            auto  iterator  = voice_mgr.iterator();

            voice_adr->memory.clear();
            while(iterator) {
                if(iterator.current_voice == voice_adr) {
                    *iterator.prev_voice = voice_adr->next_entry;
                    break;
                }
                iterator.next();
            }
        }
    }
}