// SPDX-License-Identifier: LGPL-2.1-or-later

/***************************************************************************
 *   Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include <BRep_Tool.hxx>
#include <Geom_Curve.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/events/SoLocation2Event.h>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/nodes/SoComplexity.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoLineSet.h>
#include <Inventor/nodes/SoLocateHighlight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoTransform.h>


#include <App/Application.h>
#include <Base/Console.h>
#include <Base/Parameter.h>
#include <Gui/Selection/SoFCSelection.h>
#include <Gui/View3DInventorViewer.h>

#include "ViewProviderCurveNet.h"


using namespace PartGui;

//**************************************************************************
// Construction/Destruction

PROPERTY_SOURCE(PartGui::ViewProviderCurveNet, PartGui::ViewProviderPart)


ViewProviderCurveNet::ViewProviderCurveNet()
{
    LineWidth.setValue(4.0f);
    PointSize.setValue(0.05f);
    /*
    hGrp = App::GetApplication().GetParameterGroupByPath("User
    parameter:BaseApp/Preferences/Mod/Part");

    fMeshDeviation      = hGrp->GetFloat("MeshDeviation",0.2);
    bNoPerVertexNormals = hGrp->GetBool("NoPerVertexNormals",false);
    lHilightColor       = hGrp->GetInt ("HilightColor",0);
    bQualityNormals     = hGrp->GetBool("QualityNormals",false);
    */
}

ViewProviderCurveNet::~ViewProviderCurveNet() = default;

void ViewProviderCurveNet::attach(App::DocumentObject* pcFeat)
{
    // call parent attach method
    ViewProviderGeometryObject::attach(pcFeat);  // clazy:exclude=skipped-base-method

    // setup the root and material for the edges
    SoSeparator* ModeRoot = new SoSeparator();
    EdgeRoot = new SoSeparator();
    ModeRoot->addChild(EdgeRoot);
    EdgeRoot->addChild(pcLineStyle);
    EdgeRoot->addChild(pcLineMaterial);

    // setup the root and material for the vertexes
    VertexRoot = new SoSeparator();
    ModeRoot->addChild(VertexRoot);
    VertexRoot->addChild(pcPointMaterial);
    SoComplexity* copl = new SoComplexity();
    copl->value = (float)0.2;
    VertexRoot->addChild(copl);

    addDisplayMaskMode(ModeRoot, "Edge");
}

void ViewProviderCurveNet::updateData(const App::Property* prop)
{
    Gui::ViewProviderGeometryObject::updateData(prop);  // clazy:exclude=skipped-base-method
    if (prop->is<Part::PropertyPartShape>()) {
        TopoDS_Shape cShape = static_cast<const Part::PropertyPartShape*>(prop)->getValue();
        if (cShape.IsNull()) {
            return;
        }

        Gui::coinRemoveAllChildren(EdgeRoot);
        Gui::coinRemoveAllChildren(VertexRoot);

        try {
            computeEdges(EdgeRoot, cShape);
            computeVertices(VertexRoot, cShape);
        }
        catch (...) {
            Base::Console()
                .error("ViewProviderPart::create() Cannot compute Inventor representation for the actual shape");
        }
    }
}

void ViewProviderCurveNet::setDisplayMode(const char* ModeName)
{
    if (strcmp("Edge", ModeName) == 0) {
        setDisplayMaskMode("Edge");
    }

    ViewProviderGeometryObject::setDisplayMode(ModeName);
}

std::vector<std::string> ViewProviderCurveNet::getDisplayModes() const
{
    // get the modes of the father
    std::vector<std::string> StrList;

    // add your own modes
    StrList.emplace_back("Edge");

    return StrList;
}

// **********************************************************************************

bool ViewProviderCurveNet::setEdit(int)
{
    bInEdit = true;
    return true;
    // getWidget()->setCursor( QCursor( 13 /*ArrowCursor*/) )
}

void ViewProviderCurveNet::unsetEdit(int)
{
    bInEdit = false;
}

bool ViewProviderCurveNet::handleEvent(const SoEvent* const ev, Gui::View3DInventorViewer& Viewer)
{
    SbVec3f point, norm;

    // get the position of the mouse
    const SbVec2s pos(ev->getPosition());

    // Keyboard events
    if (ev->getTypeId().isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
        const SoKeyboardEvent* ke = static_cast<const SoKeyboardEvent*>(ev);
        switch (ke->getKey()) {
            case SoKeyboardEvent::LEFT_ALT:
            case SoKeyboardEvent::RIGHT_ALT:
            case SoKeyboardEvent::LEFT_CONTROL:
            case SoKeyboardEvent::RIGHT_CONTROL:
            case SoKeyboardEvent::LEFT_SHIFT:
            case SoKeyboardEvent::RIGHT_SHIFT:
                break;
            default:
                break;
        }
    }

    // switching the mouse buttons
    if (ev->getTypeId().isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
        const SoMouseButtonEvent* const event = static_cast<const SoMouseButtonEvent*>(ev);
        const int button = event->getButton();
        const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;

        // which button pressed?
        switch (button) {
            case SoMouseButtonEvent::BUTTON1:
                if (press) {
                    Base::Console().log("ViewProviderCurveNet::handleEvent() press left\n");

                    bool bIsNode = false;
                    for (const auto& It : NodeList) {
                        if (It.pcHighlight->isHighlighted()) {
                            bIsNode = true;
                            PointToMove = It;
                            break;
                        }
                    }

                    if (bIsNode) {
                        // set the provider in point move mode and remember the point
                        bMovePointMode = true;
                        // PointToMove = *It; ### Error 'It' is out of scope->move inside the loop

                        return true;
                    }
                    else if (Viewer.pickPoint(pos, point, norm)) {
                        Node n;
                        Base::Console().log("Picked(%f,%f,%f)\n", point[0], point[1], point[2]);

                        SoSeparator* TransRoot = new SoSeparator();
                        n.pcTransform = new SoTransform();
                        TransRoot->addChild(n.pcTransform);
                        n.pcTransform->translation.setValue(point);
                        n.pcHighlight = new Gui::SoFCSelection();
                        SoSphere* sphere = new SoSphere;
                        sphere->radius = (float)pcLineStyle->pointSize.getValue();
                        n.pcHighlight->addChild(sphere);
                        TransRoot->addChild(n.pcHighlight);
                        VertexRoot->addChild(TransRoot);

                        NodeList.push_back(n);

                        return true;
                    }
                }
                else  // if(pressd)..
                {
                    if (bMovePointMode) {
                        bMovePointMode = false;
                        return true;
                    }
                }
                break;
        }
    }

    // Mouse Movement handling
    if (ev->getTypeId().isDerivedFrom(SoLocation2Event::getClassTypeId())) {
        //    const SoLocation2Event * const event = (const SoLocation2Event *) ev;

        if (bMovePointMode && Viewer.pickPoint(pos, point, norm)) {
            PointToMove.pcTransform->translation.setValue(point);
            return true;
        }
    }

    // event not processed
    return false;
}

Standard_Boolean ViewProviderCurveNet::computeEdges(SoSeparator* root, const TopoDS_Shape& myShape)
{
    unsigned long ulNbOfPoints = 50;

    TopExp_Explorer ex;
    SoSeparator* EdgeRoot = new SoSeparator();
    root->addChild(EdgeRoot);

    EdgeRoot->addChild(pcLineStyle);
    EdgeRoot->addChild(pcLineMaterial);

    for (ex.Init(myShape, TopAbs_EDGE); ex.More(); ex.Next()) {
        // get the shape and mesh it
        const TopoDS_Edge& aEdge = TopoDS::Edge(ex.Current());

        Standard_Real fBegin, fEnd;
        SbVec3f* vertices = new SbVec3f[ulNbOfPoints];

        Handle(Geom_Curve) hCurve = BRep_Tool::Curve(aEdge, fBegin, fEnd);
        float fLen = float(fEnd - fBegin);

        for (unsigned long i = 0; i < ulNbOfPoints; i++) {
            gp_Pnt gpPt = hCurve->Value(fBegin + (fLen * float(i)) / float(ulNbOfPoints - 1));
            vertices[i].setValue((float)(gpPt.X()), (float)(gpPt.Y()), (float)(gpPt.Z()));
        }

        // define vertices
        SoCoordinate3* coords = new SoCoordinate3;
        coords->point.setValues(0, ulNbOfPoints, vertices);
        EdgeRoot->addChild(coords);

        // define the indexed face set
        SoLocateHighlight* h = new SoLocateHighlight();
        h->color.setValue((float)0.2, (float)0.5, (float)0.2);

        SoLineSet* lineset = new SoLineSet;
        h->addChild(lineset);
        EdgeRoot->addChild(h);
    }

    return true;
}

Standard_Boolean ViewProviderCurveNet::computeVertices(SoSeparator* root, const TopoDS_Shape& myShape)
{
    TopExp_Explorer ex;
    SoSeparator* VertexRoot = new SoSeparator();
    root->addChild(VertexRoot);

    VertexRoot->addChild(pcPointMaterial);

    SoComplexity* copl = new SoComplexity();
    copl->value = (float)0.2;
    VertexRoot->addChild(copl);

    for (ex.Init(myShape, TopAbs_VERTEX); ex.More(); ex.Next()) {
        // get the shape
        const TopoDS_Vertex& aVertex = TopoDS::Vertex(ex.Current());
        gp_Pnt gpPt = BRep_Tool::Pnt(aVertex);

        SoSeparator* TransRoot = new SoSeparator();
        SoTransform* Trans = new SoTransform();
        TransRoot->addChild(Trans);
        Trans->translation.setValue((float)(gpPt.X()), (float)(gpPt.Y()), (float)(gpPt.Z()));

        SoLocateHighlight* h = new SoLocateHighlight();
        h->color.setValue((float)0.2, (float)0.5, (float)0.2);

        SoSphere* sphere = new SoSphere;
        sphere->radius = (float)pcPointStyle->pointSize.getValue();

        h->addChild(sphere);
        TransRoot->addChild(h);
        VertexRoot->addChild(TransRoot);
    }

    return true;
}
