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

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

#include "vtkCommand.h"
#include "vtkDataObject.h"
#include "vtkInformation.h"
#include "vtkInformationDoubleKey.h"
#include "vtkInformationObjectBaseKey.h"
#include "vtkInformationRequestKey.h"
#include "vtkLogger.h"
#include "vtkMultiProcessController.h"
#include "vtkNJsonFwd.h"
#include "vtkObjectFactory.h"
#include "vtkPVCoreApplication.h"
#include "vtkPVCoreApplicationOptions.h"
#include "vtkPVDataDeliveryManager.h"
#include "vtkPVDataRepresentation.h"
#include "vtkPVImageDelivery.h"
#include "vtkPVLogger.h"
#include "vtkPVProcessWindow.h"
#include "vtkPVRenderingCapabilitiesInformation.h"
#include "vtkPointData.h"
#include "vtkRenderWindow.h"
#include "vtkRendererCollection.h"
#include "vtkSmartPointer.h"
#include "vtkTimeStamp.h"
#include "vtkTimerLog.h"
#include "vtkViewLayout.h"

#include <cassert>
#include <sstream>

//============================================================================
class vtkPVView::vtkInternals
{
public:
  vtkSmartPointer<vtkMultiProcessController> Controller;
  vtkTypeUInt32 GlobalID{ 0 };
  vtkVector2i Position{ 0, 0 };
  vtkVector2i Size{ 300, 300 };
  int PPI{ 72 };
  double ViewTime{ 0.0 };
  std::string LogName;
  bool Initialized{ false };
  bool InitializedForDataProcessing{ false };

  vtkSmartPointer<vtkPVImageDelivery> ImageDeliverer;
  vtkSmartPointer<vtkPVDataDeliveryManager> DeliveryManager;
  std::vector<vtkSmartPointer<vtkPVDataRepresentation>> Representations;

  mutable vtkTimeStamp UpdateTimeStamp;

  /**
   * Keeps track of the active rendering pass.
   */
  mutable vtkInformation* ActiveRequest{ nullptr };

  mutable int LastRenderingPass{ 0 };
  mutable RenderModes LastRenderingMode{ vtkPVView::STILL };

  std::atomic<std::uint64_t> LocalPreviewRenderCounter{ 0 };
  std::uint64_t GlobalPreviewRenderCounter{ 0 };
  std::uint64_t RenderCounter{ 0 };

  double Progress{ 0 };
};

vtkInformationKeyMacro(vtkPVView, REQUEST_UPDATE_DATA, Request);
vtkInformationKeyMacro(vtkPVView, REQUEST_UPDATE_DATA_LOD, Request);
vtkInformationKeyMacro(vtkPVView, REQUEST_UPDATE_RENDERING_INFORMATION, Request);
vtkInformationKeyMacro(vtkPVView, REQUEST_PREPARE_FOR_RENDER, Request);
vtkInformationKeyMacro(vtkPVView, REQUEST_PREPARE_FOR_RENDER_LOD, Request);
vtkInformationKeyMacro(vtkPVView, REQUEST_RENDER, Request);
vtkInformationKeyMacro(vtkPVView, UPDATE_TIME_STEP, Double);

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

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

//============================================================================
vtkPVView::vtkPVView()
  : Internals(new vtkPVView::vtkInternals())
{
  auto& internals = (*this->Internals);
  internals.DeliveryManager.TakeReference(vtkPVDataDeliveryManager::New());
  internals.DeliveryManager->SetView(this);
}

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

//----------------------------------------------------------------------------
void vtkPVView::SetGlobalID(vtkTypeUInt32 gid)
{
  auto& internals = (*this->Internals);
  internals.GlobalID = gid;
}

//----------------------------------------------------------------------------
vtkTypeUInt32 vtkPVView::GetGlobalID() const
{
  const auto& internals = (*this->Internals);
  return internals.GlobalID;
}

//----------------------------------------------------------------------------
void vtkPVView::SetController(vtkMultiProcessController* controller)
{
  auto& internals = (*this->Internals);
  internals.Controller = controller;
}

//----------------------------------------------------------------------------
vtkMultiProcessController* vtkPVView::GetController() const
{
  const auto& internals = (*this->Internals);
  return internals.Controller;
}

//----------------------------------------------------------------------------
void vtkPVView::SetPosition(int xpos, int ypos)
{
  auto& internals = (*this->Internals);
  internals.Position = vtkVector2i(xpos, ypos);
}

//----------------------------------------------------------------------------
const int* vtkPVView::GetPosition() const
{
  const auto& internals = (*this->Internals);
  return internals.Position.GetData();
}

//----------------------------------------------------------------------------
void vtkPVView::SetSize(int width, int height)
{
  vtkLogScopeF(TRACE, "%s %dx%d", __func__, width, height);
  if (this->InTileDisplayMode() || this->InCaveDisplayMode())
  {
    vtkLogF(TRACE, "Ignore request to set size. Reason: In tile/cave display mode");
    return;
  }
  bool isInvalidSize = width <= 0 || height <= 0;
  if (isInvalidSize)
  {
    vtkLogF(WARNING, "Ignore request to set size. Reason. Invalid size=%dx%d", width, height);
    return;
  }

  auto& internals = (*this->Internals);
  if (auto* window = this->GetRenderWindow())
  {
    const auto cur_size = window->GetActualSize();
    if (cur_size[0] != width || cur_size[1] != height)
    {
      window->SetSize(width, height);
    }
    else
    {
      vtkLogF(INFO, "Ignore request to set size. Reason: current_size=%dx%d=size", width, height);
    }
  }
  internals.Size = vtkVector2i(width, height);
}

//----------------------------------------------------------------------------
const int* vtkPVView::GetSize() const
{
  const auto& internals = (*this->Internals);
  return internals.Size.GetData();
}

//----------------------------------------------------------------------------
void vtkPVView::SetPPI(int ppi)
{
  auto& internals = (*this->Internals);
  internals.PPI = ppi;
  if (auto window = this->GetRenderWindow())
  {
    window->SetDPI(ppi);
  }
}

//----------------------------------------------------------------------------
int vtkPVView::GetPPI() const
{
  const auto& internals = (*this->Internals);
  return internals.PPI;
}

//----------------------------------------------------------------------------
void vtkPVView::SetViewTime(double value)
{
  auto& internals = (*this->Internals);
  internals.ViewTime = value;
}

//----------------------------------------------------------------------------
double vtkPVView::GetViewTime() const
{
  const auto& internals = (*this->Internals);
  return internals.ViewTime;
}

//----------------------------------------------------------------------------
void vtkPVView::SetLogName(const std::string& name)
{
  auto& internals = (*this->Internals);
  internals.LogName = name;
}

//----------------------------------------------------------------------------
const std::string& vtkPVView::GetLogName() const
{
  const auto& internals = (*this->Internals);
  return internals.LogName;
}

//----------------------------------------------------------------------------
void vtkPVView::InitializeForDataProcessing()
{
  auto& internals = (*this->Internals);
  assert("Not already initialized" && !internals.Initialized);
  internals.Initialized = true;
  internals.InitializedForDataProcessing = true;
}

//----------------------------------------------------------------------------
void vtkPVView::InitializeForRendering()
{
  auto& internals = (*this->Internals);
  assert("Not already initialized" && !internals.Initialized);
  internals.Initialized = true;
  internals.InitializedForDataProcessing = false;
}

//----------------------------------------------------------------------------
vtkRenderWindow* vtkPVView::NewRenderWindow()
{
  auto& internals = (*this->Internals);
  assert("View has been initialized." && internals.Initialized);
  assert("Called on rendering service." && !internals.InitializedForDataProcessing);

  auto* pvapp = vtkPVCoreApplication::GetInstance();
  // FIXME:
  // auto config = vtkRemotingCoreConfiguration::GetInstance();
  // // A little bit of a hack. I am not what's a good place to setup the display
  // // environment for this rank. So doing it here.
  // config->HandleDisplayEnvironment();

  vtkSmartPointer<vtkRenderWindow> window;
  // FIXME: ASYNC
  // For now, just create offscreen window always...we need to rethink this
  // quite a bit.
  window = vtkPVRenderingCapabilitiesInformation::NewOffscreenRenderWindow();
  if (window)
  {
    vtkVLogF(PARAVIEW_LOG_RENDERING_VERBOSITY(),
      "created a `%s` as a new render window for view %s", vtkLogIdentifier(window),
      vtkLogIdentifier(this));

    window->AlphaBitPlanesOn();

    std::ostringstream str;
    switch (pvapp->GetApplicationType())
    {
      case vtkPVCoreApplication::SERVER:
        str << "ParaView Server";
        break;

      default:
        str << "ParaView";
        break;
    }
    if (pvapp->GetNumberOfRanks() > 1)
    {
      str << pvapp->GetRank();
    }
    window->SetWindowName(str.str().c_str());
    window->Register(this);
    return window;
  }

  vtkVLogF(PARAVIEW_LOG_RENDERING_VERBOSITY(),
    "created `nullptr` as a new render window for view %s", vtkLogIdentifier(this));
  return nullptr;
}

//----------------------------------------------------------------------------
bool vtkPVView::InTileDisplayMode()
{
  // auto serverInfo = this->Session->GetServerInformation();
  // return serverInfo->GetIsInTileDisplay();
  // FIXME: ASYNC
  return false;
}

//----------------------------------------------------------------------------
bool vtkPVView::InCaveDisplayMode()
{
  // auto serverInfo = this->Session->GetServerInformation();
  // return serverInfo->GetIsInCave();
  // FIXME: ASYNC
  return false;
}

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

//----------------------------------------------------------------------------
void vtkPVView::Update()
{
  vtkVLogScopeF(PARAVIEW_LOG_RENDERING_VERBOSITY(), "%s: update view", this->GetLogName().c_str());
  this->InvokeEvent(vtkCommand::StartEvent);

  const auto& internals = (*this->Internals);
  if (!internals.InitializedForDataProcessing)
  {
    vtkLogF(WARNING, "Update called on non-data processing service. Ignored.");
    return;
  }

  vtkNew<vtkInformation> request;
  request->Set(vtkPVView::REQUEST_UPDATE_DATA());
  request->Set(vtkPVView::UPDATE_TIME_STEP(), internals.ViewTime);
  this->CallProcessViewRequest(request);
  internals.UpdateTimeStamp.Modified();

  // TODO:
  // reduce visible data size across all ranks.
  // this will then be used for other decision making.
  // This decision making will include which service to render on, perhaps i.e. render-server
  // or render-server-lite.
  this->InvokeEvent(vtkCommand::UpdateEvent);
  this->InvokeEvent(vtkCommand::EndEvent);
}

//----------------------------------------------------------------------------
void vtkPVView::UpdateForRendering()
{
  vtkVLogScopeF(PARAVIEW_LOG_RENDERING_VERBOSITY(), "%s: update view", this->GetLogName().c_str());
  this->InvokeEvent(vtkCommand::StartEvent);
  const auto& internals = (*this->Internals);
  if (internals.InitializedForDataProcessing)
  {
    vtkLogF(WARNING, "UpdateForRendering called on data processing service. Ignored.");
    return;
  }

  // RequestPrepareForRender is done twice. This ensures that
  // RequestRenderingInformation has as up-to-date data as possible to make
  // rendering decisions.
  this->RequestPrepareForRender();
  this->RequestRenderingInformation();
  this->RequestPrepareForRender();

  this->InvokeEvent(vtkCommand::UpdateEvent);
  this->InvokeEvent(vtkCommand::EndEvent);
}

//----------------------------------------------------------------------------
void vtkPVView::RequestPrepareForRender()
{
  vtkNew<vtkInformation> request;
  request->Set(vtkPVView::REQUEST_PREPARE_FOR_RENDER());
  this->CallProcessViewRequest(request);
}

//----------------------------------------------------------------------------
void vtkPVView::RequestRenderingInformation()
{
  vtkNew<vtkInformation> request;
  request->Set(vtkPVView::REQUEST_UPDATE_RENDERING_INFORMATION());
  this->CallProcessViewRequest(request);
}

//----------------------------------------------------------------------------
void vtkPVView::StillRender(int pass /*=0*/)
{
  vtkVLogScopeF(PARAVIEW_LOG_RENDERING_VERBOSITY(), "%s: still-render", this->GetLogName().c_str());
  const auto& internals = (*this->Internals);
  if (internals.InitializedForDataProcessing)
  {
    vtkLogF(WARNING, "StillRender called on data processing service. Ignored.");
    return;
  }

  internals.LastRenderingPass = pass;
  internals.LastRenderingMode = vtkPVView::STILL;

  vtkNew<vtkInformation> request;
  request->Set(vtkPVView::REQUEST_RENDER());
  this->CallProcessViewRequest(request);

  this->PrepareForRendering();
  if (auto* window = this->GetRenderWindow())
  {
    window->Render();
    this->PostRender();
  }
}

//----------------------------------------------------------------------------
void vtkPVView::InteractiveRender(int pass /*=0*/)
{
  vtkVLogScopeF(
    PARAVIEW_LOG_RENDERING_VERBOSITY(), "%s: interative-render", this->GetLogName().c_str());
  const auto& internals = (*this->Internals);
  if (internals.InitializedForDataProcessing)
  {
    vtkLogF(WARNING, "InteractiveRender called on data processing service. Ignored.");
    return;
  }

  internals.LastRenderingPass = pass;
  internals.LastRenderingMode = vtkPVView::INTERACTIVE;
  vtkNew<vtkInformation> request;
  request->Set(vtkPVView::REQUEST_RENDER());
  this->CallProcessViewRequest(request);

  this->PrepareForRendering();
  if (auto* window = this->GetRenderWindow())
  {
    window->Render();
    this->PostRender();
  }
}

//----------------------------------------------------------------------------
void vtkPVView::DoPreviewRender()
{
  // this is thread safe!
  auto& internals = (*this->Internals);
  ++internals.LocalPreviewRenderCounter;
}

//----------------------------------------------------------------------------
void vtkPVView::DoRender(bool interactive /*= false*/)
{
  auto& internals = (*this->Internals);
  auto& controller = internals.Controller;
  this->UpdateProgress(0);

  ++internals.RenderCounter;
  if (internals.RenderCounter < internals.GlobalPreviewRenderCounter)
  {
    // skip this render; globally we've seen newer render requests.
    return;
  }

  // we track local and global preview counters separately to avoid
  // communication between ranks for render requests that are known to be
  // obsolete since the most recent exchange among ranks.
  const uint64_t lcounter = internals.LocalPreviewRenderCounter;
  if (controller->GetNumberOfProcesses() > 1)
  {
    controller->AllReduce(
      &lcounter, &internals.GlobalPreviewRenderCounter, 1, vtkCommunicator::MAX_OP);
  }
  else
  {
    internals.GlobalPreviewRenderCounter = lcounter;
  }

  if (internals.RenderCounter < internals.GlobalPreviewRenderCounter)
  {
    return;
  }

  assert(internals.RenderCounter == internals.GlobalPreviewRenderCounter);
  if (interactive)
  {
    this->InteractiveRender();
  }
  else
  {
    this->StillRender();
  }
  this->UpdateProgress(1);
}

//----------------------------------------------------------------------------
int vtkPVView::GetLastRenderingPass() const
{
  const auto& internals = (*this->Internals);
  return internals.LastRenderingPass;
}

//----------------------------------------------------------------------------
int vtkPVView::GetLastRenderingMode() const
{
  const auto& internals = (*this->Internals);
  return internals.LastRenderingMode;
}

//----------------------------------------------------------------------------
int vtkPVView::CallProcessViewRequest(vtkInformation* request)
{
  const auto& internals = (*this->Internals);
  internals.ActiveRequest = request;

  int count = 0;
  for (auto& repr : internals.Representations)
  {
    if (repr->GetVisibility())
    {
      count += repr->ProcessViewRequest(request) ? 1 : 0;
    }
  }

  internals.ActiveRequest = nullptr;
  return count;
}

//-----------------------------------------------------------------------------
void vtkPVView::SetRepresentations(const std::vector<vtkPVDataRepresentation*>& representations)
{
  const auto& internals = (*this->Internals);

  std::vector<vtkPVDataRepresentation*> to_add;
  std::vector<vtkPVDataRepresentation*> to_remove;

  const std::set<vtkPVDataRepresentation*> old_value(
    internals.Representations.begin(), internals.Representations.end());
  const std::set<vtkPVDataRepresentation*> new_value(
    representations.begin(), representations.end());

  std::set_difference(old_value.begin(), old_value.end(), new_value.begin(), new_value.end(),
    std::back_inserter(to_remove));
  std::set_difference(new_value.begin(), new_value.end(), old_value.begin(), old_value.end(),
    std::back_inserter(to_add));

  for (auto& repr : to_remove)
  {
    if (repr)
    {
      this->RemoveRepresentation(repr);
    }
  }

  for (auto& repr : to_add)
  {
    if (repr)
    {
      this->AddRepresentation(repr);
    }
  }
}

//----------------------------------------------------------------------------
void vtkPVView::AddRepresentation(vtkPVDataRepresentation* representation)
{
  auto& internals = (*this->Internals);
  auto iter =
    std::find(internals.Representations.begin(), internals.Representations.end(), representation);
  if (iter == internals.Representations.end())
  {
    if (representation->AddToView(this))
    {
      this->AddRepresentationInternal(representation);
      internals.DeliveryManager->RegisterRepresentation(representation);
      internals.Representations.push_back(representation);
    }
    this->Modified();
  }
}

//----------------------------------------------------------------------------
void vtkPVView::RemoveRepresentation(vtkPVDataRepresentation* representation)
{
  auto& internals = (*this->Internals);
  auto iter =
    std::find(internals.Representations.begin(), internals.Representations.end(), representation);
  if (iter != internals.Representations.end())
  {
    this->RemoveRepresentationInternal(representation);
    representation->RemoveFromView(this);
    internals.DeliveryManager->UnRegisterRepresentation(representation);
    internals.Representations.erase(iter);
    this->Modified();
  }
}

//-----------------------------------------------------------------------------
void vtkPVView::ScaleRendererViewports(const double viewport[4])
{
  auto window = this->GetRenderWindow();
  if (!window)
  {
    return;
  }

  assert(viewport[0] >= 0 && viewport[0] <= 1.0);
  assert(viewport[1] >= 0 && viewport[1] <= 1.0);
  assert(viewport[2] >= 0 && viewport[2] <= 1.0);
  assert(viewport[3] >= 0 && viewport[3] <= 1.0);

  auto collection = window->GetRenderers();
  collection->InitTraversal();
  while (auto renderer = collection->GetNextItem())
  {
    renderer->SetViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
  }
}

//-----------------------------------------------------------------------------
void vtkPVView::SetTileScale(int x, int y)
{
  auto window = this->GetRenderWindow();
  if (window && !this->InTileDisplayMode() && !this->InCaveDisplayMode())
  {
    window->SetTileScale(x, y);
  }
}

//-----------------------------------------------------------------------------
void vtkPVView::SetTileViewport(double x0, double y0, double x1, double y1)
{
  auto window = this->GetRenderWindow();
  if (window && !this->InTileDisplayMode() && !this->InCaveDisplayMode())
  {
    window->SetTileViewport(x0, y0, x1, y1);
  }
}

//----------------------------------------------------------------------------
std::string vtkPVView::GetChannelName(vtkTypeUInt32 gid)
{
  return "view-image-" + std::to_string(gid);
}

//----------------------------------------------------------------------------
void vtkPVView::SetPiece(
  vtkPVDataRepresentation* repr, int index, vtkDataObject* data, int lodLevel)
{
  if (auto* self = repr ? repr->GetView() : nullptr)
  {
    auto& dManager = self->Internals->DeliveryManager;
    dManager->SetPiece(repr, index, data, lodLevel);
  }
}

//----------------------------------------------------------------------------
vtkSmartPointer<vtkDataObject> vtkPVView::GetPiece(
  vtkPVDataRepresentation* repr, int index, int lodLevel)
{
  if (auto* self = repr ? repr->GetView() : nullptr)
  {
    auto& dManager = self->Internals->DeliveryManager;
    return dManager->GetPiece(repr, index, lodLevel);
  }

  return nullptr;
}

//----------------------------------------------------------------------------
vtkPVDataDeliveryManager* vtkPVView::GetDataDeliveryManager() const
{
  const auto& internals = (*this->Internals);
  return internals.DeliveryManager;
}

//----------------------------------------------------------------------------
vtkPVImageDelivery* vtkPVView::GetImageDeliverer() const
{
  const auto& internals = (*this->Internals);
  return internals.ImageDeliverer;
}

//----------------------------------------------------------------------------
void vtkPVView::SetImageDeliverer(vtkPVImageDelivery* deliverer)
{
  vtkLogF(TRACE, "%s deliverer=%s", __func__, vtkLogIdentifier(deliverer));
  auto& internals = (*this->Internals);
  internals.ImageDeliverer = deliverer;
}

//----------------------------------------------------------------------------
void vtkPVView::PostRender()
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  // emit event with the render mode as calldata.
  this->InvokeEvent(vtkCommand::RenderEvent, &internals.LastRenderingMode);
}

//----------------------------------------------------------------------------
void vtkPVView::UpdateProgress(double amount)
{
  amount = vtkMath::ClampValue(amount, 0.0, 1.0);
  this->Internals->Progress = amount;
  this->InvokeEvent(vtkCommand::ProgressEvent, static_cast<void*>(&amount));
}

//----------------------------------------------------------------------------
double vtkPVView::GetProgress()
{
  return this->Internals->Progress;
}
