/*****************************************************************************
 * $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 "ComponentExplorer.h"

//-- CamiTK Core stuff
#include <Application.h>
#include <Component.h>
#include <Log.h>

//-- Qt stuff
#include <QInputDialog>
#include <QMenu>

//-- to stretch the first column to entirely show the names
#include <QHeaderView>

#include <utility> // as_const

using namespace camitk;

//----------------------- constructor ------------------------
ComponentExplorer::ComponentExplorer(QString name) : Viewer(name, Viewer::DOCKED) {
    setIcon(QPixmap(":/explorer"));
    setDescription("The Explorer viewer shows all the instantiated components");

    explorerTree = nullptr;
    explorerMenu = nullptr;
}

//----------------------- destructor ------------------------
ComponentExplorer::~ComponentExplorer() {
    delete explorerTree;
    explorerTree = nullptr;

    delete explorerMenu;
    explorerMenu = nullptr;
}

//----------------------- getWidget ------------------------
QWidget* ComponentExplorer::getWidget() {
    if (explorerTree == nullptr) {
        //-- create the explorer tree
        explorerTree = new QTreeWidget();
        // For explorerTree to emit the customMenu signal
        explorerTree->setContextMenuPolicy(Qt::CustomContextMenu);
        // headers
        QStringList headerTitles;
        headerTitles << "Name" << "Size" ;
        explorerTree->setHeaderLabels(headerTitles);
        explorerTree->header()->setStretchLastSection(false);
        explorerTree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // First column stretch to what is needed
        // Unsortable column
        explorerTree->setSortingEnabled(false);
        // Multiple selection (click + Shift or Control key)
        explorerTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
        // hide the number of sub components and the "name" header for now
        explorerTree->hideColumn(1);
        explorerTree->setHeaderHidden(true);

        //-- connections
        connect(explorerTree, SIGNAL(itemSelectionChanged()), this, SLOT(explorerSelectionChanged()));
        connect(explorerTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(doubleClicked(QTreeWidgetItem*, int)));
        connect(explorerTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(rightButtonPressed(QPoint)));
    }

    return explorerTree;
}

//----------------------- getMenu ------------------------
QMenu* ComponentExplorer::getMenu() {
    if (explorerMenu == nullptr) {
        //-- create the menu
        explorerMenu = new QMenu(objectName().toStdString().c_str());

        //-- create the possible actions
        editRename = new QAction(tr("&Rename Component"),  this);
        editRename->setStatusTip(tr("Rename selected component"));
        editRename->setWhatsThis(tr("Rename\n\nRename the currently selected component"));
        connect(editRename, SIGNAL(triggered()), this, SLOT(renameItem()));

        //-- add actions to the menu
        explorerMenu->addAction(editRename);

        //-- update state
        editRename->setEnabled(Application::getSelectedComponents().size() > 0);
        explorerMenu->setEnabled(Application::getSelectedComponents().size() > 0);
    }

    return explorerMenu;
}

//----------------------- refresh ------------------------
void ComponentExplorer::refresh(Viewer* whoIsAsking) {
    if (explorerTree == nullptr) {
        // no widget → no refresh needed
        return;
    }

    // if it is this instance who is asking the refresh, then only the Component names need to be checked...
    if (whoIsAsking != this) {
        //-- check the top-level component number
        ComponentList topLevelCpt = Application::getTopLevelComponents();
        ComponentList viewedcomp = topLevelCompItemMap.keys();
        if (viewedcomp.size() != topLevelCpt.size()) {
            // remove the closed/deleted top-level component
            for (Component* comp : viewedcomp) {
                if (!topLevelCpt.contains(comp)) {
                    removeTopLevel(comp);
                }
            }
            // add the new top-level component
            for (Component* comp : topLevelCpt) {
                if (!viewedcomp.contains(comp)) {
                    addTopLevel(comp);
                }
            }
        }

        //-- check the selection
        // First: block the signal
        explorerTree->blockSignals(true);
        explorerTree->clearSelection();

        if (!Application::getSelectedComponents().isEmpty()) {
            for (Component* comp : Application::getSelectedComponents()) {
                if (comp->isSelected()) {
                    QTreeWidgetItem* selected = getItem(comp);

                    if (selected) {
                        // select this item in the explorer
                        selected->setSelected(true);
                    }
                }
            }

            // ensure of the last one selected
            QTreeWidgetItem* lastSelected = getItem(Application::getSelectedComponents().last());
            explorerTree->scrollToItem(lastSelected, QAbstractItemView::PositionAtCenter);
            explorerTree->setCurrentItem(lastSelected);
        }

        // Last: unblock the signal
        explorerTree->blockSignals(false);
    }

    // check if the InterfaceNode was modified
    for (Component* c : Application::getAllComponents()) {
        if (c->getNodeModified()) {
            refreshInterfaceNode(c);
        }
    }

    //-- update the menu
    if (explorerMenu != nullptr) {
        editRename->setEnabled(Application::getSelectedComponents().size() > 0);
        explorerMenu->setEnabled(Application::getSelectedComponents().size() > 0);
    }
}

//----------------------- getItem ------------------------
QTreeWidgetItem* ComponentExplorer::getItem(Component* comp) {
    QMap<QTreeWidgetItem*, Component*>::iterator it = itemComponentMap.begin();

    while (it != itemComponentMap.end() && it.value() != comp) {
        ++it;
    }

    if (it != itemComponentMap.end()) {
        return it.key();
    }
    else {
        return nullptr;
    }
}

//----------------------- getNewItem ------------------------
QTreeWidgetItem* ComponentExplorer::getNewItem(QTreeWidgetItem* parent, Component* abstractNode) {
    //-- create the tree widget for abstractNode
    QTreeWidgetItem* tw = new QTreeWidgetItem(parent);
    // set the first column (#0)
    QString itemText = abstractNode->getName();

    if (abstractNode->getIcon().isNull()) {
        tw->setText(0, itemText);
    }
    else {
        // add a space in front of the name
        tw->setText(0, itemText);
        // add the pixmap
        tw->setIcon(0, abstractNode->getIcon());
    }

    // check the italic property
    QFont f = tw->font(0);
    f.setItalic(abstractNode->inItalic());
    tw->setFont(0, f);

    // and the second column (#1)
    tw->setText(1, QString::number(abstractNode->getChildren().size()));
    return tw;
}

//----------------------- addTopLevel ------------------------
void ComponentExplorer::addTopLevel(Component* comp, int index) {
    if (!comp->getParent()) {
        // create the items
        QTreeWidgetItem* compItem = add(nullptr, comp);

        // add to / insert in the tree
        if (index < 0) {
            explorerTree->addTopLevelItem(compItem);
        }
        else {
            explorerTree->insertTopLevelItem(index, compItem);
        }

        // insert top-level component in the map
        topLevelCompItemMap.insert(comp, compItem);
    }
}

//----------------------- add ------------------------
QTreeWidgetItem* ComponentExplorer::add(QTreeWidgetItem* parent, Component* abstractNode) {
    //-- create new item
    QTreeWidgetItem* tw = getNewItem(parent, abstractNode);

    //-- register in the map
    itemComponentMap.insert(tw, abstractNode);

    // add the explorer to the Component viewer list
    abstractNode->setVisibility(this->getName(), true);

    //-- add children recursively
    for (Component* comp : abstractNode->getChildren()) {
        add(tw, comp);
    }

    return tw;
}

//----------------------- refreshInterfaceNode ------------------------
void ComponentExplorer::refreshInterfaceNode(Component* comp) {
    QTreeWidgetItem* toDelete = getItem(comp);

    if (toDelete != nullptr) {
        // get the index in the parent list
        QTreeWidgetItem* parentTW = toDelete->parent();
        int index = -1;

        if (parentTW == nullptr)
            // if no parent, it means toDelete is at top level, get its index
        {
            index = explorerTree->indexOfTopLevelItem(toDelete);
        }
        else
            // if the parent exists, then get the index of the child
        {
            index = parentTW->indexOfChild(toDelete);
        }

        // remove from the list and from the explorer
        explorerTree->blockSignals(true);

        // recreate and add at the same place
        if (parentTW == nullptr)
            // if there is no parent, then add at the correct index on the top level
        {
            // remove from the map
            removeTopLevel(comp);
            addTopLevel(comp, index);
        }
        else
            // add where it was deleted in the parent index
        {
            remove(toDelete);
            parentTW->insertChild(index, add(nullptr, comp));
        }

        explorerTree->blockSignals(false);
    }
}


//----------------------- removeTopLevel ------------------------
void ComponentExplorer::removeTopLevel(Component* comp) {
    QTreeWidgetItem* toDelete = topLevelCompItemMap.value(comp);

    if (toDelete != nullptr) {
        explorerTree->blockSignals(true);
        // remove from the explorer
        remove(toDelete);
        explorerTree->blockSignals(false);
        // remove from the map
        topLevelCompItemMap.remove(comp);
    }
}

//----------------------- remove ------------------------
void ComponentExplorer::remove(QTreeWidgetItem* tw) {
    // tell the Component
    if (Application::isAlive(itemComponentMap.value(tw))) {
        itemComponentMap.value(tw)->setVisibility(this->getName(), false);
    }

    // remove from the map
    itemComponentMap.remove(tw);

    // remove all children from the map (recursively)
    while (tw->childCount() > 0) {
        QTreeWidgetItem* toDelete = tw->takeChild(0);
        remove(toDelete);
    }

    // remove from the explorer
    delete tw;
}

//----------------------- explorerSelectionChanged ------------------------
void ComponentExplorer::explorerSelectionChanged() {
    QList<QTreeWidgetItem*> selectedItems = explorerTree->selectedItems();
    // reset the selection list
    Application::clearSelectedComponents();
    // create the new list
    ComponentList selectedComponent;

    for (auto selectedItem : std::as_const(selectedItems)) {
        selectedComponent.append((Component*)itemComponentMap.value(selectedItem));
    }

    // add everything to the selection and update the views
    Viewer::selectionChanged(selectedComponent);

    // refresh!
    refresh(this);
}


//-------------------------- doubleClicked -------------------------
void ComponentExplorer::doubleClicked(QTreeWidgetItem* tw, int) {
    Component* comp = (Component*) itemComponentMap.value(tw);

    // tell the comp that it has been doubleclicked
    if (comp) {
        if (comp->doubleClicked()) {
            refreshInterfaceNode(comp);
        }

        // update selection
        explorerSelectionChanged();
    }
}

// ---------------------- rightButtonPressed ----------------------------
void ComponentExplorer::rightButtonPressed(const QPoint& clickedPoint) {
    QTreeWidgetItem* selected = explorerTree->itemAt(clickedPoint);

    if (selected) {
        Component* comp = (Component*) itemComponentMap.value(selected);
        QMenu* actionsMenu = comp->getActionMenu();

        if (actionsMenu) {
            QPoint pos(clickedPoint);
            actionsMenu->exec(explorerTree->mapToGlobal(pos));
        }
    }
}


//----------------------- renameItem ------------------------
void ComponentExplorer::renameItem() {
    // we take the selected Item
    Component* comp = itemComponentMap.value(explorerTree->currentItem());

    if (comp != nullptr) {
        // open of the dialog box
        bool ok;
        QString text = QInputDialog::getText(explorerTree, tr("Rename"), tr("Enter the new name:"), QLineEdit::Normal, comp->getName(),  &ok);

        //if the input dialog isn't empty, the slot in rename item is called
        if (ok && !text.isEmpty()) {
            comp->setName(text);
            refreshInterfaceNode(comp);
            // something was modified on the Component: refresh its viewers
            comp->refresh();
        }

    }
    else {
        CAMITK_INFO(tr("Please select an element in the explorer first."));
    }
}



