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

// CamiTK
#include <Application.h>
#include <Property.h>
#include <Log.h>

// Qt
#include <QVector3D>

using namespace camitk;


// --------------- Constructor -------------------
HardCodedPipelineAction::HardCodedPipelineAction(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName(tr("Hard-Coded Action Pipeline"));
    setDescription("Demonstrate how to call one or more actions from another action (that is, implement a hard-coded action pipeline without modifying anything of the called actions).");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    setFamily(tr("Tutorial"));
    addTag(tr("Pipeline"));

    // Setting the action's parameters
    addParameter(new Property(tr("Resample Factor"), QVariant(0.5), tr("The resample factor to use when applying the Resample action"), tr("factor")));

}

// --------------- destructor -------------------
HardCodedPipelineAction::~HardCodedPipelineAction() {
    // Do not do anything yet.
    // Delete stuff if you create stuff
    // (except if you use smart pointers of course !!)
}

// --------------- apply -------------------
Action::ApplyStatus HardCodedPipelineAction::apply() {

    PipelineActionStatus pipelineStatus = PipelineActionStatus::OK;

    //
    // Hard-coded Pipeline starts here
    // 1. apply Resample action, and check that everything is ok
    // 2. apply Otsu threshold
    // if everything is alright, remove the intermediate results and force the application refresh

    // 1. apply the resample action (first action in the pipeline) on the last selected component
    pipelineStatus = applyResample(getTargets().last());

    // If the image has been saved, then it is not modified any more...
    if (pipelineStatus == OK) {
        // You can most of the time safely ignore the safeguard bits below and simply use:
        // Component* resampled = resampleAction->getOutputComponent()
        // But in some rare case, the second action of this pipeline is not going to use the correct
        // input. For these cases, you need to use:
        Component* resampled = getLastTopLevelOutputComponents("Resample");

        // 2. apply the Otsu action (second action in the pipeline)
        pipelineStatus = applyOtsu(resampled);

        if (pipelineStatus == OK) {
            // if all goes well, don't show the intermediate result
            delete resampled;
        }
    }

    switch (pipelineStatus) {
        case OTSU_APPLY_ERROR:
            CAMITK_ERROR("\"Otsu Threshold Filter\" apply error");
            break;
        case OTSU_NOT_FOUND:
            CAMITK_ERROR("\"Otsu Threshold Filter\" action not found");
            break;
        case RESAMPLE_APPLY_ERROR:
            CAMITK_ERROR("\"Resample Factor\" apply error.");
            break;
        case RESAMPLE_NOT_FOUND:
            CAMITK_ERROR("\"Resample Factor\" action not found");
            break;
        case OK:
            CAMITK_TRACE("Pipeline successful");
            break;
    }

    refreshApplication();

    return ((pipelineStatus == OK) ? SUCCESS : ERROR);
}

// --------------- getLastTopLevelOutputComponents -------------------
Component* HardCodedPipelineAction::getLastTopLevelOutputComponents(const QString& actionName) {
    Component* lastOutputComponent = nullptr;

    Action* currentAction = Application::getAction(actionName);

    if (currentAction != nullptr) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
        // it Qt >= 5.6 crbegin() exist and we can use a lambda expression to find the first
        // top level component, looking from the end to the beginning of the component list.
        auto it = find_if(currentAction->getOutputComponents().crbegin(),
                          currentAction->getOutputComponents().crend(),
        [](const Component * c) {
            return c->isTopLevel();
        });
        // This is the equivalent of:
        // ComponentList::reverse_iterator it = currentAction->getOutputComponents().rbegin();
        // while (it != currentAction->getOutputComponents().rend() && !(*it)->isTopLevel()) {
        //    it++;
        // }
        lastOutputComponent = (*it);
#else
        // In Qt < 5.6, too bad: crbegin(), crend(), rbegin() and rend() do not exist
        unsigned int i = currentAction->getOutputComponents().size() - 1;
        while (i > 0 && !currentAction->getOutputComponents().at(i)->isTopLevel()) {
            i--;
        }
        if (i >= 0) {
            lastOutputComponent = currentAction->getOutputComponents().at(i);
        }
#endif
    }

    return lastOutputComponent;
}

// --------------- applyResample -------------------
HardCodedPipelineAction::PipelineActionStatus HardCodedPipelineAction::applyResample(Component* input) {
    Action* resampleAction = Application::getAction("Resample");

    if (resampleAction == nullptr) {
        return RESAMPLE_NOT_FOUND;
    }
    else {
        // set the input
        resampleAction->setInputComponent(input);

        // set the parameter
        ImageComponent* inputImage = dynamic_cast<ImageComponent*>(input);
        // Note: as Resample action takes ImageComponent as inputs, the dynamic_cast is
        // guaranty to work perfectly well.
        int* dims = inputImage->getImageData()->GetDimensions();
        double factor = getParameterValue("Resample Factor").toDouble();
        resampleAction->setParameterValue("New Image X Dimension", int(dims[0]*factor));
        resampleAction->setParameterValue("New Image Y Dimension", int(dims[1]*factor));
        resampleAction->setParameterValue("New Image Z Dimension", std::max(1, int(dims[2]*factor)));

        // apply the action
        Action::ApplyStatus status = resampleAction->applyInPipeline();

        // reset the flag (just in case)
        input->setModified(false);

        return (status == Action::SUCCESS) ? OK : RESAMPLE_APPLY_ERROR;
    }


}

// --------------- applyOtsu -------------------
HardCodedPipelineAction::PipelineActionStatus HardCodedPipelineAction::applyOtsu(Component* input) {
    Action* otsuThreshold = Application::getAction("Otsu Threshold Filter");
    if (otsuThreshold == nullptr) {
        return OTSU_NOT_FOUND;
    }
    else {
        // set the input
        otsuThreshold->setInputComponent(input);

        // nothing special to do on the action parameters

        // apply the action
        Action::ApplyStatus status = otsuThreshold->applyInPipeline();

        return (status == Action::SUCCESS) ? OK : OTSU_APPLY_ERROR;
    }
}

