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

/**
 * Makes sure that the vtkWebGPUComputeOcclusion culler's internal machinery for resizing the
 * hierarchical z-buffer works properly.
 *
 * The test renders some props and then resizes the window. If the occlusion culler handles the
 * resizing properly, the number of props culled shouldn't have change (and we also shouldn't get
 * any WebGPU validation errors)
 */

#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 "vtkWebGPUComputeOcclusionCuller.h"
#include "vtkWebGPURenderWindow.h"
#include "vtkXMLMultiBlockDataReader.h"

//------------------------------------------------------------------------------
namespace
{
vtkSmartPointer<vtkActor> CreateTriangle(
  double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3)
{
  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<vtkUnsignedCharArray> colors = vtkSmartPointer<vtkUnsignedCharArray>::New();
  colors->SetNumberOfComponents(4);
  colors->SetNumberOfTuples(3);
  for (int i = 0; i < 3; i++)
  {
    colors->InsertComponent(i, 0, 255);
    colors->InsertComponent(i, 1, 255);
    colors->InsertComponent(i, 2, 255);
    colors->InsertComponent(i, 3, 255);
  }
  polydata->GetPointData()->SetScalars(colors);

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

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

  return actor;
}

void RenderNewTriangle(vtkRenderWindow* renWin, vtkRenderer* renderer,
  std::vector<int>& renderedPropCounts, double x1, double y1, double z1, double x2, double y2,
  double z2, double x3, double y3, double z3)
{
  renderer->AddActor(CreateTriangle(x1, y1, z1, x2, y2, z2, x3, y3, z3));
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());
}

void CheckRenderCount(
  const std::vector<int>& renderedPropCounts, const std::vector<int>& renderedPropCountsReference)
{
  for (int i = 0; i < renderedPropCounts.size(); i++)
  {
    if (renderedPropCounts[i] != renderedPropCountsReference[i])
    {
      std::string expectedSequence;
      std::string actualSequence;
      for (int seqIndex = 0; seqIndex < renderedPropCountsReference.size(); seqIndex++)
      {
        expectedSequence += std::to_string(renderedPropCountsReference[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);

      std::exit(EXIT_FAILURE);
    }
  }
}

}

#include "vtkActorCollection.h"
#include "vtkGLTFImporter.h"
#include "vtkOutlineFilter.h"
#include "vtkPNGWriter.h"
#include "vtkTransformFilter.h"
#include "vtkWindowToImageFilter.h"
vtkSmartPointer<vtkRenderer> ReadGLTF(const char* filepath)
{
  vtkNew<vtkGLTFImporter> importer;
  importer->SetFileName(filepath);
  importer->Update();
  return importer->GetRenderer();
}

void screenshotCallback(vtkObject* caller, unsigned long eid, void* cliendata, void* calldata)
{
  vtkRenderWindow* renWin = reinterpret_cast<vtkRenderWindow*>(cliendata);

  // Screenshot
  vtkNew<vtkWindowToImageFilter> windowToImageFilter;
  windowToImageFilter->SetInput(renWin);
#if VTK_MAJOR_VERSION >= 8 || VTK_MAJOR_VERSION == 8 && VTK_MINOR_VERSION >= 90
  windowToImageFilter->SetScale(2); // image quality
#else
  windowToImageFilter->SetMagnification(2); // image quality
#endif
  windowToImageFilter->SetInputBufferTypeToRGBA(); // also record the alpha
                                                   // (transparency) channel
  windowToImageFilter->ReadFrontBufferOff();       // read from the back buffer
  windowToImageFilter->Update();

  vtkNew<vtkPNGWriter> writer;
  writer->SetFileName("screenshot2.png");
  writer->SetInputConnection(windowToImageFilter->GetOutputPort());
  writer->Write();
}

//------------------------------------------------------------------------------
int TestComputeOcclusionCullingResize(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> renderedPropCountsReference = { 1, 2, 3, 4, 5, 1 };
  // How many props were actually renderer
  std::vector<int> renderedPropCounts;

  vtkNew<vtkRenderWindow> renWin;
  renWin->SetWindowName(__func__);
  renWin->SetMultiSamples(0);
  renWin->SetSize(1280, 720);

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

  {
    {
      {
        {
          {
            vtkNew<vtkRenderWindowInteractor> INTERACTOR_REMOVE_THIS;
            INTERACTOR_REMOVE_THIS->SetRenderWindow(renWin);

            vtkNew<vtkInteractorStyleTrackballCamera> style;
            INTERACTOR_REMOVE_THIS->SetInteractorStyle(style);
            vtkSmartPointer<vtkRenderer> gltfRenderer =
              ReadGLTF("/home/tom/Desktop/GeometricData/Bistro.gltf");
            gltfRenderer->SetBackground(0.2, 0.3, 0.4);
            renWin->AddRenderer(gltfRenderer);

            //
            //
            //
            gltfRenderer->GetCullers()->RemoveAllItems();
            // Adding the WebGPU compute shader frustum culler
            vtkNew<vtkWebGPUComputeOcclusionCuller> webgpuOcclusionCuller;
            webgpuOcclusionCuller->SetRenderWindow(vtkWebGPURenderWindow::SafeDownCast(renWin));
            gltfRenderer->GetCullers()->AddItem(webgpuOcclusionCuller);
            //
            //
            //

            gltfRenderer->SetBackground(0.2, 0.3, 0.4);
            renWin->Render();

            int ACTOR_BOUNDS_INDEX = 1;
            vtkActor* actorOfInterest;

            std::cout << gltfRenderer->GetActors()->GetNumberOfItems() << " actors" << std::endl;

            int index = 0;
            vtkObject* o;
            vtkPropCollection* actors = gltfRenderer->GetActors();
            actors->InitTraversal();
            // Try and iterate through the actors.
            while ((o = actors->GetNextItemAsObject()))
            {
              assert(o->IsA("vtkActor") && "How did a non-actor get into an actor collection?");
              actorOfInterest = vtkActor::SafeDownCast(o);

              if (index++ == ACTOR_BOUNDS_INDEX)
                break;
            }

            double* bounds;
            bounds = actorOfInterest->GetBounds();

            std::array<vtkVector4d, 8> boundVertices = { vtkVector4d(
                                                           bounds[0], bounds[2], bounds[4], 1.0f),
              vtkVector4d(bounds[0], bounds[2], bounds[5], 1.0f),
              vtkVector4d(bounds[0], bounds[3], bounds[4], 1.0f),
              vtkVector4d(bounds[0], bounds[3], bounds[5], 1.0f),
              vtkVector4d(bounds[1], bounds[2], bounds[4], 1.0f),
              vtkVector4d(bounds[1], bounds[2], bounds[5], 1.0f),
              vtkVector4d(bounds[1], bounds[3], bounds[4], 1.0f),
              vtkVector4d(bounds[1], bounds[3], bounds[5], 1.0f) };

            vtkSmartPointer<vtkPolyData> boundsPolydata = vtkSmartPointer<vtkPolyData>::New();
            vtkSmartPointer<vtkPoints> boundsPoints = vtkSmartPointer<vtkPoints>::New();
            for (int i = 0; i < 8; ++i)
            {
              boundsPoints->InsertNextPoint(
                boundVertices[i][0], boundVertices[i][1], boundVertices[i][2]);
            }

            boundsPolydata->SetPoints(boundsPoints);
            vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New();
            vtkIdType cell[2] = { 0, 1 };
            cells->InsertNextCell(2, cell);
            cell[0] = 0;
            cell[1] = 2;
            cells->InsertNextCell(2, cell);
            cell[0] = 3;
            cell[1] = 2;
            cells->InsertNextCell(2, cell);
            cell[0] = 3;
            cell[1] = 1;
            cells->InsertNextCell(2, cell);
            cell[0] = 4;
            cell[1] = 5;
            cells->InsertNextCell(2, cell);
            cell[0] = 4;
            cell[1] = 6;
            cells->InsertNextCell(2, cell);
            cell[0] = 7;
            cell[1] = 5;
            cells->InsertNextCell(2, cell);
            cell[0] = 7;
            cell[1] = 6;
            cells->InsertNextCell(2, cell);
            cell[0] = 1;
            cell[1] = 5;
            cells->InsertNextCell(2, cell);
            cell[0] = 0;
            cell[1] = 4;
            cells->InsertNextCell(2, cell);
            cell[0] = 2;
            cell[1] = 6;
            cells->InsertNextCell(2, cell);
            cell[0] = 3;
            cell[1] = 7;
            cells->InsertNextCell(2, cell);
            boundsPolydata->SetLines(cells);

            vtkSmartPointer<vtkPolyDataMapper> boundsMapper =
              vtkSmartPointer<vtkPolyDataMapper>::New();
            boundsMapper->SetInputData(boundsPolydata);

            vtkSmartPointer<vtkActor> boundsActor = vtkSmartPointer<vtkActor>::New();
            boundsActor->SetMapper(boundsMapper);
            boundsActor->GetProperty()->SetColor(1, 0, 0);

            // gltfRenderer->AddActor(boundsActor);

            vtkNew<vtkCallbackCommand> screenshotcallbackcommand;
            screenshotcallbackcommand->SetCallback(screenshotCallback);
            screenshotcallbackcommand->SetClientData(renWin);
            INTERACTOR_REMOVE_THIS->AddObserver(
              vtkCommand::KeyPressEvent, screenshotcallbackcommand);

            INTERACTOR_REMOVE_THIS->Start();

            return 1;
          }
        }
      }
    }
  }

  // {
  //   {
  //     {
  //       {
  //         {
  //           vtkNew<vtkRenderWindowInteractor> INTERACTOR_REMOVE_THIS;
  //           INTERACTOR_REMOVE_THIS->SetRenderWindow(renWin);

  //           vtkNew<vtkInteractorStyleTrackballCamera> style;
  //           INTERACTOR_REMOVE_THIS->SetInteractorStyle(style);
  //           vtkSmartPointer<vtkRenderer> gltfRenderer =
  //             ReadGLTF("/home/tom/Desktop/BlenderScenes/BistroFloor2.gltf");
  //           gltfRenderer->SetBackground(0.2, 0.3, 0.4);
  //           renWin->AddRenderer(gltfRenderer);

  //           gltfRenderer->GetCullers()->RemoveAllItems();

  //           // Adding the WebGPU compute shader frustum culler
  //           vtkNew<vtkWebGPUComputeOcclusionCuller> webgpuOcclusionCuller;
  //           webgpuOcclusionCuller->SetRenderWindow(vtkWebGPURenderWindow::SafeDownCast(renWin));
  //           gltfRenderer->GetCullers()->AddItem(webgpuOcclusionCuller);
  //           gltfRenderer->SetBackground(0.2, 0.3, 0.4);

  //           vtkNew<vtkMatrix4x4> actorMatrix;
  //           vtkNew<vtkTransform> actorTransform;
  //           actorTransform->SetMatrix(actorMatrix);
  //           gltfRenderer->GetActors()->GetLastActor()->GetModelToWorldMatrix(actorMatrix);

  //           vtkNew<vtkTransformFilter> transformFilter;
  //           transformFilter->SetTransform(actorTransform);
  //           transformFilter->SetInputDataObject(
  //             gltfRenderer->GetActors()->GetLastActor()->GetMapper()->GetInput());
  //           vtkNew<vtkOutlineFilter> outlineFilter;
  //           outlineFilter->SetInputConnection(transformFilter->GetOutputPort());

  //           vtkNew<vtkPolyDataMapper> outlineMapper;
  //           outlineMapper->SetInputConnection(outlineFilter->GetOutputPort());

  //           vtkNew<vtkActor> outlineActor;
  //           outlineActor->SetMapper(outlineMapper);

  //           gltfRenderer->AddActor(outlineActor);

  //           INTERACTOR_REMOVE_THIS->Start();

  //           return 1;
  //         }
  //       }
  //     }
  //   }
  // }

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

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

  // Adding the WebGPU compute shader frustum culler
  vtkNew<vtkWebGPUComputeOcclusionCuller> webgpuOcclusionCuller;
  webgpuOcclusionCuller->SetRenderWindow(vtkWebGPURenderWindow::SafeDownCast(renWin));
  renderer->GetCullers()->AddItem(webgpuOcclusionCuller);

  // Small triangle 1
  RenderNewTriangle(renWin, renderer, renderedPropCounts, -1, 0, -5, -0.5, 0.0, -5, -0.75, 0.5, -5);
  CheckRenderCount(renderedPropCounts, renderedPropCountsReference);

  // Small triangle 2
  RenderNewTriangle(
    renWin, renderer, renderedPropCounts, -0.5, 0, -5, 0.0, 0.0, -5, -0.25, 0.5, -5);
  CheckRenderCount(renderedPropCounts, renderedPropCountsReference);

  // Small triangle 3
  RenderNewTriangle(renWin, renderer, renderedPropCounts, 0, 0, -5, 0.5, 0.0, -5, 0.25, 0.5, -5);
  CheckRenderCount(renderedPropCounts, renderedPropCountsReference);

  // Small triangle 4
  RenderNewTriangle(renWin, renderer, renderedPropCounts, 0.5, 0, -5, 1.0, 0.0, -5, 0.75, 0.5, -5);
  CheckRenderCount(renderedPropCounts, renderedPropCountsReference);

  // Big triangle that covers all the small triangles. It is expected that that the first frame
  // rendered with the big triangle doesn't cull the small triangles
  RenderNewTriangle(renWin, renderer, renderedPropCounts, -1, -0.5, -1, 5.0, -0.5, -1, -1, 1.5, -1);
  CheckRenderCount(renderedPropCounts, renderedPropCountsReference);

  // However, if we render another frame, still with the big triangle in front, all the small
  // triangles should be culled
  renWin->Render();
  renderedPropCounts.push_back(renderer->GetNumberOfPropsRendered());
  CheckRenderCount(renderedPropCounts, renderedPropCountsReference);

  return EXIT_SUCCESS;
}
