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

  Program:   ParaView
  Module:    TestBasicRenderView.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.

=========================================================================*/
#include "vtkClientSession.h"
#include "vtkDistributedEnvironment.h"
#include "vtkLogger.h"
#include "vtkNew.h"
#include "vtkPVApplication.h"
#include "vtkPVApplicationOptions.h"
#include "vtkPVDataInformation.h"
#include "vtkRegressionTestImage.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSMInputProperty.h"
#include "vtkSMOutputPort.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxyProperty.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMViewProxy.h"
#include "vtkSmartPointer.h"

#include <chrono>
#include <vtk_cli11.h>
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 DoTest(int argc, char** argv, vtkClientSession* session, rxcpp::schedulers::run_loop& runLoop)
{
  auto* pxm = session->GetProxyManager();
  auto proxy = vtk::TakeSmartPointer(pxm->NewProxy("sources", "SphereSource"));
  vtkSMPropertyHelper(proxy, "PhiResolution").Set(80);
  vtkSMPropertyHelper(proxy, "ThetaResolution").Set(80);
  proxy->UpdateVTKObjects();

  auto shrink =
    vtk::TakeSmartPointer(vtkSMSourceProxy::SafeDownCast(pxm->NewProxy("filters", "ShrinkFilter")));
  vtkSMPropertyHelper(shrink, "Input").Set(proxy, 0);
  shrink->UpdateVTKObjects();

  proxy->UpdateInformation().subscribe(
    [](bool changed) { vtkLogF(INFO, "proxy properties have been updated (%d)", changed); });
  shrink->UpdatePipeline(0.0);

  auto repr = vtk::TakeSmartPointer(pxm->NewProxy("representations", "GeometryRepresentation"));
  vtkSMPropertyHelper(repr, "Input").Set(shrink, 0);
  repr->UpdateVTKObjects();

  auto viewProxy =
    vtk::TakeSmartPointer(vtkSMViewProxy::SafeDownCast(pxm->NewProxy("views", "RenderView")));
  vtkSMPropertyHelper(viewProxy, "Representations").Add(repr);
  viewProxy->UpdateVTKObjects();

  bool done = false;
  viewProxy->Update().subscribe([viewProxy, &done](bool status) {
    viewProxy->ResetCameraUsingVisiblePropBounds();
    done = true;
  });
  ProcessEventsUntil(runLoop, [&done]() { return done; });

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

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

    while (!iren->GetDone())
    {
      ProcessEventsUntil(
        runLoop, []() { return true; }, std::chrono::milliseconds(5));
      iren->ProcessEvents();
    }
  }
  viewProxy = nullptr;
  auto* app = vtkPVApplication::GetInstance();
  app->WaitForExit(runLoop,
    std::chrono::milliseconds(
      500));               // wait for render to complete. We shouldn't need to do this really.
  app->Exit(EXIT_SUCCESS); // it should use retVal once regression is set up
  return true;
}

int TestBasicRenderView(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("Test View/Representation 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 (app->GetRank() == 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) { DoTest(argc, argv, app->GetSession(id), runLoop); });
  app->WaitForExit(runLoop, std::chrono::milliseconds(100));
  app->Finalize();
  return app->GetExitCode();
}
