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

  Program:   Visualization Toolkit
  Module:    vtkPTSFileSystem.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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 "vtkPTSFileSystem.h"

#include "vtkClientSession.h"
#include "vtkPVFileInformation.h"
#include "vtkPacket.h"
#include "vtkRemoteFileSystemProvider.h"
#include "vtkRemotingCoreUtilities.h"
#include "vtkSmartPointer.h"

class vtkPTSFileSystem::vtkInternals
{
public:
  const std::thread::id OwnerTID{ std::this_thread::get_id() };
  // FIXME ideally vtkWeakPointer but threadsafe
  vtkClientSession* Session = { nullptr };
  vtkTypeUInt32 Location = vtkClientSession::DATA_SERVER;

  bool IsInitialized() const { return Session != nullptr; }
};

//-----------------------------------------------------------------------------
rxcpp::observable<vtkSmartPointer<vtkPVFileInformation>> vtkPTSFileSystem::ListDirectory(
  const std::string& absolutePath)
{
  const auto& internals = (*this->Internals);
  if (!internals.IsInitialized())
  {
    return rxcpp::observable<>::error<vtkSmartPointer<vtkPVFileInformation>>(
      std::runtime_error("Session was not set!"));
  }

  vtkNew<vtkPVFileInformation> fileInfoRequest;
  fileInfoRequest->Initialize();
  fileInfoRequest->SetListDirectories(true);
  fileInfoRequest->SetPath(absolutePath);
  fileInfoRequest->SetWorkingDirectory(absolutePath);
  fileInfoRequest->SetReadDetailedFileInformation(true);

  auto packet = vtkRemoteFileSystemProvider::ListDirectory(fileInfoRequest);
  auto eventual = internals.Session->SendRequest(internals.Location, std::move(packet));
  return eventual.GetObservable()
    .map([](const vtkPacket& packet) {
      auto fileInfoResponse = vtk::TakeSmartPointer(vtkPVFileInformation::New());
      vtkRemoteFileSystemProvider::ParseResponse(packet, fileInfoResponse);
      return fileInfoResponse;
    })
    .take(1); // this is a single value observable
}

//-----------------------------------------------------------------------------
rxcpp::observable<bool> vtkPTSFileSystem::MakeDirectory(const std::string& absolutePath)
{
  const auto& internals = (*this->Internals);
  if (!internals.IsInitialized())
  {
    return rxcpp::observable<>::error<bool>(std::runtime_error("Session was not set!"));
  }
  auto packet = vtkRemoteFileSystemProvider::MakeDirectory(absolutePath);
  auto eventual = internals.Session->SendRequest(internals.Location, std::move(packet));
  return eventual.GetObservable()
    .map([](const vtkPacket& packet) { return packet.GetJSON().value("status", false); })
    .take(1);
}

//-----------------------------------------------------------------------------
rxcpp::observable<bool> vtkPTSFileSystem::DeleteDirectory(const std::string& absolutePath)
{
  const auto& internals = (*this->Internals);
  if (!internals.IsInitialized())
  {
    return rxcpp::observable<>::error<bool>(std::runtime_error("Session was not set!"));
  }
  auto packet = vtkRemoteFileSystemProvider::DeleteDirectory(absolutePath);
  auto eventual = internals.Session->SendRequest(internals.Location, std::move(packet));
  return eventual.GetObservable()
    .map([](const vtkPacket& packet) { return packet.GetJSON().value("status", false); })
    .take(1);
}

//-----------------------------------------------------------------------------
rxcpp::observable<bool> vtkPTSFileSystem::RenameDirectory(
  const std::string& oldAbsolutePath, const std::string& newAbsolutePath)
{
  const auto& internals = (*this->Internals);
  if (!internals.IsInitialized())
  {
    return rxcpp::observable<>::error<bool>(std::runtime_error("Session was not set!"));
  }
  auto packet = vtkRemoteFileSystemProvider::RenameDirectory(oldAbsolutePath, newAbsolutePath);
  auto eventual = internals.Session->SendRequest(internals.Location, std::move(packet));
  return eventual.GetObservable()
    .map([](const vtkPacket& packet) { return packet.GetJSON().value("status", false); })
    .take(1);
}

//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkPTSFileSystem);
//-----------------------------------------------------------------------------
vtkPTSFileSystem::vtkPTSFileSystem()
  : Internals(new vtkPTSFileSystem::vtkInternals())
{
}

//-----------------------------------------------------------------------------
vtkPTSFileSystem::~vtkPTSFileSystem() = default;

//-----------------------------------------------------------------------------
void vtkPTSFileSystem::SetSession(vtkClientSession* session)
{
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  if (internals.Session != session)
  {
    internals.Session = session;
  }
}

//-----------------------------------------------------------------------------
vtkClientSession* vtkPTSFileSystem::GetSession() const
{
  const auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  return internals.Session;
}

//-----------------------------------------------------------------------------
void vtkPTSFileSystem::SetLocation(vtkTypeUInt32 location)
{
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  if (location != vtkClientSession::DATA_SERVER && location != vtkClientSession::RENDER_SERVER &&
    location != vtkClientSession::CLIENT)
  {
    vtkLogF(ERROR, "Unsupported location code %x", location);
    return;
  }
  internals.Location = location;
}

//-----------------------------------------------------------------------------
vtkTypeUInt32 vtkPTSFileSystem::GetLocation() const
{
  const auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  return internals.Location;
}

//-----------------------------------------------------------------------------
void vtkPTSFileSystem::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}
