// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkSmartPointer.h"

#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkCommand.h"
#include "vtkContourWidget.h"
#include "vtkCutter.h"
#include "vtkDEMReader.h"
#include "vtkImageData.h"
#include "vtkImageDataGeometryFilter.h"
#include "vtkImageResample.h"
#include "vtkLinearContourLineInterpolator.h"
#include "vtkLookupTable.h"
#include "vtkOrientedGlyphContourRepresentation.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkPolyLine.h"
#include "vtkPolyPlane.h"
#include "vtkProperty.h"
#include "vtkProperty2D.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTextProperty.h"
#include "vtkTriangleFilter.h"
#include "vtkUnstructuredGrid.h"
#include "vtkWarpScalar.h"
#include "vtkXMLPolyDataWriter.h"
#include "vtkXYPlotActor.h"

#include "vtkRegressionTestImage.h"
#include "vtkTestUtilities.h"

//------------------------------------------------------------------------------
// Callback for the widget interaction
class vtkTestPolyPlaneCallback : public vtkCommand
{
public:
  static vtkTestPolyPlaneCallback* New() { return new vtkTestPolyPlaneCallback; }

  void Execute(vtkObject* caller, unsigned long, void*) override
  {
    vtkContourWidget* widget = reinterpret_cast<vtkContourWidget*>(caller);
    vtkContourRepresentation* rep =
      vtkContourRepresentation::SafeDownCast(widget->GetRepresentation());

    vtkPolyData* pd = rep->GetContourRepresentationAsPolyData();

    // If less than 2 points, we can't define a polyplane..

    if (pd->GetPoints()->GetNumberOfPoints() >= 2)
    {
      vtkIdType npts;
      const vtkIdType* ptIds;
      pd->GetLines()->GetCellAtId(0, npts, ptIds);

      vtkPolyLine* polyline = vtkPolyLine::New();
      polyline->Initialize(static_cast<int>(npts), ptIds, pd->GetPoints());

      this->PolyPlane->SetPolyLine(polyline);
      polyline->Delete();

      this->Cutter->SetCutFunction(this->PolyPlane);
    }
  }

  vtkTestPolyPlaneCallback()
    : PolyPlane(nullptr)
    , Cutter(nullptr)
  {
  }
  vtkPolyPlane* PolyPlane;
  vtkCutter* Cutter;
};

//------------------------------------------------------------------------------
int TestPolyPlane(int argc, char* argv[])
{
  // Read height field.

  char* fname = vtkTestUtilities::ExpandDataFileName(argc, argv, "Data/SainteHelens.dem");

  vtkSmartPointer<vtkDEMReader> demReader = vtkSmartPointer<vtkDEMReader>::New();
  demReader->SetFileName(fname);

  delete[] fname;

  // Resample (left in case, we want to subsample, supersample)

  vtkSmartPointer<vtkImageResample> resample = vtkSmartPointer<vtkImageResample>::New();
  resample->SetInputConnection(demReader->GetOutputPort());
  resample->SetDimensionality(2);
  resample->SetAxisMagnificationFactor(0, 0.25);
  resample->SetAxisMagnificationFactor(1, 0.25);

  // Extract geometry

  vtkSmartPointer<vtkImageDataGeometryFilter> surface =
    vtkSmartPointer<vtkImageDataGeometryFilter>::New();
  surface->SetInputConnection(resample->GetOutputPort());

  // Convert to triangle mesh

  vtkSmartPointer<vtkTriangleFilter> triangleFilter = vtkSmartPointer<vtkTriangleFilter>::New();
  triangleFilter->SetInputConnection(surface->GetOutputPort());
  triangleFilter->Update();

  // Warp

  vtkSmartPointer<vtkWarpScalar> warp = vtkSmartPointer<vtkWarpScalar>::New();
  warp->SetInputConnection(triangleFilter->GetOutputPort());
  warp->SetScaleFactor(1);
  warp->UseNormalOn();
  warp->SetNormal(0, 0, 1);

  //  Update the pipeline until now.

  warp->Update();

  // Define a LUT mapping for the height field

  double lo = demReader->GetOutput()->GetScalarRange()[0];
  double hi = demReader->GetOutput()->GetScalarRange()[1];

  vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
  lut->SetHueRange(0.6, 0);
  lut->SetSaturationRange(1.0, 0);
  lut->SetValueRange(0.5, 1.0);

  // Create Renderer, Render window and interactor

  vtkSmartPointer<vtkRenderer> ren1 = vtkSmartPointer<vtkRenderer>::New();
  vtkSmartPointer<vtkRenderer> ren2 = vtkSmartPointer<vtkRenderer>::New();
  vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();
  renWin->SetMultiSamples(0);
  renWin->AddRenderer(ren1);
  renWin->AddRenderer(ren2);

  vtkSmartPointer<vtkRenderWindowInteractor> iren =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
  iren->SetRenderWindow(renWin);

  // Render the height field

  vtkSmartPointer<vtkPolyDataMapper> demMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  demMapper->SetInputConnection(warp->GetOutputPort());
  demMapper->SetScalarRange(lo, hi);
  demMapper->SetLookupTable(lut);

  vtkSmartPointer<vtkActor> demActor = vtkSmartPointer<vtkActor>::New();
  demActor->SetMapper(demMapper);

  ren1->AddActor(demActor);

  // Create a contour widget on ren1

  vtkSmartPointer<vtkContourWidget> contourWidget = vtkSmartPointer<vtkContourWidget>::New();
  contourWidget->SetInteractor(iren);
  vtkOrientedGlyphContourRepresentation* rep =
    vtkOrientedGlyphContourRepresentation::SafeDownCast(contourWidget->GetRepresentation());
  rep->GetLinesProperty()->SetColor(1, 0.2, 0);
  rep->GetLinesProperty()->SetLineWidth(3.0);

  // Use no interpolation (default is bezier).

  vtkSmartPointer<vtkLinearContourLineInterpolator> lineInterpolator =
    vtkSmartPointer<vtkLinearContourLineInterpolator>::New();
  rep->SetLineInterpolator(lineInterpolator);

  // Create a polyplane to cut with

  vtkSmartPointer<vtkPolyPlane> polyPlane = vtkSmartPointer<vtkPolyPlane>::New();

  // Create a cutter

  vtkSmartPointer<vtkCutter> cutter = vtkSmartPointer<vtkCutter>::New();
  cutter->SetInputConnection(warp->GetOutputPort());

  // Callback to update the polyplane when the contour is updated

  vtkSmartPointer<vtkTestPolyPlaneCallback> cb = vtkSmartPointer<vtkTestPolyPlaneCallback>::New();
  cb->PolyPlane = polyPlane;
  cb->Cutter = cutter;

  vtkPolyData* data = warp->GetPolyDataOutput();
  double* range = data->GetPointData()->GetScalars()->GetRange();

  //  plot the height field

  vtkSmartPointer<vtkXYPlotActor> profile = vtkSmartPointer<vtkXYPlotActor>::New();
  profile->AddDataSetInputConnection(cutter->GetOutputPort());
  profile->GetPositionCoordinate()->SetValue(0.05, 0.05, 0);
  profile->GetPosition2Coordinate()->SetValue(0.95, 0.95, 0);
  profile->SetXValuesToArcLength();
  profile->SetNumberOfXLabels(6);
  profile->SetTitle("Profile Data ");
  profile->SetXTitle("Arc length");
  profile->SetYTitle("Height");
  profile->SetYRange(range[0], range[1]);
  profile->GetProperty()->SetColor(0, 0, 0);
  profile->GetProperty()->SetLineWidth(2);
  profile->SetLabelFormat("{:g}");
  vtkTextProperty* tprop = profile->GetTitleTextProperty();
  tprop->SetColor(0.02, 0.06, 0.62);
  tprop->SetFontFamilyToArial();
  profile->SetAxisTitleTextProperty(tprop);
  profile->SetAxisLabelTextProperty(tprop);
  profile->SetTitleTextProperty(tprop);

  ren1->SetBackground(0.1, 0.2, 0.4);
  ren1->SetViewport(0, 0, 0.5, 1);

  ren2->SetBackground(1, 1, 1);
  ren2->SetViewport(0.5, 0, 1, 1);

  renWin->SetSize(800, 500);

  // Set up an interesting viewpoint

  ren1->ResetCamera();
  ren1->ResetCameraClippingRange();
  vtkCamera* camera = ren1->GetActiveCamera();
  camera->SetViewUp(0.796081, -0.277969, 0.537576);
  camera->SetParallelScale(10726.6);
  camera->SetFocalPoint(562412, 5.11456e+06, 1955.44);
  camera->SetPosition(544402, 5.11984e+06, 31359.2);
  ren1->ResetCamera();
  ren1->ResetCameraClippingRange();

  // Create some default points

  contourWidget->On();

  // First remove all nodes.
  rep->ClearAllNodes();
  rep->AddNodeAtWorldPosition(560846, 5.12018e+06, 2205.95);
  rep->AddNodeAtWorldPosition(562342, 5.11663e+06, 3630.72);
  rep->AddNodeAtWorldPosition(562421, 5.11321e+06, 3156.75);
  rep->AddNodeAtWorldPosition(565885, 5.11067e+06, 2885.73);
  contourWidget->SetWidgetState(vtkContourWidget::Manipulate);

  // Execute the cut
  cb->Execute(contourWidget, 0, nullptr);

  vtkXMLPolyDataWriter* pWriter = vtkXMLPolyDataWriter::New();
  pWriter->SetInputConnection(cutter->GetOutputPort());
  cutter->Update();
  pWriter->SetFileName("CutPolyPlane.vtp");
  pWriter->Write();
  pWriter->SetInputConnection(warp->GetOutputPort());
  pWriter->SetFileName("Dataset.vtp");
  pWriter->Write();
  pWriter->SetInputData(rep->GetContourRepresentationAsPolyData());
  pWriter->SetFileName("Contour.vtp");
  pWriter->Write();
  pWriter->Delete();

  // Observe and update profile when contour widget is interacted with
  contourWidget->AddObserver(vtkCommand::InteractionEvent, cb);

  // Render the image
  iren->Initialize();
  ren2->AddViewProp(profile);
  renWin->Render();
  ren1->ResetCamera();

  int retVal = vtkRegressionTestImage(renWin);
  if (retVal == vtkRegressionTester::DO_INTERACTOR)
  {
    iren->Start();
  }

  return !retVal;
}
