/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * 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 "ActionViewer.h"
#include "Application.h"
#include "Component.h"
#include "ActionWidget.h"
#include "HistoryItem.h"
#include "PropertyObject.h"
#include "Log.h"

namespace camitk {

// -------------------- constructor --------------------
Action::Action(ActionExtension* extension) : QObject() {
    this->extension = extension;
    this->component = "";
    qAction = nullptr;
    isEmbedded = true;
    actionWidget = nullptr;
    autoUpdateProperties = false;
    item = nullptr;
}

// -------------------- destructor --------------------
Action::~Action() {
    // delete all properties
    foreach (Property* prop, parameterMap.values()) {
        delete prop;
    }
    parameterMap.clear();

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

// -------------------- 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;
}

// -------------------- setComponent --------------------
void Action::setComponent(QString component) {
    this->component = component;
}

// -------------------- 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();
}

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

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

// -------------------- getWidget --------------------
QWidget* Action::getWidget() {
    // build or update the widget
    if (!actionWidget) {
        // 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);

    return actionWidget;
}


// -------------------- getQAction --------------------
QAction* Action::getQAction() {
    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) {
    //-- build the list of valid targets
    targetComponents.clear();
    ComponentList selComp = Application::getSelectedComponents();

    foreach (Component* comp, selComp) {
        // check compatibility
        if (comp->isInstanceOf(this->getComponent())) {
            targetComponents.append(comp);
        }
    }

    //-- if there are some valid targets or if the action is generic
    if (targetComponents.size() > 0 || getComponent().isEmpty()) {
        if (isEmbedded) {
            // if there are no parents to use use the action viewer
            if (parent == nullptr) {
                ActionViewer::getInstance()->embedActionWidget(this);
            }
            else {
                getWidget()->setParent(parent);
                getWidget()->show();
            }
        }
        else {
            QWidget* w = getWidget();

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

        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 || getComponent().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 || getComponent().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();
    foreach (Component* comp, inputs) {
        // check compatibility
        if (comp->isInstanceOf(this->getComponent())) {
            targetComponents.append(comp);
        }
    }
}

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

    // check compatibility
    if (input->isInstanceOf(this->getComponent())) {
        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->item = 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;
    foreach (Component* comp, this->aliveBeforeComponents) {
        if (comp->isSelected()) {
            this->topLevelSelectedComponents.append(comp);
            topLevelSelectedHistoryComponents.append(HistoryComponent(comp));
        }
    }

    // Save the input components information
    this->item->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();
    foreach (Component* comp, Application::getTopLevelComponents()) {
        if (!this->aliveBeforeComponents.contains(comp)) {
            topLevelComponentCreated.append(comp);
            topLevelHistoryComponentCreated.append(HistoryComponent(comp));
        }
    }

    // Save the output components information
    item->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
    foreach (QByteArray propertyName, dynamicPropertyNames()) {
        item->addProperty(propertyName, property(propertyName));
    }

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


// -------------------- postProcessInPipeline --------------------
void Action::postProcessInPipeline() {
    outputComponents.clear();
    ComponentList allComp = Application::getAllComponents();
    foreach (Component* comp, allComp) {
        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) {
        dynamic_cast<ActionWidget*>(actionWidget)->setAutoUpdateProperty(autoUpdateProperties);
    }
}

// -------------------- getProperty --------------------
Property* Action::getProperty(QString name) {
    return parameterMap.value(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;
}

// ---------------------- applyTargetPosition ----------------------------
void Action::applyTargetPosition(Component* input, Component* target) {
    applyTargetPosition(input, target, (Application::TargetPositionningPolicy) Application::getPropertyObject()->getPropertyValue("Target Positionning Policy").toInt());
}

// ---------------------- applyTargetPosition ----------------------------
void Action::applyTargetPosition(Component* input, Component* target, Application::TargetPositionningPolicy policy) {
    switch (policy) {
        case Application::NO_TRANSFORMATION:
            target->setParentFrame(nullptr);
            break;
        case Application::SUBFRAME:
            target->setParentFrame(nullptr); // reset the frame transform to Identity
            target->setParentFrame(input);
            break;
        case Application::SAME_TRANSFORMATION:
        default:
            target->setParentFrame(nullptr);
            target->setTransform(input->getTransform());
            break;
    }
}


}













