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

// includes from CamiTK
#include <Application.h>
#include <Property.h>
#include <MedicalImageViewer.h>
#include <InteractiveViewer.h>
#include <ImageComponent.h>
#include <Log.h>

// to get the enum as a string
#include <QMetaEnum>

#include "AnglesAndTranslationWidget.h"

using namespace camitk;

// --------------- Constructor -------------------
AnglesAndTranslationAction::AnglesAndTranslationAction(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Arbitrary Slice");
    setDescription(tr("This action allows user to manually modify the orientation and translation of the arbitrary slice."));
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    setFamily("View");
    addTag(tr("arbitrary slice"));
    addTag(tr("arbitrary"));
    addTag(tr("modify angles"));
    addTag(tr("angle"));
    setEmbedded(true);

    currentImageComp = nullptr;
    blockEvent = true;

    // the AnglesAndTranslationWidget is going to modify these parameters using the GUI
    // but defining them as the action parameters also allows one to set them programmatically
    // (for instance using another GUI or in a pipeline)
    // Setting parameters default values by using properties
    Property* actionParameter = new Property("Translation", 50.0, tr("Current translation inside the image"), "");
    actionParameter->setAttribute("minimum", 0.0);
    actionParameter->setAttribute("maximum", 100.0);
    addParameter(actionParameter);

    actionParameter = new Property("X Angle", 0, tr("X Angle"), "degree");
    actionParameter->setAttribute("minimum", 0);
    actionParameter->setAttribute("maximum", 360);
    addParameter(actionParameter);

    actionParameter = new Property("Y Angle", 0, tr("Y Angle"), "degree");
    actionParameter->setAttribute("minimum", 0);
    actionParameter->setAttribute("maximum", 360);
    addParameter(actionParameter);

    actionParameter = new Property("Z Angle", 0, tr("Z Angle"), "degree");
    actionParameter->setAttribute("minimum", 0);
    actionParameter->setAttribute("maximum", 360);
    addParameter(actionParameter);

    actionParameter = new Property("Visible Viewer", MedicalImageViewer::VIEWER_ARBITRARY, "Visible viewer in the main window", "");
    // To set the enum type, use the same name of the enum as declared in the class
    actionParameter->setEnumTypeName("LayoutVisibility");
    // Add the property as an action parameter
    addParameter(actionParameter);

    // be notified automatically when the parameters change
    setAutoUpdateProperties(true);

    blockEvent = false;
}

// --------------- destructor -------------------
AnglesAndTranslationAction::~AnglesAndTranslationAction() {
    delete actionWidget;
}

// --------------- getWidget -------------------
QWidget* AnglesAndTranslationAction::getWidget() {
    if (!actionWidget) {
        actionWidget = new AnglesAndTranslationWidget(this);
        // update the translation and corresponding UI whenever the arbitrary viewer slider changes
        QObject::connect(Application::getViewer("Arbitrary Viewer"), SIGNAL(selectionChanged()), this, SLOT(updateTranslation()));
    }

    // update the pointer
    camitk::ArbitrarySingleImageComponent* previousImageComp = currentImageComp;
    currentImageComp = dynamic_cast<ImageComponent*>(getTargets().last())->getArbitrarySlices();

    if (currentImageComp == nullptr) {
        CAMITK_WARNING(tr("Arbitrary Slice manipulation is not possible on 2D images. Aborting."))
        return nullptr;
    }
    else {
        // make sure the arbitrary slice is visible in 3D
        currentImageComp->setVisibility("3D Viewer", true);

        // If the image just changed, reset the arbitrary transformation to the previous value or to 0
        if (currentImageComp != previousImageComp) {
            // We need to store these properties because setParameterValue may change them
            QVector3D rotation = currentImageComp->getPropertyValue("Rotation").value<QVector3D>();
            QVariant translation = currentImageComp->getPropertyValue("Translation");

            // All three angles are updated by a parameter change event, block until the last one
            blockEvent = true;
            setParameterValue("X Angle", rotation.x());
            setParameterValue("Y Angle", rotation.y());
            blockEvent = false;
            setParameterValue("Z Angle", rotation.z());
            setParameterValue("Translation", translation.toDouble() * 100.0);

        }
        // update the properties
        update();
        return actionWidget;
    }
}

// ---------------------- update ----------------------------
void AnglesAndTranslationAction::update() {
    // update current action properties safely (i.e. without event(..) to be doing anything on the component)
    blockEvent = true;

    // current translation along the z axis of the arbitrary slice
    setParameterValue("Translation", currentImageComp->getPropertyValue("Translation").toDouble() * 100.0);

    // update widget
    dynamic_cast<AnglesAndTranslationWidget*>(actionWidget)->updateGUI();

    // update central viewer
    MedicalImageViewer::LayoutVisibility visibleViewer = static_cast<MedicalImageViewer::LayoutVisibility>(getParameterValue("Visible Viewer").toInt());

    MedicalImageViewer* medicalImageViewer = dynamic_cast<MedicalImageViewer*>(Application::getViewer("Medical Image Viewer"));
    if (medicalImageViewer != nullptr) {
        medicalImageViewer->setVisibleViewer(visibleViewer);
    }

    // keep property up-to-date when the GUI will change
    blockEvent = false;
}

// ---------------------- resetTransform ----------------------------
void AnglesAndTranslationAction::resetTransform() {
    currentImageComp->resetTransform();

    // Reset the angle to zero
    blockEvent = true;
    QVector3D rotation = currentImageComp->getPropertyValue("Rotation").value<QVector3D>();
    setParameterValue("X Angle", rotation.x());
    setParameterValue("Y Angle", rotation.y());
    setParameterValue("Z Angle", rotation.z());
    blockEvent = false;

    update();
    currentImageComp->refresh();
}

// ---------------------- event ----------------------------
bool AnglesAndTranslationAction::event(QEvent* e) {
    if (currentImageComp == nullptr || blockEvent) {
        return QObject::event(e);
    }

    if (e->type() == QEvent::DynamicPropertyChange) {
        e->accept();
        QDynamicPropertyChangeEvent* changeEvent = dynamic_cast<QDynamicPropertyChangeEvent*>(e);

        if (!changeEvent) {
            return false;
        }

        // do something depending of the property that has changed
        if (changeEvent->propertyName() != "Visible Viewer") {
            // only translation and rotation angle are of interest here (the "Visible Viewer" changes will automatically be
            // taken into account in the update() method
            if (changeEvent->propertyName() == "Translation") {
                // Setting Translation property
                currentImageComp->setPropertyValue("Translation", getParameterValue("Translation").toDouble() / 100.0);
            }
            else {
                // else the propertyName() is either "X Angle", "Y Angle" or "Z Angle" which also match the arbitrary component property names
                // Setting Rotation property
                currentImageComp->setPropertyValue("Rotation",
                                                   QVector3D(getParameterValue("X Angle").toInt(),
                                                           getParameterValue("Y Angle").toInt(),
                                                           getParameterValue("Z Angle").toInt()));
            }
        }

        // needed as rotation might change the translation value or translation might have been rejected
        update();

        // trick to force an emit selectionChanged() when the angle and translation has changed
        dynamic_cast<InteractiveViewer*>(Application::getViewer("Arbitrary Viewer"))->sliderChanged(currentImageComp->getSlice());

        // refresh component's viewer
        currentImageComp->refresh();

        return true;
    }

    // this is important to continue the process if the event is a different one
    return QObject::event(e);
}

// ---------------------- updateTranslationSlider ----------------------------
void AnglesAndTranslationAction::updateTranslation() {
    // update the translation properties safely (i.e. without event(..) to be doing anything on the component)
    blockEvent = true;

    // current translation along the z axis of the arbitrary slice
    setParameterValue("Translation", currentImageComp->getPropertyValue("Translation").toDouble() * 100.0);

    // update widget
    dynamic_cast<AnglesAndTranslationWidget*>(actionWidget)->updateGUI();

    blockEvent = false;
}
