#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H

#include "agentbounds.hpp"
#include "preparednavmeshdata.hpp"
#include "recastmesh.hpp"
#include "tileposition.hpp"

#include <atomic>
#include <cassert>
#include <cstring>
#include <list>
#include <map>
#include <mutex>
#include <vector>

namespace DetourNavigator
{
    struct RecastMeshData
    {
        Mesh mMesh;
        std::vector<CellWater> mWater;
        std::vector<Heightfield> mHeightfields;
        std::vector<FlatHeightfield> mFlatHeightfields;
    };

    inline bool operator<(const RecastMeshData& lhs, const RecastMeshData& rhs)
    {
        return std::tie(lhs.mMesh, lhs.mWater, lhs.mHeightfields, lhs.mFlatHeightfields)
            < std::tie(rhs.mMesh, rhs.mWater, rhs.mHeightfields, rhs.mFlatHeightfields);
    }

    inline bool operator<(const RecastMeshData& lhs, const RecastMesh& rhs)
    {
        return std::tie(lhs.mMesh, lhs.mWater, lhs.mHeightfields, lhs.mFlatHeightfields)
            < std::tie(rhs.getMesh(), rhs.getWater(), rhs.getHeightfields(), rhs.getFlatHeightfields());
    }

    inline bool operator<(const RecastMesh& lhs, const RecastMeshData& rhs)
    {
        return std::tie(lhs.getMesh(), lhs.getWater(), lhs.getHeightfields(), lhs.getFlatHeightfields())
            < std::tie(rhs.mMesh, rhs.mWater, rhs.mHeightfields, rhs.mFlatHeightfields);
    }

    struct NavMeshTilesCacheStats;

    class NavMeshTilesCache
    {
    public:
        struct Item
        {
            std::atomic<std::int64_t> mUseCount;
            AgentBounds mAgentBounds;
            TilePosition mChangedTile;
            RecastMeshData mRecastMeshData;
            std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData;
            std::size_t mSize;

            Item(const AgentBounds& agentBounds, const TilePosition& changedTile, RecastMeshData&& recastMeshData,
                std::size_t size)
                : mUseCount(0)
                , mAgentBounds(agentBounds)
                , mChangedTile(changedTile)
                , mRecastMeshData(std::move(recastMeshData))
                , mSize(size)
            {
            }
        };

        using ItemIterator = std::list<Item>::iterator;

        class Value
        {
        public:
            Value()
                : mOwner(nullptr)
                , mIterator()
            {
            }

            Value(NavMeshTilesCache& owner, ItemIterator iterator)
                : mOwner(&owner)
                , mIterator(iterator)
            {
            }

            Value(const Value& other) = delete;

            Value(Value&& other)
                : mOwner(other.mOwner)
                , mIterator(other.mIterator)
            {
                other.mOwner = nullptr;
            }

            ~Value()
            {
                if (mOwner)
                    mOwner->releaseItem(mIterator);
            }

            Value& operator=(const Value& other) = delete;

            Value& operator=(Value&& other)
            {
                if (mOwner)
                    mOwner->releaseItem(mIterator);

                mOwner = other.mOwner;
                mIterator = other.mIterator;

                other.mOwner = nullptr;

                return *this;
            }

            const PreparedNavMeshData& get() const { return *mIterator->mPreparedNavMeshData; }

            operator bool() const { return mOwner; }

        private:
            NavMeshTilesCache* mOwner;
            ItemIterator mIterator;
        };

        NavMeshTilesCache(const std::size_t maxNavMeshDataSize);

        Value get(const AgentBounds& agentBounds, const TilePosition& changedTile, const RecastMesh& recastMesh);

        Value set(const AgentBounds& agentBounds, const TilePosition& changedTile, const RecastMesh& recastMesh,
            std::unique_ptr<PreparedNavMeshData>&& value);

        NavMeshTilesCacheStats getStats() const;

    private:
        mutable std::mutex mMutex;
        std::size_t mMaxNavMeshDataSize;
        std::size_t mUsedNavMeshDataSize;
        std::size_t mFreeNavMeshDataSize;
        std::size_t mHitCount;
        std::size_t mGetCount;
        std::list<Item> mBusyItems;
        std::list<Item> mFreeItems;
        std::map<std::tuple<AgentBounds, TilePosition, std::reference_wrapper<const RecastMeshData>>, ItemIterator,
            std::less<>>
            mValues;

        void removeLeastRecentlyUsed();

        void acquireItemUnsafe(ItemIterator iterator);

        void releaseItem(ItemIterator iterator);
    };
}

#endif
