/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "MeshComponent.h"

#include <algorithm>

// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkExtractSelection.h>
#include <vtkConvertSelection.h>
#include "CamiTKReEnableWarnings"

#include <vtkSelection.h>
#include <vtkSelectionNode.h>
#include <vtkAbstractArray.h>
#include <vtkIdTypeArray.h>

namespace camitk {

// -------------------- constructor --------------------
MeshSelectionModel::MeshSelectionModel(MeshComponent* const meshComp) :
    QAbstractTableModel(meshComp), meshComponent(meshComp) {
    fieldName[vtkSelectionNode::POINT] = QString("Points");
    fieldName[vtkSelectionNode::CELL] = QString("Cells");

    contentName[vtkSelectionNode::INDICES] = "Indices";
}

// -------------------- rowCount --------------------
int MeshSelectionModel::rowCount(const QModelIndex& parent) const {
    if (!meshComponent) {
        return 0;
    }
    return meshComponent->getSelections().size();
}

// -------------------- columnCount --------------------
int MeshSelectionModel::columnCount(const QModelIndex& parent) const {
    return 4;
}

// -------------------- data --------------------
QVariant MeshSelectionModel::data(const QModelIndex& index, int role) const {
    if (!meshComponent) {
        return QVariant();
    }

    int row = index.row();
    int col = index.column();

    vtkSmartPointer< vtkSelectionNode > selNode = meshComponent->getSelections().at(row);

    switch (role) {
        case Qt::DisplayRole : {
            switch (col) {
                case 0 :
                    if (selNode->GetSelectionList()) {
                        return QString(selNode->GetSelectionList()->GetName());
                    }
                    else {
                        return QString("Unknown");
                    }
                    break;
                case 1 :
                    return QString(fieldName[selNode->GetFieldType()]);
                    break;
                case 2 :
                    return QString(contentName[selNode->GetContentType()]);
                    break;
                case 3 :
                    if (selNode->GetSelectionList()) {
                        return QString::number(selNode->GetSelectionList()->GetNumberOfTuples());
                    }
                    else {
                        return QString::number(0);
                    }
                    break;
                default :
                    return QVariant();
                    break;
            }
            break;
        }
        case Qt::DecorationRole : {
            if (col == 1) {
                if (selNode->GetFieldType() == vtkSelectionNode::POINT) {
                    return QIcon(":/points");
                }
                else if (selNode->GetFieldType() == vtkSelectionNode::CELL) {
                    return QIcon(":/cell");
                }
                else {
                    return QVariant();
                }
            }
            else {
                return QVariant();
            }
            break;
        }
        default :
            return QVariant();
            break;
    }

    return QVariant();
}

// -------------------- headerData --------------------
QVariant MeshSelectionModel::headerData(int section, Qt::Orientation orientation, int role) const {
    if (role == Qt::DisplayRole) {
        if (orientation == Qt::Horizontal) {
            switch (section) {
                case 0:
                    return QString("Name");
                    break;
                case 1:
                    return QString("Type");
                    break;
                case 2:
                    return QString("Content");
                    break;
                case 3 :
                    return QString("Count");
                    break;
                default:
                    return QVariant();
                    break;
            }
        }
    }
    return QVariant();
}

// -------------------- insertSelection --------------------
int MeshSelectionModel::insertSelection(const QString& name, int fieldType, int contentType,  vtkSmartPointer< vtkAbstractArray > array, InsertionPolicy policy) {
    // TODO : check if a selection of the same name already exists ?

    int index = meshComponent->getSelectionIndex(name);

    // if no selection with the same name exists, create it and add it to the model
    // except if the insertion policy is SUBTRACT
    if ((index < 0) && (policy != SUBTRACT)) {
        // insert the new row
        beginInsertRows(QModelIndex(), meshComponent->getSelections().size(), meshComponent->getSelections().size());

        vtkSmartPointer< vtkSelectionNode > selNode = vtkSmartPointer< vtkSelectionNode >::New();
        array->SetName(name.toStdString().c_str());
        selNode->SetFieldType(fieldType);
        selNode->SetContentType(contentType);
        selNode->SetSelectionList(array);
        // TODO direct manipulation of selection list should not be allowed outside MeshComponent
        // meshComponent->addSelection(name,fieldType,contentType,array);
        meshComponent->getSelections().append(selNode);
        endInsertRows();

        index = meshComponent->getSelections().size() - 1;
    }
    // else update the selection data according to insertion policy
    else {
        vtkSmartPointer< vtkSelectionNode > selNode = meshComponent->getSelections().at(index);

        // if the field types are different, the selection data cannot be merged or subtracted
        if (fieldType != selNode->GetFieldType() && (policy == MERGE || policy == SUBTRACT)) {
            index = -1;
        }
        else {
            switch (policy) {
                case REPLACE : {
                    // replace the selection data
                    array->SetName(name.toStdString().c_str());
                    selNode->SetFieldType(fieldType);
                    selNode->SetContentType(contentType);
                    selNode->SetSelectionList(array);

                    emit dataChanged(this->index(index, 1), this->index(index, 3));
                }
                break;
                // TODO the selection content must be the same
                case MERGE : {
                    // merge the selection list
                    // in this case, the content type of the new selection list are indices
                    vtkSmartPointer< vtkSelectionNode > newSelNode = vtkSmartPointer< vtkSelectionNode >::New();
                    newSelNode->SetFieldType(fieldType);
                    newSelNode->SetContentType(contentType);
                    newSelNode->SetSelectionList(array);

                    selNode->UnionSelectionList(newSelNode);

                    emit dataChanged(this->index(index, 1), this->index(index, 3));
                }
                break;
                case SUBTRACT : {
                    // subtract the selection list
                    // in this case, the content type of the new selection list are indices

                    vtkSmartPointer< vtkIdTypeArray > oldIdsArray = vtkIdTypeArray::SafeDownCast(selNode->GetSelectionList());
                    vtkSmartPointer< vtkIdTypeArray > newIdsArray = vtkIdTypeArray::SafeDownCast(array);

                    if ((!newIdsArray) || (!oldIdsArray)) {
                        index = -1;
                        break;
                    }

                    const int nbOldIndices = oldIdsArray->GetNumberOfTuples();
                    const int nbNewIndices = newIdsArray->GetNumberOfTuples();

                    std::vector< vtkIdType > oldIndices(nbOldIndices);
                    std::vector< vtkIdType > newIndices(nbNewIndices);

                    // sort the indices
                    std::partial_sort_copy(oldIdsArray->GetPointer(0), oldIdsArray->GetPointer(0) + nbOldIndices, oldIndices.begin(), oldIndices.end());
                    std::partial_sort_copy(newIdsArray->GetPointer(0), newIdsArray->GetPointer(0) + nbNewIndices, newIndices.begin(), newIndices.end());

                    std::vector< vtkIdType > diff(nbOldIndices);

                    // compute the difference between old and new indices
                    std::vector< vtkIdType >::iterator it;
                    it = std::set_difference(oldIndices.begin(), oldIndices.end(), newIndices.begin(), newIndices.end(), diff.begin());
                    diff.resize(it - diff.begin());

                    vtkSmartPointer< vtkIdTypeArray > diffArray = vtkSmartPointer< vtkIdTypeArray >::New();
                    diffArray->SetNumberOfValues(diff.size());
                    // cannot use std::copy(diff.begin(), diff.end(), diffArray->GetPointer(0)) as MSVC complains and generates
                    // a warning C4996: 'std::copy::_Unchecked_iterators::_Deprecate'
                    for (const auto val : diff) {
                        diffArray->InsertNextValue(val);
                    }

                    selNode->SetSelectionList(diffArray);

                    emit dataChanged(this->index(index, 1), this->index(index, 3));
                }
                break;
                case DISCARD :
                default :
                    index = -1;
                    break;
            }
        }
    }

    return index;
}

// -------------------- removeSelection --------------------
int MeshSelectionModel::removeSelection(const QString& name) {
    int index = meshComponent->getSelectionIndex(name);

    if (index >= 0) {
        beginRemoveRows(QModelIndex(), index, index);
        meshComponent->getSelections().removeAt(index);
        endRemoveRows();
    }

    return index;
}

// -------------------- setData --------------------
bool MeshSelectionModel::setData(const QModelIndex& index, const QVariant& value, int role) {
    if (
        index.isValid() &&
        role == Qt::EditRole &&
        index.column() == 0 /*&&
        index.row() > 0*/
    ) {
        // check if the name already exists
        if (meshComponent->getSelectionIndex(value.toString()) >= 0) {
            return false;
        }

        int row = index.row();
        meshComponent->getSelections().at(row)->GetSelectionList()->SetName(value.toString().toStdString().c_str());
        emit dataChanged(index, index);

        return true;
    }

    return false;
}

// -------------------- flags --------------------
Qt::ItemFlags MeshSelectionModel::flags(const QModelIndex& index) const {
    if (index.column() == 0) {
        return Qt::ItemIsSelectable |  Qt::ItemIsEditable | Qt::ItemIsEnabled ;
    }
    else {
        return Qt::ItemIsSelectable  | Qt::ItemIsEnabled ;
    }
}

}
