#ifndef ODBTABLEMODEL_H
#define ODBTABLEMODEL_H

#include <QAbstractTableModel>
#include <QSharedPointer>
#include <QMetaProperty>
#include <QModelIndex>
#include <QDebug>

#include "../qdecimal/src/QDecDouble.hh"

#include "define.h"
#include "core_global.h"
#include "exprevaluator.h"
#include "itablemodel.h"
#include "data/comboitem.h"

template<class T>
class AutoTableModel : public ITableModel
{

public:
    explicit AutoTableModel(QObject *parent = NULL)
        :ITableModel(parent)
    {
        filtered = false;
    }

    virtual ~AutoTableModel() {}

    // QAbstractItemModel interface
public:
    int rowCount(const QModelIndex &parent = QModelIndex()) const
    {
        Q_UNUSED(parent)

        return m_list.size();
    }

    int columnCount(const QModelIndex &parent = QModelIndex()) const
    {
        Q_UNUSED(parent)

        QObject *entity = (QObject*)new T();
        int colCount = entity->metaObject()->propertyCount() - 1;
        delete entity;

        return colCount;
    }

    QVariant data(const QModelIndex &index, int role) const
    {
        if (role == Qt::DisplayRole)
        {
            QSharedPointer<T> entity = m_list.at(index.row());
            QObject *rawEntity = (QObject*)entity.data();

            QVariant dispData = rawEntity->property(rawEntity->metaObject()->property(index.column() + 1).name());
            if (dispData.canConvert<QObject*>() && qobject_cast<ComboItem*>(dispData.value<QObject*>()))
            {
                return qobject_cast<ComboItem*>(dispData.value<QObject*>())->toString();
            }

            if (dispData.canConvert<QDecDouble>())
            {
                return dispData.value<QDecDouble>().toDouble();
            }

            return dispData;
        }

        return QVariant::Invalid;
    }

    QList<QSharedPointer<T> > list()
    {
        return m_list;
    }

    QVariant headerData(int section, Qt::Orientation orientation, int role) const
    {
        if (role != Qt::DisplayRole) {
            return QVariant();
        }

        if (orientation == Qt::Horizontal) {
            QObject *entity = (QObject*)new T();

            for (int i = 0; i < entity->metaObject()->propertyCount(); i++) {
                if (i == section) {
                    QString colName(entity->metaObject()->property(i + 1).name());
                    return tr(colName.toStdString().c_str());
                }
            }

            delete entity;
        }

        return QVariant(section + 1);
    }

    virtual void sort(int column, Qt::SortOrder order) {
        if (m_list.isEmpty()) {
            return;
        }

        beginResetModel();

        QObject *rawEntity = (QObject*)m_list.at(0).data();
        const char *prop = rawEntity->metaObject()->property(column + 1).name();

        std::sort(ALL(m_list), [prop, order](QSharedPointer<T> entA, QSharedPointer<T> entB) -> bool {
            if (order == Qt::AscendingOrder) {
                return ((QObject*)entA.data())->property(prop) < ((QObject*)entB.data())->property(prop);
            } else {
                return ((QObject*)entB.data())->property(prop) < ((QObject*)entA.data())->property(prop);
            }
        });

        endResetModel();
    }

    /*///////////////////////*/

    QSharedPointer<T> itemFromIndex(const QModelIndex &index) const
    {
        return m_list.at(index.row());
    }

    void setItemToIndex(const QModelIndex &index, QSharedPointer<T> data)
    {
        m_list.removeAt(index.row());
        m_list.insert(index.row(), data);

        emit dataChanged(index, index);
    }

    void addRow(QSharedPointer<T> data)
    {
        int index = rowCount() == 0 ? 0 : rowCount() - 1;
        beginInsertRows(QModelIndex(), index, index);
        insertRow(rowCount());
        m_list.append(data);
        endInsertRows();
    }

    void removeRowAt(const QModelIndex &index)
    {
        beginRemoveRows(QModelIndex(), index.row(), index.row());
        removeRow(index.row());
        m_list.removeAt(index.row());
        endRemoveRows();
    }

    void setData(QList<QSharedPointer<T> > list)
    {
        m_list = list;
    }

protected:
    void handleFilter(const QString &filter) override
    {
        beginResetModel();
        if (!filtered)
        {
            m_fullList = m_list;
            filtered = true;
        }

        ExprEvaluator ev;
        auto it = std::copy_if(ALL(m_fullList), m_list.begin(), [&filter, &ev](QSharedPointer<T> obj){ return ev.evaluate((QObject*)obj.data(), filter); });
        m_list.erase(it, m_list.end());

        endResetModel();
    }

    void handleRestore() override
    {
        if (filtered)
        {
            beginResetModel();
            m_list = m_fullList;
            endResetModel();
            filtered = false;
        }
    }

private:
    QList<QSharedPointer<T> > m_list;
    QList<QSharedPointer<T> > m_fullList;
    bool filtered;

};

#endif // ODBTABLEMODEL_H

