/***************************** LICENSE START ***********************************

 Copyright 2013 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQFilterTreeWidget.h"

#include <QDebug>
#include <QVBoxLayout>
#include <QScrollBar>

#include "MvQComboLine.h"
#include "MvQTreeView.h"

//========================================================
//
// MvQFilterTreeModel
//
//========================================================

MvQFilterTreeModel::MvQFilterTreeModel(QObject* parent) :
    QAbstractItemModel(parent)
{
}

void MvQFilterTreeModel::load(QStringList headerData, QVector<QStringList> valueData, bool multi, int valueColumnIndex)
{
    beginResetModel();
    multi_            = multi;
    headerData_       = headerData;
    valueData_        = valueData;
    valueColumnIndex_ = valueColumnIndex;
    checked_.clear();
    Q_ASSERT(headerData_.count() == valueData_.count());
    if (multi_ && valueData_.count() > 0 && valueData_[0].count()) {
        checked_ = QVector<bool>(valueData_[0].count(), false);
    }
    endResetModel();
}


int MvQFilterTreeModel::columnCount(const QModelIndex& /* parent */) const
{
    return headerData_.count();
}

int MvQFilterTreeModel::rowCount(const QModelIndex& parent) const
{
    if (!parent.isValid() && hasData())
        return valueData_[0].count();
    else
        return 0;
}


QVariant MvQFilterTreeModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid() || !hasData() || index.row() >= valueData_[0].count()) {
        return QVariant();
    }

    if (role == Qt::DisplayRole) {
        return valueData_[index.column()][index.row()];
    }
    if (role == Qt::CheckStateRole && multi_ && index.column() == 0) {
        return (checked_[index.row()]) ? QVariant(Qt::Checked) : QVariant(Qt::Unchecked);
    }

    return QVariant();
}

bool MvQFilterTreeModel::setData(const QModelIndex& idx, const QVariant& value, int role)
{
    if (multi_ && hasData() && idx.column() == 0 && role == Qt::CheckStateRole) {
        bool checked        = (value.toInt() == Qt::Checked) ? true : false;
        checked_[idx.row()] = checked;
        Q_EMIT valueCheckedChanged(valueData_[valueColumnIndex_][idx.row()], checked);
        QModelIndex startIdx = index(idx.row(), 0);
        QModelIndex endIdx   = index(idx.row(), 0);
        Q_EMIT dataChanged(startIdx, endIdx);
        return true;
    }
    return false;
}

QVariant MvQFilterTreeModel::headerData(const int section, const Qt::Orientation orient, const int role) const
{
    if (orient != Qt::Horizontal)
        return QAbstractItemModel::headerData(section, orient, role);

    if (role == Qt::DisplayRole) {
        if (section >= 0 && section < headerData_.count()) {
            return headerData_[section];
        }
    }
    return QVariant();
}

QModelIndex MvQFilterTreeModel::index(int row, int column, const QModelIndex& /*parent*/) const
{
    if (valueData_.count() == 0 || column < 0) {
        return QModelIndex();
    }

    return createIndex(row, column, (void*)0);
}

QModelIndex MvQFilterTreeModel::parent(const QModelIndex& /*index */) const
{
    return QModelIndex();
}

Qt::ItemFlags MvQFilterTreeModel::flags(const QModelIndex& index) const
{
    if (multi_ and index.column() == 0) {
        return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
    }
    return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}

bool MvQFilterTreeModel::hasData() const
{
    return valueData_.count() > 0 && valueData_[0].count() > 0;
}

void MvQFilterTreeModel::updateValueChecked(QStringList lst)
{
    if (multi_ && hasData()) {
        for (int i = 0; i < valueData_[valueColumnIndex_].count(); i++) {
            checked_[i] = lst.contains(valueData_[valueColumnIndex_][i]);
        }
        Q_EMIT repaintRequested();
    }
}

void MvQFilterTreeModel::queryValueChecked(QStringList& v, QVector<bool>& st) const
{
    if (multi_ && hasData()) {
        v  = valueData_[valueColumnIndex_];
        st = checked_;
    }
}

QModelIndex MvQFilterTreeModel::indexToValue(QString s) const
{
    if (hasData()) {
        int idx = valueData_[valueColumnIndex_].indexOf(s);
        if (idx != -1) {
            return index(idx, valueColumnIndex_);
        }
    }
    return {};
}


//========================================================
//
// MvQFilterTreeProxyModel
//
//========================================================

MvQFilterTreeProxyModel::MvQFilterTreeProxyModel(QObject* parent) :
    QSortFilterProxyModel(parent)
{
}

void MvQFilterTreeProxyModel::slotFilterColumnChanged(int index)
{
    if (index >= 0 && index < sourceModel()->columnCount()) {
        filterColumn_ = index;
        filterText_.clear();
        invalidateFilter();
    }
}

void MvQFilterTreeProxyModel::slotFilterTextChanged(QString txt)
{
    filterText_ = txt;
    invalidateFilter();
}

bool MvQFilterTreeProxyModel::filterAcceptsRow(int sourceRow,
                                               const QModelIndex& sourceParent) const
{
    if (filterText_.isEmpty())
        return true;

    QModelIndex index;
    QStringList lst;

    index = sourceModel()->index(sourceRow, filterColumn_, sourceParent);
    return sourceModel()->data(index).toString().contains(filterText_, Qt::CaseInsensitive);
}

//====================================
//
// MvQFilterTreeWidget
//
//====================================

MvQFilterTreeWidget::MvQFilterTreeWidget(QStringList headerData, QVector<QStringList> valueData, int selectionColumnIndex, bool multi, QWidget* parent) :
    QWidget(parent),
    multi_(multi),
    selectionColumnIndex_(selectionColumnIndex)
{
    auto* vb = new QVBoxLayout(this);
    vb->setSpacing(0);
    vb->setContentsMargins(1, 1, 1, 1);

    tree_ = new MvQTreeView(this, false);
    vb->addWidget(tree_);

    model_     = new MvQFilterTreeModel(this);
    sortModel_ = new MvQFilterTreeProxyModel(this);
    sortModel_->setSourceModel(model_);
    sortModel_->setDynamicSortFilter(true);

    model_->load(headerData, valueData, multi, selectionColumnIndex_);

    tree_->setModel(sortModel_);
    tree_->setSortingEnabled(true);
    tree_->setUniformRowHeights(true);
    tree_->setRootIsDecorated(false);
    tree_->setAlternatingRowColors(true);

    for (int i = 0; i < model_->columnCount() - 1; i++) {
        tree_->resizeColumnToContents(i);
    }

    tree_->sortByColumn(0, Qt::AscendingOrder);

    filterCb_ = new MvQComboLine(headerData, this);
    vb->addWidget(filterCb_);

    filterCb_->setCurrentIndex(sortModel_->filterColumn());

    connect(tree_, SIGNAL(selectionChanged(const QModelIndex&)),
            this, SLOT(slotSelected(const QModelIndex&)));

    connect(filterCb_, SIGNAL(currentIndexChanged(int)),
            sortModel_, SLOT(slotFilterColumnChanged(int)));

    connect(filterCb_, SIGNAL(textChanged(QString)),
            sortModel_, SLOT(slotFilterTextChanged(QString)));

    connect(model_, SIGNAL(repaintRequested()),
            this, SLOT(slotReqpaintTree()));

    connect(model_, SIGNAL(valueCheckedChanged(QString, bool)),
            this, SIGNAL(valueCheckedChanged(QString, bool)));
}

void MvQFilterTreeWidget::slotSelected(const QModelIndex& index)
{
    if (!multi_) {
        auto index1 = sortModel_->index(index.row(), selectionColumnIndex_);
        emit valueCheckedChanged(sortModel_->data(index1).toString(), true);
    }
}

void MvQFilterTreeWidget::queryValueChecked(QStringList& v, QVector<bool>& st) const
{
    model_->queryValueChecked(v, st);
}

void MvQFilterTreeWidget::updateValueChecked(QStringList s)
{
    model_->updateValueChecked(s);
}

void MvQFilterTreeWidget::setCurrentValue(QString s)
{
    tree_->setCurrentIndex(sortModel_->mapFromSource(model_->indexToValue(s)));
}

void MvQFilterTreeWidget::slotReqpaintTree()
{
    tree_->viewport()->update();
}

void MvQFilterTreeWidget::adjustHeight()
{
    //Try to set the optimal widget height to approximartely 10 rows
    if (model_->rowCount() > 0) {
        QFont f;
        QFontMetrics fm(f);
        int itemHeight  = (fm.height() + 2);
        int totalHeight = itemHeight * ((model_->rowCount() >= 10) ? 10 : model_->rowCount() + 1);
        if (tree_->horizontalScrollBar()) {
            totalHeight += tree_->horizontalScrollBar()->height();
        }
        tree_->setMinimumHeight(totalHeight);
        tree_->setMaximumHeight(totalHeight);
    }
    else {
        tree_->setMaximumHeight(32);
    }
}
