/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2024 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 "ContourWidget.h"
#include "ContourWidgetVtkCommand.h"

// -- vtk stuff --
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderWindow.h>
#include <vtkOrientedGlyphContourRepresentation.h>
#include <vtkWidgetEventTranslator.h>
#include <vtkEvent.h>
#include <vtkBoundedPlanePointPlacer.h>
#include <vtkProperty.h>
#include <CamiTKReEnableWarnings>

#include <vtkContourWidget.h>
#include <vtkPolyData.h>

//-- Qt
#include <QBoxLayout>
#include <QFrame>

// -- Application --
#include <Application.h>
#include <InteractiveSliceViewer.h>
#include <Component.h>
#include <MeshComponent.h>
#include <ImageComponent.h>
#include <SingleImageComponent.h>

#include <Log.h>
#include <Property.h>
#include <RendererWidget.h>
#include <ActionWidget.h>

using namespace camitk;

// --------------- constructor -------------------
ContourWidget::ContourWidget(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Contour Widget");
    setDescription("Add a VTK Contour widget to a 2D viewer.<ul><li>Left Click: Add/select a point</li><li>Right Click: Add final point</li><li>Middle Click: Translate</li><li>Reset Contour: Start again</li><li>Close Contour: Either join start/end points or click the push button</li></ul>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    setFamily("Tutorial");
    addTag("Demo");
    addTag("Segmentation");
    addTag("2D Interaction");
    addTag("VTK Widget");

    setProperty("Contour Color", QVariant(QColor(250.0, 150.0, 50.0))); // color of the contour (default is orange)
    setProperty("Line Width", QVariant(2.0));

    Property* nbOfPointsProperty = new Property("Number Of Points", 0, "Number of points in the current contour", "");
    nbOfPointsProperty->setReadOnly(true);
    addParameter(nbOfPointsProperty);

    viewer = Axial;
    contourWidget = nullptr;
    contourWidgetCommand = nullptr;
    transformFromAxialToWorld = nullptr;
    recording = false;

    //-- widget lazy instantiation
    informationFrame = nullptr;

    //-- currently selected image
    currentImage = nullptr;
    currentMesh = nullptr;
}

// --------------- getViewer -------------------
ContourWidget::Viewer ContourWidget::getViewer() const {
    return viewer;
}

// --------------- setWiewer -------------------
void ContourWidget::setWiewer(const ContourWidget::Viewer viewer) {
    bool viewerChanged;
    viewerChanged = (this->viewer != viewer);

    this->viewer = viewer;

    if (viewerChanged) {
        initContour();
    }
}

// --------------- getWidget -------------------
QWidget* ContourWidget::getWidget() {
    // update image
    ImageComponent* selectedImage = dynamic_cast<ImageComponent*>(getTargets().last());

    // check if the current image is still the same
    if (selectedImage != currentImage) {
        // update image
        currentImage = selectedImage;
        // initialize the contour
        initContour();
    }

    // create widget
    if (!informationFrame) {
        //-- the frame
        informationFrame = new QFrame();
        informationFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
        informationFrame->setLineWidth(3);

        //-- the vertical layout, put every GUI elements in it
        auto* informationFrameLayout = new QVBoxLayout();

        // add the default action widget
        informationFrameLayout->addWidget(Action::getWidget());

        // add a reset button
        QPushButton* reset = new QPushButton("Reset Contour");
        informationFrameLayout->addWidget(reset);
        QObject::connect(reset, SIGNAL(released()), this, SLOT(initContour()));

        QPushButton* closeContour = new QPushButton("Close Contour");
        informationFrameLayout->addWidget(closeContour);
        QObject::connect(closeContour, SIGNAL(released()), this, SLOT(closeContour()));

        //-- set the layout for the action widget
        informationFrame->setLayout(informationFrameLayout);
    }

    return informationFrame;
}

// --------------- apply -------------------
Action::ApplyStatus ContourWidget::apply() {
    //-- update the contour
    if (contourWidget != nullptr) {
        updateContour();
    }

    //-- create/update the transformed mesh
    if (currentMesh == nullptr) {
        // create mesh for the first time
        QString meshName = "";
        if (currentImage != nullptr) {
            meshName = currentImage->getName();
        }
        if (transformFromAxialToWorld != nullptr) {
            currentMesh = new MeshComponent(transformFromAxialToWorld->GetOutput(), meshName + " Contour");
        }
        else {
            currentMesh = new MeshComponent(nullptr, meshName + " Contour");
        }
    }
    else {
        // update the current mesh (here you can also concatenate contours instead of replacing them)
        currentMesh->setPointSet(transformFromAxialToWorld->GetOutput());
    }

    //-- update color
    QColor contourColor = property("Contour Color").value<QColor>();
    currentMesh->setColor(contourColor.redF(), contourColor.greenF(), contourColor.blueF());

    //-- update 3D line width
    if (currentMesh->getActor(InterfaceGeometry::Surface) != nullptr) {
        currentMesh->getActor(InterfaceGeometry::Surface)->GetProperty()->SetLineWidth(property("Line Width").toFloat());
    }

    Application::refresh();

    return SUCCESS;
}

// --------------- initContour -------------------
void ContourWidget::initContour() {

    //-- if already present reset observers
    if (contourWidget) {
        contourWidget->RemoveObserver(contourWidgetCommand);
        contourWidget->Off();
        contourWidget = nullptr;
        contourWidgetCommand = nullptr;
        transformFromAxialToWorld = nullptr;
    }

    //-- create contour
    contourWidget = vtkSmartPointer<vtkContourWidget>::New();

    //-- set the interactor depending on the viewer chosen by the user
    // Also computes the z center = the correct position in the viewer so that the contour widget
    // can be seen on top of the other widgets (hence use the pixel actor position)
    vtkRenderWindowInteractor* interactor = nullptr;
    SingleImageComponent* viewedSIC = nullptr;
    double zCenter; // center of the image in the Z direction

    if (currentImage != nullptr && Application::getViewer("Axial Viewer") != nullptr) {
        if (viewer == Coronal) {
#if VTK_MAJOR_VERSION < 9
            interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Coronal Viewer"))->getRendererWidget()->GetRenderWindow()->GetInteractor();
#else
            interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Coronal Viewer"))->getRendererWidget()->renderWindow()->GetInteractor();
#endif
            viewedSIC = currentImage->getCoronalSlices();
        }
        else {
            if (viewer == Sagittal) {
#if VTK_MAJOR_VERSION < 9
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Sagittal Viewer"))->getRendererWidget()->GetRenderWindow()->GetInteractor();
#else
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Sagittal Viewer"))->getRendererWidget()->renderWindow()->GetInteractor();
#endif
                viewedSIC = currentImage->getSagittalSlices();
            }
            else {
#if VTK_MAJOR_VERSION < 9
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Axial Viewer"))->getRendererWidget()->GetRenderWindow()->GetInteractor();
#else
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Axial Viewer"))->getRendererWidget()->renderWindow()->GetInteractor();
#endif
                viewedSIC = currentImage->getAxialSlices();
            }
        }
    }
    contourWidget->SetInteractor(interactor);

    //-- create 3D representation
    contourWidget->CreateDefaultRepresentation();
    contourWidget->ContinuousDrawOn();
    contourWidget->On();

    //-- add the callback
    contourWidgetCommand = new ContourWidgetVtkCommand(this);
    contourWidget->AddObserver(vtkCommand::EndInteractionEvent, contourWidgetCommand);

    //-- move the representation forward in the z direction in order to see it in the interactive viewers
    if (viewedSIC != nullptr) {
        zCenter = viewedSIC->getPixelActor()->GetCenter()[2];
    }
    else {
        CAMITK_WARNING(tr("No current image or current image has no slice in the current plane (%1)").arg(QString::number(viewer)))
        zCenter = 0.0;
    }

    vtkSmartPointer<vtkOrientedGlyphContourRepresentation> contourRep = vtkOrientedGlyphContourRepresentation::SafeDownCast(contourWidget->GetRepresentation());
    vtkSmartPointer<vtkBoundedPlanePointPlacer> frontPlanePlacer = vtkSmartPointer<vtkBoundedPlanePointPlacer>::New();
    contourRep->SetPointPlacer(frontPlanePlacer);
    frontPlanePlacer->SetProjectionNormalToZAxis();
    frontPlanePlacer->SetProjectionPosition(zCenter);

    //-- init interactor
    if (interactor != nullptr) {
        interactor->ReInitialize();
    }

    //-- transform it relatively to the slice type
    // contourWidget -> GetRepresentation (== contourRep) => PolyData
    // + Transform from image frame to world
    // = 3D representation of the contour at the right place
    //
    // The transformation is required as the contour is a widget drawn in the z=0 plane
    // of the axial (or coronal or Sagittal) plane.
    //
    // It has to be transformed back to 3D using the current image frame
    transformFromAxialToWorld = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
    if (currentImage != nullptr) {
        transformFromAxialToWorld->SetTransform(currentImage->getTransformFromWorld());
    }
    else {
        vtkSmartPointer<vtkTransform> idTransform = vtkSmartPointer<vtkTransform>::New();
        transformFromAxialToWorld->SetTransform(idTransform);
    }
    transformFromAxialToWorld->SetInputData(contourRep->GetContourRepresentationAsPolyData());

    //-- update color and line width
    updateContour();

    Application::refresh();
}

// --------------- updateContour -------------------
void ContourWidget::updateContour() {
    // change color of the VTK widget representation and current mesh using the current user choice
    QColor contourColor = property("Contour Color").value<QColor>();

    vtkSmartPointer<vtkOrientedGlyphContourRepresentation> contourRep = vtkOrientedGlyphContourRepresentation::SafeDownCast(contourWidget->GetRepresentation());
    contourRep->GetLinesProperty()->SetColor(contourColor.redF(), contourColor.greenF(), contourColor.blueF());

    // change line width
    contourRep->GetLinesProperty()->SetLineWidth(property("Line Width").toFloat());

    // update the fransformation filter
    transformFromAxialToWorld->Update();
}

// --------------- closeContour -------------------
void ContourWidget::closeContour() {
    contourWidget->CloseLoop();
}

// --------------- updateWidget -------------------
void ContourWidget::updateWidget() {
    // update action's widget from modified property (this is not the "usual" way, normally
    // the user modify some property in the widget and the action gets the value to do
    // something. In this case, the action's property is modified by some external
    // mechanism (not the user), e.g. here the vtContourWidget, therefore the action's widget has
    // to be updated
    dynamic_cast<camitk::ActionWidget*>(Action::getWidget())->update();
}

