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

#include "vtkCamera.h"
#include "vtkCellData.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataPipeline.h"
#include "vtkCompositeDataSet.h"
#include "vtkDataObject.h"
#include "vtkDataSet.h"
#include "vtkDiscretizableColorTransferFunction.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkRegressionTestImage.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSmartPointer.h"
#include "vtkTestUtilities.h"

#include "vtkCompositeSurfaceLICMapper.h"
#include "vtkSurfaceLICInterface.h"
#include "vtkSurfaceLICMapper.h"

#include "vtk_glad.h"

#include <iostream>
#include <string>
#include <vector>

#ifndef vtkFloatingPointTemplateMacro
#define vtkFloatingPointTemplateMacro(call)                                                        \
  vtkTemplateMacroCase(VTK_DOUBLE, double, call);                                                  \
  vtkTemplateMacroCase(VTK_FLOAT, float, call)
#endif

// Helper to compute range
static void Range(vtkDataArray* S, double* range)
{
  double Srange[2];
  S->GetRange(Srange);
  range[0] = Srange[0] < range[0] ? Srange[0] : range[0];
  range[1] = Srange[1] > range[1] ? Srange[1] : range[1];
}

// helper to compute magnitude
static vtkDataArray* Magnitude(vtkDataArray* V)
{
  vtkIdType nTups = V->GetNumberOfTuples();
  vtkIdType nComps = V->GetNumberOfComponents();
  vtkDataArray* magV = V->NewInstance();
  magV->SetNumberOfTuples(nTups);
  switch (V->GetDataType())
  {
    vtkFloatingPointTemplateMacro(VTK_TT* pV = (VTK_TT*)V->GetVoidPointer(0);
                                  VTK_TT* pMagV = (VTK_TT*)magV->GetVoidPointer(0);
                                  for (vtkIdType i = 0; i < nTups; ++i) {
                                    VTK_TT mag = VTK_TT(0);
                                    for (vtkIdType j = 0; j < nComps; ++j)
                                    {
                                      VTK_TT v = pV[i * nComps + j];
                                      mag += v * v;
                                    }
                                    pMagV[i] = sqrt(mag);
                                  });
    default:
      std::cerr << "ERROR: vectors must be float or double" << std::endl;
      break;
  }
  return magV;
}

// Compute the magnitude of the named vector and add it to
// dataset, return range.
static vtkDataArray* Magnitude(vtkDataSet* ds, std::string& vectors)
{
  vtkDataArray* V = nullptr;
  V = ds->GetPointData()->GetArray(vectors.c_str());
  if (V == nullptr)
  {
    std::cerr << "ERROR: point vectors " << vectors << " not found" << std::endl;
    return nullptr;
  }
  vtkDataArray* magV = Magnitude(V);
  std::string magVName = "mag" + vectors;
  magV->SetName(magVName.c_str());
  return magV;
}

// This example demonstrates the use of vtkSurfaceLICmapper for rendering
// geometry with LIC on the surface.
int vtkSurfaceLICTestDriver(int argc, char** argv, vtkDataObject* dataObj, int num_steps,
  double step_size, int enhanced_lic, int normalize_vectors, int camera_config,
  int generate_noise_texture, int noise_type, int noise_texture_size, int noise_grain_size,
  double min_noise_value, double max_noise_value, int number_of_noise_levels,
  double impulse_noise_prob, double impulse_noise_bg_value, int noise_gen_seed,
  int enhance_contrast, double low_lic_contrast_enhancement_factor,
  double high_lic_contrast_enhancement_factor, double low_color_contrast_enhancement_factor,
  double high_color_contrast_enhancement_factor, int anti_alias, int color_mode,
  double lic_intensity, double map_mode_bias, int color_by_mag, int mask_on_surface,
  double mask_threshold, double mask_intensity, int interpolate_scalars_before_mapping,
  int num_discrete_colors, std::vector<double>& mask_color_rgb, std::string& vectors)
{
  // Set up the render window, renderer, interactor.
  vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();

  vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();

  vtkSmartPointer<vtkRenderWindowInteractor> iren =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();

  renWin->AddRenderer(renderer);
  iren->SetRenderWindow(renWin);

  if (camera_config == 1)
  {
    renWin->SetSize(300, 300);
  }
  else if (camera_config == 2)
  {
    renWin->SetSize(300, 270);
  }
  else if (camera_config == 3)
  {
    renWin->SetSize(400, 340);
  }
  else if (camera_config == 4)
  {
    renWin->SetSize(364, 256);
  }
  renWin->Render();

  if (!vtkSurfaceLICInterface::IsSupported(renWin))
  {
    std::cerr << "WARNING: The rendering context does not support required extensions."
              << std::endl;
    dataObj = nullptr;
    renWin = nullptr;
    renderer = nullptr;
    iren = nullptr;
    vtkAlgorithm::SetDefaultExecutivePrototype(nullptr);
    return 0;
  }

  // Create a mapper and insert the vtkSurfaceLICmapper mapper into the
  // mapper chain. This is essential since the entire logic of performing the
  // LIC is present in the vtkSurfaceLICmapper.

  vtkSmartPointer<vtkCompositeSurfaceLICMapper> mapper =
    vtkSmartPointer<vtkCompositeSurfaceLICMapper>::New();
  // vtkSmartPointer<vtkSurfaceLICMapper> mapper
  //   = vtkSmartPointer<vtkSurfaceLICMapper>::New();

  // print details of the test
  // convenient for debugging failed
  // tests on remote dashboards.
  const char* vtkGLVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
  const char* vtkGLVendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
  const char* vtkGLRenderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
  const char* vtkLICClass = mapper->GetClassName();
  std::string details =
    std::string("\n\n====================================================================\n") +
    std::string("VTK mapper:\n") + std::string("    ") + std::string(vtkLICClass) +
    std::string("\n") + std::string("OpenGL:\n") + std::string("    ") +
    std::string(vtkGLVersion ? vtkGLVersion : "unknown") + std::string("\n") + std::string("    ") +
    std::string(vtkGLRenderer ? vtkGLRenderer : "unknown") + std::string("\n") +
    std::string("    ") + std::string(vtkGLVendor ? vtkGLVendor : "unknown") + std::string("\n") +
    std::string("====================================================================\n\n\n");
  std::cerr << details << std::endl;

  // If user chose a vector field, select it.
  if (!vectors.empty())
  {
    mapper->SetInputArrayToProcess(
      0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS_THEN_CELLS, vectors.c_str());
  }
  else
  {
    std::cerr << "ERROR: vectors must be set using --vectors." << std::endl;
    return 1;
  }

  // Set the mapper input
  mapper->SetInputDataObject(dataObj);

  if (color_by_mag)
  {
    if (vectors.empty())
    {
      std::cerr << "ERROR: color by mag requires using --vectors." << std::endl;
      vtkAlgorithm::SetDefaultExecutivePrototype(nullptr);
      return 1;
    }

    const char* magVName = nullptr;
    double range[2] = { VTK_FLOAT_MAX, -VTK_FLOAT_MAX };
    vtkCompositeDataSet* cd = dynamic_cast<vtkCompositeDataSet*>(dataObj);
    if (cd)
    {
      vtkCompositeDataIterator* iter = cd->NewIterator();
      for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
      {
        vtkDataSet* ds = dynamic_cast<vtkDataSet*>(iter->GetCurrentDataObject());
        if (ds && ds->GetNumberOfCells())
        {
          vtkDataArray* magV = Magnitude(ds, vectors);
          magVName = magV->GetName();
          Range(magV, range);
          ds->GetPointData()->SetScalars(magV);
          magV->Delete();
        }
      }
      iter->Delete();
    }
    vtkDataSet* ds = dynamic_cast<vtkDataSet*>(dataObj);
    if (ds && ds->GetNumberOfCells())
    {
      vtkDataArray* magV = Magnitude(ds, vectors);
      magVName = magV->GetName();
      Range(magV, range);
      ds->GetPointData()->SetScalars(magV);
      magV->Delete();
    }
    if (!magVName)
    {
      std::cerr << "ERROR: color by mag could not generate magV." << std::endl;
      vtkAlgorithm::SetDefaultExecutivePrototype(nullptr);
      return 1;
    }
    vtkDiscretizableColorTransferFunction* lut = vtkDiscretizableColorTransferFunction::New();
    lut->SetColorSpaceToRGB();
    lut->AddRGBPoint(range[0], 0.0, 0.0, 1.0);
    lut->AddRGBPoint(range[1], 1.0, 0.0, 0.0);
    lut->SetColorSpaceToDiverging();
    lut->Build();
    lut->SetDiscretize(true);
    lut->SetNumberOfValues(num_discrete_colors);
    mapper->SetLookupTable(lut);
    mapper->SetScalarModeToUsePointData();
    mapper->SetScalarVisibility(1);
    mapper->SelectColorArray(magVName);
    mapper->SetUseLookupTableScalarRange(1);
    mapper->SetScalarMode(VTK_SCALAR_MODE_USE_POINT_FIELD_DATA);
    mapper->SetInterpolateScalarsBeforeMapping(interpolate_scalars_before_mapping);
    lut->Delete();
  }

  // Pass parameters.
  vtkSurfaceLICInterface* li = mapper->GetLICInterface();
  li->SetNumberOfSteps(num_steps);
  li->SetStepSize(step_size);
  li->SetEnhancedLIC(enhanced_lic);
  li->SetGenerateNoiseTexture(generate_noise_texture);
  li->SetNoiseType(noise_type);
  li->SetNormalizeVectors(normalize_vectors);
  li->SetNoiseTextureSize(noise_texture_size);
  li->SetNoiseGrainSize(noise_grain_size);
  li->SetMinNoiseValue(min_noise_value);
  li->SetMaxNoiseValue(max_noise_value);
  li->SetNumberOfNoiseLevels(number_of_noise_levels);
  li->SetImpulseNoiseProbability(impulse_noise_prob);
  li->SetImpulseNoiseBackgroundValue(impulse_noise_bg_value);
  li->SetNoiseGeneratorSeed(noise_gen_seed);
  li->SetEnhanceContrast(enhance_contrast);
  li->SetLowLICContrastEnhancementFactor(low_lic_contrast_enhancement_factor);
  li->SetHighLICContrastEnhancementFactor(high_lic_contrast_enhancement_factor);
  li->SetLowColorContrastEnhancementFactor(low_color_contrast_enhancement_factor);
  li->SetHighColorContrastEnhancementFactor(high_color_contrast_enhancement_factor);
  li->SetAntiAlias(anti_alias);
  li->SetColorMode(color_mode);
  li->SetLICIntensity(lic_intensity);
  li->SetMapModeBias(map_mode_bias);
  li->SetMaskOnSurface(mask_on_surface);
  li->SetMaskThreshold(mask_threshold);
  li->SetMaskIntensity(mask_intensity);
  li->SetMaskColor(mask_color_rgb.data());

  vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();

  actor->SetMapper(mapper);
  renderer->AddActor(actor);
  renderer->SetBackground(0.3, 0.3, 0.3);
  mapper = nullptr;
  actor = nullptr;

  vtkCamera* camera = renderer->GetActiveCamera();

  if (camera_config == 1)
  {
    renWin->SetSize(300, 300);
    renderer->SetBackground(0.3216, 0.3412, 0.4314);
    renderer->SetBackground2(0.0, 0.0, 0.1647);
    renderer->GradientBackgroundOn();
    camera->SetFocalPoint(-1.88, -0.98, -1.04);
    camera->SetPosition(13.64, 4.27, -31.59);
    camera->SetViewAngle(30);
    camera->SetViewUp(0.41, 0.83, 0.35);
    renderer->ResetCamera();
  }
  else if (camera_config == 2)
  {
    renWin->SetSize(300, 270);
    camera->SetFocalPoint(0.0, 0.0, 0.0);
    camera->SetPosition(1.0, 0.0, 0.0);
    camera->SetViewAngle(30);
    camera->SetViewUp(0.0, 0.0, 1.0);
    renderer->ResetCamera();
    camera->Zoom(1.2);
  }
  else if (camera_config == 3)
  {
    renWin->SetSize(400, 340);
    camera->SetFocalPoint(0.0, 0.0, 0.0);
    camera->SetPosition(1.0, 0.0, 0.0);
    camera->SetViewAngle(30);
    camera->SetViewUp(0.0, 0.0, 1.0);
    renderer->ResetCamera();
    camera->Zoom(1.4);
  }
  else if (camera_config == 4)
  {
    renWin->SetSize(364, 256);
    renderer->SetBackground(0.3216, 0.3412, 0.4314);
    renderer->SetBackground2(0.0, 0.0, 0.1647);
    renderer->GradientBackgroundOn();
    camera->SetFocalPoint(-30.3, 15.2, 7.0);
    camera->SetPosition(64.7, 3.2, -14.0);
    camera->SetViewAngle(30);
    camera->SetViewUp(0.25, 0.5, 0.8);
    // renderer->ResetCamera();
    camera->Zoom(1.09);
  }

  int retVal = vtkTesting::Test(argc, argv, renWin, 75);
  if (retVal == vtkRegressionTester::DO_INTERACTOR)
  {
    renWin->Render();
    iren->Start();
  }

  renderer = nullptr;
  renWin = nullptr;
  iren = nullptr;

  if ((retVal == vtkTesting::PASSED) || (retVal == vtkTesting::DO_INTERACTOR))
  {
    return 0;
  }
  // test failed.
  return 1;
}
