#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkCleanPolyData.h>
#include <vtkContourFilter.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPNGWriter.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkVersion.h>
#include <vtkWindowToImageFilter.h>
#include <vtkXMLPolyDataReader.h>

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

  vtkSmartPointer<vtkPolyData> data3d;

  if (argc > 1)
  {
    // E.g. Bunny.vtp
    vtkNew<vtkXMLPolyDataReader> reader;
    reader->SetFileName(argv[1]);
    reader->Update();

    data3d = reader->GetOutput();
  }
  else
  {
    vtkNew<vtkSphereSource> source;
    source->SetCenter(0.0, 0.0, 5.0);
    source->SetRadius(2.0);
    source->SetPhiResolution(20.);
    source->SetThetaResolution(20.);
    source->Update();

    data3d = source->GetOutput();
  }

  double bounds_data[6], center_data[3];
  data3d->GetBounds(bounds_data);
  data3d->GetCenter(center_data);

  // Black and white scene with the data in order to print the view.
  vtkNew<vtkPolyDataMapper> mapperData;
  mapperData->SetInputData(data3d);

  vtkNew<vtkActor> actorData;
  actorData->SetMapper(mapperData);
  actorData->GetProperty()->SetColor(colors->GetColor3d("Black").GetData());

  vtkNew<vtkRenderer> tmpRend;
  tmpRend->SetBackground(colors->GetColor3d("White").GetData());

  tmpRend->AddActor(actorData);
  tmpRend->ResetCamera();
  tmpRend->GetActiveCamera()->SetParallelProjection(1);

  vtkNew<vtkRenderWindow> tmpRW;
  tmpRW->SetOffScreenRendering(1);
  tmpRW->AddRenderer(tmpRend);

  tmpRW->Render();

  // Get a print of the window.
  vtkNew<vtkWindowToImageFilter> windowToImageFilter;
  windowToImageFilter->SetInput(tmpRW);
#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->Update();

  /*
  vtkNew<vtkPNGWriter> writer;
  writer->SetFileName("ExternalContourWindowPrint.png");
  writer->SetInputConnection(windowToImageFilter->GetOutputPort());
  writer->Write();
  */

  // Extract the silhouette corresponding to the black limit of the image.
  vtkNew<vtkContourFilter> contFilter;
  contFilter->SetInputConnection(windowToImageFilter->GetOutputPort());
  contFilter->SetValue(0, 255);
  contFilter->Update();

  // Clean the data.
  vtkNew<vtkCleanPolyData> clean;
  clean->SetInputData(contFilter->GetOutput());
  clean->Update();
  auto contour = clean->GetOutput();

  // Make the contour coincide with the data.

  double bounds_contour[6], center_contour[3];
  double transX = 0.0, transY = 0.0, transZ = 0.0, ratioX = 0.0, ratioY = 0.0;
  contour->GetBounds(bounds_contour);

  ratioX = (bounds_data[1] - bounds_data[0]) /
      (bounds_contour[1] - bounds_contour[0]);
  ratioY = (bounds_data[3] - bounds_data[2]) /
      (bounds_contour[3] - bounds_contour[2]);

  // Rescale the contour so that it shares the same bounds as the
  // input data.
  vtkNew<vtkTransform> transform1;
  transform1->Scale(ratioX, ratioY, 1.0);

  vtkNew<vtkTransformPolyDataFilter> tfilter1;
  tfilter1->SetInputData(contour);
  tfilter1->SetTransform(transform1);
  tfilter1->Update();

  contour = tfilter1->GetOutput();

  // Translate the contour so that it shares the same center as the
  // input data.
  contour->GetCenter(center_contour);
  transX = center_data[0] - center_contour[0];
  transY = center_data[1] - center_contour[1];
  transZ = center_data[2] - center_contour[2];

  vtkNew<vtkTransform> transform2;
  transform2->Translate(transX, transY, transZ);

  vtkNew<vtkTransformPolyDataFilter> tfilter2;
  tfilter2->SetInputData(contour);
  tfilter2->SetTransform(transform2);
  tfilter2->Update();

  contour = tfilter2->GetOutput();

  // Render the result : Input data + resulting silhouette

  // Updating the color of the data.
  actorData->GetProperty()->SetColor(colors->GetColor3d("MistyRose").GetData());

  // Create a mapper and actor of the silhouette.
  vtkNew<vtkPolyDataMapper> mapperContour;
  mapperContour->SetInputData(contour);

  vtkNew<vtkActor> actor_contour;
  actor_contour->SetMapper(mapperContour);
  actor_contour->GetProperty()->SetLineWidth(2.0);

  // Create the renderers, render window, and interactor.

  vtkNew<vtkRenderer> renderer1;
  renderer1->SetViewport(0.0, 0.0, 0.5, 1.0);
  renderer1->SetBackground(colors->GetColor3d("DarkSlateGray").GetData());
  vtkNew<vtkRenderer> renderer2;
  renderer2->SetViewport(0.5, 0.0, 1.0, 1.0);
  renderer2->SetBackground(colors->GetColor3d("MidnightBlue").GetData());

  vtkNew<vtkRenderWindow> renderWindow;
  renderWindow->SetSize(400, 400);
  renderWindow->SetWindowName("ExternalContour");
  renderWindow->AddRenderer(renderer1);
  renderWindow->AddRenderer(renderer2);

  vtkNew<vtkInteractorStyleTrackballCamera> style;

  vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
  renderWindowInteractor->SetRenderWindow(renderWindow);
  renderWindowInteractor->SetInteractorStyle(style);

  // Add the actors.
  renderer1->AddActor(actorData);
  // renderer1->AddActor(actor_contour);
  renderer2->AddActor(actor_contour);

  // Set the same initial view as in renderer1.
  renderWindow->Render();
  vtkNew<vtkCamera> ren2Camera;
  ren2Camera->DeepCopy(renderer1->GetActiveCamera());
  renderer2->SetActiveCamera(ren2Camera);
  // If you want the views linked.
  // renderer2->SetActiveCamera(renderer1->GetActiveCamera());

  renderWindow->Render();
  renderWindowInteractor->Start();

  return EXIT_SUCCESS;
}
