#pragma once
#include "../../../GPU/gpu_types.hpp"
#include "ioport.hpp"

namespace JabyEngine {
    namespace GPU_IO_Values {
        namespace internal {
            template<typename T>
            static constexpr T construct_cmd(uint32_t cmd, uint32_t value) {
                return T::from(T::ID.with(cmd), T::Value.with(value)); 
            }
        }

        __declare_io_struct(DisplayMode, uint32_t) {
            enum AreaColorDepth {
                $15bit = 0,
                $24bit = 1,
            };

            enum State {
                On  = 0,
                Off = 1
            };

            enum HorizontalResolution {
                $256 = 0,
                $320 = 1,
                $512 = 2,
                $640 = 3,
            };

            enum struct TVEncoding {
                NTSC = 0,
                PAL  = 1,
            };

            enum VerticalResolution {
                $240 = 0,
                $480 = 1
            };

            static constexpr auto HorizontalResolution368 = Bit(6);
            static constexpr auto VerticalInterlace       = Bit(5);
            static constexpr auto DisplayAreaColorDepth   = BitRange::from_to(4, 4);
            static constexpr auto VideoMode               = BitRange::from_to(3, 3);
            static constexpr auto VerticalResolution      = BitRange::from_to(2, 2);
            static constexpr auto HorizontalResolution    = BitRange::from_to(0, 1);

            static constexpr DisplayMode PAL() {
                return DisplayMode::from(
                    HorizontalResolution.with(HorizontalResolution::$320),
                    VerticalResolution.with(VerticalResolution::$240),
                    VideoMode.with(TVEncoding::PAL),
                    DisplayAreaColorDepth.with(AreaColorDepth::$15bit)
                );
            }

            static constexpr DisplayMode NTSC() {
                return DisplayMode::from(
                    HorizontalResolution.with(HorizontalResolution::$320),
                    VerticalResolution.with(VerticalResolution::$240),
                    VideoMode.with(TVEncoding::NTSC),
                    DisplayAreaColorDepth.with(AreaColorDepth::$15bit)
                );
            }
        };

        __declare_io_struct(GPUREAD, uint32_t) {
        };

        __declare_io_struct(GPUSTAT, uint32_t) {
            enum DMADirection {
                Off     = 0,
                Fifo    = 1,
                CPU2GPU = 2,
                GPU2CPU = 3,
            };

            static constexpr auto DrawingOddLinesInterlaced    = Bit(31);
            static constexpr auto DMADirectionValue            = BitRange::from_to(29, 30);
            static constexpr auto DMAReady                     = Bit(28);
            static constexpr auto VRAMtoCPUtransferReay        = Bit(27);
            static constexpr auto GP0ReadyForCMD               = Bit(26);
            static constexpr auto FifoNotFull                  = Bit(25); // Only for Fifo
            static constexpr auto InterruptRequest             = Bit(24);
            static constexpr auto DisplayDisabled              = Bit(23);
            static constexpr auto VerticalInterlaceOn          = Bit(22);
            static constexpr auto DisplayAreaColorDepth        = BitRange::from_to(21, 21);
            static constexpr auto VideoModePal                 = Bit(20);
            static constexpr auto VerticalResolutionValue      = BitRange::from_to(19, 19);
            static constexpr auto HorizontalResolutionValue    = BitRange::from_to(17, 18);
            static constexpr auto HorizontalResolution368      = Bit(16);
            static constexpr auto TexturesDisabled             = Bit(15);
            static constexpr auto NotDrawingMaskedPixels       = Bit(12);
            static constexpr auto MaskBitSetDuringDrawEnabled  = Bit(11);
            static constexpr auto DrawingToDisplayAreadAllowed = Bit(10);
            static constexpr auto DitherEnabled                = Bit(9);
            static constexpr auto TexturePageColorValue        = BitRange::from_to(7, 8);
            static constexpr auto SemiTransparencyValue        = BitRange::from_to(5, 6);
            static constexpr auto TexturePageY                 = BitRange::from_to(4, 4); // N*256
            static constexpr auto TexturePageX                 = BitRange::from_to(0, 3); // N*64

            static constexpr auto VerticalResolution480 = Bit(19);
            static constexpr auto TexturePageY256       = Bit(4);
        };

        __declare_io_struct(GP0, uint32_t) {
            static constexpr auto ID    = BitRange::from_to(24, 31);
            static constexpr auto Value = BitRange::from_to(0, 23);

            static constexpr GP0 DrawAreaTemplate(uint8_t code, uint16_t x, uint16_t y) {
                constexpr auto Command = BitRange::from_to(24, 31);
                constexpr auto Y       = BitRange::from_to(10, 18);
                constexpr auto X       = BitRange::from_to(0,   9);

                return internal::construct_cmd<GP0>(code, Y.as_value(static_cast<uint32_t>(y)) | X.as_value(static_cast<uint32_t>(x)));
            }

            static constexpr GP0 ClearCache() {
                return internal::construct_cmd<GP0>(0x01, 0x0);
            }

            static constexpr GP0 QuickFill(GPU::Color24 color) {
                return internal::construct_cmd<GP0>(0x02, color.raw());
            }

            static constexpr GP0 VRAM2VRAMBlitting() {
                return internal::construct_cmd<GP0>(0x80, 0);
            }

            static constexpr GP0 CPU2VRAMBlitting() {
                return internal::construct_cmd<GP0>(0xA0, 0);
            }

            static constexpr GP0 TexPage(const GPU::PositionU16& page_pos, GPU::SemiTransparency transparency, GPU::TextureColorMode tex_color, bool dither, bool draw_on_display_area) {
                constexpr auto TexXRange            = BitRange::from_to(0,   3);
                constexpr auto TexYRange            = BitRange::from_to(4,   4);
                constexpr auto TransparencyRange    = BitRange::from_to(5,   6);
                constexpr auto TextureColorRange    = BitRange::from_to(7,   8);
                constexpr auto DitherBit            = BitRange::from_to(9,   9);
                constexpr auto DrawOnDisplayAreaBit = BitRange::from_to(10, 10);

                return internal::construct_cmd<GP0>(0xE1, 
                    TexXRange.as_value(page_pos.x >> 6) | TexYRange.as_value(page_pos.y >> 8) | 
                    TransparencyRange.as_value(static_cast<uint32_t>(transparency)) | TextureColorRange.as_value(static_cast<uint32_t>(tex_color)) | 
                    DitherBit.as_value(static_cast<uint32_t>(dither)) | DrawOnDisplayAreaBit.as_value(static_cast<uint32_t>(draw_on_display_area))
                );
            }

            static constexpr GP0 DrawAreaTopLeft(const GPU::PositionU16& position) {
                return GP0::DrawAreaTemplate(0xE3, position.x, position.y);
            }

            static constexpr GP0 DrawAreaBottomRight(const GPU::PositionU16& position) {
                return GP0::DrawAreaTemplate(0xE4, position.x, position.y);
            }

            static constexpr GP0 DrawOffset(const GPU::PositionI16& offset) {
                constexpr auto X = BitRange::from_to(0,  10);
                constexpr auto Y = BitRange::from_to(11, 21);

                return internal::construct_cmd<GPU_IO_Values::GP0>(0xE5, X.as_value(static_cast<int32_t>(offset.x)) | Y.as_value(static_cast<int32_t>(offset.y)));
            }

            static constexpr GP0 PostionTopLeft(const GPU::PositionU16& position) {
                return GP0{(static_cast<uint32_t>(position.y) << 16u) | position.x};
            }

            static constexpr GP0 WidthHeight(const GPU::SizeU16& size) {
                return GP0{(static_cast<uint32_t>(size.height) << 16u) | size.width};
            }
        };

        __declare_io_struct(GP1, uint32_t) {
            static constexpr auto ID    = BitRange::from_to(24, 31);
            static constexpr auto Value = BitRange::from_to(0, 23);

            static constexpr GP1 Reset() {
                return GP1{0};
            }

            static constexpr GP1 ResetCMDBuffer() {
                return internal::construct_cmd<GP1>(0x01, 0);
            }

            static constexpr GP1 DisplayState(DisplayMode::State state) {
                return internal::construct_cmd<GP1>(0x03, static_cast<uint32_t>(state));
            }

            static constexpr GP1 DMADirection(GPUSTAT::DMADirection dir) {
                return internal::construct_cmd<GP1>(0x04, dir);
            }

            static constexpr GP1 DisplayArea(const GPU::PositionU16& position) {
                constexpr auto X = BitRange::from_to(0, 9);
                constexpr auto Y = BitRange::from_to(10, 18);

                return internal::construct_cmd<GP1>(0x05, X.as_value(static_cast<uint32_t>(position.x)) | Y.as_value(static_cast<uint32_t>(position.y)));
            }

            static constexpr GP1 HorizontalDisplayRange(uint16_t x1, uint16_t x2) {
                constexpr auto X1 = BitRange::from_to(0,  11);
                constexpr auto X2 = BitRange::from_to(12, 23);

                return internal::construct_cmd<GP1>(0x06, X1.as_value(static_cast<uint32_t>(x1)) | X2.as_value(static_cast<uint32_t>(x2)));
            }

            static constexpr GP1 VerticalDisplayRange(uint16_t y1, uint16_t y2) {
                constexpr auto Y1 = BitRange::from_to(0,   9);
                constexpr auto Y2 = BitRange::from_to(10, 19);

                return internal::construct_cmd<GP1>(0x07, Y1.as_value(static_cast<uint32_t>(y1)) | Y2.as_value(static_cast<uint32_t>(y2)));
            }

            static constexpr GP1 DisplayMode(GPU_IO_Values::DisplayMode mode) {
                return internal::construct_cmd<GP1>(0x08, mode.raw);
            }
        };
    }
}