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

/**
 * This test creates a few triangle and moves them around in the scene. The frustum culler is
 * expected to pick up on the recomputed bounds when the actors are moved around and the culling
 * should cull accordingly to the position of the actors.
 * The number of props rendered by the renderer + compute frustum culler at each frame is then
 * compared to a reference list to make sure that the culler indeed culled (or not) props as it was
 * supposed to
 */

#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkCullerCollection.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkNew.h"
#include "vtkObject.h"
#include "vtkPointData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSphereSource.h"
#include "vtkUnsignedCharArray.h"
#include "vtkWebGPUComputeFrustumCuller.h"
#include "vtkXMLMultiBlockDataReader.h"

//------------------------------------------------------------------------------
vtkSmartPointer<vtkActor> CreateTriangle(
  float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3)
{
  // the one and only true triangle
  vtkSmartPointer<vtkPolyData> polydata = vtkSmartPointer<vtkPolyData>::New();
  vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
  points->InsertPoint(0, x1, y1, z1);
  points->InsertPoint(1, x2, y2, z2);
  points->InsertPoint(2, x3, y3, z3);
  polydata->SetPoints(points);
  vtkSmartPointer<vtkCellArray> triangle = vtkSmartPointer<vtkCellArray>::New();
  triangle->InsertNextCell({ 0, 1, 2 });
  polydata->SetPolys(triangle);

  vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->DebugOn();
  mapper->SetInputData(polydata);

  vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);

  return actor;
}

//------------------------------------------------------------------------------
int TestComputeFrustumCulling(int argc, char* argv[])
{
  // How many props are expected to be rendered at each frame (with modification of the props in
  // between the frames)
  std::vector<int> rendererPropCountsReference = { 0, 1, 2, 2, 1 };
  // How many props were actually renderer
  std::vector<int> renderedPropCounts;

  vtkNew<vtkRenderWindow> renWin;
  renWin->SetWindowName(__func__);
  renWin->SetMultiSamples(0);

  vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
  renWin->AddRenderer(renderer);

  vtkNew<vtkCamera> camera;
  camera->SetFocalPoint(0, 0.25, -1);
  renderer->SetActiveCamera(camera);
  renderer->SetBackground(0.2, 0.3, 0.4);

  // Removing the default culler
  renderer->GetCullers()->RemoveAllItems();

  // Adding the WebGPU compute shader frustum culler
  vtkNew<vtkWebGPUComputeFrustumCuller> webgpuFrustumCuller;
  renderer->GetCullers()->AddItem(webgpuFrustumCuller);

  renderer->AddActor(CreateTriangle(-5, 0, -3, -3, 0, -3, -4, 1, -3));
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());

  // This one should not be culled
  vtkSmartPointer<vtkActor> secondTriangle = CreateTriangle(-1, 0.5, -3, 1, 0.5, -3, 0, 1.5, -3);
  renderer->AddActor(secondTriangle);
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());

  // This one should not be culled
  vtkSmartPointer<vtkActor> thirdTriangle = CreateTriangle(0, 0.5, -3, 1, 0.25, -5, 0.5, 1.05, -4);
  renderer->AddActor(thirdTriangle);
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());

  // Moving the second triangle down, should still not be culled
  secondTriangle->SetPosition(0, -0.5, 0);
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());

  // Moving the third triangle behind the camera, should be culled
  thirdTriangle->SetPosition(0, 0, 10);
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());

  for (int i = 0; i < renderedPropCounts.size(); i++)
  {
    if (renderedPropCounts[i] != rendererPropCountsReference[i])
    {
      std::string expectedSequence;
      std::string actualSequence;
      for (int seqIndex = 0; seqIndex < rendererPropCountsReference.size(); seqIndex++)
      {
        expectedSequence += std::to_string(rendererPropCountsReference[seqIndex]) + ", ";
        actualSequence += std::to_string(renderedPropCounts[seqIndex]) + ", ";
      }

      vtkLog(ERROR,
        "The right number of props wasn't rendered. Expected sequence of rendered props was: "
          << expectedSequence << " but the actual sequence was: " << actualSequence);
      return EXIT_FAILURE;
    }
  }

  return EXIT_SUCCESS;
}
