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

Program:   ParaView
Module:    TestPTSFileSystem.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 vtkPTSFileSystem.
 */

#include "vtkClientSession.h"
#include "vtkDistributedEnvironment.h"
#include "vtkLogger.h"
#include "vtkPTSFileSystem.h"
#include "vtkPVApplication.h"
#include "vtkPVApplicationOptions.h"
#include "vtkTestUtilities.h"
#include "vtkrxcpp/rx-util.hpp"

#include <vtksys/SystemTools.hxx>

#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 DoPTSFileSystemTest(vtkClientSession* session, rxcpp::schedulers::run_loop& runLoop)
{
  vtkNew<vtkPTSFileSystem> fservice;
  fservice->SetLocation(vtkClientSession::DATA_SERVER);

  const std::string baseDirectory = vtksys::SystemTools::GetCurrentWorkingDirectory();

  // if session is not set an error is raised
  int errorsCaught = 0;
  fservice->ListDirectory(baseDirectory)
    .subscribe(
      // on_next
      [](vtkSmartPointer<vtkPVFileInformation> /*fileInfo*/) { VALIDATE(false); },
      // on_error
      [&errorsCaught](rxcpp::util::error_ptr ep) {
        errorsCaught++;
        vtkLogF(INFO, "%s", rxcpp::util::what(ep).c_str());
      });

  fservice->SetSession(session);

  // Test listing of a directory
  // TODO validate the result as part of the test

  fservice->ListDirectory(baseDirectory)
    .subscribe([](vtkSmartPointer<vtkPVFileInformation> fileInfo) { fileInfo->Print(std::cout); });

  const std::string oldDirectory = vtksys::SystemTools::JoinPath({ baseDirectory, "TestDir" });

  fservice->MakeDirectory(oldDirectory).subscribe([](bool makeDirStatus) {
    VALIDATE(makeDirStatus == true);
  });

  const std::string newDirectory = oldDirectory + "_new";

  fservice->RenameDirectory(oldDirectory, newDirectory).subscribe([](bool renameDirStatus) {
    VALIDATE(renameDirStatus == true);
  });

  // deleting a directory that does not exist returns an error
  fservice->DeleteDirectory(oldDirectory)
    .subscribe([](bool /*deleteDirStatus*/) { VALIDATE(false); },
      [&errorsCaught](std::exception_ptr error_ptr) {
        vtkLogF(INFO, "%s", rxcpp::util::what(error_ptr).c_str());
        errorsCaught++;
      });

  bool done = false;
  fservice->DeleteDirectory(newDirectory).subscribe([&done](bool deleteDirStatus) {
    VALIDATE(deleteDirStatus == true);
    done = true;
  });

  ProcessEventsUntil(runLoop, [&done]() { return done; });
  VALIDATE(errorsCaught == 2);
  return true;
}

int TestPTSFileSystem(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("TestRemoteFileService 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());
  app->CreateBuiltinSession().subscribe(
    [&](vtkTypeUInt32 id) { DoPTSFileSystemTest(app->GetSession(id), runLoop); });
  app->WaitForExit(runLoop, std::chrono::seconds(1));
  app->Finalize();
  return app->GetExitCode();
}
