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

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

#include "vtkBoundingBox.h"
#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkChannelSubscription.h"
#include "vtkClientSession.h"
#include "vtkGenericOpenGLRenderWindow.h"
#include "vtkImageData.h"
#include "vtkLogger.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLRenderWindow.h"
#include "vtkOpenGLState.h"
#include "vtkOpenGLVideoFrame.h"
#include "vtkPNGReader.h"
#include "vtkPVCoreApplication.h"
#include "vtkPVCoreApplicationOptions.h"
#include "vtkPVRenderingCapabilitiesInformation.h"
#include "vtkPVXMLElement.h"
#include "vtkPacket.h"
#include "vtkPointData.h"
#include "vtkRawVideoFrame.h"
#include "vtkRemoteViewResultItem.h"
#include "vtkRemotingCoreUtilities.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSMIntVectorProperty.h"
#include "vtkSMParaViewPipelineControllerWithRendering.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxyIterator.h"
#include "vtkSMProxyProperty.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMUncheckedPropertyHelper.h"
#include "vtkSMViewProxyInternals.h"
#include "vtkServiceEndpoint.h"
#include "vtkSmartPointer.h"
#include "vtkSynchronizedRenderers.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkTextureObject.h"
#include "vtkTimerLog.h"
#include "vtkVariant.h"
#include "vtkVideoCodecTypes.h"
#include "vtkVideoProcessingWorkUnitTypes.h"
#include "vtkVpxDecoder.h"
#include "vtk_glew.h"

#include <chrono>
#include <stdexcept>
#include <string>
#include <vtk_fmt.h> // needed for `fmt`
// clang-format off
#include VTK_FMT(fmt/core.h)
// clang-format on

#include <cstdint>
#include <sstream>

namespace vtkSMViewProxyNS
{
const char* GetRepresentationNameFromHints(const char* viewType, vtkPVXMLElement* hints, int port)
{
  if (!hints)
  {
    return nullptr;
  }

  for (unsigned int cc = 0, max = hints->GetNumberOfNestedElements(); cc < max; ++cc)
  {
    vtkPVXMLElement* child = hints->GetNestedElement(cc);
    if (child == nullptr || child->GetName() == nullptr)
    {
      continue;
    }

    // LEGACY: support DefaultRepresentations hint.
    // <Hints>
    //    <DefaultRepresentations representation="Foo" />
    // </Hints>
    if (strcmp(child->GetName(), "DefaultRepresentations") == 0)
    {
      return child->GetAttribute("representation");
    }

    // <Hints>
    //    <Representation port="outputPort" view="ViewName" type="ReprName" />
    // </Hints>
    else if (strcmp(child->GetName(), "Representation") == 0 &&
      // has an attribute "view" that matches the viewType.
      child->GetAttribute("view") && strcmp(child->GetAttribute("view"), viewType) == 0 &&
      child->GetAttribute("type") != nullptr)
    {
      // if port is present, it must match "port".
      int xmlPort;
      if (child->GetScalarAttribute("port", &xmlPort) == 0 || xmlPort == port)
      {
        return child->GetAttribute("type");
      }
    }
  }
  return nullptr;
}

} // namespace vtkSMViewProxyNS

namespace
{
inline std::string getNiceTime(vtkIdType time_ns)
{
  if (time_ns > 1e9)
  {
    return fmt::format("{:>3}s", vtkIdType(time_ns / 1e9));
  }
  else if (time_ns > 1e6)
  {
    return fmt::format("{:>3}ms", vtkIdType(time_ns / 1e6));
  }
  else if (time_ns > 1e3)
  {
    return fmt::format("{:>3}us", vtkIdType(time_ns / 1e3));
  }
  else
  {
    return fmt::format("{:>3}ns", time_ns);
  }
}

inline std::string getNiceBytes(int num_bytes)
{
  if (num_bytes > 1024 * 1024 * 1024)
  {
    return fmt::format("{:>3}GB", int(num_bytes / (1024 * 1024 * 1024)));
  }
  else if (num_bytes > 1024 * 1024)
  {
    return fmt::format("{:>3}MB", int(num_bytes / (1024 * 1024)));
  }
  else if (num_bytes > 1024)
  {
    return fmt::format("{:>3}KB", int(num_bytes / 1024));
  }
  else
  {
    return fmt::format("{:>3}By", num_bytes);
  }
}
}

vtkStandardNewMacro(vtkSMViewProxy);

bool vtkSMViewProxy::UseGenericOpenGLRenderWindow = false;

//----------------------------------------------------------------------------
vtkSMViewProxy::vtkSMViewProxy()
  : Internals(new vtkSMViewProxyInternals())
{
  auto& internals = (*this->Internals);

  auto* options = vtkPVCoreApplication::GetInstance()->GetOptions();
  if (vtkSMViewProxy::GetUseGenericOpenGLRenderWindow())
  {
    vtkLogF(TRACE, "Using generic opengl render window.");
    internals.RenderWindow = vtk::TakeSmartPointer(vtkGenericOpenGLRenderWindow::New());
  }
  else if (options->GetForceOffscreenRendering())
  {
    vtkLogF(TRACE, "Using offscreen opengl render window.");
    internals.RenderWindow = vtkPVRenderingCapabilitiesInformation::NewOffscreenRenderWindow();
  }
  else
  {
    vtkLogF(TRACE, "Using opengl render window factory override.");
    internals.RenderWindow = vtk::TakeSmartPointer(vtkRenderWindow::New());
  }

  internals.Renderer = vtk::TakeSmartPointer(vtkRenderer::New());
  internals.Renderer->SetBackground(0, 0, 0);
  internals.RenderWindow->AddRenderer(internals.Renderer);
  internals.RenderWindow->SetNumberOfLayers(2);

  internals.Renderer2DForKeys = vtk::TakeSmartPointer(vtkRenderer::New());
  internals.Renderer2DForKeys->SetLayer(1);
  internals.Renderer2DForKeys->InteractiveOff();
  internals.RenderWindow->AddRenderer(internals.Renderer2DForKeys);

  internals.Renderer2DForValues = vtk::TakeSmartPointer(vtkRenderer::New());
  internals.Renderer2DForValues->SetLayer(1);
  internals.Renderer2DForValues->InteractiveOff();
  internals.RenderWindow->AddRenderer(internals.Renderer2DForValues);

  // Setup Corner annotation for rendering/encoding status.
  internals.AnnotationKeys = vtk::TakeSmartPointer(vtkTextRepresentation::New());
  internals.Renderer2DForKeys->AddActor(internals.AnnotationKeys);
  internals.AnnotationKeys->SetText("Key");
  internals.AnnotationKeys->SetRenderer(internals.Renderer2DForKeys);
  internals.AnnotationKeys->BuildRepresentation();
  internals.AnnotationKeys->SetWindowLocation(vtkTextRepresentation::UpperLeftCorner);
  internals.AnnotationKeys->GetTextActor()->SetTextScaleModeToNone();
  internals.AnnotationKeys->GetTextActor()->GetTextProperty()->SetJustificationToLeft();
  internals.AnnotationKeys->SetVisibility(false);

  internals.AnnotationValues = vtk::TakeSmartPointer(vtkTextRepresentation::New());
  internals.Renderer2DForValues->AddActor(internals.AnnotationValues);
  internals.AnnotationValues->SetText("Value");
  internals.AnnotationValues->SetRenderer(internals.Renderer2DForValues);
  internals.AnnotationValues->BuildRepresentation();
  internals.AnnotationValues->SetWindowLocation(vtkTextRepresentation::AnyLocation);
  internals.AnnotationValues->GetTextActor()->SetTextScaleModeToNone();
  internals.AnnotationValues->GetTextActor()->GetTextProperty()->SetJustificationToRight();
  internals.AnnotationValues->SetVisibility(false);

  // monitor render window so we can setup observers on interactor
  // for events that send render requests to the server.
  internals.RenderWindowObserverId = internals.RenderWindow->AddObserver(
    vtkCommand::ModifiedEvent, this, &vtkSMViewProxy::SetupInteractor);
  // this gives us a chance to render the RS (render-service) rendering result on top of our window.
  internals.Renderer->AddObserver(
    vtkCommand::EndEvent, this, &vtkSMViewProxy::HandleRendererEndEvent);
}

//----------------------------------------------------------------------------
vtkSMViewProxy::~vtkSMViewProxy()
{
  const auto& internals = (*this->Internals);
  // clean up observers.
  if (auto* iren = internals.RenderWindow->GetInteractor())
  {
    for (const auto& observerId : internals.InteractorObserverIds)
    {
      iren->RemoveObserver(observerId);
    }
  }
  // unsub the output stream observable.
  this->UnsubscribeOutputStreams();
}

//----------------------------------------------------------------------------
vtkRenderWindow* vtkSMViewProxy::GetRenderWindow() const
{
  const auto& internals = (*this->Internals);
  return internals.RenderWindow;
}

//----------------------------------------------------------------------------
vtkRenderWindowInteractor* vtkSMViewProxy::GetRenderWindowInteractor() const
{
  const auto& internals = (*this->Internals);
  return internals.RenderWindow ? internals.RenderWindow->GetInteractor() : nullptr;
}

//----------------------------------------------------------------------------
vtkCamera* vtkSMViewProxy::GetCamera() const
{
  const auto& internals = (*this->Internals);
  return internals.Renderer->GetActiveCamera();
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::RequestProgressiveRenderingPassIfNeeded()
{
  if (this->GetContinueRendering())
  {
    this->RequestProgressiveRenderingPass();
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::SetUseGenericOpenGLRenderWindow(bool val)
{
  vtkSMViewProxy::UseGenericOpenGLRenderWindow = val;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::GetUseGenericOpenGLRenderWindow()
{
  return vtkSMViewProxy::UseGenericOpenGLRenderWindow;
}

//----------------------------------------------------------------------------
rxcpp::observable<bool> vtkSMViewProxy::Update()
{
  vtkLogScopeF(TRACE, "%s", __func__);
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);

  using BehaviorT = rxcpp::subjects::behavior<vtkVariant>;
  auto behavior = std::make_shared<BehaviorT>(vtkVariant());
  this->ExecuteCommand("Update", vtkClientSession::DATA_SERVER).subscribe([behavior, this](bool) {
    this->ExecuteCommand("UpdateForRendering", vtkClientSession::RENDER_SERVER)
      .subscribe([=](bool status) {
        behavior->get_subscriber().on_next(vtkVariant(status ? 1 : 0));
        this->StillRender();
      });
  });

  return behavior->get_observable()
    .filter([](const vtkVariant& variant) { return variant.IsValid(); })
    .take(1)
    .map([behavior](const vtkVariant& variant) {
      // capture behavior to ensure it hangs around until the returned value hangs around.
      (void)behavior;
      return variant.ToInt() != 0;
    });
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::ResetCameraUsingVisiblePropBounds()
{
  vtkLogScopeF(TRACE, "%s", __func__);
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  if (internals.VisiblePropBounds.IsValid())
  {
    double bds[6];
    internals.VisiblePropBounds.GetBounds(bds);
    internals.Renderer->ResetCamera(bds);

    // If 'CenterOfRotation' is present, set it to the center of bounds as well.
    if (auto* centerProperty = this->GetProperty("CenterOfRotation"))
    {
      double center[3];
      internals.VisiblePropBounds.GetCenter(center);
      vtkSMPropertyHelper(centerProperty).Set(center, 3);
    }
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::InteractiveRender()
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  internals.InteractiveRender = true;
  internals.FlushPackets = false;
  this->SetupInteractor();
  this->ResetCameraUsingVisiblePropBounds();
  this->RequestRender();
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::StillRender()
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  internals.InteractiveRender = false;
  internals.FlushPackets = true;
  this->SetupInteractor();
  this->ResetCameraUsingVisiblePropBounds();
  this->RequestRender();
}

//----------------------------------------------------------------------------
const vtkBoundingBox& vtkSMViewProxy::GetVisiblePropBounds() const
{
  return this->Internals->VisiblePropBounds;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::HideOtherRepresentationsIfNeeded(vtkSMProxy* repr)
{
  if (repr == nullptr || this->GetHints() == nullptr ||
    this->GetHints()->FindNestedElementByName("ShowOneRepresentationAtATime") == nullptr)
  {
    return false;
  }

  vtkPVXMLElement* oneRepr =
    this->GetHints()->FindNestedElementByName("ShowOneRepresentationAtATime");
  const char* reprType = oneRepr->GetAttribute("type");

  if (reprType && strcmp(repr->GetXMLName(), reprType) != 0)
  {
    return false;
  }

  vtkNew<vtkSMParaViewPipelineControllerWithRendering> controller;

  bool modified = false;
  vtkSMPropertyHelper helper(this, "Representations");
  for (unsigned int cc = 0, max = helper.GetNumberOfElements(); cc < max; ++cc)
  {
    auto* arepr = helper.GetAsProxy(cc);
    if (arepr && arepr != repr)
    {
      if (vtkSMPropertyHelper(arepr, "Visibility", /*quiet*/ true).GetAsInt() == 1 &&
        (!reprType || (reprType && !strcmp(arepr->GetXMLName(), reprType))))
      {
        controller->Hide(arepr, this);
        modified = true;
      }
    }
  }
  return modified;
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::RepresentationVisibilityChanged(vtkSMProxy*, bool) {}

//----------------------------------------------------------------------------
int vtkSMViewProxy::ReadXMLAttributes(vtkSMSessionProxyManager* pm, vtkPVXMLElement* element)
{
  if (!this->Superclass::ReadXMLAttributes(pm, element))
  {
    return 0;
  }

  auto& internals = (*this->Internals);
  const char* repr_name = element->GetAttribute("representation_name");
  if (repr_name)
  {
    internals.DefaultRepresentationName = repr_name;
  }
  return 1;
}

//----------------------------------------------------------------------------
vtkSMProxy* vtkSMViewProxy::CreateDefaultRepresentation(vtkSMProxy* proxy, int outputPort)
{
  vtkSMSourceProxy* producer = vtkSMSourceProxy::SafeDownCast(proxy);
  if ((producer == nullptr) || (outputPort < 0) ||
    (static_cast<int>(producer->GetNumberOfOutputPorts()) <= outputPort) ||
    (producer->GetSession() != this->GetSession()))
  {
    return nullptr;
  }

  // Update with time from the view to ensure we have up-to-date data.
#if 0
  double view_time = vtkSMPropertyHelper(this, "ViewTime").GetAsDouble();
#else
  // FIXME ASYNC: Until and if we support animation, let's just request the first time step.
  // Otherwise, output messages window will annoyingly pop up with an error complaining
  // the property "ViewTime" does not exist on the view proxy, rightly so, it doesn't exist.
  double view_time = 0;
#endif
  producer->UpdatePipeline(view_time);

  const char* representationType = this->GetRepresentationType(producer, outputPort);
  if (!representationType)
  {
    return nullptr;
  }

  vtkSMSessionProxyManager* pxm = this->GetSessionProxyManager();
  if (auto* repr = pxm->NewProxy("representations", representationType))
  {
    return repr;
  }
  vtkWarningMacro(
    "Failed to create representation (representations," << representationType << ").");
  return nullptr;
}

//----------------------------------------------------------------------------
const char* vtkSMViewProxy::GetRepresentationType(vtkSMSourceProxy* producer, int outputPort)
{
  assert(producer && static_cast<int>(producer->GetNumberOfOutputPorts()) > outputPort);

  // Process producer hints to see if indicates what type of representation
  // to create for this view.
  if (const char* reprName = vtkSMViewProxyNS::GetRepresentationNameFromHints(
        this->GetXMLName(), producer->GetHints(), outputPort))
  {
    return reprName;
  }

  const auto& internals = (*this->Internals);
  // check if we have default representation name specified in XML.
  if (!internals.DefaultRepresentationName.empty())
  {
    vtkSMSessionProxyManager* pxm = this->GetSessionProxyManager();
    vtkSMProxy* prototype =
      pxm->GetPrototypeProxy("representations", internals.DefaultRepresentationName.c_str());
    if (prototype)
    {
      vtkSMProperty* inputProp = prototype->GetProperty("Input");
      vtkSMUncheckedPropertyHelper helper(inputProp);
      helper.Set(producer, outputPort);
      bool acceptable = (inputProp->IsInDomains() > 0);
      helper.SetNumberOfElements(0);

      if (acceptable)
      {
        return internals.DefaultRepresentationName.c_str();
      }
    }
  }

  return nullptr;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::CanDisplayData(vtkSMSourceProxy* producer, int outputPort)
{
  if (producer == nullptr || outputPort < 0 ||
    static_cast<int>(producer->GetNumberOfOutputPorts()) <= outputPort ||
    producer->GetSession() != this->GetSession())
  {
    return false;
  }

  const char* type = this->GetRepresentationType(producer, outputPort);
  if (type != nullptr)
  {
    vtkSMSessionProxyManager* pxm = this->GetSessionProxyManager();
    return (pxm->GetPrototypeProxy("representations", type) != nullptr);
  }

  return false;
}

//----------------------------------------------------------------------------
vtkSMProxy* vtkSMViewProxy::FindRepresentation(vtkSMSourceProxy* producer, int outputPort)
{
  vtkSMPropertyHelper helper(this, "Representations");
  for (unsigned int cc = 0, max = helper.GetNumberOfElements(); cc < max; ++cc)
  {
    auto* repr = helper.GetAsProxy(cc);
    if (repr && repr->GetProperty("Input"))
    {
      vtkSMPropertyHelper helper2(repr, "Input");
      if (helper2.GetAsProxy() == producer &&
        static_cast<int>(helper2.GetOutputPort()) == outputPort)
      {
        return repr;
      }
    }
  }

  return nullptr;
}

//----------------------------------------------------------------------------
vtkSMViewProxy* vtkSMViewProxy::FindView(vtkSMProxy* repr, const char* reggroup /*=views*/)
{
  if (!repr)
  {
    return nullptr;
  }

  vtkSMSessionProxyManager* pxm = repr->GetSessionProxyManager();
  vtkNew<vtkSMProxyIterator> iter;
  iter->SetSessionProxyManager(pxm);
  iter->SetModeToOneGroup();
  for (iter->Begin(reggroup); !iter->IsAtEnd(); iter->Next())
  {
    if (vtkSMViewProxy* view = vtkSMViewProxy::SafeDownCast(iter->GetProxy()))
    {
      auto reprs = vtkSMProxyProperty::SafeDownCast(view->GetProperty("Representations"));
      auto hreprs = vtkSMProxyProperty::SafeDownCast(view->GetProperty("HiddenRepresentations"));
      if ((reprs != nullptr && reprs->IsProxyAdded(repr)) ||
        (hreprs != nullptr && hreprs->IsProxyAdded(repr)))
      {
        return view;
      }
    }
  }
  return nullptr;
}

//----------------------------------------------------------------------------
rxcpp::observable<vtkSmartPointer<vtkCompressedVideoPacket>>
vtkSMViewProxy::GetViewOutputObservable() const
{
  return this->Internals->OutputStream.get_observable().tap(
    [](auto package) { vtkLogF(INFO, "push package to output observable"); });
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::UnsubscribeOutputStreams()
{
  vtkLogScopeFunction(TRACE);
  this->Internals->OutputStream.get_subscriber().on_completed();
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::ProcessPiggybackedInformation(const vtkNJson& json)
{
  this->Superclass::ProcessPiggybackedInformation(json);

  auto iter = json.find("VisiblePropBounds");
  if (iter != json.end())
  {
    std::vector<double> bounds = iter.value().get<std::vector<double>>();
    this->Internals->VisiblePropBounds.SetBounds(bounds.data());
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::InitializeInteractor(vtkRenderWindowInteractor* iren)
{
  vtkLogScopeF(TRACE, "%s iren=%s", __func__, vtkLogIdentifier(iren));
  auto& internals = (*this->Internals);
  if (iren != nullptr)
  {
    if (!iren->GetInitialized())
    {
      vtkLogF(TRACE, "Initializing interactor because it's unininitialized.");
      iren->Initialize();
    }
    // Apply the "ViewSize" on the client window. This is done for the first time.
    const int width = vtkSMPropertyHelper(this, "ViewSize").GetAsInt(0);
    const int height = vtkSMPropertyHelper(this, "ViewSize").GetAsInt(1);
    iren->UpdateSize(width, height);
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::PostRenderingResult(const vtkPacket& packet)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);

  vtkRemoteViewResultItem result;
  result.FromJson(packet.GetJSON());

  auto content = packet.GetPayload<vtkUnsignedCharArray>("content");
  if (content == nullptr)
  {
    throw std::runtime_error("Render Service sent invalid content.");
  }
  else if (content->GetNumberOfValues() != result.Size)
  {
    vtkLogF(ERROR, "Transmitted size (%lld) != received size (%lld)", result.Size,
      content->GetNumberOfValues());
    throw std::runtime_error("Received corrupt content.");
  }

  auto payload = this->ParseContent(result, content);
  // this marks the end of journey for RequestRender()
  result.Stats.Latency =
    std::chrono::high_resolution_clock::now().time_since_epoch().count() - result.Stats.Timestamp;
  if (vtkSMPropertyHelper(this, "StreamOutput").GetAsInt(0) == 1)
  {
    internals.OutputStream.get_subscriber().on_next(payload);
  }
  this->ExtractAnnotations(result);
  if (vtkSMPropertyHelper(this, "Display").GetAsInt(0) == 1)
  {
    this->InitializeVideoDecoder(result.Codec, result.Mime);
    internals.PayloadQueue.push(payload);
    // let's draw the results right away so we present the latest image from RS.
    if (internals.FlushPackets)
    {
      internals.RenderWindow->Render();
    }
  }
  this->GetSession()->GetRemoteMetricInformationSubscriber().on_next(
    std::make_tuple("rs", result.Stats.ToJson()));
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::ExtractAnnotations(const vtkRemoteViewResultItem& result)
{
  // parse annotations from the metadata.
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);

  const auto& stats = result.Stats;
  this->UpdateRayTracingState(stats);

  std::ostringstream keys, values;

  if (stats.RayTracing)
  {
    keys << "render pass: \n";
    values << fmt::format("{:3d}/{:3d}\n", internals.RenderingPass, internals.TotalRenderingPasses);
  }

  const auto delta =
    std::chrono::high_resolution_clock::now().time_since_epoch().count() - stats.Timestamp;
  internals.FrameRate = 1000000000 / delta;

  keys << "pkt size: \n";
  values << ::getNiceBytes(result.Stats.RenderPayloadSizeBytes) << '\n';

  keys << "mime type: \n";
  values << result.Mime << '\n';

  if (result.Mime != "image/rgb24" && result.Mime != "image/rgba32")
  {
    keys << "codec: \n";
    values << vtkVideoCodecTypeUtilities::ToString(result.Codec) << '\n';

    keys << "encode: \n";
    values << ::getNiceTime(stats.EncodeTime) << '\n';
  }

  keys << "rs-latency: \n";
  values << ::getNiceTime(stats.Latency) << '\n';

  keys << "server: \n";
  values << stats.Framerate << " fps" << '\n';

  keys << "client: \n";
  values << internals.FrameRate << " fps" << '\n';

  keys << "capture: \n";
  values << ::getNiceTime(stats.DisplayGrabTime) << '\n';

  keys << "gpu2cpuxfer: \n";
  values << ::getNiceTime(stats.GPU2CPUXferTime) << '\n';

  // for now, we always display annotations.
  // TODO: move this to proxy property.
  internals.AnnotationKeys->SetVisibility(true);
  internals.AnnotationValues->SetVisibility(true);
  internals.AnnotationKeys->SetText(keys.str().c_str());
  internals.AnnotationValues->SetText(values.str().c_str());
}

//----------------------------------------------------------------------------
vtkSmartPointer<vtkCompressedVideoPacket> vtkSMViewProxy::ParseContent(
  const vtkRemoteViewResultItem& result, vtkSmartPointer<vtkUnsignedCharArray> content)
{
  auto& internals = (*this->Internals);
  auto payload = vtk::TakeSmartPointer(vtkCompressedVideoPacket::New());
  payload->SetDisplayWidth(result.Width);
  payload->SetDisplayHeight(result.Height);
  payload->SetIsKeyFrame(result.KeyFrame);
  payload->SetPresentationTS(result.PTS);
  payload->SetMimeType(result.Mime.c_str());
  payload->SetCodecLongName(result.CodecLongName.c_str());
  payload->CopyData(content);
  vtkLogF(TRACE, "pts: %lld type: %s, validated-packet %s", payload->GetPresentationTS(),
    payload->GetIsKeyFrame() ? "key" : "delta", ::getNiceBytes(payload->GetSize()).c_str());
  return payload;
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::InitializeVideoDecoder(VTKVideoCodecType codecType, const std::string& mime)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);

  if (mime == "image/rgb24" || mime == "image/rgba32" || mime.empty())
  {
    internals.VideoDecoder = nullptr;
    return;
  }

  if (internals.VideoDecoder != nullptr && internals.VideoDecoder->GetCodec() != codecType)
  {
    internals.VideoDecoder->Shutdown();
    internals.VideoDecoder = nullptr;
  }

  if (internals.VideoDecoder == nullptr)
  {
    switch (codecType)
    {
      case VTKVideoCodecType::VTKVC_VP9:
        internals.VideoDecoder = vtk::TakeSmartPointer(vtkVpxDecoder::New());
        internals.VideoDecoder->SetCodec(codecType);
        internals.VideoDecoder->Initialize();
        break;
      default:
        vtkLogF(ERROR, "No suitable decoder for codec type: '%s' and mime: '%s'",
          vtkVideoCodecTypeUtilities::ToString(codecType), mime.c_str());
        break;
    }
  }
}

//----------------------------------------------------------------------------
vtkSmartPointer<vtkRawVideoFrame> vtkSMViewProxy::LoadLosslessImage(
  vtkSmartPointer<vtkCompressedVideoPacket> payload)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  auto oglRenWin = vtkOpenGLRenderWindow::SafeDownCast(internals.RenderWindow);

  int dims[2] = { payload->GetDisplayWidth(), payload->GetDisplayHeight() };

  const int displayWidth = internals.RenderWindow->GetSize()[0];
  const int displayHeight = internals.RenderWindow->GetSize()[1];

  auto displayViewport = vtkBoundingBox(0, displayWidth - 1, 0, displayHeight - 1, 0, 0);
  auto frameBox = vtkBoundingBox(0, dims[0] - 1, 0, dims[1] - 1, 0, 0);

  if (displayViewport != frameBox)
  {
    vtkLogF(WARNING, "Skip outdated frame with dims %dx%d != viewport dims %dx%d", dims[0], dims[1],
      displayWidth, displayHeight);
    return nullptr;
  }

  auto frame = vtk::TakeSmartPointer(vtkOpenGLVideoFrame::New());
  frame->SetWidth(displayWidth);
  frame->SetHeight(displayHeight);
  int rowsize = 0;
  if (payload->GetMimeType() == "image/rgb24")
  {
    rowsize = dims[0] * 3;
    frame->SetPixelFormat(VTKPixelFormatType::VTKPF_RGB24);
  }
  else if (payload->GetMimeType() == "image/rgba32")
  {
    rowsize = dims[0] * 4;
    frame->SetPixelFormat(VTKPixelFormatType::VTKPF_RGBA32);
  }
  frame->SetContext(oglRenWin);
  frame->SetSliceOrderType(vtkRawVideoFrame::SliceOrderType::BottomUp);
  frame->AllocateDataStore();
  frame->CopyData(payload->GetData()->GetPointer(0), rowsize, dims[1]);
  return frame;
}

//----------------------------------------------------------------------------
vtkSmartPointer<vtkRawVideoFrame> vtkSMViewProxy::LoadJpegImage(
  vtkSmartPointer<vtkCompressedVideoPacket> payload)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  auto oglRenWin = vtkOpenGLRenderWindow::SafeDownCast(internals.RenderWindow);

  int dims[2] = { payload->GetDisplayWidth(), payload->GetDisplayHeight() };

  const int displayWidth = internals.RenderWindow->GetSize()[0];
  const int displayHeight = internals.RenderWindow->GetSize()[1];

  auto displayViewport = vtkBoundingBox(0, displayWidth - 1, 0, displayHeight - 1, 0, 0);
  auto frameBox = vtkBoundingBox(0, dims[0] - 1, 0, dims[1] - 1, 0, 0);

  if (displayViewport != frameBox)
  {
    vtkLogF(WARNING, "Skip outdated frame with dims %dx%d != viewport dims %dx%d", dims[0], dims[1],
      displayWidth, displayHeight);
    return nullptr;
  }

  vtkUnsignedCharArray* buffer = payload->GetData();
  internals.JpegReader->SetMemoryBufferLength(buffer->GetDataSize());
  internals.JpegReader->SetMemoryBuffer(buffer->GetPointer(0));
  internals.JpegReader->Update();
  auto img = internals.JpegReader->GetOutput();
  img->GetDimensions(dims);

  auto frame = vtk::TakeSmartPointer(vtkOpenGLVideoFrame::New());
  frame->SetContext(oglRenWin);
  frame->SetWidth(dims[0]);
  frame->SetHeight(dims[1]);
  frame->SetPixelFormat(VTKPixelFormatType::VTKPF_RGB24);
  frame->SetSliceOrderType(vtkRawVideoFrame::SliceOrderType::BottomUp);
  frame->AllocateDataStore();

  auto src = reinterpret_cast<unsigned char*>(img->GetScalarPointer());
  frame->CopyData(src, dims[0] * 3, dims[1]);
  return frame;
}

//----------------------------------------------------------------------------
vtkSmartPointer<vtkRawVideoFrame> vtkSMViewProxy::LoadVideoFrame(
  vtkSmartPointer<vtkCompressedVideoPacket> payload)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  auto oglRenWin = vtkOpenGLRenderWindow::SafeDownCast(internals.RenderWindow);

  vtkSmartPointer<vtkRawVideoFrame> frame;
  VTKVideoProcessingStatusType status;
  internals.VideoDecoder->SetOutputHandler([&frame, &status](VTKVideoDecoderResultType result) {
    status = result.first;
    if (!result.second.empty())
    {
      frame = result.second.back();
    }
  });
  internals.VideoDecoder->SetGraphicsContext(oglRenWin);
  internals.VideoDecoder->Decode(payload);
  if (status != VTKVideoProcessingStatusType::VTKVPStatus_Success)
  {
    vtkLog(WARNING, "Video decoder failed.");
    return nullptr;
  }
  else if (frame == nullptr)
  {
    vtkLog(TRACE, "Video decoder did not generate output.");
    return nullptr;
  }

  int dims[2] = {};
  dims[0] = frame->GetWidth();
  dims[1] = frame->GetHeight();

  const int displayWidth = internals.RenderWindow->GetSize()[0];
  const int displayHeight = internals.RenderWindow->GetSize()[1];

  auto displayViewport = vtkBoundingBox(0, displayWidth - 1, 0, displayHeight - 1, 0, 0);
  auto frameBox = vtkBoundingBox(0, dims[0] - 1, 0, dims[1] - 1, 0, 0);

  if (displayViewport != frameBox)
  {
    vtkLogF(WARNING, "Skip outdated frame with dims %dx%d != viewport dims %dx%d", dims[0], dims[1],
      displayWidth, displayHeight);
    return nullptr;
  }

  frame->SetWidth(displayWidth);
  frame->SetHeight(displayHeight);
  return frame;
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::DrawOverlay(vtkSmartPointer<vtkRawVideoFrame> frame)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);

  internals.InDrawOverlay = true;
  frame->Render(internals.RenderWindow);
  internals.InDrawOverlay = false;
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::RequestRender()
{
  vtkLogScopeFunction(TRACE);
  this->UpdateCameraIfNeeded(/*force*/ true);
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::RequestProgressiveRenderingPass()
{
  vtkLogScopeFunction(TRACE);
  this->UpdateCameraIfNeeded(/*force*/ true, /* progressiveRendering*/ true);
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::SetupInteractor()
{
  auto& internals = (*this->Internals);
  auto* iren = internals.RenderWindow->GetInteractor();
  if (!iren)
  {
    vtkLogF(TRACE, "Interactor null. Yet to be setup..");
    return;
  }
  if (!internals.InteractorObserverIds.empty())
  {
    vtkLogF(TRACE, "Interactor already setup.");
    return;
  }

  // Remove the observer so we don't end up calling this method again.
  // doing so prevents re-entry due to side-effects of `InitializeInteractor` below.
  internals.RenderWindow->RemoveObserver(internals.RenderWindowObserverId);
  internals.RenderWindowObserverId = 0;

  this->InitializeInteractor(iren);

  internals.InteractorObserverIds.push_back(iren->AddObserver(
    vtkCommand::StartInteractionEvent, this, &vtkSMViewProxy::HandleInteractorEvents));
  internals.InteractorObserverIds.push_back(iren->AddObserver(
    vtkCommand::EndInteractionEvent, this, &vtkSMViewProxy::HandleInteractorEvents));
  internals.InteractorObserverIds.push_back(
    iren->AddObserver(vtkCommand::RenderEvent, this, &vtkSMViewProxy::HandleInteractorEvents));
  internals.InteractorObserverIds.push_back(iren->AddObserver(
    vtkCommand::WindowResizeEvent, this, &vtkSMViewProxy::HandleInteractorEvents));

  // XOpenGLRenderWindow fires ConfigureEvent when window resizes.
  internals.InteractorObserverIds.push_back(
    iren->AddObserver(vtkCommand::ConfigureEvent, this, &vtkSMViewProxy::HandleInteractorEvents));
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::HandleRendererEndEvent(
  vtkObject* /*caller*/, unsigned long eventId, void* /*calldata*/)
{
  auto& internals = (*this->Internals);
  if (eventId != vtkCommand::EndEvent)
  {
    return;
  }

  vtkLogScopeF(TRACE, "%s, payload_qsize=%lu", __func__, internals.PayloadQueue.size());
  // 1. adjust positioning of value annotations to appear next to keys.
  auto pos = internals.AnnotationKeys->GetPositionCoordinate();
  auto pos2 = internals.AnnotationKeys->GetPosition2Coordinate();
  internals.AnnotationValues->SetPosition(pos2->GetValue()[0] + 0.01, 0.99 - pos2->GetValue()[1]);

  // 2. Handle 'mute' display.
  if (vtkSMPropertyHelper(this, "Display").GetAsInt(0) != 1)
  {
    // we're not supposed to decode and render the frame. display stats only.
    internals.RenderWindow->SwapBuffersOn();
    internals.Renderer->Clear();
    return;
  }
  else if (internals.PayloadQueue.empty())
  {
    // do not swap buffers. it avoids presenting a black screen.
    internals.RenderWindow->SwapBuffersOff();
    return;
  }

  // 3. Flush packets to display.
  this->FlushDisplayFramebuffer();
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::HandleInteractorEvents(
  vtkObject* caller, unsigned long eventId, void* /*calldata*/)
{
  vtkLogScopeF(TRACE, "%s, eventId=%ld", __func__, eventId);

  auto& internals = (*this->Internals);
  auto* interactor = vtkRenderWindowInteractor::SafeDownCast(caller);
  switch (eventId)
  {
    case vtkCommand::StartInteractionEvent:
      // user provided an interactor and wants us to handle still/interactive modes.
      if (interactor->GetEnableRender())
      {
        internals.InteractiveRender = true;
        // no need for spontaneous renders while interacting.
        internals.FlushPackets = false;
      }
      break;
    case vtkCommand::EndInteractionEvent:
      // user provided an interactor and wants us to handle still/interactive modes.
      if (interactor->GetEnableRender())
      {
        internals.InteractiveRender = false;
        // a render event is not fired after interaction ends, let's request one.
        this->RequestRender();
        // draw the result of the above request as soon as it arrives.
        internals.FlushPackets = true;
      }
      break;
    case vtkCommand::RenderEvent:
      if (!internals.InDrawOverlay && interactor->GetEnableRender())
      {
        this->UpdateCameraIfNeeded();
      }
      break;
    case vtkCommand::WindowResizeEvent:
    case vtkCommand::ConfigureEvent:
      // TODO: we'd like to present a high quality render after resize and low quality renders
      // during resize, but, vtk does not have events that signal StartResize, EndResize, so, let's
      // naively force all resize events to invoke still render.
      internals.InteractiveRender = false;
      // implicitly requests a new render with updated size.
      this->UpdateSize();
      // draw the result of the above request as soon as it arrives.
      internals.FlushPackets = true;
      break;
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::FlushDisplayFramebuffer()
{
  auto& internals = (*this->Internals);
  vtkLogScopeF(TRACE, "%s, payload_qsize=%lu", __func__, internals.PayloadQueue.size());
  while (!internals.PayloadQueue.empty())
  {
    auto next = internals.PayloadQueue.front();
    internals.PayloadQueue.pop();
    vtkSmartPointer<vtkRawVideoFrame> frame;
    const auto& mime = next->GetMimeType();
    if (mime.empty() && internals.VideoDecoder != nullptr)
    {
      throw std::runtime_error("invalid mime-type");
    }
    else if (mime == std::string("image/rgb24") || mime == std::string("image/rgba32"))
    {
      frame = this->LoadLosslessImage(next);
    }
    else if (mime == std::string("image/jpeg"))
    {
      frame = this->LoadJpegImage(next);
    }
    else if (internals.VideoDecoder != nullptr)
    {
      frame = this->LoadVideoFrame(next);
    }
    else
    {
      // clear the screen as we do not know how to present the payload.
      internals.RenderWindow->SwapBuffersOn();
      internals.Renderer->Clear();
    }
    if (frame != nullptr)
    {
      internals.RenderWindow->SwapBuffersOn();
      this->DrawOverlay(frame);
    }
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::UpdateCameraIfNeeded(
  bool force /*= false*/, bool progressiveRendering /*= false*/)
{
  vtkLogScopeF(
    TRACE, "%s, force=%d, progressiveRendering=%d", __func__, force, progressiveRendering);

  auto& internals = (*this->Internals);
  auto* camera = internals.Renderer->GetActiveCamera();
  if (force || progressiveRendering || internals.LastCameraMTime != camera->GetMTime())
  {
    internals.RenderingPass = progressiveRendering ? internals.RenderingPass + 1 : 0;
    vtkLogF(TRACE, "push camera");
    internals.LastCameraMTime = camera->GetMTime();
    auto packet = vtkRemoteObjectProviderViews::Render(
      this->GetGlobalID(), camera, internals.InteractiveRender);
    this->GetSession()->SendMessage(vtkClientSession::RENDER_SERVER, std::move(packet));
  }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::UpdateSize()
{
  auto& internals = (*this->Internals);
  auto& window = internals.RenderWindow;
  vtkSMPropertyHelper(this, "ViewSize").Set(window->GetSize(), 2);
  this->UpdateVTKObjects();
  this->UpdateCameraIfNeeded(true);
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::UpdateRayTracingState(const vtkRemoteViewStatsItem& stats)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  internals.UsingRayTracing = stats.RayTracing;
  internals.TotalRenderingPasses = stats.NumberOfRenderPasses;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::GetContinueRendering() const
{
  auto& internals = (*this->Internals);
  if (!internals.UsingRayTracing)
  {
    return false;
  }
  if (internals.RenderingPass < internals.TotalRenderingPasses)
  {
    return true;
  }
  return false;
}

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