#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 "record.hpp"

namespace CSMWorld
{
    template<typename ESXRecordT>
    struct Column
    {
        std::string mTitle;

        Column (const std::string& title) : mTitle (title) {}

        virtual ~Column() {}

        virtual QVariant get (const Record<ESXRecordT>& record) const = 0;

        virtual void set (Record<ESXRecordT>& record, const QVariant& data)
        {
            throw std::logic_error ("Column " + mTitle + " is not editable");
        }

        virtual bool isEditable() const = 0;
    };

    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 std::string getTitle (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 bool isEditable (int column) const = 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 Throw san exception, if the type of \a record does not match.

            virtual const RecordBase& getRecord (const std::string& id) const = 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 std::string getTitle (int column) const;

            virtual bool isEditable (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;

            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;

        std::transform (record.mId.begin(), record.mId.end(), std::back_inserter (id),
            (int(*)(int)) std::tolower);

        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 (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>
    std::string IdCollection<ESXRecordT>::getTitle (int column) const
    {
        return mColumns.at (column)->mTitle;
    }

    template<typename ESXRecordT>
    bool IdCollection<ESXRecordT>::isEditable (int column) const
    {
        return mColumns.at (column)->isEditable();
    }

    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;

        std::transform (id.begin(), id.end(), std::back_inserter (id2),
            (int(*)(int)) std::tolower);

        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 (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>
    const RecordBase& IdCollection<ESXRecordT>::getRecord (const std::string& id) const
    {
        int index = getIndex (id);
        return mRecords.at (index);
    }
}

#endif