/*
 * Copyright (c) Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#pragma once
#include 
#include 
#include 
#include 
namespace ams::sf {
    enum class BufferTransferMode {
        MapAlias,
        Pointer,
        AutoSelect,
    };
    namespace impl {
        /* Buffer utilities. */
        struct BufferBaseTag{};
        template
        constexpr inline u32 BufferTransferModeAttributes = [] {
            if constexpr (TransferMode == BufferTransferMode::MapAlias) {
                return SfBufferAttr_HipcMapAlias;
            } else if constexpr (TransferMode == BufferTransferMode::Pointer) {
                return SfBufferAttr_HipcPointer;
            } else if constexpr(TransferMode == BufferTransferMode::AutoSelect) {
                return SfBufferAttr_HipcAutoSelect;
            } else {
                static_assert(false, "Invalid BufferTransferMode");
            }
        }();
    }
    template
    constexpr inline bool IsLargeData = std::is_base_of::value;
    template
    constexpr inline bool IsLargeData> = IsLargeData;
    template
    constexpr inline size_t LargeDataSize = sizeof(T);
    template
    constexpr inline size_t LargeDataSize> = sizeof(T);
    template
    constexpr inline BufferTransferMode PreferredTransferMode = [] {
        constexpr bool prefers_map_alias   = std::is_base_of::value;
        constexpr bool prefers_pointer     = std::is_base_of::value;
        constexpr bool prefers_auto_select = std::is_base_of::value;
        if constexpr (prefers_map_alias) {
            static_assert(!prefers_pointer && !prefers_auto_select, "Type T must only prefer one transfer mode.");
            return BufferTransferMode::MapAlias;
        } else if constexpr (prefers_pointer) {
            static_assert(!prefers_map_alias && !prefers_auto_select, "Type T must only prefer one transfer mode.");
            return BufferTransferMode::Pointer;
        } else if constexpr (prefers_auto_select) {
            static_assert(!prefers_map_alias && !prefers_pointer, "Type T must only prefer one transfer mode.");
            return BufferTransferMode::AutoSelect;
        } else if constexpr (IsLargeData) {
            return BufferTransferMode::Pointer;
        } else {
            return BufferTransferMode::MapAlias;
        }
    }();
    template
    constexpr inline BufferTransferMode PreferredTransferMode> = PreferredTransferMode;
    namespace impl {
        class BufferBase : public BufferBaseTag {
            public:
                static constexpr u32 AdditionalAttributes = 0;
            private:
                const cmif::PointerAndSize m_pas;
            protected:
                constexpr uintptr_t GetAddressImpl() const {
                    return m_pas.GetAddress();
                }
                template
                constexpr inline size_t GetSizeImpl() const {
                    return m_pas.GetSize() / sizeof(Entry);
                }
            public:
                constexpr BufferBase() : m_pas() { /* ... */ }
                constexpr BufferBase(const cmif::PointerAndSize &pas) : m_pas(pas) { /* ... */ }
                constexpr BufferBase(uintptr_t ptr, size_t sz) : m_pas(ptr, sz) { /* ... */ }
        };
        class InBufferBase : public BufferBase {
            public:
                using BaseType = BufferBase;
                static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes |
                                                            SfBufferAttr_In;
            public:
                constexpr InBufferBase() : BaseType() { /* ... */ }
                constexpr InBufferBase(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ }
                constexpr InBufferBase(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ }
                InBufferBase(const void *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
                InBufferBase(const u8 *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
        };
        class OutBufferBase : public BufferBase {
            public:
                using BaseType = BufferBase;
                static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes |
                                                            SfBufferAttr_Out;
            public:
                constexpr OutBufferBase() : BaseType() { /* ... */ }
                constexpr OutBufferBase(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ }
                constexpr OutBufferBase(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ }
                OutBufferBase(void *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
                OutBufferBase(u8 *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
        };
        template
        class InBufferImpl : public InBufferBase {
            public:
                using BaseType = InBufferBase;
                static constexpr BufferTransferMode TransferMode = TMode;
                static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes |
                                                            ExtraAttributes;
            public:
                constexpr InBufferImpl() : BaseType() { /* ... */ }
                constexpr InBufferImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ }
                constexpr InBufferImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ }
                InBufferImpl(const void *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
                InBufferImpl(const u8 *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
                constexpr const u8 *GetPointer() const {
                    return reinterpret_cast(this->GetAddressImpl());
                }
                constexpr size_t GetSize() const {
                    return this->GetSizeImpl();
                }
        };
        template
        class OutBufferImpl : public OutBufferBase {
            public:
                using BaseType = OutBufferBase;
                static constexpr BufferTransferMode TransferMode = TMode;
                static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes |
                                                            ExtraAttributes;
            public:
                constexpr OutBufferImpl() : BaseType() { /* ... */ }
                constexpr OutBufferImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ }
                constexpr OutBufferImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ }
                OutBufferImpl(void *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
                OutBufferImpl(u8 *ptr, size_t sz) : BaseType(reinterpret_cast(ptr), sz) { /* ... */ }
                constexpr u8 *GetPointer() const {
                    return reinterpret_cast(this->GetAddressImpl());
                }
                constexpr size_t GetSize() const {
                    return this->GetSizeImpl();
                }
        };
        template>
        struct InArrayImpl : public InBufferBase {
            public:
                using BaseType = InBufferBase;
                static constexpr BufferTransferMode TransferMode = TMode;
                static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes;
            public:
                constexpr InArrayImpl() : BaseType() { /* ... */ }
                constexpr InArrayImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ }
                InArrayImpl(const T *ptr, size_t num_elements) : BaseType(reinterpret_cast(ptr), num_elements * sizeof(T)) { /* ... */ }
                constexpr const T *GetPointer() const {
                    return reinterpret_cast(this->GetAddressImpl());
                }
                constexpr size_t GetSize() const {
                    return this->GetSizeImpl();
                }
                constexpr const T &operator[](size_t i) const {
                    return this->GetPointer()[i];
                }
                constexpr explicit operator Span() const {
                    return {this->GetPointer(), this->GetSize()};
                }
                constexpr Span ToSpan() const {
                    return {this->GetPointer(), this->GetSize()};
                }
        };
        template>
        struct OutArrayImpl : public OutBufferBase {
            public:
                using BaseType = OutBufferBase;
                static constexpr BufferTransferMode TransferMode = TMode;
                static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes;
            public:
                constexpr OutArrayImpl() : BaseType() { /* ... */ }
                constexpr OutArrayImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ }
                OutArrayImpl(T *ptr, size_t num_elements) : BaseType(reinterpret_cast(ptr), num_elements * sizeof(T)) { /* ... */ }
                constexpr T *GetPointer() const {
                    return reinterpret_cast(this->GetAddressImpl());
                }
                constexpr size_t GetSize() const {
                    return this->GetSizeImpl();
                }
                constexpr T &operator[](size_t i) const {
                    return this->GetPointer()[i];
                }
                constexpr explicit operator Span() const {
                    return {this->GetPointer(), this->GetSize()};
                }
                constexpr Span ToSpan() const {
                    return {this->GetPointer(), this->GetSize()};
                }
        };
    }
    /* Buffer Types. */
    using InBuffer            = typename impl::InBufferImpl;
    using InMapAliasBuffer    = typename impl::InBufferImpl;
    using InPointerBuffer     = typename impl::InBufferImpl;
    using InAutoSelectBuffer  = typename impl::InBufferImpl;
    using InNonSecureBuffer   = typename impl::InBufferImpl;
    using InNonDeviceBuffer   = typename impl::InBufferImpl;
    using InNonSecureAutoSelectBuffer = typename impl::InBufferImpl;
    using OutBuffer           = typename impl::OutBufferImpl;
    using OutMapAliasBuffer   = typename impl::OutBufferImpl;
    using OutPointerBuffer    = typename impl::OutBufferImpl;
    using OutAutoSelectBuffer = typename impl::OutBufferImpl;
    using OutNonSecureBuffer  = typename impl::OutBufferImpl;
    using OutNonDeviceBuffer  = typename impl::OutBufferImpl;
    using OutNonSecureAutoSelectBuffer = typename impl::OutBufferImpl;
    template
    using InArray             = typename impl::InArrayImpl;
    template
    using InMapAliasArray     = typename impl::InArrayImpl;
    template
    using InPointerArray      = typename impl::InArrayImpl;
    template
    using InAutoSelectArray   = typename impl::InArrayImpl;
    template
    using OutArray            = typename impl::OutArrayImpl;
    template
    using OutMapAliasArray    = typename impl::OutArrayImpl;
    template
    using OutPointerArray     = typename impl::OutArrayImpl;
    template
    using OutAutoSelectArray  = typename impl::OutArrayImpl;
    /* Attribute serialization structs. */
    template
    constexpr inline bool IsBuffer = [] {
        const bool is_buffer = std::is_base_of::value;
        const bool is_large_data = IsLargeData;
        static_assert(!(is_buffer && is_large_data), "Invalid sf::IsBuffer state");
        return is_buffer || is_large_data;
    }();
    template
    constexpr inline u32 BufferAttributes = [] {
        static_assert(IsBuffer, "BufferAttributes requires IsBuffer");
        if constexpr (std::is_base_of::value) {
            return impl::BufferTransferModeAttributes | T::AdditionalAttributes;
        } else if constexpr (IsLargeData) {
            u32 attr = SfBufferAttr_FixedSize | impl::BufferTransferModeAttributes>;
            if constexpr (std::is_base_of::value) {
                attr |= SfBufferAttr_Out;
            } else {
                attr |= SfBufferAttr_In;
            }
            return attr;
        } else {
            static_assert(!std::is_same::value, "Invalid BufferAttributes");
        }
    }();
}