#pragma once
#include "IOValues/cd_io_values.hpp"

namespace JabyEngine {
    namespace CD_IO {
        using AudioVolumeApply_IO = IOPort<CD_IO_Values::AudioVolumeApply>;
        using CommandFifo_IO      = IOPort<CD_IO_Values::CommandFifo>;
        using DataFifo_IO         = IOPort<CD_IO_Values::DataFifo>;
        using DataFifo16_IO       = IOPort<CD_IO_Values::DataFifo16>;
        using IndexStatus_IO      = IOPort<CD_IO_Values::IndexStatus>;
        using InterruptEnable_IO  = IOPort<CD_IO_Values::InterruptEnable>;
        using InterruptFlag_IO    = IOPort<CD_IO_Values::InterruptFlag>;
        using LeftCD2LeftSPU_IO   = IOPort<CD_IO_Values::LeftCD2LeftSPU>;
        using LeftCD2RightSPU_IO  = IOPort<CD_IO_Values::LeftCD2RightSPU>;
        using ParameterFifo_IO    = IOPort<CD_IO_Values::ParameterFifo>;
        using Request_IO          = IOPort<CD_IO_Values::Request>;
        using ResponseFifo_IO     = IOPort<CD_IO_Values::ResponseFifo>;
        using RightCD2LeftSPU_IO  = IOPort<CD_IO_Values::RightCD2LeftSPU>;
        using RightCD2RightSPU_IO = IOPort<CD_IO_Values::RightCD2RightSPU>;
        using SoundMapDataOut_IO  = IOPort<CD_IO_Values::SoundMapDataOut>;
        using SoundMapCoding_IO   = IOPort<CD_IO_Values::SoundMapCoding>;

        struct DataSector {
            static constexpr size_t SizeBytes = 2048;
            static constexpr size_t SizeWords = (SizeBytes/sizeof(uint32_t));

            uint32_t data[SizeWords];

            template<typename T>
            static constexpr T words_to_sectors(T size) {
                return (size + static_cast<T>(DataSector::SizeWords - 1))/static_cast<T>(DataSector::SizeWords);
            }

            constexpr size_t hash() const {
                uint32_t value = 0;

                for(const auto word : this->data) {
                    value += word;
                }
                return value;
            }
        };

        enum Index {
            Index0 = 0,
            Index1 = 1,
            Index2 = 2,
            Index3 = 3,
        };

        struct Interrupt {
            static constexpr auto ExtendedFlags = CD_IO_Values::InterruptEnable::from(
                    CD_IO_Values::InterruptEnable::InterruptTypValue.range_max<uint8_t>(), 
                    CD_IO_Values::InterruptEnable::UnknownIRQ, 
                    CD_IO_Values::InterruptEnable::CommandStartIRQ
            );

            enum Type : uint8_t {
                None        = 0,
                DataReady   = 1,
                Complete    = 2,
                Acknowledge = 3,
                DataEnd     = 4,
                DiskError   = 5
            };

            static void enable(InterruptEnable_IO& port) {
                port.write(port.read().set(CD_IO_Values::InterruptEnable::InterruptTypValue.range_max<uint8_t>()));
            }

            static void enable_extended(InterruptEnable_IO& port) {
                port.write(Interrupt::ExtendedFlags);
            }

            static Type get_type(const InterruptFlag_IO& port) {
                return static_cast<Type>(port.read().get(CD_IO_Values::InterruptFlag::InterruptTypValue));
            }

            static void ack(InterruptFlag_IO& port) {
                port.write(port.read().set(CD_IO_Values::InterruptFlag::InterruptTypValue.range_max<uint8_t>()));
            }

            static void ack_extended(InterruptFlag_IO& port) {
                port.write(Interrupt::ExtendedFlags);
            }

            static void reset_parameter_fifo(InterruptFlag_IO& port) {
                port.write(CD_IO_Values::InterruptFlag{0x40});
            }
        };

        struct Command {
            struct Desc {
                uint8_t         id;
                Interrupt::Type complete_irq;
            };

            static constexpr Desc GetStat{0x01, Interrupt::Type::Acknowledge};
            static constexpr Desc  SetLoc{0x02, Interrupt::Type::Acknowledge};
            static constexpr Desc    Play{0x03, Interrupt::Type::Acknowledge};
            static constexpr Desc   ReadN{0x06, Interrupt::Type::DataReady};
            static constexpr Desc   Pause{0x09, Interrupt::Type::Complete};
            static constexpr Desc    Init{0x0A, Interrupt::Type::Complete};
            static constexpr Desc  Demute{0x0C, Interrupt::Type::Acknowledge};
            static constexpr Desc  Filter{0x0D, Interrupt::Type::Acknowledge};
            static constexpr Desc SetMode{0x0E, Interrupt::Type::Acknowledge};
            static constexpr Desc GetLocL{0x10, Interrupt::Type::Acknowledge};
            static constexpr Desc GetLocP{0x11, Interrupt::Type::Acknowledge};
            static constexpr Desc   GetTN{0x13, Interrupt::Type::Acknowledge};
            static constexpr Desc   GetTD{0x14, Interrupt::Type::Acknowledge};
            static constexpr Desc   SeekL{0x15, Interrupt::Type::Complete};
            static constexpr Desc   ReadS{0x1B, Interrupt::Type::DataReady};
        };
        
        static constexpr auto IORegister1Adr = 0x1F801801;
        static constexpr auto IORegister2Adr = 0x1F801802;
        static constexpr auto IORegister3Adr = 0x1F801803;

        static auto& IndexStatus = __declare_io_port(IndexStatus_IO, 0x1F801800);

        struct PortIndex0 {
            static inline const auto& ResponseFifo    = __declare_io_port(ResponseFifo_IO,    IORegister1Adr);
            static inline auto&       CommandFifo     = __declare_io_port(CommandFifo_IO,     IORegister1Adr);

            static inline const auto& DataFifo        = __declare_io_port(DataFifo_IO,        IORegister2Adr);
            static inline const auto& DataFifo16      = __declare_io_port(DataFifo16_IO,      IORegister2Adr);
            static inline auto&       ParameterFifo   = __declare_io_port(ParameterFifo_IO,   IORegister2Adr);

            static inline const auto& InterruptEnable = __declare_io_port(InterruptEnable_IO, IORegister3Adr);
            static inline auto&       Request         = __declare_io_port(Request_IO,         IORegister3Adr);

            static void change_to() {
                IndexStatus.write({Index::Index0});
            }
        };

        struct PortIndex1 {
            static inline const auto& ResponseFifo    = __declare_io_port(ResponseFifo_IO,    IORegister1Adr);
            static inline auto&       SoundMapDataOut = __declare_io_port(SoundMapDataOut_IO, IORegister1Adr);

            static inline const auto& DataFifo        = __declare_io_port(DataFifo_IO,        IORegister2Adr);
            static inline const auto& DataFifo16      = __declare_io_port(DataFifo16_IO,      IORegister2Adr);
            static inline auto&       InterruptEnable = __declare_io_port(InterruptEnable_IO, IORegister2Adr);

            static inline auto&       InterruptFlag   = __declare_io_port(InterruptFlag_IO,   IORegister3Adr);

            static void change_to() {
                IndexStatus.write({Index::Index1});
            }
        };

        struct PortIndex2 {
            static inline const auto& ResponseFifo    = __declare_io_port(ResponseFifo_IO,    IORegister1Adr);
            static inline auto&       SoundMapCoding  = __declare_io_port(SoundMapCoding_IO,  IORegister1Adr);

            static inline const auto& DataFifo        = __declare_io_port(DataFifo_IO,        IORegister2Adr);
            static inline const auto& DataFifo16      = __declare_io_port(DataFifo16_IO,      IORegister2Adr);
            static inline auto&       LeftCD2LeftSPU  = __declare_io_port(LeftCD2LeftSPU_IO,  IORegister2Adr);

            static inline const auto& InterruptEnable = __declare_io_port(InterruptEnable_IO, IORegister3Adr);
            static inline auto&       LeftCD2RightSPU = __declare_io_port(LeftCD2RightSPU_IO, IORegister3Adr);

            static void change_to() {
                IndexStatus.write({Index::Index2});
            }
        };

        struct PortIndex3 {
            static inline const auto& ResponseFifo     = __declare_io_port(ResponseFifo_IO,     IORegister1Adr);
            static inline auto&       RightCD2RightSPU = __declare_io_port(RightCD2RightSPU_IO, IORegister1Adr);

            static inline const auto& DataFifo         = __declare_io_port(DataFifo_IO,        IORegister2Adr);
            static inline const auto& DataFifo16       = __declare_io_port(DataFifo16_IO,      IORegister2Adr);
            static inline auto&       RightCD2LeftSPU  = __declare_io_port(RightCD2LeftSPU_IO, IORegister1Adr);

            static inline const auto& InterruptFlag    = __declare_io_port(InterruptFlag_IO,    IORegister3Adr);
            static inline auto&       AudioVolumeApply = __declare_io_port(AudioVolumeApply_IO, IORegister3Adr);

            static void change_to() {
                IndexStatus.write({Index::Index3});
            }
        };
    }
}