#pragma once
#include "IOValues/spu_io_values.hpp"
#include <limits.hpp>

namespace JabyEngine {
    namespace SPU_IO {
        static constexpr size_t VoiceCount  = 24;
        static constexpr size_t ReverbCount = 1;

        struct ControlRegister_IO : public IOPort<SPU_IO_Values::ControlRegister> {
            using TransferMode = Value::RAMTransferMode;

            void set_transfer_mode(TransferMode mode) {
                this->write(this->read().set(Value::ControlRegister::TransferMode.with(mode)));
                while(this->read().get(Value::ControlRegister::TransferMode) != mode);
            }
        };

        using AD_IO                  = IOPort<SPU_IO_Values::AD>;
        using DataTransferControl_IO = IOPort<SPU_IO_Values::DataTransferControl>;
        using Echo_IO                = IOPort32<SPU_IO_Values::Echo>;
        using KeyOff_IO              = IOPort32<SPU_IO_Values::KeyOff>;
        using KeyOn_IO               = IOPort32<SPU_IO_Values::KeyOn>;
        using KeyStatus_IO           = IOPort32<SPU_IO_Values::KeyStatus>;
        using Noise_IO               = IOPort<SPU_IO_Values::Noise>;
        using PitchModulation_IO     = IOPort32<SPU_IO_Values::PitchModulation>;
        using SampleRate_IO          = IOPort<SPU_IO_Values::SampleRate>;
        using SimpleVolume_IO        = IOPort<SPU_IO_Values::SimpleVolume>;
        using StatusRegister_IO      = IOPort<SPU_IO_Values::StatusRegister>;
        using SR_IO                  = IOPort<SPU_IO_Values::SR>;
        using SRAMAdr_IO             = IOPort<SPU_IO_Values::SRAMAdr>;
        using SweepVolume_IO         = IOPort<SPU_IO_Values::SweepVolume>;

        #pragma pack(push, 1)
        struct Voice {
            SweepVolume_IO  volumeLeft;  //Offset: 0x0
            SweepVolume_IO  volumeRight; //Offset: 0x2
            SampleRate_IO   sampleRate;  //Offset: 0x4;
            SRAMAdr_IO      adr;         //Offset: 0x6
            AD_IO           ad;          //Offset: 0x8
            SR_IO           sr;          //Offset: 0xA
            SimpleVolume_IO adsr_volume; //Offset: 0xC
            SRAMAdr_IO      repeatAdr;   //Offset: 0xE
        };
        #pragma pack(pop)

        // Required so GCC does not create guards for the ReverbON reference
        #define __eon_declaration __declare_io_port(Echo_IO, 0x1F801D98)

        static auto& Voice               = __declare_io_port_array(struct Voice, VoiceCount, 0x1F801C00);
        static auto& PMON                = __declare_io_port(PitchModulation_IO,             0x1F801D90);
        static auto& NON                 = __declare_io_port(Noise_IO,                       0x1F801D94);
        static auto& EON                 = __eon_declaration;
        static auto& SRAMTransferAdr     = __declare_io_port(SRAMAdr_IO,                     0x1F801DA6);
        static auto& ControlRegister     = __declare_io_port(ControlRegister_IO,             0x1F801DAA);
        static auto& DataTransferControl = __declare_io_port(DataTransferControl_IO,         0x1F801DAC);
        static auto& StatusRegister      = __declare_io_port(StatusRegister_IO,              0x1F801DAE);

        struct CDVolume {
            static inline auto& Left  = __declare_io_port(SimpleVolume_IO, 0x1F801DB0);
            static inline auto& Right = __declare_io_port(SimpleVolume_IO, 0x1F801DB2);
        };

        struct ExternalAudioInputVolume {
            static inline auto& Left  = __declare_io_port(SimpleVolume_IO, 0x1F801DB4);
            static inline auto& Right = __declare_io_port(SimpleVolume_IO, 0x1F801DB6);
        };

        struct Key {
            static inline auto& On     = __declare_io_port(KeyOn_IO,     0x1F801D88);
            static inline auto& Off    = __declare_io_port(KeyOff_IO,    0x1F801D8C);
            static inline auto& Status = __declare_io_port(KeyStatus_IO, 0x1F801D9C);
        };

        struct MainVolume {
            static inline auto& Left  = __declare_io_port(SweepVolume_IO, 0x1F801D80);
            static inline auto& Right = __declare_io_port(SweepVolume_IO, 0x1F801D82);
        };

        struct Reverb {
            struct Volume {
                static inline auto& Left  = __declare_io_port(SimpleVolume_IO, 0x1F801D84);
                static inline auto& Right = __declare_io_port(SimpleVolume_IO, 0x1F801D86);
            };
            static inline auto& On          = __eon_declaration;
            static inline auto& WorkAreaAdr = __declare_io_port(SRAMAdr_IO, 0x1F801DA2);
        };
    }
}