/*****************************************************************************
 * $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 "Action.h"
#include "ActionExtension.h"
#include "Application.h"
#include "TransformationManager.h"
#include "Component.h"
#include "ImageComponent.h"
#include "Property.h"
#include "ActionWidget.h"
#include "HistoryItem.h"
#include "PersistenceManager.h"
#include "Log.h"

#include <utility> // as_const

namespace camitk {

// -------------------- constructor --------------------
Action::Action(ActionExtension* extension) : QObject() {
    this->extension = extension;
    componentClassName = "";
    qAction = nullptr;
    isEmbedded = true;
    actionWidget = nullptr;
    autoUpdateProperties = false;
    historyItem = nullptr;
    defaultWidgetButtonVisibility = true;
    defaultWidgetApplyButtonText = tr("Apply");
}

// -------------------- destructor --------------------
Action::~Action() {
    // delete all properties
    for (auto prop : std::as_const(parameterMap)) {
        delete prop;
    }

    parameterMap.clear();

    //delete History item
    if (historyItem) {
        delete historyItem;
    }
}

// -------------------- getStatusAsString --------------------
QString Action::getStatusAsString(ApplyStatus status) {
    QString statusStr;

    switch (status) {
        case Action::ABORTED:
            statusStr = "ABORTED";
            break;

        case Action::ERROR:
            statusStr = "ERROR";
            break;

        case Action::SUCCESS:
            statusStr = "SUCCESS";
            break;

        case Action::TRIGGERED:
            statusStr = "TRIGGERED";
            break;

        case Action::WARNING:
            statusStr = "WARNING";
            break;

        default:
            statusStr = "UNKNOWN";
            break;
    }

    return statusStr;
}

// -------------------- setName --------------------
void Action::setName(QString name) {
    this->name = name;
    setObjectName(name);
}

// -------------------- setDescription --------------------
void Action::setDescription(QString description) {
    this->description = description;
}

// -------------------- setComponentClassName --------------------
void Action::setComponentClassName(QString componentClassName) {
    this->componentClassName = componentClassName;
}

// -------------------- setFamily --------------------
void Action::setFamily(QString family) {
    this->family = family;
}

// -------------------- setTag --------------------
void Action::addTag(QString tag) {
    this->tags.append(tag);
}

// -------------------- setEmbedded --------------------
void Action::setEmbedded(bool isEmbedded) {
    this->isEmbedded = isEmbedded;
}

// -------------------- setIcon --------------------
void Action::setIcon(QPixmap icon) {
    this->icon = icon;
}

// -------------------- getExtensionName --------------------
QString Action::getExtensionName() const {
    return extension->getName();
}

// -------------------- getExtension --------------------
const ActionExtension* Action::getExtension() const {
    return extension;
}

// -------------------- refreshApplication --------------------
void Action::refreshApplication() {
    TransformationManager::cleanupFramesAndTransformations();
    Application::refresh();
}

// -------------------- getIcon --------------------
QPixmap Action::getIcon() {
    return icon;
}

// -------------------- getTargets --------------------
const ComponentList Action::getTargets() const {
    return targetComponents;
}

// -------------------- updateTargets --------------------
void Action::updateTargets() {
    //-- build the list of valid targets
    targetComponents.clear();
    for (Component* comp : Application::getSelectedComponents()) {
        // check compatibility
        if (comp->isInstanceOf(this->getComponentClassName())) {
            targetComponents.append(comp);
        }
    }
}

// -------------------- getWidget --------------------
QWidget* Action::getWidget() {
    // build or update the widget
    if (actionWidget == nullptr) {
        // Setting the widget containing the parameters, using the default widget
        actionWidget = new ActionWidget(this);
    }
    else {
        // make sure the widget has updated targets
        dynamic_cast<ActionWidget*>(actionWidget)->update();
    }

    // make sure the widget updates its parameters
    dynamic_cast<ActionWidget*>(actionWidget)->setAutoUpdateProperty(autoUpdateProperties);

    // update default UI
    dynamic_cast<ActionWidget*>(actionWidget)->setApplyButtonText(defaultWidgetApplyButtonText);
    dynamic_cast<ActionWidget*>(actionWidget)->setButtonVisibility(defaultWidgetButtonVisibility);

    return actionWidget;
}

// -------------------- setDefaultWidgetButtonVisibility --------------------
void Action::setDefaultWidgetButtonVisibility(bool visible) {
    defaultWidgetButtonVisibility = visible;
    if (dynamic_cast<ActionWidget*>(actionWidget) != nullptr) {
        dynamic_cast<ActionWidget*>(actionWidget)->setButtonVisibility(defaultWidgetButtonVisibility);
    }
}

// -------------------- setDefaultWidgetApplyButtonText --------------------
void Action::setDefaultWidgetApplyButtonText(QString text) {
    defaultWidgetApplyButtonText = text;
    if (dynamic_cast<ActionWidget*>(actionWidget) != nullptr) {
        dynamic_cast<ActionWidget*>(actionWidget)->setApplyButtonText(defaultWidgetApplyButtonText);
    }
}

// -------------------- getQAction --------------------
QAction* Action::getQAction(Component* target) {
    if (!qAction) {
        // create the corresponding QAction (using the icon, name and descriptions)
        qAction = new QAction(getIcon(), getName(), this);
        qAction->setStatusTip(getDescription());
        qAction->setWhatsThis(getName() + "\n" + getDescription());
        // connect it to the trigger slot
        connect(qAction, SIGNAL(triggered()), this, SLOT(trigger()));
    }

    return qAction;
}

// -------------------- trigger --------------------
Action::ApplyStatus Action::trigger(QWidget* parent) {
    updateTargets();

    //-- if there are some valid targets or if the action is generic
    if (targetComponents.size() > 0 || getComponentClassName().isEmpty()) {
        if (isEmbedded && getWidget() != nullptr) {
            if (parent != nullptr) {
                // set the widget in the given parent
                getWidget()->setParent(parent);
                getWidget()->show();
            }
        }
        else {
            QWidget* w = getWidget();

            if (w) {
                // non embedded, just show/raise the window
                w->show();
            }
            else {
                // non embedded, no widget -> call apply() directly
                return applyAndRegister();
            }
        }

        // tell the application this action is ready to show its widget
        Application::setTriggeredAction(this);
        // make sure that any viewer that wants to show something about it is refreshed
        refreshApplication();
        // now that everyone that wanted to be notified about this change of action selection,
        // there is no need to do more, just empty the current selection
        Application::setTriggeredAction(nullptr);

        return TRIGGERED;
    }
    else {
        // should never happened
        CAMITK_WARNING(tr("Cannot apply this Action to currently selected component(s)"))
        return ERROR;
    }

}

// -------------------- applyAndRegister --------------------
Action::ApplyStatus Action::applyAndRegister() {
    ApplyStatus status;

    // call preprocess function to compute number of components before applying the action
    this->preProcess();

    //-- if there are some valid targets or if the action is generic
    if (targetComponents.size() > 0 || getComponentClassName().isEmpty()) {
        CAMITK_TRACE(tr("Applying..."))
        status = apply();
        CAMITK_TRACE(tr("Done. Status: %1").arg(getStatusAsString(status)))
        this->postProcess();
    }
    else {
        status = Action::ABORTED;
    }

    return status;
}


// -------------------- applyInPipeline --------------------
Action::ApplyStatus Action::applyInPipeline() {
    ApplyStatus status;

    // call preprocess function, such as in pipeline count components during the process
    preProcessInPipeline();

    //-- if there are some valid targets or if the action is generic
    if (targetComponents.size() > 0 || getComponentClassName().isEmpty()) {
        CAMITK_TRACE(tr("Applying..."))
        status = apply();
        CAMITK_TRACE(tr("Done. Status: %1").arg(getStatusAsString(status)))
        postProcessInPipeline();
    }
    else {
        status = Action::ABORTED;
        CAMITK_TRACE(tr("Action requires targets, but none set. Status: %1").arg(getStatusAsString(status)))
    }

    return status;
}

// -------------------- setInputComponents --------------------
void Action::setInputComponents(ComponentList inputs) {
    //-- build the list of valid targets
    targetComponents.clear();

    for (Component* comp : inputs) {
        // check compatibility
        if (comp->isInstanceOf(this->getComponentClassName())) {
            targetComponents.append(comp);
        }
    }
}

// -------------------- setInputComponent --------------------
void Action::setInputComponent(Component* input) {
    targetComponents.clear();

    // check compatibility
    if (input->isInstanceOf(this->getComponentClassName())) {
        targetComponents.append(input);
    }
}

// -------------------- getOutputComponents --------------------
ComponentList Action::getOutputComponents() {
    return this->outputComponents;
}

// -------------------- getOutputComponent --------------------
Component* Action::getOutputComponent() {
    if (outputComponents.isEmpty()) {
        return nullptr;
    }
    else {
        return outputComponents.first();
    }
}

// -------------------- preProcess ------------------------------
void Action::preProcess() {
    // Create a new entry in the history
    this->historyItem = new HistoryItem(this->name);

    // Get track of alive top level components before applying the action so
    // that we can deduce from list of components after the action,
    // the components created by the action.
    this->aliveBeforeComponents = Application::getTopLevelComponents();

    // We will select the one selected as input of the current action
    this->topLevelSelectedComponents.clear();
    QList<HistoryComponent> topLevelSelectedHistoryComponents;

    for (Component* comp : this->aliveBeforeComponents) {
        if (comp->isSelected()) {
            this->topLevelSelectedComponents.append(comp);
            topLevelSelectedHistoryComponents.append(HistoryComponent(comp));
        }
    }

    // Save the input components information
    this->historyItem->setInputHistoryComponents(topLevelSelectedHistoryComponents);
}


// -------------------- preProcessInPipeline --------------------
void Action::preProcessInPipeline() {
    // get track of alive components before applying the action so
    // that we can deduce from list of components after the action,
    // the components created by the action.
    this->aliveBeforeComponents = Application::getAllComponents();
}

// -------------------- postProcess ------------------------------
void Action::postProcess() {
    // Deduce the number of top level components created during the action, from aliveBeforeComponents
    ComponentList topLevelComponentCreated;
    QList<HistoryComponent> topLevelHistoryComponentCreated;
    topLevelHistoryComponentCreated.clear();

    for (Component* comp : Application::getTopLevelComponents()) {
        if (!this->aliveBeforeComponents.contains(comp)) {
            topLevelComponentCreated.append(comp);
            topLevelHistoryComponentCreated.append(HistoryComponent(comp));
        }
    }

    // Save the output components information
    historyItem->setOutputHistoryComponents(topLevelHistoryComponentCreated);

    // Add the action's parameters to the history item
    // get back all the properties dynamically added to the action using Qt meta object
    for (QByteArray propertyName : dynamicPropertyNames()) {
        historyItem->addProperty(propertyName, property(propertyName));
    }

    // Store the entry in the history.
    Application::addHistoryItem(*historyItem);
}


// -------------------- postProcessInPipeline --------------------
void Action::postProcessInPipeline() {
    outputComponents.clear();
    for (Component* comp : Application::getAllComponents()) {
        if (!aliveBeforeComponents.contains(comp) || comp->getModified()) {
            outputComponents.append(comp);
        }
    }
}

// -------------------- getAutoUpdateProperties --------------------
bool Action::getAutoUpdateProperties() const {
    return autoUpdateProperties;
}

// -------------------- setAutoUpdateProperties --------------------
void Action::setAutoUpdateProperties(bool autoUpdateProperties) {
    this->autoUpdateProperties = autoUpdateProperties;

    if (actionWidget != nullptr) {
        dynamic_cast<ActionWidget*>(actionWidget)->setAutoUpdateProperty(autoUpdateProperties);
    }
}

// -------------------- getProperty --------------------
Property* Action::getProperty(QString name) {
    return parameterMap.value(name);
}

// -------------------- getParameterValue --------------------
QVariant Action::getParameterValue(const QString& name) const {
    if (!parameterMap.contains(name)) {
        CAMITK_WARNING(tr("Parameter \"%1\" undeclared. Check the spelling.").arg(name))
        return QVariant(); // invalid QVariant
    }
    else {
        return property(name.toStdString().c_str());
    }
}

// -------------------- setParameterValue --------------------
bool Action::setParameterValue(const QString& name, QVariant newValue) {
    if (!parameterMap.contains(name)) {
        CAMITK_WARNING(tr("Parameter \"%1\" undeclared. Check the spelling. Cannot set value to %2").arg(name).arg(newValue.toString()))
        return false;
    }
    else {
        setProperty(name.toStdString().c_str(), newValue);
        return true;
    }
}

// -------------------- getParameterValueAsString --------------------
QString Action::getParameterValueAsString(const QString& name) const {
    return Property::getValueAsString(getParameterValue(name));
}

// ---------------------- addParameter ----------------------------
bool Action::addParameter(Property* prop) {
    // add a dynamic Qt Meta Object property with the same name
    bool returnStatus = setProperty(prop->getName().toStdString().c_str(), prop->getInitialValue());
    // add to the map
    parameterMap.insert(prop->getName(), prop);

    return returnStatus;
}

// ---------------------- toVariant ----------------------------
QVariant Action::toVariant() const {
    return PersistenceManager::fromProperties(this);
}

// ---------------------- fromVariant ----------------------------
void Action::fromVariant(const QVariant& variant) {
    PersistenceManager::loadProperties(this, variant);
}

// ---------------------- getUuid ----------------------------
QUuid Action::getUuid() const {
    return uuid;
}

// ---------------------- setUuid ----------------------------
bool Action::setUuid(QUuid id) {
    if (uuid.isNull()) {
        uuid = id;
        return true;
    }
    else {
        return false;
    }
}

} //namespace camitk
