// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
// This test verifies that sizing of implicits spheres and cylinders for
// points and lines works as expected.
//
// The command line arguments are:
// -I         => run in interactive mode; unless this is used, the program will
//               not allow interaction and exit.
//               In interactive mode it responds to the keys listed
//               vtkOSPRayTestInteractor.h
// -GL        => users OpenGL instead of OSPRay to render

#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkDataArray.h"
#include "vtkDataSetSurfaceFilter.h"
#include "vtkDoubleArray.h"
#include "vtkExtractEdges.h"
#include "vtkGlyph3D.h"
#include "vtkGlyphSource2D.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkOSPRayActorNode.h"
#include "vtkOSPRayPass.h"
#include "vtkOSPRayRendererNode.h"
#include "vtkOpenGLRenderer.h"
#include "vtkPiecewiseFunction.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRTAnalyticSource.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkScalarsToColors.h"
#include "vtkShrinkFilter.h"
#include "vtkSmartPointer.h"

#include <string>
#include <vector>

#include "vtkOSPRayTestInteractor.h"

int TestOSPRayImplicits(int argc, char* argv[])
{
  bool useGL = false;
  for (int i = 0; i < argc; i++)
  {
    if (!strcmp(argv[i], "-GL"))
    {
      useGL = true;
    }
  }

  vtkSmartPointer<vtkRenderWindowInteractor> iren =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
  vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();
  iren->SetRenderWindow(renWin);
  vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
  renWin->AddRenderer(renderer);
  renderer->AutomaticLightCreationOn();
  renderer->SetBackground(0.75, 0.75, 0.75);
  renderer->SetEnvironmentalBG(0.75, 0.75, 0.75);
  renWin->SetSize(600, 550);

  vtkSmartPointer<vtkOSPRayPass> ospray = vtkSmartPointer<vtkOSPRayPass>::New();
  if (!useGL)
  {
    for (int i = 0; i < argc; ++i)
    {
      if (!strcmp(argv[i], "--OptiX"))
      {
        vtkOSPRayRendererNode::SetRendererType("optix pathtracer", renderer);
        break;
      }
    }

    renderer->SetPass(ospray);
  }

  vtkSmartPointer<vtkRTAnalyticSource> wavelet = vtkSmartPointer<vtkRTAnalyticSource>::New();
  wavelet->SetWholeExtent(-10, 10, -10, 10, -10, 10);
  wavelet->SetSubsampleRate(5);
  wavelet->Update();
  // use a more predictable array
  vtkSmartPointer<vtkDoubleArray> da = vtkSmartPointer<vtkDoubleArray>::New();
  da->SetName("testarray1");
  da->SetNumberOfComponents(1);
  vtkDataSet* ds = wavelet->GetOutput();
  ds->GetPointData()->AddArray(da);
  int np = ds->GetNumberOfPoints();
  for (int i = 0; i < np; i++)
  {
    da->InsertNextValue((double)i / (double)np);
  }

  vtkSmartPointer<vtkDataSetSurfaceFilter> surfacer =
    vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
  surfacer->SetInputData(ds);
  vtkSmartPointer<vtkShrinkFilter> shrinker = vtkSmartPointer<vtkShrinkFilter>::New();
  shrinker->SetShrinkFactor(0.5);
  shrinker->SetInputConnection(surfacer->GetOutputPort());

  // measure it for placements
  shrinker->Update();
  const double* bds = vtkDataSet::SafeDownCast(shrinker->GetOutputDataObject(0))->GetBounds();
  double x0 = bds[0];
  double y0 = bds[2];
  double z0 = bds[4];
  double dx = (bds[1] - bds[0]) * 1.2;
  double dy = (bds[3] - bds[2]) * 1.2;
  double dz = (bds[5] - bds[4]) * 1.2;

  // make points, point rep works too but only gets outer shell
  vtkSmartPointer<vtkGlyphSource2D> glyph = vtkSmartPointer<vtkGlyphSource2D>::New();
  glyph->SetGlyphTypeToVertex();
  vtkSmartPointer<vtkGlyph3D> glyphFilter = vtkSmartPointer<vtkGlyph3D>::New();
  glyphFilter->SetInputConnection(shrinker->GetOutputPort());
  glyphFilter->SetSourceConnection(glyph->GetOutputPort());

  vtkSmartPointer<vtkExtractEdges> edgeFilter = vtkSmartPointer<vtkExtractEdges>::New();
  edgeFilter->SetInputConnection(shrinker->GetOutputPort());

  // spheres ///////////////////////
  vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(glyphFilter->GetOutputPort());
  vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToPoints();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 0, y0 + dy * 0, z0 + dz * 0);
  vtkOSPRayTestInteractor::AddName("Points default");

  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(glyphFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToPoints();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 1, y0 + dy * 0, z0 + dz * 0);
  actor->GetProperty()->SetPointSize(5);
  vtkOSPRayTestInteractor::AddName("Points SetPointSize()");

  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(glyphFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToPoints();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 2, y0 + dy * 0, z0 + dz * 0);
  vtkInformation* mapInfo = mapper->GetInformation();
  mapInfo->Set(vtkOSPRayActorNode::ENABLE_SCALING(), 1);
  mapInfo->Set(vtkOSPRayActorNode::SCALE_ARRAY_NAME(), "testarray1");
  vtkOSPRayTestInteractor::AddName("Points SCALE_ARRAY");

  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(glyphFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToPoints();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 3, y0 + dy * 0, z0 + dz * 0);
  mapInfo = mapper->GetInformation();
  mapInfo->Set(vtkOSPRayActorNode::ENABLE_SCALING(), 1);
  mapInfo->Set(vtkOSPRayActorNode::SCALE_ARRAY_NAME(), "testarray1");
  vtkSmartPointer<vtkPiecewiseFunction> scaleFunction =
    vtkSmartPointer<vtkPiecewiseFunction>::New();
  scaleFunction->AddPoint(0.00, 0.0);
  scaleFunction->AddPoint(0.50, 0.0);
  scaleFunction->AddPoint(0.51, 0.1);
  scaleFunction->AddPoint(1.00, 1.2);
  mapInfo->Set(vtkOSPRayActorNode::SCALE_FUNCTION(), scaleFunction);
  vtkOSPRayTestInteractor::AddName("Points SCALE_FUNCTION on SCALE_ARRAY");

  // cylinders ////////////////
  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(edgeFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToWireframe();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 0, y0 + dy * 2, z0 + dz * 0);
  vtkOSPRayTestInteractor::AddName("Wireframe default");

  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(edgeFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToWireframe();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 1, y0 + dy * 2, z0 + dz * 0);
  actor->GetProperty()->SetLineWidth(5);
  vtkOSPRayTestInteractor::AddName("Wireframe LineWidth");

  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(edgeFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToWireframe();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 2, y0 + dy * 2, z0 + dz * 0);
  vtkOSPRayActorNode::SetEnableScaling(1, actor);
  vtkOSPRayActorNode::SetScaleArrayName("testarray1", actor);
  vtkOSPRayTestInteractor::AddName("Wireframe SCALE_ARRAY");

  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(edgeFilter->GetOutputPort());
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToWireframe();
  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 3, y0 + dy * 2, z0 + dz * 0);
  mapInfo = mapper->GetInformation();
  mapInfo->Set(vtkOSPRayActorNode::ENABLE_SCALING(), 1);
  mapInfo->Set(vtkOSPRayActorNode::SCALE_ARRAY_NAME(), "testarray1");
  scaleFunction = vtkSmartPointer<vtkPiecewiseFunction>::New();
  scaleFunction->AddPoint(0.00, 0.0);
  scaleFunction->AddPoint(0.50, 0.0);
  scaleFunction->AddPoint(0.51, 0.1);
  scaleFunction->AddPoint(1.00, 1.2);
  mapInfo->Set(vtkOSPRayActorNode::SCALE_FUNCTION(), scaleFunction);
  vtkOSPRayTestInteractor::AddName("Wireframe SCALE_FUNCTION on SCALE_ARRAY");

  // reference values shown as colors /////////////////
  mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(surfacer->GetOutputPort());
  surfacer->Update();
  mapper->ScalarVisibilityOn();
  mapper->CreateDefaultLookupTable();
  mapper->SetColorModeToMapScalars();
  mapper->SetScalarModeToUsePointFieldData();
  mapper->SelectColorArray("testarray1");
  double* range = surfacer->GetOutput()->GetPointData()->GetArray("testarray1")->GetRange();
  mapper->SetScalarRange(range);
  actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetRepresentationToSurface();

  renderer->AddActor(actor);
  actor->SetPosition(x0 + dx * 2, y0 + dy * 1, z0 + dz * 0);
  vtkOSPRayTestInteractor::AddName("Reference values as colors");

  // just show it //////////////////
  renWin->Render();
  renderer->ResetCamera();

  vtkSmartPointer<vtkOSPRayTestInteractor> style = vtkSmartPointer<vtkOSPRayTestInteractor>::New();
  style->SetPipelineControlPoints(renderer, ospray, nullptr);
  iren->SetInteractorStyle(style);
  style->SetCurrentRenderer(renderer);

  iren->Start();

  return 0;
}
