/*=========================================================================

Program:   ParaView
Module:    TestPTSPipelineBuilderWithRendering.cxx

Copyright (c) Kitware, Inc.
All rights reserved.
See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.

This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
 * Tests vtkPTSPipelineBuilder rendering API.
 */
#include "vtkClientSession.h"
#include "vtkDataObject.h"
#include "vtkDistributedEnvironment.h"
#include "vtkLogger.h"
#include "vtkPTSPipelineBuilder.h"
#include "vtkPTSPropertyManager.h"
#include "vtkPVApplication.h"
#include "vtkPVApplicationOptions.h"
#include "vtkRegressionTestImage.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSMProxy.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMViewProxy.h"
#include "vtkSmartPointer.h"
#include "vtkTestUtilities.h"

#include <vtk_cli11.h>

#define VALIDATE(x)                                                                                \
  if (!(x))                                                                                        \
  {                                                                                                \
    vtkLogF(ERROR, "Validating : (" #x ")...failed!");                                             \
  }

template <typename Predicate>
bool ProcessEventsUntil(rxcpp::schedulers::run_loop& runLoop, Predicate stopProcessing,
  const std::chrono::milliseconds& timeout = std::chrono::milliseconds(1000))
{
  const auto start = std::chrono::system_clock::now();
  do
  {
    std::this_thread::sleep_for(std::chrono::milliseconds(5));
    while ((!runLoop.empty() && runLoop.peek().when < runLoop.now()))
    {
      runLoop.dispatch();
    }
  } while (!stopProcessing() && timeout > (std::chrono::system_clock::now() - start));
  return stopProcessing();
}

bool DoPTSPipelineBuilderTestWithRendering(
  int argc, char** argv, vtkClientSession* session, rxcpp::schedulers::run_loop& runLoop)
{

  vtkNew<vtkPTSPipelineBuilder> pipelineBuilder;
  vtkNew<vtkPTSPropertyManager> propertyManager;

  pipelineBuilder->SetSession(session);

  // Create source proxy
  vtkSmartPointer<vtkSMSourceProxy> source = nullptr;
  bool done = false;
  pipelineBuilder->CreateSource("sources", "SphereSource")
    .subscribe([&source, &done](vtkSMSourceProxy* newProxy) {
      VALIDATE(newProxy);
      source = vtk::TakeSmartPointer(newProxy);
      done = true;
    });

  ProcessEventsUntil(runLoop, [&done]() { return done; });
  VALIDATE(source != nullptr);

  propertyManager->SetPropertyValue(source, "EndPhi", 90);
  propertyManager->SetPropertyValue(source, "Center", { 1, 2, 3 });

  // Create a filter
  vtkSmartPointer<vtkSMSourceProxy> filter = nullptr;
  done = false;
  pipelineBuilder->CreateFilter("filters", "ShrinkFilter", source)
    .subscribe([&filter, &done](vtkSMSourceProxy* newProxy) {
      VALIDATE(newProxy);
      filter = vtk::TakeSmartPointer(newProxy);
      done = true;
    });

  ProcessEventsUntil(runLoop, [&done]() { return done; });
  VALIDATE(filter != nullptr);

  // Create View
  vtkSMViewProxy* view = nullptr;
  done = false;
  pipelineBuilder->CreateView("views", "RenderView")
    .subscribe([&view, &done](vtkSMViewProxy* newProxy) {
      VALIDATE(newProxy);
      view = vtk::TakeSmartPointer(newProxy);
      done = true;
    });

  ProcessEventsUntil(runLoop, [&done]() { return done; });

  // Create a Geometry representation for the output of the filter
  vtkSmartPointer<vtkSMProxy> representation = nullptr;

  done = false;
  pipelineBuilder->CreateRepresentation(filter, 0, view, "GeometryRepresentation")
    .subscribe([&representation, &done](vtkSMProxy* newProxy) {
      VALIDATE(newProxy);
      representation = vtk::TakeSmartPointer(newProxy);
      done = true;
    });

  ProcessEventsUntil(runLoop, [&done]() { return done; });

  done = false;
  view->Update().subscribe([view, &done](bool) {
    view->ResetCameraUsingVisiblePropBounds();
    done = true;
  });

  ProcessEventsUntil(runLoop, [&done]() { return done; });

  int retVal = vtkRegressionTestImage(view->GetRenderWindow());

  if (retVal == vtkTesting::DO_INTERACTOR)
  {
    vtkNew<vtkRenderWindowInteractor> iren;
    iren->SetRenderWindow(view->GetRenderWindow());
    iren->Initialize();

    while (!iren->GetDone())
    {
      ProcessEventsUntil(
        runLoop, []() { return true; }, std::chrono::milliseconds(5));
      iren->ProcessEvents();
    }
  }

  // representation should not prevent deleting the proxy

  vtkSMSessionProxyManager* pxm = session->GetProxyManager();
  vtkTypeInt32 representationId = representation->GetGlobalID();
  vtkTypeInt32 shrinkId = filter->GetGlobalID();
  done = false;
  pipelineBuilder->DeleteProxy(filter).subscribe([&done](bool status) {
    VALIDATE(status);
    done = true;
  });

  ProcessEventsUntil(runLoop, [&done]() { return done; });
  // release local references, FindProxy looks into all proxies not just registered
  filter = nullptr;
  representation = nullptr;
  VALIDATE(pxm->FindProxy(representationId) == nullptr);
  VALIDATE(pxm->FindProxy(shrinkId) == nullptr);

  return true;
}

int TestPTSPipelineBuilderWithRendering(int argc, char* argv[])
{
  // Create the primary event-loop for the application.
  rxcpp::schedulers::run_loop runLoop;

  // initialize the MPI environment.
  vtkDistributedEnvironment environment(&argc, &argv);
  const int rank = environment.GetRank();

  // determine what mode this test is running under
  // and create appropriate application type.
  vtkNew<vtkPVApplication> app;

  // process command line arguments.
  CLI::App cli("TestPTSPipelineBuilderWithRendering application");

  // let's use nicer formatter for help text.
  vtkPVApplicationOptions::SetupDefaults(cli);

  app->GetOptions()->Populate(cli);

  try
  {
    cli.parse(argc, argv);
  }
  catch (const CLI::ParseError& e)
  {
    return (rank == 0 ? cli.exit(e) : EXIT_SUCCESS);
  }

  app->Initialize(argv[0], rxcpp::observe_on_run_loop(runLoop), environment.GetController());

  auto* options = app->GetOptions();

  rxcpp::observable<vtkTypeUInt32> sessionIdObservable;

  if (options->GetServerURL().empty())
  {
    sessionIdObservable = app->CreateBuiltinSession();
  }
  else
  {
    sessionIdObservable = app->CreateRemoteSession(options->GetServerURL());
  }

  sessionIdObservable.subscribe([&](vtkTypeUInt32 id) {
    DoPTSPipelineBuilderTestWithRendering(argc, argv, app->GetSession(id), runLoop);
  });
  app->WaitForExit(runLoop, std::chrono::milliseconds(100));
  app->Finalize();
  return app->GetExitCode();
}
