#pragma once
#include "../../Auxiliary/bits.hpp"
#include "../../Auxiliary/type_traits.hpp"
#include "../../System/IOPorts/gpu_io.hpp"

namespace JabyEngine {
    namespace GPU {
        struct Link {
            static constexpr auto AdrRange  = BitRange::from_to(0, 23);
            static constexpr auto SizeRange = BitRange::from_to(24, 31);
            
            static constexpr uint32_t TerminationValue = 0x00FFFFFF;

            static constexpr uint32_t default_link_value(size_t size) {
                return SizeRange.as_value(size >> 2) | TerminationValue;
            }

            uint32_t link_value;

            constexpr void set_link_identitiy(size_t size) {
                this->link_value = default_link_value(size);
            }

            void set_adr(const void* adr) {
                this->link_value = bit::value::set_normalized(this->link_value, AdrRange.with(reinterpret_cast<uint32_t>(adr)));
            }

            void* get_adr() const {
                return reinterpret_cast<void*>(bit::value::get_normalized(this->link_value, AdrRange));
            }

            template<typename T>
            T& insert_after(T& obj) {
                const auto adr = Link::get_adr();

                Link::set_adr(&obj);
                obj.set_adr(adr);
                return obj;
            }

            template<typename T>
            enable_if<!is_pointer<T>::value, Link&>::type concat(T& obj) {
                Link::set_adr(&obj);
                return *this;
            }

            template<typename T>
            enable_if<!is_pointer<T>::value, const Link&>::type concat(const T& obj) {
                return concat(const_cast<Link&>(obj));
            }

            constexpr void terminate() {
                this->link_value |= TerminationValue;
            }
        };

        template<typename T>
        struct LinkedElement : public Link {
            T element;

            static constexpr LinkedElement create(const T& element) {
                LinkedElement new_element;

                new_element.element = element;
                new_element.set_link_identitiy();
                return new_element;
            }

            constexpr void set_link_identitiy() {
                Link::set_link_identitiy(sizeof(T));
            }

            constexpr const T* operator->() const {
                return &this->element;
            }

            constexpr T* operator->() {
                return &this->element;
            }

            static_assert((sizeof(T) >> 2) <= GPU_IO::FIFOWordSize);
        };

        namespace internal {
            template<typename T>
            struct LinkedElementCreator {
                typedef LinkedElement<T> Linked;

                constexpr LinkedElement<T> linked() {
                    return LinkedElement<T>::create(*static_cast<T*>(this));
                }
            };
        }

        namespace LinkHelper {
            template<typename T>
            static Link* link_array(Link* last_prim, T* elements, size_t length) {
                for(size_t n = 0; n < length; n++) {
                    last_prim->concat(elements[n]);
                    last_prim = &elements[n];
                }

                last_prim->terminate();
                return last_prim;
            }

            template<typename T, size_t N>
            static Link* link_array(Link* last_prim, T(&elements)[N]) {
                return link_array(last_prim, elements, N);
            }
        }
    }
}