///////////////////////////////////////////////////////////////////
//*-------------------------------------------------------------*//
//| Part of the Game Jolt API C++ Library (http://gamejolt.com) |//
//*-------------------------------------------------------------*//
//| Released under the zlib License                             |//
//| More information available in the readme file               |//
//*-------------------------------------------------------------*//
///////////////////////////////////////////////////////////////////
#pragma once
#ifndef _GJ_GUARD_LOOKUP_H_
#define _GJ_GUARD_LOOKUP_H_

typedef unsigned int gjUint;
#ifndef ASSERT
    #define ASSERT(x) 
#endif


// ****************************************************************
// lookup container class
template <typename T> class gjLookup final
{
public:
    //! internal types
    typedef std::pair<std::string, T>       gjEntry;
    typedef std::vector<gjEntry>            gjList;
    typedef typename gjList::iterator       gjIterator;
    typedef typename gjList::const_iterator gjConstIterator;


private:
    gjList m_aList;   //!< vector-list with pair-values


public:
    gjLookup()noexcept;
    gjLookup(const gjLookup<T>& c)noexcept;
    gjLookup(gjLookup<T>&& m)noexcept;
    ~gjLookup();

    //! assignment operator
    //! @{
    gjLookup<T>& operator = (gjLookup<T> o)noexcept;
    template <typename S> friend void swap(gjLookup<S>& a, gjLookup<S>& b)noexcept;
    //! @}

    //! access specific entry
    //! @{
    const T& at(const char* pcKey)const noexcept;
    T& operator [] (const char* pcKey)noexcept;
    inline T& operator [] (const gjUint& iIndex)noexcept {return m_aList[iIndex].second;}
    //! @}

    //! check number of existing entries
    //! @{
    inline gjUint count(const char* pcKey)const noexcept {return this->__check(this->__retrieve(pcKey)) ? 1 : 0;}
    inline gjUint size()const noexcept                   {return m_aList.size();}
    inline bool empty()const noexcept                    {return m_aList.empty();}
    //! @}

    //! control memory consumption
    //! @{
    inline void reserve(const gjUint& iReserve)noexcept {m_aList.reserve(iReserve);}
    inline gjUint capacity()const noexcept              {return m_aList.capacity();}
    //! @}

    //! remove existing entries
    //! @{
    void erase(const T& Entry)noexcept;
    void erase(const char* pcKey)noexcept;
    inline void erase(const gjUint& iIndex)noexcept            {ASSERT(iIndex < m_aList.size()) m_aList.erase(m_aList.begin()+iIndex);}
    inline void erase(const gjConstIterator& Iterator)noexcept {m_aList.erase(Iterator);}
    inline void clear()noexcept                                {m_aList.clear();}
    //! @}

    //! retrieve internal iterator
    //! @{
    inline gjIterator begin()noexcept            {return m_aList.begin();}
    inline gjConstIterator begin()const noexcept {return m_aList.begin();}
    inline gjIterator end()noexcept              {return m_aList.end();}
    inline gjConstIterator end()const noexcept   {return m_aList.end();}
    //! @}


private:
    //! retrieve iterator
    //! @{
    gjIterator __retrieve(const T& Entry)noexcept;
    gjConstIterator __retrieve(const T& Entry)const noexcept;
    gjIterator __retrieve(const char* pcKey)noexcept;
    gjConstIterator __retrieve(const char* pcKey)const noexcept;
    //! @}

    //! check for valid iterator
    //! @{
    inline bool __check(const gjIterator& it)const noexcept      {return (it != m_aList.end()) ? true : false;}
    inline bool __check(const gjConstIterator& it)const noexcept {return (it != m_aList.end()) ? true : false;}
    //! @}
};


// ****************************************************************
// constructor
template <typename T> gjLookup<T>::gjLookup()noexcept
{
    // reserve variable sized memory
    constexpr_var gjUint iSize = 1 + 64/sizeof(T);
    m_aList.reserve(iSize);
}

template <typename T> gjLookup<T>::gjLookup(const gjLookup<T>& c)noexcept
: m_aList (c.m_aList)
{
}

template <typename T> gjLookup<T>::gjLookup(gjLookup<T>&& m)noexcept
: m_aList (std::move(m.m_aList))
{
}


// ****************************************************************
// destructor
template <typename T> gjLookup<T>::~gjLookup()
{
    m_aList.clear();
}


// ****************************************************************
// assignment operator
template <typename T> gjLookup<T>& gjLookup<T>::operator = (gjLookup<T> o)noexcept
{
    swap(*this, o);
    return *this;
}

template <typename S> void swap(gjLookup<S>& a, gjLookup<S>& b)noexcept
{
    using std::swap;
    swap(a.m_aList, b.m_aList);
}


// ****************************************************************
// access specific entry
template <typename T> const T& gjLookup<T>::at(const char* pcKey)const noexcept
{
    // retrieve and check iterator by specific key
    auto it = this->__retrieve(pcKey);
    ASSERT(this->__check(it))

    return it->second;
}


// ****************************************************************
// access specific entry and create it if necessary
template <typename T> T& gjLookup<T>::operator [] (const char* pcKey)noexcept
{
    // retrieve and check iterator by specific key
    auto it = this->__retrieve(pcKey);
    if(!this->__check(it))
    {
        // create new entry
        m_aList.push_back(gjEntry(pcKey, T()));
        it = m_aList.end()-1;
    }

    return it->second;
}


// ****************************************************************
// remove existing entry
template <typename T> void gjLookup<T>::erase(const T& Entry)noexcept
{
    // retrieve and check iterator by specific value
    auto it = this->__retrieve(Entry);
    if(this->__check(it))
    {
        // remove existing entry
        m_aList.erase(it);
    }
}


template <typename T> void gjLookup<T>::erase(const char* pcKey)noexcept
{
    // retrieve and check iterator by specific key
    auto it = this->__retrieve(pcKey);
    if(this->__check(it))
    {
        // remove existing entry
        m_aList.erase(it);
    }
}


// ****************************************************************
// retrieve iterator by specific value
template <typename T> typename gjLookup<T>::gjIterator gjLookup<T>::__retrieve(const T& Entry)noexcept
{
    // loop through all entries
    FOR_EACH(it, m_aList)
    {
        // compare values
        if(it->second == Entry)
            return it;
    }
    return m_aList.end();
}

template <typename T> typename gjLookup<T>::gjConstIterator gjLookup<T>::__retrieve(const T& Entry)const noexcept
{
    // loop through all entries
    FOR_EACH(it, m_aList)
    {
        // compare values
        if(it->second == Entry)
            return it;
    }
    return m_aList.end();
}


// ****************************************************************
// retrieve iterator by specific key
template <typename T> typename gjLookup<T>::gjIterator gjLookup<T>::__retrieve(const char* pcKey)noexcept
{
    // loop through all entries
    FOR_EACH(it, m_aList)
    {
        // compare string-keys
        if(!std::strcmp(it->first.c_str(), pcKey))
            return it;
    }
    return m_aList.end();
}

template <typename T> typename gjLookup<T>::gjConstIterator gjLookup<T>::__retrieve(const char* pcKey)const noexcept
{
    // loop through all entries
    FOR_EACH(it, m_aList)
    {
        // compare string-keys
        if(!std::strcmp(it->first.c_str(), pcKey))
            return it;
    }
    return m_aList.end();
}


#endif /* _GJ_GUARD_LOOKUP_H_ */