/***************************************************************************
 *   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>

#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;
            }
            // 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);
            switch (leftData.userType()) {
            case QVariant::Invalid:
                return (rightData.type() != QVariant::Invalid);
            case QVariant::Int:
                return leftData.toInt() < rightData.toInt();
            case QVariant::UInt:
                return leftData.toUInt() < rightData.toUInt();
            case QVariant::LongLong:
                return leftData.toLongLong() < rightData.toLongLong();
            case QVariant::ULongLong:
                return leftData.toULongLong() < rightData.toULongLong();
            case QMetaType::Float:
                return leftData.toFloat() < rightData.toFloat();
            case QVariant::Double:
                return leftData.toDouble() < rightData.toDouble();
            case QVariant::Char:
                return leftData.toChar() < rightData.toChar();
            case QVariant::Date:
                return leftData.toDate() < rightData.toDate();
            case QVariant::Time:
                return leftData.toTime() < rightData.toTime();
            case QVariant::DateTime:
                return leftData.toDateTime() < rightData.toDateTime();
            case QVariant::String:
            default:
                if (this->isSortLocaleAware()) {
                    return leftData.toString().localeAwareCompare(rightData.toString()) < 0;
                } else {
                    return leftData.toString().compare(rightData.toString(), this->sortCaseSensitivity()) < 0;
                }
            }
        }
    }
    return false;
}

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

    // Initialisation
    bool output = (filterRegExp().isEmpty());
    if (!output) {
        // Build list of criterias
        QList< SKGServices::SKGSearchCriteria > 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[i].mode;

            bool validateAllWords =  filterAcceptsRowWords(source_row, source_parent, criterias[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);

            // 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()) {
                    output = model->data(index0).toString().contains(word , Qt::CaseInsensitive);
                    if (!output) {
                        output = model->data(index0, Qt::UserRole).toString().contains(word , Qt::CaseInsensitive);
                    }
                }
            }
        }
    }
    return output;
}
