#include <vtkActor.h>
#include <vtkCallbackCommand.h>
#include <vtkConeSource.h>
#include <vtkCubeSource.h>
#include <vtkCylinderSource.h>
#include <vtkInteractorObserver.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkRendererCollection.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>

#include <array>
#include <iostream>
#include <vector>

namespace {
/**
 * Select the layer to manipulate.
 */
void SelectLayer(vtkObject* caller, long unsigned int eventId, void* clientData,
                 void* callData);

} // namespace

int main(int, char*[])
{
  vtkNew<vtkNamedColors> colors;

  // Set the background color.
  std::array<unsigned char, 4> cubeColor{{250, 128, 114, 255}};
  colors->SetColor("CubeColor", cubeColor.data());
  std::array<unsigned char, 4> bkg{{230, 230, 230, 255}};
  colors->SetColor("BkgColor", bkg.data());

  // Create the rendering windows and renderers.
  std::array<vtkSmartPointer<vtkRenderer>, 2> leftRends;
  std::array<vtkSmartPointer<vtkRenderer>, 2> rightRends;
  vtkNew<vtkRenderWindow> renWin;
  renWin->SetSize(1200, 600);
  renWin->SetPosition(0, 50);
  renWin->SetWindowName("MultipleLayersAndWindows");
  renWin->SetNumberOfLayers(2);

  // Define the renderers setting viewport, layer and color.
  for (int i = 0; i < 2; ++i)
  {
    leftRends[i] = vtkSmartPointer<vtkRenderer>::New();
    leftRends[i]->SetViewport(0, 0, 0.5, 1);
    leftRends[i]->SetBackground(colors->GetColor3d("BkgColor").GetData());
    leftRends[i]->SetLayer(i);
    renWin->AddRenderer(leftRends[i]);
    rightRends[i] = vtkSmartPointer<vtkRenderer>::New();
    rightRends[i]->SetViewport(0.5, 0, 1, 1);
    rightRends[i]->SetBackground(colors->GetColor3d("Linen").GetData());
    rightRends[i]->SetLayer(i);
    renWin->AddRenderer(rightRends[i]);
  }

  vtkNew<vtkRenderWindowInteractor> iRen;
  iRen->SetRenderWindow(renWin);
  vtkNew<vtkInteractorStyleTrackballCamera> style;
  iRen->SetInteractorStyle(style);

  // Create an actor and give it conical geometry.
  vtkNew<vtkConeSource> cone;
  cone->SetResolution(8);
  vtkNew<vtkPolyDataMapper> coneMapper;
  coneMapper->SetInputConnection(cone->GetOutputPort());
  vtkNew<vtkActor> coneActor;
  coneActor->SetMapper(coneMapper);
  coneActor->GetProperty()->SetColor(colors->GetColor3d("CubeColor").GetData());

  // Create an actor and give it cylindrical geometry.
  vtkNew<vtkCylinderSource> cylinder;
  cylinder->SetResolution(8);
  cylinder->SetHeight(0.5);
  cylinder->SetRadius(0.25);
  vtkNew<vtkPolyDataMapper> cylinderMapper;
  cylinderMapper->SetInputConnection(cylinder->GetOutputPort());
  vtkNew<vtkActor> cylinderActor;
  cylinderActor->SetMapper(cylinderMapper);
  cylinderActor->GetProperty()->SetColor(
      colors->GetColor3d("Peacock").GetData());

  // Create an actor and give it cubic geometry.
  vtkNew<vtkCubeSource> cube;
  vtkNew<vtkPolyDataMapper> cubeMapper;
  cubeMapper->SetInputConnection(cube->GetOutputPort());
  vtkNew<vtkActor> cubeActor;
  cubeActor->SetMapper(cubeMapper);
  cubeActor->GetProperty()->SetColor(colors->GetColor3d("CubeColor").GetData());

  // Create an actor and give it spherical geometry.
  vtkNew<vtkSphereSource> sphere;
  sphere->SetRadius(0.25);
  vtkNew<vtkPolyDataMapper> sphereMapper;
  sphereMapper->SetInputConnection(sphere->GetOutputPort());
  vtkNew<vtkActor> sphereActor;
  sphereActor->SetMapper(sphereMapper);
  sphereActor->GetProperty()->SetColor(colors->GetColor3d("Peacock").GetData());

  // Assign our actors to the renderers.
  leftRends[0]->AddActor(cubeActor);      // Cube -> Left renderer, Layer 0
  leftRends[1]->AddActor(sphereActor);    // Sphere -> Left renderer, Layer 1
  rightRends[0]->AddActor(coneActor);     // Cone -> Right renderer, Layer 0
  rightRends[1]->AddActor(cylinderActor); // Cylinder -> Right renderer, Layer 1

  // Hide the layer 0 actors.
  cubeActor->GetProperty()->SetOpacity(0.1);
  coneActor->GetProperty()->SetOpacity(0.1);

  vtkNew<vtkCallbackCommand> keypressCallback;
  keypressCallback->SetCallback(SelectLayer);
  iRen->AddObserver(vtkCommand::KeyPressEvent, keypressCallback);

  // Draw the resulting scene.
  renWin->Render();

  // Set the active cameras.
  leftRends[1]->SetActiveCamera(leftRends[0]->GetActiveCamera());
  rightRends[1]->SetActiveCamera(rightRends[0]->GetActiveCamera());

  iRen->Start();

  return EXIT_SUCCESS;
}

namespace {
void SelectLayer(vtkObject* caller, long unsigned int vtkNotUsed(eventId),
                 void* vtkNotUsed(clientData), void* vtkNotUsed(callData))
{
  vtkRenderWindowInteractor* iRen =
      static_cast<vtkRenderWindowInteractor*>(caller);
  vtkRendererCollection* renderers = iRen->GetRenderWindow()->GetRenderers();
  auto numberOfItems = renderers->GetNumberOfItems();
  if (numberOfItems < 4)
  {
    std::cerr << "We need at least four renderers, we have only "
              << renderers->GetNumberOfItems() << std::endl;
    return;
  }
  std::vector<vtkRenderer*> rens;
  renderers->InitTraversal();
  // Top item is rens[0] and the bottom item is rens[numberOfItems-1].
  for (auto i = 0; i < numberOfItems; ++i)
  {
    rens.push_back(renderers->GetNextItem());
  }
  // Reverse so that the bottom item is rens[0]
  //  and the top item is rens[numberOfItems-1].
  std::reverse(rens.begin(), rens.end());

  /**
   * Set the actor properties.
   *
   * Note: rens is the list of renderers defined in the caller.
   *
   * @param idx Index of the specified renderer.
   * @param opacity Opacity of the first actor in the specified renderer.
   *
   */
  auto SetActorProperties = [&](const int& idx, const double& opacity) {
    auto actor = rens[idx]->GetActors();
    actor->InitTraversal();
    actor->GetNextActor()->GetProperty()->SetOpacity(opacity);
  };

  std::string key = iRen->GetKeySym();

  if (key == "0" || key == "KP_0")
  {
    SetActorProperties(0, 0.1);
    SetActorProperties(1, 0.1);
    SetActorProperties(2, 1);
    SetActorProperties(3, 1);

    std::cout << "Selected layer 0." << std::endl;
    rens[0]->InteractiveOff(); // Cylinder -> Right renderer, Layer 1
    rens[1]->InteractiveOff(); // Sphere -> Left renderer, Layer 1
    rens[2]->InteractiveOn();  // Cone -> Right renderer, Layer 0
    rens[3]->InteractiveOn();  // Cube -> Left renderer, Layer 0
  }
  if (key == "1" || key == "KP_1")
  {
    SetActorProperties(0, 1);
    SetActorProperties(1, 1);
    SetActorProperties(2, 0.1);
    SetActorProperties(3, 0.1);

    std::cout << "Selected layer 1." << std::endl;
    rens[0]->InteractiveOn();  // Cylinder -> Right renderer, Layer 1
    rens[1]->InteractiveOn();  // Sphere -> Left renderer, Layer 1
    rens[2]->InteractiveOff(); // Cone -> Right renderer, Layer 0
    rens[3]->InteractiveOff(); // Cube -> Left renderer, Layer 0
  }
  iRen->Render();
}
} // namespace
