#ifndef OPENMW_COMPONENTS_ESM3_INFOORDER_H
#define OPENMW_COMPONENTS_ESM3_INFOORDER_H

#include "components/esm/refid.hpp"

#include <iterator>
#include <list>
#include <type_traits>
#include <unordered_map>
#include <utility>

namespace ESM
{
    template <class T>
    class InfoOrder
    {
    public:
        const std::list<T>& getOrderedInfo() const { return mOrderedInfo; }

        template <class V>
        void insertInfo(V&& value, bool deleted)
        {
            static_assert(std::is_same_v<std::decay_t<V>, T>);

            auto it = mInfoPositions.find(value.mId);

            if (it != mInfoPositions.end() && it->second.mPosition->mPrev == value.mPrev)
            {
                *it->second.mPosition = std::forward<V>(value);
                it->second.mDeleted = deleted;
                return;
            }

            if (it == mInfoPositions.end())
                it = mInfoPositions.emplace(value.mId, Item{ .mPosition = mOrderedInfo.end(), .mDeleted = deleted })
                         .first;

            Item& item = it->second;

            const auto insertOrSplice = [&](typename std::list<T>::const_iterator before) {
                if (item.mPosition == mOrderedInfo.end())
                    item.mPosition = mOrderedInfo.insert(before, std::forward<V>(value));
                else
                    mOrderedInfo.splice(before, mOrderedInfo, item.mPosition);
            };

            if (value.mPrev.empty())
            {
                insertOrSplice(mOrderedInfo.begin());
                return;
            }

            const auto prevIt = mInfoPositions.find(value.mPrev);
            if (prevIt != mInfoPositions.end())
            {
                insertOrSplice(std::next(prevIt->second.mPosition));
                return;
            }

            const auto nextIt = mInfoPositions.find(value.mNext);
            if (nextIt != mInfoPositions.end())
            {
                insertOrSplice(nextIt->second.mPosition);
                return;
            }

            insertOrSplice(mOrderedInfo.end());
        }

        void removeInfo(const RefId& infoRefId)
        {
            const auto it = mInfoPositions.find(infoRefId);

            if (it == mInfoPositions.end())
                return;

            mOrderedInfo.erase(it->second.mPosition);
            mInfoPositions.erase(it);
        }

        void removeDeleted()
        {
            for (auto it = mInfoPositions.begin(); it != mInfoPositions.end();)
            {
                if (!it->second.mDeleted)
                {
                    ++it;
                    continue;
                }

                mOrderedInfo.erase(it->second.mPosition);
                it = mInfoPositions.erase(it);
            }
        }

        void extractOrderedInfo(std::list<T>& info)
        {
            info = mOrderedInfo;
            mInfoPositions.clear();
        }

    private:
        struct Item
        {
            typename std::list<T>::iterator mPosition;
            bool mDeleted = false;
        };

        std::list<T> mOrderedInfo;
        std::unordered_map<RefId, Item> mInfoPositions;
    };
}

#endif