#ifndef JINJA2CPP_SRC_GENERIC_ADAPTERS_H
#define JINJA2CPP_SRC_GENERIC_ADAPTERS_H

#include <jinja2cpp/value.h>
#include "internal_value.h"

namespace jinja2
{

template<typename ImplType, typename List, typename ValType, typename Base>
class IndexedEnumeratorImpl : public Base
{
public:
    using ValueType = ValType;
    using ThisType = IndexedEnumeratorImpl<ImplType, List, ValType, Base>;

    IndexedEnumeratorImpl(const List* list)
        : m_list(list)
        , m_maxItems(list->GetSize().value())
    { }

    void Reset() override
    {
        m_curItem = m_invalidIndex;
    }

    bool MoveNext() override
    {
        if (m_curItem == m_invalidIndex)
            m_curItem = 0;
        else
            ++ m_curItem;

        return m_curItem < m_maxItems;
    }

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ThisType*>(&other);
        if (!val)
            return false;
        if (m_list && val->m_list && !m_list->IsEqual(*val->m_list))
            return false;
        if ((m_list && !val->m_list) || (!m_list && val->m_list))
            return false;
        if (m_curItem != val->m_curItem)
            return false;
        if (m_maxItems != val->m_maxItems)
            return false;
        return true;
    }

protected:
    constexpr static auto m_invalidIndex = std::numeric_limits<size_t>::max();
    const List* m_list{};
    size_t m_curItem = m_invalidIndex;
    size_t m_maxItems{};
};


template<typename T>
class IndexedListItemAccessorImpl : public IListItemAccessor, public IIndexBasedAccessor
{
public:
    using ThisType = IndexedListItemAccessorImpl<T>;
    class Enumerator : public IndexedEnumeratorImpl<Enumerator, ThisType, Value, IListEnumerator>
    {
    public:
        using BaseClass = IndexedEnumeratorImpl<Enumerator, ThisType, Value, IListEnumerator>;
#if defined(_MSC_VER)
        using IndexedEnumeratorImpl::IndexedEnumeratorImpl;
#else
        using BaseClass::BaseClass;
#endif

        typename BaseClass::ValueType GetCurrent() const override
        {
            auto indexer = this->m_list->GetIndexer();
            if (!indexer)
                return Value();

            return indexer->GetItemByIndex(this->m_curItem);
        }
        ListEnumeratorPtr Clone() const override
        {
            auto result = MakeEnumerator<Enumerator>(this->m_list);
            auto base = static_cast<Enumerator*>(&(*result));
            base->m_curItem = this->m_curItem;
            return result;
        }

        ListEnumeratorPtr Move() override
        {
            auto result = MakeEnumerator<Enumerator>(this->m_list);
            auto base = static_cast<Enumerator*>(&(*result));
            base->m_curItem = this->m_curItem;
            this->m_list = nullptr;
            this->m_curItem = this->m_invalidIndex;
            this->m_maxItems = 0;
            return result;
        }
    };

    Value GetItemByIndex(int64_t idx) const override
    {
        return IntValue2Value(std::move(static_cast<const T*>(this)->GetItem(idx).value()));
    }

    std::optional<size_t> GetSize() const override
    {
        return static_cast<const T*>(this)->GetItemsCountImpl();
    }

    const IIndexBasedAccessor* GetIndexer() const override
    {
        return this;
    }

    ListEnumeratorPtr CreateEnumerator() const override;

    bool IsEqual(const IComparable& other) const override
    {
        auto* val = dynamic_cast<const ThisType*>(&other);
        if (!val)
            return false;
        auto enumerator = CreateEnumerator();
        auto otherEnum = val->CreateEnumerator();
        if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum))
            return false;
        return true;
    }
};

template<typename T>
class IndexedListAccessorImpl : public IListAccessor, public IndexedListItemAccessorImpl<T>
{
public:
    using ThisType = IndexedListAccessorImpl<T>;
    class Enumerator : public IndexedEnumeratorImpl<Enumerator, ThisType, InternalValue, IListAccessorEnumerator>
    {
    public:
        using BaseClass = IndexedEnumeratorImpl<Enumerator, ThisType, InternalValue, IListAccessorEnumerator>;
#if defined(_MSC_VER)
        using IndexedEnumeratorImpl::IndexedEnumeratorImpl;
#else
        using BaseClass::BaseClass;
#endif

        typename BaseClass::ValueType GetCurrent() const override
        {
            const auto& result = this->m_list->GetItem(this->m_curItem);
            if (!result)
                return InternalValue();

            return result.value();
        }

        IListAccessorEnumerator* Clone() const override
        {
            auto result = new Enumerator(this->m_list);
            auto base = result;
            base->m_curItem = this->m_curItem;
            return result;
        }

        IListAccessorEnumerator* Transfer() override
        {
            auto result = new Enumerator(std::move(*this));
            auto base = result;
            base->m_curItem = this->m_curItem;
            this->m_list = nullptr;
            this->m_curItem = this->m_invalidIndex;
            this->m_maxItems = 0;
            return result;
        }
    };

    std::optional<size_t> GetSize() const override
    {
        return static_cast<const T*>(this)->GetItemsCountImpl();
    }
    ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override;
};

template<typename T>
class MapItemAccessorImpl : public IMapItemAccessor
{
public:
    Value GetValueByName(const std::string& name) const
    {
        return IntValue2Value(static_cast<const T*>(this)->GetItem(name));
    }
};

template<typename T>
class MapAccessorImpl : public IMapAccessor, public MapItemAccessorImpl<T>
{
public:
};

template<typename T>
inline ListAccessorEnumeratorPtr IndexedListAccessorImpl<T>::CreateListAccessorEnumerator() const
{
    return ListAccessorEnumeratorPtr(new Enumerator(this));
}

template<typename T>
inline ListEnumeratorPtr IndexedListItemAccessorImpl<T>::CreateEnumerator() const
{
    return MakeEnumerator<Enumerator>(this);
}

} // namespace jinja2

#endif // JINJA2CPP_SRC_GENERIC_ADAPTERS_H