#pragma once
#include "../Auxiliary/bits.hpp"
#include "../jabyengine_defines.hpp"

namespace JabyEngine {
    namespace GPU {
        namespace internal {
            template<typename T, typename S>
            struct XYMovement {
                constexpr T& add(S dx, S dy) {
                    static_cast<T*>(this)->x += dx;
                    static_cast<T*>(this)->y += dy;
                    
                    return *static_cast<T*>(this);
                }

                constexpr T add(S dx, S dy) const {
                    return T::create(static_cast<const T*>(this)->x, static_cast<const T*>(this)->y).add(dx, dy);
                }

                constexpr T& add(const T& offset) {
                    return add(offset.x, offset.y);
                }

                constexpr T add(const T& offset) const {
                    return add(offset.x, offset.y);
                }

                constexpr T& sub(S dx, S dy) {
                    static_cast<T*>(this)->x -= dx;
                    static_cast<T*>(this)->y -= dy;
                    
                    return *static_cast<T*>(this);
                }

                constexpr T sub(S dx, S dy) const {
                    return T::create(static_cast<const T*>(this)->x, static_cast<const T*>(this)->y).sub(dx, dy);
                }

                constexpr T& sub(const T& offset) {
                    return sub(offset.x, offset.y);
                }

                constexpr T sub(const T& offset) const {
                    return sub(offset.x, offset.y);
                }

                constexpr T& move(S dx, S dy) {    
                    return this->add(dx, dy);
                }

                constexpr T move(S dx, S dy) const {
                    return T::create(static_cast<const T*>(this)->x, static_cast<const T*>(this)->y).move(dx, dy);
                }
            };

            template<typename T>
            struct PredefinedColors {
                static constexpr T Black() {
                    return T::from_rgb(0, 0, 0);
                }

                static constexpr T Grey() {
                    return T::from_rgb(0x80, 0x80, 0x80);
                }

                static constexpr T White(uint8_t base = 0xFF) {
                    return T::from_rgb(base, base, base);
                }

                static constexpr T Red(uint8_t base = 0xFF) {
                    return T::from_rgb(base, 0x0, 0x0);
                }

                static constexpr T Green(uint8_t base = 0xFF) {
                    return T::from_rgb(0x0, base, 0x0);
                }

                static constexpr T Blue(uint8_t base = 0xFF) {
                    return T::from_rgb(0x0, 0x0, base);
                }

                static constexpr T Yellow(uint8_t base = 0xFF) {
                    return T::from_rgb(base, base, 0x0);
                }

                static constexpr T Purple(uint8_t base = 0xFF) {
                    return T::from_rgb(base, 0x0, base);
                }

                static constexpr T Turquoise(uint8_t base = 0xFF) {
                    return T::from_rgb(0x0, base, base);
                }
            };
        }

        enum struct SemiTransparency {
            B_Half_add_F_Half = 0,
            B_add_F           = 1,
            B_sub_F           = 2,
            B_add_F_Quarter   = 3,
        };

        enum struct TextureColorMode {
            clut4    = 0,
            clut8    = 1,
            direct16 = 2,
        };

        struct Color24 : public internal::PredefinedColors<Color24> {
            uint8_t red;
            uint8_t green;
            uint8_t blue;        

            static constexpr Color24 from_rgb(uint8_t r, uint8_t g, uint8_t b) {
                return Color24{.red = r, .green = g, .blue = b};
            }

            constexpr uint32_t raw() const {
                return ((this->blue << 16) | (this->green << 8) | this->red);
            }

            constexpr Color24 invert() const {
                return Color24::from_rgb(this->red^0xFF, this->green^0xFF, this->blue^0xFF);
            }
        };

        struct Color : public internal::PredefinedColors<Color> {
            static constexpr auto RedRange            = BitRange::from_to(0, 4);
            static constexpr auto GreenRange          = BitRange::from_to(5, 9);
            static constexpr auto BlueRange           = BitRange::from_to(10, 14);
            static constexpr auto SemiTransperancyBit = Bit(15);

            uint16_t raw;

            static constexpr Color from_rgb(uint8_t r, uint8_t g, uint8_t b) {
                return Color().set_red(r >> 3).set_green(g >> 3).set_blue(b >> 3);
            }

            static constexpr Color from(const Color24& color) {
                return Color::from_rgb(color.red, color.green, color.blue);
            }

            constexpr Color invert() const {
                return Color{.raw = static_cast<uint16_t>(this->raw^0xFFFF)};
            }

            constexpr Color& set_red(uint8_t red) {
                this->raw = bit::value::set_normalized(this->raw, RedRange.with(red));
                return *this;
            }

            constexpr Color& set_green(uint8_t green) {
                this->raw = bit::value::set_normalized(this->raw, GreenRange.with(green));
                return *this;
            }

            constexpr Color& set_blue(uint8_t blue) {
                this->raw = bit::value::set_normalized(this->raw, BlueRange.with(blue));
                return *this;
            }
        };

        struct LookUpColor8 {
            uint8_t lu_id[2];

            static constexpr LookUpColor8 create(const uint8_t (&data)[2]) {
                return LookUpColor8::create(data[0], data[1]);
            }

            static constexpr LookUpColor8 create(uint8_t px0, uint8_t px1) {
                return LookUpColor8{{px0, px1}};
            }

            constexpr uint8_t get_lu_id(size_t at) const {
                return this->lu_id[at];
            }

            constexpr void set_lu_id(uint8_t new_lu_id, size_t at) {
                this->lu_id[at] = new_lu_id;
            }
        };

        struct LookUpColor4 {
            uint8_t lu_id[2];

            static constexpr LookUpColor4 create(const uint8_t (&data)[4]) {
                return LookUpColor4::create(data[0], data[1], data[2], data[3]);
            }

            static constexpr LookUpColor4 create(uint8_t px0, uint8_t px1, uint8_t px2, uint8_t px3) {
                return LookUpColor4{{static_cast<uint8_t>((px1 << 4) | px0), static_cast<uint8_t>((px3 << 4) | px2)}};
            }
        };

        template<typename T>
        struct Position : public internal::XYMovement<Position<T>, T> {
            using PrimitiveType = T;
            
            T x;
            T y;

            static constexpr Position create(T x, T y) {
                return Position{.x = x, .y = y};
            }
        };
        using PositionI8  = Position<int8_t>;
        using PositionU8  = Position<uint8_t>;
        using PositionI16 = Position<int16_t>;
        using PositionU16 = Position<uint16_t>;
        using Vertex      = PositionI16;

        template<typename T>
        struct Size {
            T width;
            T height;

            static constexpr Size create(T w, T h) {
                return Size{w, h};
            }

            template<typename S>
            static constexpr Size from(const Size<S>& size) {
                return Size::create(static_cast<T>(size.width), static_cast<T>(size.height));
            }

            auto operator<=>(const Size<T>&) const = default;
        };
        using SizeI8  = Size<int8_t>;
        using SizeU8  = Size<uint8_t>;
        using SizeI16 = Size<int16_t>;
        using SizeU16 = Size<uint16_t>;

        template<typename T, typename S>
        static constexpr T tile_id_for(S id, S row_count, Size<S> size) {
            const auto x = id%row_count;
            const auto y = id/row_count;

            return T::create(static_cast<S>(size.width*x), static_cast<S>(size.height*y));
        }

        template<typename T, typename S>
        static constexpr T tile_id_for16(S id, Size<S> size) {
            const auto x = id&0xF;
            const auto y = id >> 4;

            return T::create(static_cast<S>(x*size.width), static_cast<S>(y*size.height));
        }

        template<typename T>
        struct Area {
            Position<T> position;
            Size<T>     size;

            static constexpr Area create(Position<T> position, Size<T> size) {
                return Area{position, size};
            }

            static constexpr Area create(T position_x, T position_y, T size_width, T size_height) {
                return Area{Position<T>::create(position_x, position_y), Size<T>::create(size_width, size_height)};
            }
            
            constexpr Area centered() const { 
                return Area(this->position.sub(this->size.width/2, this->size.height/2), this->size);
            }

            constexpr Position<T> get_top_left() const {
                return this->position;
            }

            constexpr Position<T> get_top_right() const {
                return this->position.add(this->size.width, 0);
            }

            constexpr Position<T> get_bottom_left() const {
                return this->position.add(0, this->size.height);
            }

            constexpr Position<T> get_bottom_right() const {
                return this->position.move(this->size.width, this->size.height);
            }
        };
        typedef Area<int16_t>  AreaI16;
        typedef Area<uint16_t> AreaU16;

        // Type used for primitives
        struct PageOffset : public internal::XYMovement<PageOffset, uint8_t> {
            union {
                uint8_t x;
                uint8_t u;
            };

            union {
                uint8_t y;
                uint8_t v;
            };

            static constexpr PageOffset create(uint8_t u, uint8_t v) {
                return PageOffset{.x = u, .y = v}; //< Activate x and y to use XYMovement during constexpr
            }

            static constexpr PageOffset from_tile_id(int16_t id, int16_t row_count, SizeI16 size) {
                return tile_id_for<PageOffset>(id, row_count, size);
            }

            static constexpr PageOffset from_tile_id16(int16_t id, SizeI16 size) {
                return tile_id_for16<PageOffset>(id, size);
            }
        };

        struct VertexColor {
            Vertex  position;
            Color24 color;

            static constexpr VertexColor create(Vertex vertex, Color24 color) {
                return VertexColor{vertex, color};
            }
        };

        struct ColorVertex {
                       Color24 color;
            alignas(2) Vertex  position;

            static constexpr ColorVertex create(Color24 color, Vertex vertex) {
                return ColorVertex{color, vertex};
            }
        };
    }
}