#pragma once
#include "ioport.hpp"

namespace JabyEngine {
    namespace DMA_IO_Values {
        using Priority = uint32_t;
        static constexpr Priority HighestPriority = 0;
        static constexpr Priority LowestPriority  = 7;

        __declare_io_struct(BCR, uint32_t) {
            struct SyncMode0 {
                static constexpr auto NumberOfWords = BitRange::from_to(0, 15);
                static constexpr auto CD_OneBlock   = Bit(16);

                static constexpr BCR for_cd(size_t words) {
                    return BCR::from(SyncMode0::CD_OneBlock, SyncMode0::NumberOfWords.with(words));
                }
            };

            struct SyncMode1 {
                static constexpr auto BlockSize   = BitRange::from_to(0, 15);
                static constexpr auto BlockAmount = BitRange::from_to(16, 31);
            };

            struct SyncMode2 {
                static constexpr BCR for_gpu_cmd() {
                    return {0};
                }
            };
        };

        __declare_io_struct(CHCHR, uint32_t) {
            enum SyncMode_t {
                Sync0 = 0, //Start immediately,
                Sync1 = 1, //Sync blocks to DMA requests
                Sync2 = 2, //Linked List
            };

            static constexpr auto ManualStart = Bit(28);

            static constexpr auto Start = Bit(24);
            static constexpr auto Busy  = Start;

            static constexpr auto ChoppingCPUWindowSize = BitRange::from_to(20, 22);
            static constexpr auto ChoppingDMAWindowSize = BitRange::from_to(16, 18);

            static constexpr auto SyncMode     = BitRange::from_to(9, 10);
            static constexpr auto UseSyncMode0 = SyncMode.with(Sync0);
            static constexpr auto UseSyncMode1 = SyncMode.with(Sync1);
            static constexpr auto UseSyncMode2 = SyncMode.with(Sync2);

            static constexpr auto UseChopping   = Bit(8);

            static constexpr auto MemoryAdrDecreaseBy4 = Bit(1);
            static constexpr auto MemoryAdrIncreaseBy4 = !MemoryAdrDecreaseBy4;

            static constexpr auto FromMainRAM = Bit(0);
            static constexpr auto ToMainRAM   = !FromMainRAM;

            static constexpr CHCHR StartMDECin() {
                return CHCHR{0x01000201};
            }

            static constexpr CHCHR StartMDECout() {
                return CHCHR{0x01000200};
            }

            static constexpr CHCHR StartGPUReceive() {
                return CHCHR{0x01000201};
            }

            static constexpr CHCHR StartGPULinked() {
                return CHCHR{0x01000401};
            }

            static constexpr CHCHR StartCDROM() {
                return CHCHR{0x11000000};
            }

            static constexpr CHCHR StartSPUReceive() {
                return CHCHR{0x01000201};
            }

            static constexpr CHCHR StartOTC() {
                return CHCHR{0x11000002};
            }
        };

        __declare_io_struct(DICR, uint32_t) { 
            static constexpr auto MasterEnable     = Bit(31);
            static constexpr auto Flags            = BitRange::from_to(24, 30);
            static constexpr auto MasterEnableDPCR = Bit(23);
            static constexpr auto EnableDPCR       = BitRange::from_to(16, 22);
            static constexpr auto ForceIRQ         = Bit(15);

            static constexpr DICR empty() {
                return DICR{0};
            }
        };

        __declare_io_struct(DPCR, uint32_t) {
            struct DMASetting {
                uint16_t master_bit;

                static constexpr DMASetting create(uint16_t master_bit) {
                    return DMASetting{master_bit};
                }

                constexpr BitRange::RangeValuePair<uint32_t> turn_on(uint8_t priority) const {
                    return BitRange::from_to(this->master_bit - 3, this->master_bit).with(static_cast<uint32_t>(0b1000 + (priority & 0b111)));
                }

                constexpr ClearBit turn_off() const {
                    return ClearBit(this->master_bit);
                }
            };

            static constexpr const auto OTC      = DMASetting(27);
            static constexpr const auto PIO      = DMASetting(23);
            static constexpr const auto SPU      = DMASetting(19);
            static constexpr const auto CDROM    = DMASetting(15);
            static constexpr const auto GPU      = DMASetting(11);
            static constexpr const auto MDEC_Out = DMASetting(7);
            static constexpr const auto MDEC_In  = DMASetting(3);

            static constexpr auto OTCEnabled     = Bit(27);
            static constexpr auto OTCPriority    = BitRange::from_to(24, 26);

            static constexpr auto PIOEnabled     = Bit(23);
            static constexpr auto PIOPriority    = BitRange::from_to(20, 22);

            static constexpr auto SPUEnabled     = Bit(19);
            static constexpr auto SPUPriority    = BitRange::from_to(16, 18);

            static constexpr auto CDROMEnabled    = Bit(15);
            static constexpr auto CDROMPriority   = BitRange::from_to(12, 14);

            static constexpr auto GPUEnabled      = Bit(11);
            static constexpr auto GPUPriority     = BitRange::from_to(8, 10);

            static constexpr auto MDECoutEnabled  = Bit(7);
            static constexpr auto MDECoutPriority = BitRange::from_to(4, 6);

            static constexpr auto MDECinEnabled   = Bit(3);
            static constexpr auto MDECinPriority  = BitRange::from_to(0, 2);
        };

        __declare_io_struct(MADR, uint32_t) { 
            static constexpr auto MemoryAdr = BitRange::from_to(0, 23);
        };
    }
}