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

  Program: ParaView
  Module:  TestPVApplication.h

  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 "vtkPVApplication.h"
#include "vtkPVApplicationOptions.h"
#include "vtkPVServerApplication.h"
#include "vtkSMOutputPort.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSourceProxy.h"
#include "vtkSmartPointer.h"

#include <chrono>
#include <vtk_cli11.h>

bool DoClientTest(vtkTypeUInt32 sessionId, rxcpp::schedulers::run_loop& runLoop)
{
  auto* app = vtkPVApplication::GetInstance();
  auto* session = app->GetSession(sessionId);
  auto* pxm = session->GetProxyManager();
  auto proxy =
    vtk::TakeSmartPointer(vtkSMSourceProxy::SafeDownCast(pxm->NewProxy("sources", "SphereSource")));
  vtkSMPropertyHelper(proxy, "PhiResolution").Set(80);
  vtkSMPropertyHelper(proxy, "ThetaResolution").Set(80);
  proxy->UpdateVTKObjects();
  proxy->UpdatePipeline(0.0);

  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);
  app->WaitForExit(runLoop, std::chrono::milliseconds(100));
  return true;
}

int TestPVApplication(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.
  std::vector<char*> args;
  vtkSmartPointer<vtkPVCoreApplication> app;
  for (int cc = 0; cc < argc; ++cc)
  {
    if (strcmp(argv[cc], "--server") == 0)
    {
      app = vtk::TakeSmartPointer(vtkPVServerApplication::New());
    }
    else if (strcmp(argv[cc], "--client") == 0)
    {
      app = vtk::TakeSmartPointer(vtkPVApplication::New());
    }
    else if (strcmp(argv[cc], "--batch") == 0)
    {
    }
    else
    {
      args.push_back(argv[cc]);
    }
  }

  if (!app)
  {
    const int rank = environment.GetRank();
    vtkLogIfF(ERROR, rank == 0, "Failed to determine app type. Quitting!");
    return EXIT_SUCCESS;
  }

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

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

  // populate CLI with ParaView options
  app->GetOptions()->Populate(cli);

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

  app->Initialize(args[0], rxcpp::observe_on_run_loop(runLoop), environment.GetController());
  if (auto* pvapp = vtkPVApplication::SafeDownCast(app)) // builtin-client
  {
    pvapp->CreateBuiltinSession()
      .observe_on(rxcpp::observe_on_run_loop(runLoop))
      .subscribe([&](vtkTypeUInt32 sessionId) { DoClientTest(sessionId, runLoop); });
  }
  app->WaitForExit(runLoop, std::chrono::milliseconds(100));
  app->Finalize();
  return app->GetExitCode();
}
