/*
 * 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 <http://www.gnu.org/licenses/>.
 */
#pragma once
#include <mesosphere/kern_common.hpp>
#include <mesosphere/kern_k_class_token.hpp>

namespace ams::kern {

    /* NOTE: This header is included after all other KAutoObjects. */
    namespace impl {

        template<typename T> requires std::derived_from<T, KAutoObject>
        consteval bool IsAutoObjectInheritanceValidImpl() {
            #define CLASS_TOKEN_HANDLER(CLASSNAME)                                                                               \
                if constexpr (std::same_as<T, CLASSNAME>) {                                                                      \
                    if (T::GetStaticTypeObj().GetClassToken() != ::ams::kern::ClassToken<CLASSNAME>) {                           \
                        return false;                                                                                            \
                    }                                                                                                            \
                } else {                                                                                                         \
                    if (T::GetStaticTypeObj().IsDerivedFrom(CLASSNAME::GetStaticTypeObj()) != std::derived_from<T, CLASSNAME>) { \
                        return false;                                                                                            \
                    }                                                                                                            \
                }

            FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER)
            #undef CLASS_TOKEN_HANDLER

            return true;
        }

        consteval bool IsEveryAutoObjectInheritanceValid() {
            #define CLASS_TOKEN_HANDLER(CLASSNAME) if (!IsAutoObjectInheritanceValidImpl<CLASSNAME>()) { return false; }
            FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER)
            #undef CLASS_TOKEN_HANDLER

            return true;
        }

        static_assert(IsEveryAutoObjectInheritanceValid());

        template<typename T>
        concept IsAutoObjectWithSpecializedGetId = std::derived_from<T, KAutoObjectWithList> && requires (const T &t, const KAutoObjectWithList &l) {
            { t.GetIdImpl() } -> std::same_as<decltype(l.GetId())>;
        };

        template<typename T>
        struct AutoObjectWithListComparatorImpl {
            using RedBlackKeyType = u64;

            static ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const RedBlackKeyType &v) { return v; }

            static ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const KAutoObjectWithList &v) {
                if constexpr (IsAutoObjectWithSpecializedGetId<T>) {
                    return static_cast<const T &>(v).GetIdImpl();
                } else {
                    return reinterpret_cast<u64>(std::addressof(v));
                }
            }

            template<typename U> requires (std::same_as<U, KAutoObjectWithList> || std::same_as<U, RedBlackKeyType>)
            static ALWAYS_INLINE int Compare(const U &lhs, const KAutoObjectWithList &rhs) {
                const u64 lid = GetRedBlackKey(lhs);
                const u64 rid = GetRedBlackKey(rhs);

                if (lid < rid) {
                    return -1;
                } else if (lid > rid) {
                    return 1;
                } else {
                    return 0;
                }
            }
        };

        template<typename T>
        using AutoObjectWithListComparator = AutoObjectWithListComparatorImpl<typename std::conditional<IsAutoObjectWithSpecializedGetId<T>, T, KAutoObjectWithList>::type>;

        template<typename T>
        using TrueObjectContainerListType = typename KAutoObjectWithListContainer<T>::ListType<AutoObjectWithListComparator<T>>;

        template<typename T>
        ALWAYS_INLINE TrueObjectContainerListType<T> &GetTrueObjectContainerList(typename KAutoObjectWithListContainer<T>::DummyListType &l) {
            static_assert(alignof(l) == alignof(impl::TrueObjectContainerListType<T>));
            static_assert(sizeof(l)  == sizeof(impl::TrueObjectContainerListType<T>));
            return *reinterpret_cast<TrueObjectContainerListType<T> *>(std::addressof(l));
        }

    }

    inline NOINLINE void KAutoObject::ScheduleDestruction() {
        MESOSPHERE_ASSERT_THIS();

        /* Set our object to destroy. */
        m_next_closed_object = GetCurrentThread().GetClosedObject();

        /* Set ourselves as the thread's next object to destroy. */
        GetCurrentThread().SetClosedObject(this);
    }

    template<typename T>
    class KAutoObjectWithListContainer<T>::ListAccessor : public impl::KAutoObjectWithListContainerBase::ListAccessorImpl<impl::TrueObjectContainerListType<T>> {
        NON_COPYABLE(ListAccessor);
        NON_MOVEABLE(ListAccessor);
        private:
            using BaseListAccessor = impl::KAutoObjectWithListContainerBase::ListAccessorImpl<impl::TrueObjectContainerListType<T>>;
        public:
            explicit ALWAYS_INLINE ListAccessor(KAutoObjectWithListContainer *container) : BaseListAccessor(container, impl::GetTrueObjectContainerList<T>(container->m_dummy_object_list)) { /* ... */ }
            explicit ALWAYS_INLINE ListAccessor(KAutoObjectWithListContainer &container) : BaseListAccessor(container, impl::GetTrueObjectContainerList<T>(container.m_dummy_object_list)) { /* ... */ }

            ALWAYS_INLINE ~ListAccessor() { /* ... */ }
    };


    template<typename T>
    ALWAYS_INLINE void KAutoObjectWithListContainer<T>::Register(T *obj) {
        return this->RegisterImpl(obj, impl::GetTrueObjectContainerList<T>(m_dummy_object_list));
    }

    template<typename T>
    ALWAYS_INLINE void KAutoObjectWithListContainer<T>::Unregister(T *obj) {
        return this->UnregisterImpl(obj, impl::GetTrueObjectContainerList<T>(m_dummy_object_list));
    }

    template<typename T>
    ALWAYS_INLINE size_t KAutoObjectWithListContainer<T>::GetOwnedCountChecked(const KProcess *owner) {
        return this->GetOwnedCountImpl<T>(owner, impl::GetTrueObjectContainerList<T>(m_dummy_object_list));
    }

    inline u64 KAutoObjectWithList::GetId() const {
        #define CLASS_TOKEN_HANDLER(CLASSNAME)                                                                      \
            if constexpr (impl::IsAutoObjectWithSpecializedGetId<CLASSNAME>) {                                      \
                if (const CLASSNAME * const derived = this->DynamicCast<const CLASSNAME *>(); derived != nullptr) { \
                    return []<typename T>(const T * const t_derived) ALWAYS_INLINE_LAMBDA -> u64 {                  \
                        static_assert(std::same_as<T, CLASSNAME>);                                                  \
                        if constexpr (impl::IsAutoObjectWithSpecializedGetId<CLASSNAME>) {                          \
                            return impl::AutoObjectWithListComparator<CLASSNAME>::GetRedBlackKey(*t_derived);       \
                        } else {                                                                                    \
                            AMS_ASSUME(false);                                                                      \
                        }                                                                                           \
                    }(derived);                                                                                     \
                }                                                                                                   \
            }

        FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER)
        #undef CLASS_TOKEN_HANDLER

        return impl::AutoObjectWithListComparator<KAutoObjectWithList>::GetRedBlackKey(*this);
    }

}