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

//-- Core
#include <Application.h>
#include <Component.h>
#include <Action.h>
#include <PropertyObject.h>

#include <QFrame>
#include <QComboBox>
#include <QCompleter>
#include <QLineEdit>
#include <QDialog>
#include <QGridLayout>
#include <QDockWidget>

using namespace camitk;

// ---------------- constructor ----------------
ActionViewer::ActionViewer(QString name) : Viewer(name, Viewer::DOCKED) {
    myWidget = nullptr;
    familyComboBox = nullptr;
    nameComboBox = nullptr;
    actionWidgetStack = nullptr;
    searchFramePanel = nullptr;
    myMenu = nullptr;

    // empty the list as this is an action viewer, it has nothing to do with Components
    setComponentClassNames(QStringList());

    setIcon(QPixmap(":/play"));

    setDescription("\"Action Viewer\" displays the currently selected action's GUI as well as an action selector");

    // init property object using settings and install the event filter to be notified of the changes
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".ActionViewer." + objectName().simplified().replace(" ", ""));
    bool searchPanelVisibility = settings.value("showSearchPanel", true).toBool();
    settings.endGroup();

    propertyObject = new PropertyObject(getName());
    propertyObject->addProperty(new Property("Show Search Panel", searchPanelVisibility, "Visibility of the action search panel (where you can search for an action by family, tag or keywords).", ""));

    // trigger change for the property
    propertyObject->installEventFilter(this);
    ActionViewer::eventFilter(propertyObject, new QEvent(QEvent::DynamicPropertyChange));
}

// ---------------- destructor ----------------
ActionViewer::~ActionViewer() {
}

// -------------------- refresh --------------------
void ActionViewer::refresh(Viewer*) {
    // only need to refresh if widget is created
    if (myWidget != nullptr) {
        updateActionWidget(Application::getTriggeredAction());
        updateSearchPanel(ViewerRefresh);
    }
}

// -------------------- getWidget --------------------
QWidget* ActionViewer::getWidget() {
    // lazy instantiation
    if (myWidget == nullptr) {
        myWidget = new QWidget();

        auto* actionWidgetLayout = new QVBoxLayout();

        //-- build the search frame widget
        searchFramePanel = new QFrame();
        searchFramePanel->setFrameShape(QFrame::StyledPanel);
        searchFramePanel->setFrameShadow(QFrame::Sunken);
        searchFramePanel->setLineWidth(3);

        auto* searchFrameLayout = new QGridLayout();
        searchFrameLayout->addWidget(new QLabel("Family"), 0, 0);
        familyComboBox = new QComboBox();
        searchFrameLayout->addWidget(familyComboBox, 0, 1);
        searchFrameLayout->addWidget(new QLabel("Action"), 1, 0);
        nameComboBox = new QComboBox();
        searchFrameLayout->addWidget(nameComboBox, 1, 1);
        searchFrameLayout->addWidget(new QLabel("Tag"), 2, 0);
        tagLineEdit = new QLineEdit();
        searchFrameLayout->addWidget(tagLineEdit, 2, 1);

        // add everything to the research frame
        searchFramePanel->setLayout(searchFrameLayout);
        actionWidgetLayout->addWidget(searchFramePanel);
        // Search panel active and visible by default
        setSearchPanelVisible(propertyObject->property("Show Search Panel").toBool());

        //-- build the stackedWidget to the action widget layout
        actionWidgetStack = new QStackedWidget();
        actionWidgetStack->setFrameShape(QFrame::StyledPanel);
        actionWidgetStack->setFrameShadow(QFrame::Sunken);
        actionWidgetStack->setLineWidth(3);
        actionWidgetStack->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
        // insert empty widget to fill the space by default
        emptyActionWidgetIndex = actionWidgetStack->addWidget(new QWidget());
        // init history insert new index in history for empty selection
        ComponentList emptySelection;
        widgetHistory.insert(emptySelection, nullptr);
        actionWidgetLayout->addWidget(actionWidgetStack);

        // set the action widget layout
        actionWidgetLayout->setSizeConstraint(QLayout::SetMinimumSize);
        myWidget->setLayout(actionWidgetLayout);
    }

    return myWidget;
}

// ---------------------- getPropertyObject ----------------------------
PropertyObject* ActionViewer::getPropertyObject() {
    return propertyObject;
}

// -------------------- setSearchPanelVisible --------------------
void ActionViewer::setSearchPanelVisible(bool visibility) {
    if (searchFramePanel != nullptr) {
        searchFramePanel->setVisible(visibility);

        if (visibility) {
            // Connect buttons
            QObject::connect(familyComboBox, SIGNAL(activated(int)), this, SLOT(changeFamily()));
            QObject::connect(nameComboBox, SIGNAL(activated(int)), this, SLOT(changeName()));
            QObject::connect(tagLineEdit, SIGNAL(editingFinished()), this, SLOT(changeTag()));
        }
        else {
            // disconnect buttons
            QObject::disconnect(familyComboBox, SIGNAL(activated(int)), this, SLOT(changeFamily()));
            QObject::disconnect(nameComboBox, SIGNAL(activated(int)), this, SLOT(changeName()));
            QObject::disconnect(tagLineEdit, SIGNAL(editingFinished()), this, SLOT(changeTag()));
        }
    }
}

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

        //-- create the possible actions
        QAction* showSearchPanelQAction = new QAction(tr("&Show Search Panel"),  this);
        // make sure the status is visible
        showSearchPanelQAction->setEnabled(true);
        showSearchPanelQAction->setCheckable(true);
        // update visibility GUI
        showSearchPanelQAction->setChecked(propertyObject->property("Show Search Panel").toBool());
        showSearchPanelQAction->setStatusTip(tr("Show/hide the action search panel on top of the action viewer."));
        showSearchPanelQAction->setWhatsThis(tr("Show/hide the action search panel on top of the action viewer."));
        QObject::connect(showSearchPanelQAction, &QAction::triggered, [this]() {
            propertyObject->setProperty("Show Search Panel", !propertyObject->property("Show Search Panel").toBool());
        });

        //-- add actions to the menu
        myMenu->addAction(showSearchPanelQAction);
    }

    return myMenu;
}


// -------------------- changeActionNameComboBox --------------------
void ActionViewer::changeName() {
    action = Application::getAction(nameComboBox->currentText());
    updateSearchPanel(ActionNameChanged);
}

// -------------------- changeTag --------------------
void ActionViewer::changeTag() {
    updateSearchPanel(ActionTagChanged);
}

// -------------------- changeFamilyComboBox --------------------
void ActionViewer::changeFamily() {
    updateSearchPanel(ActionFamilyChanged);
}

// ---------------- componentListLessThan ----------------
bool operator<(const ComponentList& l1, const ComponentList& l2) {
    // This method is needed by the QMap for history
    if (l1.size() < l2.size()) {
        return true;
    }
    else {
        int i = 0;

        while (i < l1.size() && l1.value(i) == l2.value(i)) {
            i++;
        }

        if (i == l1.size()) {
            return false;
        }
        else {
            return (l1.value(i) < l2.value(i));
        }
    }
}

// -------------------- updateSearchPanel --------------------
void ActionViewer::updateSearchPanel(UpdateReason reason) {
    familyComboBox->blockSignals(true);
    nameComboBox->blockSignals(true);
    tagLineEdit->blockSignals(true);

    ActionList possibleActions;

    switch (reason) {
        case ActionFamilyChanged:
            //-- family was changed, fill the name combo box with this family actions
            nameComboBox->clear();
            nameComboBox->addItem("-- Select Action --");

            if (Application::getSelectedComponents().size() > 0) {
                possibleActions = Application::getActions(Application::getSelectedComponents());
            }
            else {
                // no selection => select only empty component actions
                possibleActions = Application::getActions(nullptr);
            }

            for (Action* action : possibleActions) {
                if (action->getFamily() == familyComboBox->currentText() || familyComboBox->currentText() == QString("-- Select Family --")) {
                    nameComboBox->addItem(action->getName());
                }
            }

            nameComboBox->model()->sort(0);
            break;

        case ActionNameChanged:

            //-- action name whas changed -> trigger the corresponding action
            if (action != nullptr) {
                familyComboBox->setCurrentIndex(familyComboBox->findText(action->getFamily()));
                action->trigger();
            }

            break;

        case ActionTagChanged: {
            //-- tag field was changed, fill the name combo box with the corresponding actions
            ActionList actionset = Application::getActions(Application::getSelectedComponents(), tagLineEdit->text());

            if (!actionset.isEmpty()) {
                if (actionset.size() == 1) {
                    action = *(actionset.begin());
                    nameComboBox->setCurrentIndex(nameComboBox->findText(action->getName()));
                    updateSearchPanel(ActionNameChanged);
                }
                else {
                    nameComboBox->clear();
                    nameComboBox->addItem("-- Select Action --");

                    for (Action* action : actionset) {
                        nameComboBox->addItem(action->getName());
                    }
                }

                nameComboBox->model()->sort(0);
            }
        }
        break;
        case ViewerRefresh:
        default: {

            //-- selection was changed, update combo boxes and tag list, and then check history
            // copy selected component list (so that references are not used for comparison)
            ComponentList selected(Application::getSelectedComponents());

            // if selection did not change between two updates, no need to refresh the widget
            if (currentlySelected != selected || selected.size() == 0) {
                currentlySelected = selected;
                //-- update the search panel
                familyComboBox->clear();
                nameComboBox->clear();
                QStringList wordList;
                familyComboBox->addItem("-- Select Family --");
                nameComboBox->addItem("-- Select Action --");

                if (selected.size() > 0) {
                    possibleActions = Application::getActions(selected.last());
                }
                else {
                    // no selection => select only empty component actions
                    possibleActions = Application::getActions(nullptr);
                }

                // Complete family and action lists depending of the last selected component
                for (Action* action : possibleActions) {
                    if (familyComboBox->findText(action->getFamily()) == -1) {
                        familyComboBox->addItem(action->getFamily());
                    }

                    if (nameComboBox->findText(action->getName()) == -1) {
                        nameComboBox->addItem(action->getName());
                    }

                    for (QString tag : action->getTag()) {
                        if (!wordList.contains(tag)) {
                            wordList.append(tag);
                        }
                    }
                }

                nameComboBox->model()->sort(0);
                familyComboBox->model()->sort(0);
                auto* completer = new QCompleter(wordList, myWidget);
                completer->setCaseSensitivity(Qt::CaseInsensitive);
                tagLineEdit->setCompleter(completer);

                //-- check history
                // remove all actions that were unloaded/removed, remove it from the widgetHistory
                auto it = widgetHistory.begin();
                while (it != widgetHistory.end()) {
                    if (!Application::isAlive(it.value())) {
                        it = widgetHistory.erase(it);  // erase returns the next iterator
                    }
                    else {
                        ++it;
                    }
                }

                // check if the same list of Component* was already used
                it = widgetHistory.find(selected);
                if (it != widgetHistory.constEnd()) {
                    // show the previously used widget
                    if (it.value() == nullptr || !Application::isAlive(it.value())) {
                        actionWidgetStack->setCurrentIndex(emptyActionWidgetIndex);
                    }
                    else {
                        updateActionWidget(it.value());
                    }
                }
                else {
                    // insert new index in history (nullptr action, with empty widget)
                    widgetHistory.insert(selected, nullptr);
                    actionWidgetStack->setCurrentIndex(emptyActionWidgetIndex);
                }
            }

        }
    }

    familyComboBox->blockSignals(false);
    nameComboBox->blockSignals(false);
    tagLineEdit->blockSignals(false);
}

// -------------------- updateActionWidget --------------------
void ActionViewer::updateActionWidget(Action* action) {
    // no need to update if there is no currently selected action or the current action is not to embed
    if (action != nullptr && action->getEmbedded() && Application::isAlive(action)) {

        // make sure the action viewer is visible (even if in a dockwidget)
        if (getWidget()->parentWidget()) {
            getWidget()->parentWidget()->setVisible(true);
            getWidget()->setVisible(true);
        }

        //-- check history
        action->updateTargets();
        QWidget* actionWidget = action->getWidget();
        int actionWidgetIndex = actionWidgetStack->indexOf(actionWidget);

        if (actionWidgetIndex == -1 && actionWidget != nullptr) {
            // add the widget (beware that actionWidgetStack then takes ownership of the widget!)
            actionWidgetIndex = actionWidgetStack->addWidget(actionWidget);
        }

        // insert new index in history
        ComponentList selected(Application::getSelectedComponents());
        widgetHistory.insert(selected, action);
        actionWidgetStack->setCurrentIndex(actionWidgetIndex);
        actionWidgetStack->update();
        // ignore size policy of widget so that it can be resized
        actionWidgetStack->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

        // update search panel
        familyComboBox->setCurrentIndex(familyComboBox->findText(action->getFamily()));
        updateSearchPanel(ActionFamilyChanged); // to have only the actions of the same family
        nameComboBox->setCurrentIndex(nameComboBox->findText(action->getName()));
    }
}


// ---------------- eventFilter ----------------
bool ActionViewer::eventFilter(QObject* object, QEvent* event) {

    // watch propertyObject instance for dynamic property changes
    if (event->type() == QEvent::DynamicPropertyChange && object == propertyObject) {
        //-- first perform the proper action
        bool searchPanelVisibility = propertyObject->property("Show Search Panel").toBool();
        setSearchPanelVisible(searchPanelVisibility);

        //-- and then save the settings accordingly
        QSettings& settings = Application::getSettings();
        settings.beginGroup(Application::getName() + ".ActionViewer." + objectName().simplified().replace(" ", ""));
        settings.setValue("showSearchPanel", searchPanelVisibility);
        settings.endGroup();

        return true;
    }
    else {
        // otherwise pass the event on to the parent class
        return Viewer::eventFilter(object, event);
    }
}
