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

  Program:   ParaView
  Module:    vtkRemoteObjectProviderViews.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 "vtkRemoteObjectProviderViews.h"

#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkChannelSubscription.h"
#include "vtkObjectFactory.h"
#include "vtkObjectStore.h"
#include "vtkObjectWrapper.h"
#include "vtkPVDataDeliveryManager.h"
#include "vtkPVDataRepresentation.h"
#include "vtkPVLogger.h"
#include "vtkPVRenderView.h"
#include "vtkPVView.h"
#include "vtkService.h"
#include "vtkSmartPointer.h"
#include "vtkTimerLog.h"

#include <cassert>
namespace
{
void FromJSON(vtkCamera* camera, const vtkNJson& json)
{
  double viewUp[3];
  double position[3];
  double focalPoint[3];
  double viewAngle;

  VTK_NJSON_LOAD_MEMBER_ARRAY(json, viewUp);
  VTK_NJSON_LOAD_MEMBER_ARRAY(json, position);
  VTK_NJSON_LOAD_MEMBER_ARRAY(json, focalPoint);
  VTK_NJSON_LOAD_MEMBER(json, viewAngle);

  camera->SetViewUp(viewUp);
  camera->SetPosition(position);
  camera->SetFocalPoint(focalPoint);
  camera->SetViewAngle(viewAngle);
}

vtkNJson ToJSON(vtkCamera* camera)
{
  double viewUp[3];
  camera->GetViewUp(viewUp);

  double position[3];
  camera->GetPosition(position);

  double focalPoint[3];
  camera->GetFocalPoint(focalPoint);

  double viewAngle = camera->GetViewAngle();

  vtkNJson json;
  VTK_NJSON_SAVE_MEMBER_ARRAY(json, viewUp);
  VTK_NJSON_SAVE_MEMBER_ARRAY(json, position);
  VTK_NJSON_SAVE_MEMBER_ARRAY(json, focalPoint);
  VTK_NJSON_SAVE_MEMBER(json, viewAngle);
  return json;
}
bool UseCompressedStreams = false;
} // end of namespace {}

class vtkRemoteObjectProviderViews::vtkInternals
{
public:
  vtkSmartPointer<vtkChannelSubscription> DataChannelSubscription;
  bool DataProcessing{ false };
  std::map<vtkTypeUInt32, vtkTimeStamp> PushTimeStamps;
  double RenderTimeStamp{ 0.0 };
};

vtkObjectFactoryNewMacro(vtkRemoteObjectProviderViews);
//----------------------------------------------------------------------------
vtkRemoteObjectProviderViews::vtkRemoteObjectProviderViews()
  : Internals(new vtkRemoteObjectProviderViews::vtkInternals())
{
}

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

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::UseCompressedStreamsOn()
{
  ::UseCompressedStreams = true;
}

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::UseCompressedStreamsOff()
{
  ::UseCompressedStreams = false;
}

//----------------------------------------------------------------------------
bool vtkRemoteObjectProviderViews::UsesCompressedStreams()
{
  return ::UseCompressedStreams;
}

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::HandleObjectStoreEvent(
  vtkObject* caller, unsigned long eventId, void* callData)
{
  // this is called on service's main thread.
  auto& internals = (*this->Internals);
  if (eventId == vtkObjectStore::RegisterObjectEvent)
  {
    auto* wrapper = vtkObjectWrapper::SafeDownCast(reinterpret_cast<vtkObject*>(callData));
    if (auto* view = wrapper ? vtkPVView::SafeDownCast(wrapper->GetVTKObject()) : nullptr)
    {
      if (internals.DataProcessing)
      {
        vtkVLogF(PARAVIEW_LOG_PROVIDER_VERBOSITY(), "Initialize for data processing (%s)",
          vtkLogIdentifier(view));
        view->AddObserver(
          vtkCommand::UpdateEvent, this, &vtkRemoteObjectProviderViews::PushViewData);
      }
      else
      {
        vtkVLogF(PARAVIEW_LOG_PROVIDER_VERBOSITY(), "Initialize for rendering (%s)",
          vtkLogIdentifier(view));
        view->AddObserver(
          vtkCommand::RenderEvent, this, &vtkRemoteObjectProviderViews::HandleViewRenderEvent);

        // TODO(jaswant): needs more thought.
        vtkNew<vtkCallbackCommand> onEmitPktEventCb;
        onEmitPktEventCb->SetClientData(this);
        onEmitPktEventCb->SetCallback(
          [](vtkObject*, unsigned long, void* clientData, void* packet_with_payload) {
            auto packet = reinterpret_cast<vtkPacket*>(packet_with_payload);
            auto self = reinterpret_cast<vtkRemoteObjectProviderViews*>(clientData);
            self->GetService()->Publish(
              vtkRemoteObjectProviderViews::GetPacketChannelName(), *packet);
          });
        view->AddObserver(vtkCommand::ProgressEvent, onEmitPktEventCb);
      }
      internals.PushTimeStamps[view->GetGlobalID()].Modified();
    }
  }
}

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::PushViewData(
  vtkObject* caller, unsigned long eventId, void* callData)
{
  auto& internals = (*this->Internals);
  assert("Only supported on data-processing services" && internals.DataProcessing);

  auto* view = vtkPVView::SafeDownCast(caller);
  auto& tstamp = internals.PushTimeStamps.at(view->GetGlobalID());

  auto* dmanager = view->GetDataDeliveryManager();
  auto dataMap = dmanager->GetData(tstamp);
  tstamp.Modified();

  if (!dataMap.empty())
  {
    vtkVLogScopeF(PARAVIEW_LOG_DATA_MOVEMENT_VERBOSITY(), "Pushing rendering data");
    vtkNJson json;
    json["gid"] = view->GetGlobalID();
    this->GetService()->Publish(
      vtkRemoteObjectProviderViews::GetDataChannelName(), vtkPacket{ std::move(json), dataMap });
  }
}

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::HandleViewRenderEvent(
  vtkObject* caller, unsigned long eventId, void* callData)
{
  auto& internals = (*this->Internals);
  assert("Only supported on rendering services" && !internals.DataProcessing);
  auto* view = vtkPVView::SafeDownCast(caller);

  vtkNJson metadata;
  if (auto* renderView = vtkPVRenderView::SafeDownCast(view))
  {
    metadata["using-raytracing"] = renderView->IsRayTracingEnabled();
    metadata["total-rendering-passes"] = renderView->GetNumberOfProgressivePasses();
  }

  if (internals.RenderTimeStamp > 0)
  {
    metadata["timestamp"] = internals.RenderTimeStamp;
  }

  if (::UseCompressedStreams)
  {
    vtkVLogScopeF(PARAVIEW_LOG_RENDERING_VERBOSITY(), "Encoding rendered image");
    view->EncodeRenderedResult(metadata);
  }
  else
  {
    vtkVLogScopeF(PARAVIEW_LOG_RENDERING_VERBOSITY(), "Pushing rendered image");
    this->GetService()->Publish(
      vtkRemoteObjectProviderViews::GetImageChannelName(), view->GetRenderedResult(metadata));
  }
}

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::InitializeInternal(vtkService* service)
{
  this->Superclass::InitializeInternal(service);

  auto store = this->GetObjectStore();
  store->AddObserver(vtkObjectStore::RegisterObjectEvent, this,
    &vtkRemoteObjectProviderViews::HandleObjectStoreEvent);
  store->AddObserver(vtkObjectStore::UnregisterObjectEvent, this,
    &vtkRemoteObjectProviderViews::HandleObjectStoreEvent);

  auto& internals = (*this->Internals);
  internals.DataProcessing = (service->GetName() == "ds"); // TODO: need a better way
  if (!internals.DataProcessing)
  {
    internals.DataChannelSubscription =
      service->Subscribe(vtkRemoteObjectProviderViews::GetDataChannelName(), "ds");
    internals.DataChannelSubscription->GetObservable().subscribe(
      [&internals, this](const vtkPacket& data) {
        vtkVLogScopeF(PARAVIEW_LOG_DATA_MOVEMENT_VERBOSITY(), "Received rendering data");
        const auto& json = data.GetJSON();
        const auto gid = json.at("gid").get<vtkTypeUInt32>();
        auto view = this->GetObjectStore()->FindVTKObject<vtkPVView>(gid);
        if (view)
        {
          auto* dmanager = view->GetDataDeliveryManager();
          dmanager->SetData(data.GetPayload());
        }
      });
  }
}

//----------------------------------------------------------------------------
vtkNJson vtkRemoteObjectProviderViews::PiggybackInformation(vtkObject* object) const
{
  if (auto* rvview = vtkPVRenderView::SafeDownCast(object))
  {
    auto& internals = (*this->Internals);
    if (!internals.DataProcessing)
    {
      auto bbox = rvview->GetVisiblePropBounds();
      std::vector<double> bounds(6, 0.0);
      bbox.GetBounds(&bounds[0]);
      vtkNJson json;
      json["VisiblePropBounds"] = bounds;
      return json;
    }
  }

  return this->Superclass::PiggybackInformation(object);
}

//----------------------------------------------------------------------------
vtkPacket vtkRemoteObjectProviderViews::Render(vtkTypeUInt32 gid, vtkCamera* camera)
{
  vtkNJson json = vtkNJson::object();
  json["type"] = "vtk-remote-object-render";
  json["gid"] = gid;
  json["camera"] = ::ToJSON(camera);
  // add timestamp for each request so we can use to compute frame rate.
  json["timestamp"] = vtkTimerLog::GetUniversalTime();
  return { json };
}

//----------------------------------------------------------------------------
void vtkRemoteObjectProviderViews::Preview(const vtkPacket& packet)
{
  const auto& json = packet.GetJSON();
  const auto type = json.at("type").get<std::string>();
  if (type == "vtk-remote-object-render")
  {
    const auto gid = json.at("gid").get<vtkTypeUInt32>();
    auto view = this->GetObjectStore()->FindVTKObject<vtkPVView>(gid);
    if (view)
    {
      view->DoPreviewRender();
    }
  }
  else
  {
    this->Superclass::Preview(packet);
  }
}

//----------------------------------------------------------------------------
vtkPacket vtkRemoteObjectProviderViews::Process(const vtkPacket& packet)
{
  const auto& json = packet.GetJSON();
  const auto type = json.at("type").get<std::string>();
  if (type == "vtk-remote-object-render")
  {
    vtkTypeUInt32 gid = json.at("gid").get<vtkTypeUInt32>();
    auto view = this->GetObjectStore()->FindVTKObject<vtkPVView>(gid);
    if (view)
    {
      if (auto* camera = view->GetActiveCamera())
      {
        ::FromJSON(camera, json.at("camera"));
      }

      auto& internals = (*this->Internals);
      internals.RenderTimeStamp = json.at("timestamp").get<double>();
      view->DoRender();
      internals.RenderTimeStamp = 0;
    }

    return {};
  }
  else
  {
    return this->Superclass::Process(packet);
  }
}

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