#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkNamedColors.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkRenderer.h>
#include <vtkXMLPolyDataReader.h>
#include <vtkNew.h>
#include <vtkArrowSource.h>
#include <vtkGlyph3D.h>
#include <vtkCellData.h>
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkDataArray.h>
#include <vtkCleanPolyData.h>
#include <vtkPlane.h>
#include <vtkCutter.h>
#include <vtkStripper.h>
#include <vtkContourTriangulator.h>
#include <vtkPolyDataNormals.h>
#include <vtkFeatureEdges.h>

#include <iostream>


void render(vtkPolyData* pd);
void renderCells(vtkPolyData* pd, bool showNormals = true);


int main(int, char*[])
{    
    vtkNew<vtkXMLPolyDataReader> reader;
    reader->SetFileName("../bd.vtp");
    reader->Update();
    float sliceHeight = 21.3;

    render(reader->GetOutput());

    vtkNew<vtkCleanPolyData> cleaner;
    cleaner->SetInputData(reader->GetOutput());
    cleaner->SetTolerance(0.00001); // tried different tolerances here, but then there are errors on other layers
    cleaner->Update();

    vtkNew<vtkPlane> plane;
    plane->SetNormal(0, 0, 1);
    plane->SetOrigin(0, 0, sliceHeight);

    vtkNew<vtkCutter> cutter;
    cutter->SetCutFunction(plane);
    cutter->SetInputConnection(cleaner->GetOutputPort());

    auto stripper = vtkSmartPointer<vtkStripper>::New();
    stripper->SetInputConnection(cutter->GetOutputPort());
    stripper->JoinContiguousSegmentsOn();
    stripper->Update();

    renderCells(stripper->GetOutput(), false);

    vtkNew<vtkContourTriangulator> contourTriangulator;
    contourTriangulator->SetInputConnection(stripper->GetOutputPort());
    contourTriangulator->TriangulationErrorDisplayOn();
    contourTriangulator->Update();

    renderCells(contourTriangulator->GetOutput());

    vtkNew<vtkFeatureEdges> featureEdges;
    featureEdges->BoundaryEdgesOn();
    featureEdges->SetInputConnection(contourTriangulator->GetOutputPort());
    featureEdges->FeatureEdgesOff();
    featureEdges->NonManifoldEdgesOff();
    featureEdges->ManifoldEdgesOff();
    featureEdges->Update();

    //...

    return EXIT_SUCCESS;
}


void render(vtkPolyData* pd)
{
    vtkNew<vtkNamedColors> colors;

    vtkNew<vtkPolyDataMapper> mapper;
    mapper->SetInputData(pd);

    vtkNew<vtkActor> actor;
    actor->SetMapper(mapper);
    actor->GetProperty()->SetColor(colors->GetColor3d("NavajoWhite").GetData());

    vtkNew<vtkRenderer> renderer;
    vtkNew<vtkRenderWindow> renderWindow;
    renderWindow->AddRenderer(renderer);
    vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
    renderWindowInteractor->SetRenderWindow(renderWindow);
    vtkNew<vtkInteractorStyleTrackballCamera> style;
    renderWindowInteractor->SetInteractorStyle(style);

    renderer->AddActor(actor);
    renderer->GetActiveCamera()->Pitch(90);
    renderer->GetActiveCamera()->SetViewUp(0, 0, 1);
    renderer->ResetCamera();

    renderWindow->SetSize(600, 600);
    renderWindow->Render();
    
    renderWindowInteractor->Start();
}


void renderCells(vtkPolyData* pd, bool showNormals)
{
    vtkNew<vtkPolyDataNormals> normalsCalc;
    normalsCalc->SetInputData(pd);
    normalsCalc->ComputeCellNormalsOn();
    normalsCalc->ComputePointNormalsOff();
    normalsCalc->ConsistencyOn();
    normalsCalc->Update();

    vtkPolyData* normals = normalsCalc->GetOutput();

    vtkNew<vtkPolyDataMapper> mapper;
    mapper->SetInputData(normals);

    vtkNew<vtkActor> actor;
    actor->SetMapper(mapper);

    actor->GetProperty()->SetRepresentationToWireframe();
    vtkNew<vtkNamedColors> colors;
    actor->GetProperty()->SetColor(
        colors->GetColor3d("Red").GetData());


    vtkNew<vtkPoints> centroids;
    for (vtkIdType i = 0; i < normals->GetNumberOfCells(); ++i) {
        vtkCell* cell = normals->GetCell(i);
        double centroid[3] = {0, 0, 0};
        for (vtkIdType j = 0; j < cell->GetNumberOfPoints(); ++j) {
            double p[3];
            normals->GetPoint(cell->GetPointId(j), p);
            centroid[0] += p[0];
            centroid[1] += p[1];
            centroid[2] += p[2];
        }
        centroid[0] /= cell->GetNumberOfPoints();
        centroid[1] /= cell->GetNumberOfPoints();
        centroid[2] /= cell->GetNumberOfPoints();
        centroids->InsertNextPoint(centroid);
    }

    vtkDataArray* cellNormals = normals->GetCellData()->GetNormals();
    vtkNew<vtkPolyData> glyphPolyData;
    glyphPolyData->SetPoints(centroids);
    glyphPolyData->GetCellData()->SetNormals(cellNormals);
    glyphPolyData->GetPointData()->SetVectors(cellNormals);

    vtkNew<vtkArrowSource> arrowSource;
    vtkNew<vtkGlyph3D> glyphs;
    glyphs->SetInputData(glyphPolyData);
    glyphs->SetSourceConnection(arrowSource->GetOutputPort());
    glyphs->SetVectorModeToUseVector();
    glyphs->SetScaleFactor(1); // Adjust as needed
    glyphs->OrientOn();
    glyphs->Update();

    vtkNew<vtkPolyDataMapper> glyphMapper;
    glyphMapper->SetInputConnection(glyphs->GetOutputPort());

    vtkNew<vtkActor> glyphActor;
    glyphActor->SetMapper(glyphMapper);

    vtkNew<vtkRenderer> renderer;
    vtkNew<vtkRenderWindow> renderWindow;
    renderWindow->AddRenderer(renderer);
    vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
    renderWindowInteractor->SetRenderWindow(renderWindow);
    vtkNew<vtkInteractorStyleTrackballCamera> style;
    renderWindowInteractor->SetInteractorStyle(style);

    renderer->AddActor(actor);
    if (showNormals)
    {
        renderer->AddActor(glyphActor);
    }

    renderWindow->SetSize(600, 600);
    renderWindow->Render();
    renderWindowInteractor->Start();
}