#ifndef CSM_WOLRD_IDCOLLECTION_H
#define CSM_WOLRD_IDCOLLECTION_H

#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <cctype>
#include <stdexcept>
#include <functional>

#include <QVariant>

#include <components/esm/esmreader.hpp>

#include <components/misc/stringops.hpp>

#include "columnbase.hpp"

namespace CSMWorld
{
    class IdCollectionBase
    {
            // not implemented
            IdCollectionBase (const IdCollectionBase&);
            IdCollectionBase& operator= (const IdCollectionBase&);

        public:

            IdCollectionBase();

            virtual ~IdCollectionBase();

            virtual int getSize() const = 0;

            virtual std::string getId (int index) const = 0;

            virtual int getIndex (const std::string& id) const = 0;

            virtual int getColumns() const = 0;

            virtual const ColumnBase& getColumn (int column) const = 0;

            virtual QVariant getData (int index, int column) const = 0;

            virtual void setData (int index, int column, const QVariant& data) = 0;

            virtual void merge() = 0;
            ///< Merge modified into base.

            virtual void purge() = 0;
            ///< Remove records that are flagged as erased.

            virtual void removeRows (int index, int count) = 0;

            virtual void appendBlankRecord (const std::string& id) = 0;

            virtual int searchId (const std::string& id) const = 0;
            ////< Search record with \a id.
            /// \return index of record (if found) or -1 (not found)

            virtual void replace (int index, const RecordBase& record) = 0;
            ///< If the record type does not match, an exception is thrown.
            ///
            /// \attention \a record must not change the ID.

            virtual void appendRecord (const RecordBase& record) = 0;
            ///< If the record type does not match, an exception is thrown.

            virtual std::string getId (const RecordBase& record) const = 0;
            ///< Return ID for \a record.
            ///
            /// \attention Throws an exception, if the type of \a record does not match.

            virtual const RecordBase& getRecord (const std::string& id) const = 0;

            virtual void load (ESM::ESMReader& reader, bool base) = 0;
    };

    ///< \brief Collection of ID-based records
    template<typename ESXRecordT>
    class IdCollection : public IdCollectionBase
    {
            std::vector<Record<ESXRecordT> > mRecords;
            std::map<std::string, int> mIndex;
            std::vector<Column<ESXRecordT> *> mColumns;

            // not implemented
            IdCollection (const IdCollection&);
            IdCollection& operator= (const IdCollection&);

        public:

            IdCollection();

            virtual ~IdCollection();

            void add (const ESXRecordT& record);
            ///< Add a new record (modified)

            virtual int getSize() const;

            virtual std::string getId (int index) const;

            virtual int getIndex (const std::string& id) const;

            virtual int getColumns() const;

            virtual QVariant getData (int index, int column) const;

            virtual void setData (int index, int column, const QVariant& data);

            virtual const ColumnBase& getColumn (int column) const;

            virtual void merge();
            ///< Merge modified into base.

            virtual void purge();
            ///< Remove records that are flagged as erased.

            virtual void removeRows (int index, int count) ;

            virtual void appendBlankRecord (const std::string& id);

            virtual int searchId (const std::string& id) const;
            ////< Search record with \a id.
            /// \return index of record (if found) or -1 (not found)

            virtual void replace (int index, const RecordBase& record);
            ///< If the record type does not match, an exception is thrown.
            ///
            /// \attention \a record must not change the ID.

            virtual void appendRecord (const RecordBase& record);
            ///< If the record type does not match, an exception is thrown.

            virtual std::string getId (const RecordBase& record) const;
            ///< Return ID for \a record.
            ///
            /// \attention Throw san exception, if the type of \a record does not match.

            virtual const RecordBase& getRecord (const std::string& id) const;

            virtual void load (ESM::ESMReader& reader, bool base);

            void addColumn (Column<ESXRecordT> *column);
    };

    template<typename ESXRecordT>
    IdCollection<ESXRecordT>::IdCollection()
    {}

    template<typename ESXRecordT>
    IdCollection<ESXRecordT>::~IdCollection()
    {
        for (typename std::vector<Column<ESXRecordT> *>::iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter)
            delete *iter;
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::add (const ESXRecordT& record)
    {
        std::string id = Misc::StringUtils::lowerCase(record.mId);

        std::map<std::string, int>::iterator iter = mIndex.find (id);

        if (iter==mIndex.end())
        {
            Record<ESXRecordT> record2;
            record2.mState = Record<ESXRecordT>::State_ModifiedOnly;
            record2.mModified = record;

            mRecords.push_back (record2);
            mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1));
        }
        else
        {
            mRecords[iter->second].setModified (record);
        }
    }

    template<typename ESXRecordT>
    int IdCollection<ESXRecordT>::getSize() const
    {
        return mRecords.size();
    }

    template<typename ESXRecordT>
    std::string IdCollection<ESXRecordT>::getId (int index) const
    {
        return mRecords.at (index).get().mId;
    }

    template<typename ESXRecordT>
    int  IdCollection<ESXRecordT>::getIndex (const std::string& id) const
    {
        int index = searchId (id);

        if (index==-1)
            throw std::runtime_error ("invalid ID: " + id);

        return index;
    }

    template<typename ESXRecordT>
    int IdCollection<ESXRecordT>::getColumns() const
    {
        return mColumns.size();
    }

    template<typename ESXRecordT>
    QVariant IdCollection<ESXRecordT>::getData (int index, int column) const
    {
        return mColumns.at (column)->get (mRecords.at (index));
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::setData (int index, int column, const QVariant& data)
    {
        return mColumns.at (column)->set (mRecords.at (index), data);
    }

    template<typename ESXRecordT>
    const ColumnBase& IdCollection<ESXRecordT>::getColumn (int column) const
    {
        return *mColumns.at (column);
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::addColumn (Column<ESXRecordT> *column)
    {
        mColumns.push_back (column);
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::merge()
    {
        for (typename std::vector<Record<ESXRecordT> >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter)
            iter->merge();

        purge();
    }

    template<typename ESXRecordT>
    void  IdCollection<ESXRecordT>::purge()
    {
        mRecords.erase (std::remove_if (mRecords.begin(), mRecords.end(),
            std::mem_fun_ref (&Record<ESXRecordT>::isErased) // I want lambda :(
            ), mRecords.end());
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::removeRows (int index, int count)
    {
        mRecords.erase (mRecords.begin()+index, mRecords.begin()+index+count);

        typename std::map<std::string, int>::iterator iter = mIndex.begin();

        while (iter!=mIndex.end())
        {
            if (iter->second>=index)
            {
                if (iter->second>=index+count)
                {
                    iter->second -= count;
                }
                else
                {
                    mIndex.erase (iter++);
                }
            }

            ++iter;
        }
    }

    template<typename ESXRecordT>
    void  IdCollection<ESXRecordT>::appendBlankRecord (const std::string& id)
    {
        ESXRecordT record;
        record.mId = id;
        record.blank();
        add (record);
    }

    template<typename ESXRecordT>
    int IdCollection<ESXRecordT>::searchId (const std::string& id) const
    {
        std::string id2 = Misc::StringUtils::lowerCase(id);

        std::map<std::string, int>::const_iterator iter = mIndex.find (id2);

        if (iter==mIndex.end())
            return -1;

        return iter->second;
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::replace (int index, const RecordBase& record)
    {
        mRecords.at (index) = dynamic_cast<const Record<ESXRecordT>&> (record);
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::appendRecord (const RecordBase& record)
    {
        mRecords.push_back (dynamic_cast<const Record<ESXRecordT>&> (record));
        mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (getId (record)), mRecords.size()-1));
    }

    template<typename ESXRecordT>
    std::string IdCollection<ESXRecordT>::getId (const RecordBase& record) const
    {
        const Record<ESXRecordT>& record2 = dynamic_cast<const Record<ESXRecordT>&> (record);
        return (record2.isModified() ? record2.mModified : record2.mBase).mId;
    }

    template<typename ESXRecordT>
    void IdCollection<ESXRecordT>::load (ESM::ESMReader& reader, bool base)
    {
        std::string id = reader.getHNOString ("NAME");

        int index = searchId (id);

        if (reader.isNextSub ("DELE"))
        {
            reader.skipRecord();

            if (index==-1)
            {
                // deleting a record that does not exist

                // ignore it for now

                /// \todo report the problem to the user
            }
            else if (base)
            {
                removeRows (index, 1);
            }
            else
            {
                mRecords[index].mState = RecordBase::State_Deleted;
            }
        }
        else
        {
            ESXRecordT record;
            record.mId = id;
            record.load (reader);

            if (index==-1)
            {
                // new record
                Record<ESXRecordT> record2;
                record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
                (base ? record2.mBase : record2.mModified) = record;

                appendRecord (record2);
            }
            else
            {
                // old record
                Record<ESXRecordT>& record2 = mRecords[index];

                if (base)
                    record2.mBase = record;
                else
                    record2.setModified (record);
            }
        }
    }

    template<typename ESXRecordT>
    const RecordBase& IdCollection<ESXRecordT>::getRecord (const std::string& id) const
    {
        int index = getIndex (id);
        return mRecords.at (index);
    }
}

#endif
