#pragma once
#include "linked_elements.hpp"
#include "primitive_support_types.hpp"

namespace JabyEngine {
    namespace GPU {
        namespace internal {
            struct LineCode : public CodeBase<LineCode> {
                static constexpr uint8_t CmdValue = 0b010;

                static constexpr auto GouraudShading  = Bit(28 - BitCorrection);
                static constexpr auto FlatShading     = !GouraudShading;
                static constexpr auto PolyLine        = Bit(27 - BitCorrection);
                static constexpr auto SingleLine      = !PolyLine;

                static constexpr LineCode create() {
                    return CodeBase<LineCode>::create(CmdValue);
                }
            };

            template<typename T>
            struct LineCodeInterface {
                typedef ::JabyEngine::GPU::internal::LineCode Code;

                constexpr T& set_semi_transparent(bool set = true) {
                    static_cast<T*>(this)->head.code.set_tenary(set, Code::SemiTransparent, Code::NonTransparent);
                    return *static_cast<T*>(this);
                }
            };

            struct LineHead {
                Color24  color;
                LineCode code;

                static constexpr LineHead create(const Color24& color, const LineCode& code, bool is_poly) {
                    return LineHead{.color = color, .code = code}.set_poly_line(is_poly);
                }

                constexpr LineHead& set_poly_line(bool set = true) {
                    if(set) {
                        this->code.set(LineCode::PolyLine);
                    }

                    else {
                        this->code.set(LineCode::SingleLine);
                    }
                    return *this;
                }
            };

            struct Termination {
                Vertex value;

                static constexpr Termination create() {
                    // 0x55555555 is more common then 0x50005000
                    return {.value = Vertex::create(static_cast<short>(0x5555), static_cast<short>(0x5555))};
                }
            };

            template<typename Body> 
            struct SingleLine : public internal::RenderPrimitive<SingleLine<Body>>, public LineCodeInterface<SingleLine<Body>>, public internal::LinkedElementCreator<SingleLine<Body>> {
                LineHead head;
                Vertex   start_point;
                Body     end_point;
            };

            template<typename Body, size_t N>
            struct MultiLine : public internal::RenderPrimitive<MultiLine<Body, N>>, public LineCodeInterface<MultiLine<Body, N>>, public internal::LinkedElementCreator<MultiLine<Body, N>> {
                LineHead    head;
                Vertex      start_point;
                Body        points[N];
                Termination end;

                // Could also be none const in the future
                template<typename T = Body>
                constexpr typename enable_if<is_same<T, Vertex>::value, Vertex>::type operator[](size_t idx) const {
                    if(idx == 0) {
                        return this->start_point;
                    }
                    return this->points[idx - 1];
                }

                template<typename T = Body>
                constexpr typename enable_if<is_same<T, ColorVertex>::value, ColorVertex>::type operator[](size_t idx) const {
                    if(idx == 0) {
                        return ColorVertex::create(this->head.color, this->start_point);
                    }
                    return this->points[idx - 1];
                }
            };
        }

        struct LINE_F {
            typedef internal::LineCode Code;

            static constexpr auto IdentityCode = Code::create().set(Code::FlatShading).set(Code::SingleLine).set(Code::NonTransparent); 

            static constexpr internal::SingleLine<Vertex> create(const Color24& color, const Vertex& start_point, const Vertex& end_point) {
                using namespace internal;
                return {.head = LineHead::create(color, IdentityCode, false), .start_point = start_point, .end_point = end_point};
            }

            template<typename...ARGS>
            static constexpr internal::MultiLine<Vertex, sizeof...(ARGS)> create(const Color24& color, const Vertex& start_point, const ARGS&...rest) {
                using namespace internal;
                return {.head = LineHead::create(color, IdentityCode, true), .start_point = start_point, .points = {rest...}, .end = Termination::create()};
            }
        };

        struct LINE_G {
            typedef LINE_F::Code Code;

            static constexpr auto IdentityCode = Code(LINE_F::IdentityCode).set(Code::GouraudShading);

            static constexpr internal::SingleLine<ColorVertex> create(const ColorVertex& start_point, const ColorVertex& end_point) {
                using namespace internal;
                return {.head = LineHead::create(start_point.color, IdentityCode, false), .start_point = start_point.position, .end_point = end_point};
            }

            template<typename...ARGS>
            static constexpr internal::MultiLine<ColorVertex, sizeof...(ARGS)> create(const ColorVertex& start_point, const ARGS&...rest) {
                using namespace internal;
                return {.head = LineHead::create(start_point.color, IdentityCode, true), .start_point = start_point.position, .points = {rest...}, .end = Termination::create()};
            }
        };

        using LINE_F_SINGLE = internal::SingleLine<Vertex>;
        template<size_t N>
        using LINE_F_MULTI = internal::MultiLine<Vertex, N - 1>;

        using LINE_G_SINGLE = internal::SingleLine<ColorVertex>;
        template<size_t N>
        using LINE_G_MULTI = internal::MultiLine<ColorVertex, N - 1>;
    }
}