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

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

// -- Qt stuff
#include <QPushButton>
#include <QMetaProperty>

#include <Log.h>


using namespace camitk;

// ------------- constructor -----------------
ActionState::ActionState(QState* parent, QString name, QString description, QTextStream* logStream)
    : QState(parent) {
    this->name = name;
    this->description = description;
    this->logStream = logStream;
    this->myAction = nullptr;
    this->myStateWidget = new ActionStateWidget(this);
}

// ------------- setAction -----------------
void ActionState::setAction(Action* action, QMap<QString, QVariant> parameters, QMap<QString, QString> inputComponentNames, QMap<QString, QString> outputComponentNames) {
    this->myAction = action;
    this->parameters = parameters;
    this->inputComponentNames  = inputComponentNames;
    this->outputComponentNames = outputComponentNames;

    // auto update properties by default
    if (myAction) {
        myAction->setAutoUpdateProperties(true);
    }
}

// ------------- getName -----------------
QString ActionState::getName() {
    return name;
}

// ------------- getDescription -----------------
QString ActionState::getDescription() {
    return description;
}

// ------------- getWidget -----------------
ActionStateWidget* ActionState::getWidget() {
    return myStateWidget;
}

// ------------- addActionTransition -----------------
ActionTransition* ActionState::addActionTransition(QString transitionName, QAbstractState* nextState, bool applyAction, QVector<Action::ApplyStatus> disableConditions) {

    QPushButton* button = myStateWidget->addTransitionButton(transitionName);
    ActionTransition* transition = new ActionTransition(button, SIGNAL(clicked()), this, applyAction, logStream);
    transition->setTargetState(nextState);

    for (QVector<Action::ApplyStatus>::const_iterator it = disableConditions.constBegin();
            it != disableConditions.constEnd(); ++it) {
        Action::ApplyStatus status = (*it);
        QMap<Action::ApplyStatus, QVector<QPushButton* > >::iterator buts = conditionalButtons.find(status);

        if (buts != conditionalButtons.end()) {
            buts.value().append(button);
        }
        else {
            QVector<QPushButton*> vect;
            vect.append(button);
            conditionalButtons.insert(status, vect);
        }
    }

    return transition;

}

// ------------- applyAction -----------------
Action::ApplyStatus ActionState::applyAction() {
    Action::ApplyStatus status(Action::TRIGGERED);

    if (myAction != nullptr) {
        if (logStream != nullptr) {
            (*logStream) << "\t\t<applyAction>" << Qt::endl;
        }

        // apply the action
        status = myAction->applyInPipeline();

        // set the right names to outputComponents
        ComponentList outputComps = myAction->getOutputComponents();
        ComponentList::const_iterator outIt;
        QMap<QString, QString>::const_iterator namesIt;
        namesIt = outputComponentNames.constBegin();

        if ((logStream != nullptr) && (outputComps.size() > 0)) {
            (*logStream) << "\t\t\t<outputs>" << Qt::endl;
        }

        for (outIt = outputComps.constBegin(); outIt != outputComps.constEnd(); ++outIt) {
            if (namesIt != outputComponentNames.constEnd()) {
                QString compName = namesIt.key();
                QString compType = namesIt.value();

                if ((*outIt)->isInstanceOf(compType)) {
                    (*outIt)->setName(compName);
                    ++namesIt;

                    if (logStream != nullptr) {
                        (*logStream) << "\t\t\t\t<component name='" ;
                        (*logStream) << compName << "' type='" << compType << "'/>" << Qt::endl;
                    }
                }
            }
        }

        if ((logStream != nullptr) && (outputComps.size() > 0)) {
            (*logStream) << "\t\t\t</outputs>" << Qt::endl;
        }

        Application::refresh();

        if (logStream != nullptr) {
            (*logStream) << "\t\t\t<status>" << Action::getStatusAsString(status) << "</status>" << Qt::endl;
            (*logStream) << "\t\t</applyAction>" << Qt::endl;
        }
    }

    return status;
}

// ------------- setAlternativeDesc -----------------
void ActionState::setAlternativeDesc(QString altDescText, QVector<Action::ApplyStatus> statusList) {
    for (QVector<Action::ApplyStatus>::const_iterator it = statusList.constBegin();
            it != statusList.constEnd(); ++it) {
        Action::ApplyStatus status = (*it);
        conditionalDescriptions.insert(status, altDescText);
    }

}

// ------------- setPreviousActionStatus -----------------
void ActionState::setPreviousActionStatus(camitk::Action::ApplyStatus status) {
    // Change the description
    QMap<Action::ApplyStatus, QString>::const_iterator desc = conditionalDescriptions.constFind(status);

    if (desc != conditionalDescriptions.constEnd()) {
        myStateWidget->setDescription(desc.value());
    }
    else {
        myStateWidget->setDescription(description);
    }

    // Inhibate/Re-enable proper buttons
    for (QMap<Action::ApplyStatus, QVector<QPushButton* > >::const_iterator it = conditionalButtons.constBegin();
            it != conditionalButtons.constEnd(); ++it) {
        Action::ApplyStatus itStatus = it.key();
        QVector<QPushButton*> itButtons = it.value();

        for (QVector<QPushButton*>::const_iterator but = itButtons.constBegin(); but != itButtons.constEnd(); ++but) {
            if (itStatus == status) {
                (*but)->setEnabled(false);
            }
            else {
                (*but)->setEnabled(true);
            }
        }
    }
}

// ------------- autoNextEntry -----------------
void ActionState::autoNextEntry() {
    onEntry(new QEvent(QEvent::ApplicationStateChange));
}

// ------------- onEntry -----------------
void ActionState::onEntry(QEvent* event) {

    if (logStream != nullptr) {
        //-- log the start
        actionTimer = QTime::currentTime();

        (*logStream) << "\t<state>" << Qt::endl;
        (*logStream) << "\t\t<name>" << this->name << "</name>" << Qt::endl;
        (*logStream) << "\t\t<startTime>" << actionTimer.toString("hh:mm:ss:zzz") << "</startTime>" << Qt::endl;

        //-- prepare the action and GUI
        if (myAction != nullptr) {
            // log the action entry
            (*logStream) << "\t\t<actionName>" << myAction->getName() << "</actionName>" << Qt::endl;

            //-- set the input components
            ComponentList allComps = Application::getAllComponents();
            QMap<QString, QString>::const_iterator namesIt;
            ComponentList::const_iterator compIt;

            // log inputs
            if (inputComponentNames.size() > 0) {
                (*logStream) << "\t\t<inputs>" << Qt::endl;
            }

            // search for proper input considering name and type
            ComponentList inputComps;

            for (namesIt = inputComponentNames.constBegin(); namesIt != inputComponentNames.constEnd(); ++namesIt) {
                QString compName = namesIt.key();
                QString compType = namesIt.value();

                // Look for the corresponding component into the list of all components
                compIt = allComps.constBegin();

                while ((compIt != allComps.constEnd()) && !((*compIt)->getName() == compName && (*compIt)->isInstanceOf(compType))) {
                    ++compIt;
                }

                if (compIt != allComps.constEnd()) {
                    (*logStream) << "\t\t\t<component name='";
                    (*logStream) << (*compIt)->getName() << "' type='" << compType << "'/>" << Qt::endl;
                    inputComps.append((*compIt));
                }
            }

            if (inputComponentNames.size() > 0) {
                (*logStream) << "\t\t</inputs>" << Qt::endl;
            }

            myAction->setInputComponents(inputComps);

            //-- reset modification flags on input components to avoid mismatching output component
            for (Component* inputComponent : inputComps) {
                inputComponent->setModified(false);
            }

            //-- set the widget if there is a CamiTK action
            // Note: call to getWidget should be done _before_ setting the parameters values, as sometimes
            // parameters are re-initialized in the getWidget action depending on the currently selected component(s)
            // E.g. in the "Manual Threshold Filter" action, getWidget() re-compute
            // the "Low Threshold" and "High Threshold" values, min/max and steps depending on
            // the voxel values of the currently selected target component image
            ActionWidget* actionWidget = dynamic_cast<ActionWidget*>(this->myAction->getWidget());

            // if the Action has a widget inheriting from ActionWidget, force the buttons' visibility to false
            if (actionWidget != nullptr) {
                actionWidget->setButtonVisibility(false);
            }

            // set the widget
            this->myStateWidget->setActionWidget(actionWidget);

            //-- set the parameters values
            for (QMap<QString, QVariant>::const_iterator it = parameters.constBegin(); it != parameters.constEnd(); ++it) {
                myAction->setProperty(it.key().toStdString().c_str(), it.value().toString());
            }

            // Properties
            int nbStaticProps = myAction->metaObject()->propertyCount();
            QList<QByteArray> propertyNames = myAction->dynamicPropertyNames();

            // static properties
            if ((nbStaticProps > 0) || (! propertyNames.isEmpty())) {
                (*logStream) << "\t\t<parameters>" << Qt::endl;

                for (int index = 0 ; index < nbStaticProps; index++) {
                    QString staticPropName = myAction->metaObject()->property(index).name();

                    if (staticPropName != "objectName") {
                        (*logStream) << "\t\t\t<parameter name='";
                        (*logStream) << staticPropName << "' value='";
                        (*logStream) << myAction->property(staticPropName.toStdString().c_str()).toString() ;
                        (*logStream) << "' static='true'/>" << Qt::endl;
                    }
                }

                // dynamic properties
                for (QList<QByteArray>::const_iterator it = propertyNames.constBegin(); it != propertyNames.constEnd(); ++it) {
                    (*logStream) << "\t\t\t<parameter name='";
                    (*logStream) << (*it) << "' value='" << myAction->property(*it).toString() << "'/>" << Qt::endl;
                }

                (*logStream) << "\t\t</parameters>" << Qt::endl;
            }
        }
    }
}

// ------------- autoNextExit -----------------
void ActionState::autoNextExit() {
    onExit(new QEvent(QEvent::ApplicationStateChange));
}

// -------------onExit -----------------
void ActionState::onExit(QEvent* event) {
    if (logStream != nullptr) {
        QTime endTime = QTime::currentTime();
        (*logStream) << "\t\t<!-- exiting " << this->name << " -->" << Qt::endl;
        (*logStream) << "\t\t<endTime>" << endTime.toString("hh:mm:ss:zzz") << "</endTime>" << Qt::endl;
        (*logStream) << "\t\t<timeElapsed unit='ms'>" << actionTimer.msecsTo(QTime::currentTime()) << "</timeElapsed>" << Qt::endl;
        (*logStream) << "\t</state>" << Qt::endl;
    }
}

// ------------- getInputComponents -----------------
const QMap< QString, QString >& ActionState::getInputComponents() {
    return inputComponentNames;
}

// ------------- getOutputComponents -----------------
const QMap< QString, QString >& ActionState::getOutputComponents() {
    return outputComponentNames;
}
