/*****************************************************************************
 * $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$
 ****************************************************************************/
// CamiTK includes
#include "RenderingOption.h"
#include <MeshComponent.h>
#include <Property.h>

//-- Vtk includes
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkDataSetMapper.h>
#include <CamiTKReEnableWarnings>

#include <vtkDataSetSurfaceFilter.h>
#include <vtkPolyDataNormals.h>
#include <vtkGlyph3D.h>
#include <vtkArrowSource.h>

using namespace camitk;

// --------------- constructor -------------------
RenderingOption::RenderingOption(ActionExtension* extension) : Action(extension) {
    setName("Rendering Option");
    setIcon(QPixmap(":/renderingOption"));
    setDescription("Change the rendering option (surface, wireframe, points, label...) of the selected mesh(es).");
    setComponentClassName("MeshComponent");
    setFamily("Basic Mesh");
    addTag("RenderingMode");
    addTag("Label");


    Property* surfaceProperty = new Property(tr("Surface representation?"), false, tr("Allow the selected mesh(es) to be representated as a surface?"), "");
    addParameter(surfaceProperty);

    Property* wireframeProperty = new Property(tr("Wireframe representation?"), false, tr("Allow the selected mesh(es) to be representated as wireframes?"), "");
    addParameter(wireframeProperty);

    Property* pointsProperty = new Property(tr("Points representation?"), false, tr("Allow the selected mesh(es) to be representated as with points?"), "");
    addParameter(pointsProperty);

    Property* labelProperty = new Property(tr("Label representation?"), false, tr("Allow the selected mesh(es) to be representated with its label?"), "");
    addParameter(labelProperty);

    Property* glyphProperty = new Property(tr("Glyph representation?"), false, tr("Allow the selected mesh(es) to have glyphs representation?"), "");
    addParameter(glyphProperty);

    Property* normalProperty = new Property(tr("Normals representation?"), false, tr("Allow the selected mesh(es) to be representated with its normals?"), "");
    addParameter(normalProperty);
}

RenderingOption::~RenderingOption() {
    // do not delete the widget has it might have been used in the ActionViewer (i.e. the ownership might have been taken by the stacked widget)
}

// --------------- getWidget -------------------
QWidget* RenderingOption::getWidget() {

    //-- init the property values using the selection state
    if (getTargets().size() == 1) {
        // only one selected, easy!
        setParameterValue("Surface representation?", (bool)(getTargets().first()->getRenderingModes() & InterfaceGeometry::Surface));
        setParameterValue("Wireframe representation?", (bool)(getTargets().first()->getRenderingModes() & InterfaceGeometry::Wireframe));
        setParameterValue("Points representation?", (bool)(getTargets().first()->getRenderingModes() & InterfaceGeometry::Points));
    }
    else {
        // more than one: they should all have the same rendering mode to set a proper state
        InterfaceGeometry::RenderingModes m = getTargets().first()->getRenderingModes();
        int i = 1;

        while (i < getTargets().size() && (m == getTargets()[i]->getRenderingModes())) {
            i++;
        }

        if (i == getTargets().size()) {
            setParameterValue("Surface representation?", (bool)(m & InterfaceGeometry::Surface));
            setParameterValue("Wireframe representation?", (bool)(m & InterfaceGeometry::Wireframe));
            setParameterValue("Points representation?", (bool)(m & InterfaceGeometry::Points));
        }
    }

    // additional prop visibility
    if (getTargets().size() > 0) {
        setParameterValue("Glyph representation?", (bool)(getTargets().first()->getProp("glyph")->GetVisibility()));
        setParameterValue("Label representation?", (bool)(getTargets().first()->getProp("label")->GetVisibility()));
        // normal is an extra prop build by this action if needed, check if it exists first)
        setParameterValue("Normals representation?", getTargets().first()->getProp("normals") != nullptr && (bool)(getTargets().first()->getProp("normals")->GetVisibility()));
    }

    return Action::getWidget();
};


// --------------- apply -------------------
Action::ApplyStatus RenderingOption::apply() {
    // get the current rendering mode from the property values
    InterfaceGeometry::RenderingModes m = InterfaceGeometry::None;

    if (getParameterValue("Surface representation?").toBool()) {
        m |= InterfaceGeometry::Surface;
    }

    if (getParameterValue("Wireframe representation?").toBool()) {
        m |= InterfaceGeometry::Wireframe;
    }

    if (getParameterValue("Points representation?").toBool()) {
        m |= InterfaceGeometry::Points;
    }

    // update the rendering mode of selected
    for (Component* comp : getTargets()) {
        comp->setRenderingModes(m);
        // set the additional prop visibility
        comp->getProp("glyph")->SetVisibility(getParameterValue("Glyph representation?").toBool());
        comp->getProp("label")->SetVisibility(getParameterValue("Label representation?").toBool());

        // normal is a specific prop, create it only if needed
        if (comp->getProp("normals") == nullptr) {
            if (getParameterValue("Normals representation?").toBool()) {

                //-- get the surface (in order to have polygon only)
                vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter = vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
                surfaceFilter->SetInputData(comp->getPointSet());
                surfaceFilter->Update();

                //-- generate the normals
                vtkSmartPointer<vtkPolyDataNormals> normalGenerator = vtkSmartPointer<vtkPolyDataNormals>::New();
                normalGenerator->SetInputConnection(surfaceFilter->GetOutputPort());
                normalGenerator->ComputePointNormalsOn();
                normalGenerator->ComputeCellNormalsOff();
                normalGenerator->SplittingOff();
                normalGenerator->Update();

                //-- create the arrow glyph for the surface normal
                vtkSmartPointer<vtkArrowSource> arrowSource = vtkSmartPointer<vtkArrowSource>::New();
                vtkSmartPointer<vtkGlyph3D> arrowGlyph = vtkSmartPointer<vtkGlyph3D>::New();
                arrowGlyph->ScalingOn();
                arrowGlyph->SetScaleFactor(comp->getBoundingRadius() / 10.0);
                arrowGlyph->SetVectorModeToUseNormal();
                arrowGlyph->SetScaleModeToScaleByVector();
                arrowGlyph->OrientOn();
                arrowGlyph->SetSourceConnection(arrowSource->GetOutputPort());
                arrowGlyph->SetInputConnection(normalGenerator->GetOutputPort());

                vtkSmartPointer<vtkDataSetMapper> normalMapper = vtkSmartPointer<vtkDataSetMapper>::New();
                normalMapper->SetInputConnection(arrowGlyph->GetOutputPort());

                vtkSmartPointer<vtkActor> normalActor = vtkSmartPointer<vtkActor>::New();
                normalActor->SetMapper(normalMapper);

                //-- add the prop to the Component
                comp->addProp("normals", normalActor);
                comp->getProp("normals")->SetVisibility(true);
            }
            // else: nothing todo there is no normal to display anyway
        }
        else {
            // check weither to show the normals or no
            comp->getProp("normals")->SetVisibility(getParameterValue("Normals representation?").toBool());
        }


    }

    // refresh the viewers (here only the viewer of the last component, but should be ok)
    getTargets().last()->refresh();

    return SUCCESS;

}
