/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2016 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 "Log.h"
#include "HistoryItem.h"
#include "Property.h"

#include <QHBoxLayout>
#include <QGroupBox>
#include <QMessageBox>

namespace camitk {

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

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

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

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

    // 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 )
                ActionViewer::getActionViewer()->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 {
        QMessageBox::information ( parent, "Cannot apply this Action to selection", "Sorry, but the action \"" + getName() + "\" cannot be applied on the 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() ) {
        status = apply();
        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() ) {
        status = apply();
        postProcessInPipeline();
    } else
        status = Action::ABORTED;

    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 NULL;
    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::getDefaultTargetPositionningMethod());
}

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


}













