/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
 * This file is a proxy model with better filter mechanism.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgsortfilterproxymodel.h"

#include <qregexp.h>

#include "skgtraces.h"
#include "skgservices.h"
#include "skgobjectmodelbase.h"

/**
 * This private class of SKGObjectBase
 */
class SKGSortFilterProxyModelPrivate
{
public:
    /**
     * the previous sort column
     */
    int m_previous;

    /**
     * the current sort column
     */
    int m_current;

    /**
     * to know if we are in a sub sort
     */
    bool m_insubsort;
};


SKGSortFilterProxyModel::SKGSortFilterProxyModel(QObject* iParent)
    : QSortFilterProxyModel(iParent), d(new SKGSortFilterProxyModelPrivate)
{
    _SKGTRACEINFUNC(10);

    setSortCaseSensitivity(Qt::CaseInsensitive);
    setSortLocaleAware(true);
    setFilterKeyColumn(0);

    d->m_previous = -1;
    d->m_current = -1;
    d->m_insubsort = false;
}

SKGSortFilterProxyModel::~SKGSortFilterProxyModel()
{
    _SKGTRACEINFUNC(10);
}

int SKGSortFilterProxyModel::getPreviousSortColumn() const
{
    return d->m_previous;
}

void SKGSortFilterProxyModel::setPreviousSortColumn(int iCol) const
{
    d->m_previous = iCol;
}

bool SKGSortFilterProxyModel::lessThan(const QModelIndex& left,
                                       const QModelIndex& right) const
{
    // Get source
    SKGObjectModelBase* model = qobject_cast<SKGObjectModelBase*>(this->sourceModel());
    if (model && d) {
        // Set previous column
        if (!d->m_insubsort) {
            int newCol = left.column();
            if (newCol != d->m_current && d->m_current != -1) {
                d->m_previous = d->m_current;
            }

            d->m_current = newCol;
        }

        // Comparison
        QVariant leftData = model->data(left, Qt::UserRole);
        QVariant rightData = model->data(right, Qt::UserRole);
        SKGObjectBase* leftObj = model->getObjectPointer(left);
        if (leftData == rightData && leftObj) {
            if (leftObj->getTable().isEmpty() && d->m_current != 0) {
                // Groups
                d->m_insubsort = true;
                bool test = SKGSortFilterProxyModel::lessThan(model->index(left.row(), 0), model->index(right.row(), 0));
                d->m_insubsort = false;
                return test;
            }
            if (leftData.userType() != QVariant::Date &&
                leftData.userType() != QVariant::DateTime &&
                leftData.userType() != QVariant::Time) {
                // Compare on previous column
                if (!d->m_insubsort && d->m_previous != -1) {
                    d->m_insubsort = true;
                    bool test = QSortFilterProxyModel::lessThan(model->index(left.row(), d->m_previous), model->index(right.row(), d->m_previous));
                    d->m_insubsort = false;
                    return test;
                }
            }

            // Compare on ID for stability
            SKGObjectBase* rightObj = model->getObjectPointer(right);
            return (rightObj && leftObj->getID() < rightObj->getID());
        } else {
            // For better performances, and avoid too many calls to QAbstractItemModel::data
            // return QSortFilterProxyModel::lessThan(left, right);
            return lessThan(leftData, rightData);
        }
    }
    return false;
}

bool SKGSortFilterProxyModel::lessThan(const QVariant& iLeftData, const QVariant& iRightData) const
{
    switch (iLeftData.userType()) {
    case QVariant::Invalid:
        return (iRightData.type() != QVariant::Invalid);
    case QVariant::Int:
        return iLeftData.toInt() < iRightData.toInt();
    case QVariant::UInt:
        return iLeftData.toUInt() < iRightData.toUInt();
    case QVariant::LongLong:
        return iLeftData.toLongLong() < iRightData.toLongLong();
    case QVariant::ULongLong:
        return iLeftData.toULongLong() < iRightData.toULongLong();
    case QMetaType::Float:
        return iLeftData.toFloat() < iRightData.toFloat();
    case QVariant::Double:
        return iLeftData.toDouble() < iRightData.toDouble();
    case QVariant::Char:
        return iLeftData.toChar() < iRightData.toChar();
    case QVariant::Date:
        return iLeftData.toDate() < iRightData.toDate();
    case QVariant::Time:
        return iLeftData.toTime() < iRightData.toTime();
    case QVariant::DateTime:
        return iLeftData.toDateTime() < iRightData.toDateTime();
    case QVariant::String:
    default:
        if (this->isSortLocaleAware()) {
            return iLeftData.toString().localeAwareCompare(iRightData.toString()) < 0;
        } else {
            return iLeftData.toString().compare(iRightData.toString(), this->sortCaseSensitivity()) < 0;
        }
    }
}

bool SKGSortFilterProxyModel::moreThan(const QVariant& iLeftData, const QVariant& iRightData) const
{
    switch (iLeftData.userType()) {
    case QVariant::Invalid:
        return (iRightData.type() != QVariant::Invalid);
    case QVariant::Int:
        return iLeftData.toInt() > iRightData.toInt();
    case QVariant::UInt:
        return iLeftData.toUInt() > iRightData.toUInt();
    case QVariant::LongLong:
        return iLeftData.toLongLong() > iRightData.toLongLong();
    case QVariant::ULongLong:
        return iLeftData.toULongLong() > iRightData.toULongLong();
    case QMetaType::Float:
        return iLeftData.toFloat() > iRightData.toFloat();
    case QVariant::Double:
        return iLeftData.toDouble() > iRightData.toDouble();
    case QVariant::Char:
        return iLeftData.toChar() > iRightData.toChar();
    case QVariant::Date:
        return iLeftData.toDate() > iRightData.toDate();
    case QVariant::Time:
        return iLeftData.toTime() > iRightData.toTime();
    case QVariant::DateTime:
        return iLeftData.toDateTime() > iRightData.toDateTime();
    case QVariant::String:
    default:
        if (this->isSortLocaleAware()) {
            return iLeftData.toString().localeAwareCompare(iRightData.toString()) > 0;
        } else {
            return iLeftData.toString().compare(iRightData.toString(), this->sortCaseSensitivity()) > 0;
        }
    }
}

bool SKGSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
    _SKGTRACEINFUNC(10);

    // Initialisation
    bool output = (filterRegExp().isEmpty());
    if (!output) {
        // Build list of criterias
        SKGServices::SKGSearchCriteriaList criterias = SKGServices::stringToSearchCriterias(filterRegExp().pattern());

        // Check if at least one group validate the line
        int nbList = criterias.count();
        output = false;
        for (int i = 0; i < nbList; ++i) {
            QChar mode = criterias.at(i).mode;

            bool validateAllWords =  filterAcceptsRowWords(source_row, source_parent, criterias.at(i).words);
            if (mode == '+') {
                output |= validateAllWords;
            } else if (mode == '-' && validateAllWords) {
                output = false;
            }
        }

        if (!output) {
            QAbstractItemModel* model = this->sourceModel();
            if (model) {
                QModelIndex index0 = model->index(source_row, 0, source_parent);

                int nb = model->rowCount(index0);
                for (int i = 0; !output && i < nb; ++i) {
                    output = filterAcceptsRow(i, index0);
                }
            }
        }
    }
    return output;
}

bool SKGSortFilterProxyModel::filterAcceptsRowWords(int source_row, const QModelIndex& source_parent, const QStringList& iWords) const
{
    _SKGTRACEINFUNC(10);

    // Initialisation
    bool output = true;

    // Get source
    QAbstractItemModel* model = this->sourceModel();
    if (model) {
        int nbwords = iWords.count();
        for (int w = 0; output && w < nbwords; ++w) {
            QString word = iWords.at(w).toLower();
            QString att;
            QString op(':');

            // Check if the word follows the format attribut:value
            int pos = word.indexOf(QStringLiteral(":"));
            int pos2 = word.indexOf(QStringLiteral("<="));
            int pos3 = word.indexOf(QStringLiteral(">="));
            int pos4 = word.indexOf(QStringLiteral("="));
            int pos5 = word.indexOf(QStringLiteral("<"));
            int pos6 = word.indexOf(QStringLiteral(">"));
            int pos7 = word.indexOf(QStringLiteral("#"));
            int opLength = 1;
            if (pos2 != -1 && (pos2 < pos || pos == -1)) {
                pos = pos2;
                opLength = 2;
            }
            if (pos3 != -1 && (pos3 < pos || pos == -1)) {
                pos = pos3;
                opLength = 2;
            }
            if (pos4 != -1 && (pos4 < pos || pos == -1)) {
                pos = pos4;
            }
            if (pos5 != -1 && (pos5 < pos || pos == -1)) {
                pos = pos5;
            }
            if (pos6 != -1 && (pos6 < pos || pos == -1)) {
                pos = pos6;
            }
            if (pos7 != -1 && (pos7 < pos || pos == -1)) {
                pos = pos7;
            }

            if (pos != -1) {
                att = word.left(pos);
                op = word.mid(pos, opLength);
                word = word.right(word.count() - pos - op.count());
            }

            // Is this word validated by at least one column ?
            output = false;
            int nbcol = model->columnCount();
            for (int i = 0; !output && i < nbcol; ++i) {
                QModelIndex index0 = model->index(source_row, i, source_parent);
                if (index0.isValid()) {
                    // Check if the header validates the attribut
                    if (att.isEmpty() || model->headerData(i, Qt::Horizontal).toString().startsWith(att, Qt::CaseInsensitive)) {
                        // Check if the value validate the attribute
                        if (op == QStringLiteral(":")) {
                            output = model->data(index0).toString().contains(word , Qt::CaseInsensitive);
                            if (!output) {
                                output = model->data(index0, Qt::UserRole).toString().contains(word , Qt::CaseInsensitive);
                            }
                        } else if (op == QStringLiteral("<")) {
                            QVariant var = model->data(index0, Qt::UserRole);
                            output = lessThan(var, QVariant(word));
                        } else if (op == QStringLiteral(">")) {
                            QVariant var = model->data(index0, Qt::UserRole);
                            output = moreThan(var, QVariant(word));
                        } else if (op == QStringLiteral("<=")) {
                            QVariant var = model->data(index0, Qt::UserRole);
                            output = (var == QVariant(word)) || lessThan(var, QVariant(word));
                        } else if (op == QStringLiteral(">=")) {
                            QVariant var = model->data(index0, Qt::UserRole);
                            output = (var == QVariant(word)) || moreThan(var, QVariant(word));
                        } else if (op == QStringLiteral("=")) {
                            QVariant var = model->data(index0, Qt::UserRole);
                            output = (var == QVariant(word));
                        } else if (op == QStringLiteral("#")) {
                            QVariant var = model->data(index0, Qt::UserRole);
                            QRegExp pattern(word, Qt::CaseInsensitive, QRegExp::RegExp2);
                            output = pattern.exactMatch(var.toString());
                        }
                    }
                }
            }
        }
    }
    return output;
}
