// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkSynchronizedRenderWindows.h"

#include "vtkCommand.h"
#include "vtkMultiProcessController.h"
#include "vtkMultiProcessStream.h"
#include "vtkObjectFactory.h"
#include "vtkRenderWindow.h"
#include "vtkWeakPointer.h"

#include <map>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
class vtkSynchronizedRenderWindows::vtkObserver : public vtkCommand
{
public:
  static vtkObserver* New()
  {
    vtkObserver* obs = new vtkObserver();
    obs->Target = nullptr;
    return obs;
  }

  void Execute(vtkObject*, unsigned long eventId, void*) override
  {
    if (this->Target)
    {
      switch (eventId)
      {
        case vtkCommand::StartEvent:
          this->Target->HandleStartRender();
          break;

        case vtkCommand::EndEvent:
          this->Target->HandleEndRender();
          break;

        case vtkCommand::AbortCheckEvent:
          this->Target->HandleAbortRender();
          break;
      }
    }
  }

  vtkSynchronizedRenderWindows* Target;
};

//------------------------------------------------------------------------------
namespace
{
typedef std::map<unsigned int, vtkWeakPointer<vtkSynchronizedRenderWindows>>
  GlobalSynRenderWindowsMapType;
GlobalSynRenderWindowsMapType GlobalSynRenderWindowsMap;

void RenderRMI(
  void* vtkNotUsed(localArg), void* remoteArg, int remoteArgLength, int vtkNotUsed(remoteProcessId))
{
  vtkMultiProcessStream stream;
  stream.SetRawData(reinterpret_cast<unsigned char*>(remoteArg), remoteArgLength);
  unsigned int id = 0;
  stream >> id;
  GlobalSynRenderWindowsMapType::iterator iter = GlobalSynRenderWindowsMap.find(id);
  if (iter != GlobalSynRenderWindowsMap.end() && iter->second != nullptr &&
    iter->second->GetRenderWindow() != nullptr)
  {
    iter->second->GetRenderWindow()->Render();
  }
}
}

//------------------------------------------------------------------------------

vtkStandardNewMacro(vtkSynchronizedRenderWindows);
//------------------------------------------------------------------------------
vtkSynchronizedRenderWindows::vtkSynchronizedRenderWindows()
{
  this->Observer = vtkSynchronizedRenderWindows::vtkObserver::New();
  this->Observer->Target = this;

  this->RenderWindow = nullptr;
  this->ParallelController = nullptr;
  this->Identifier = 0;
  this->ParallelRendering = true;
  this->RenderEventPropagation = true;
  this->RootProcessId = 0;
}

//------------------------------------------------------------------------------
vtkSynchronizedRenderWindows::~vtkSynchronizedRenderWindows()
{
  this->SetIdentifier(0);

  this->Observer->Target = nullptr;

  this->SetRenderWindow(nullptr);
  this->SetParallelController(nullptr);
  this->Observer->Delete();
  this->Observer = nullptr;
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::SetIdentifier(unsigned int id)
{
  if (this->Identifier == id)
  {
    return;
  }

  if (this->Identifier != 0)
  {
    GlobalSynRenderWindowsMap.erase(this->Identifier);
    this->Identifier = 0;
  }

  GlobalSynRenderWindowsMapType::iterator iter = GlobalSynRenderWindowsMap.find(id);
  if (iter != GlobalSynRenderWindowsMap.end())
  {
    vtkErrorMacro("Identifier already in use: " << id);
    return;
  }

  this->Identifier = id;
  if (id > 0)
  {
    GlobalSynRenderWindowsMap[id] = this;
  }
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::SetRenderWindow(vtkRenderWindow* renWin)
{
  if (this->RenderWindow != renWin)
  {
    if (this->RenderWindow)
    {
      this->RenderWindow->RemoveObserver(this->Observer);
    }
    vtkSetObjectBodyMacro(RenderWindow, vtkRenderWindow, renWin);
    if (this->RenderWindow)
    {
      this->RenderWindow->AddObserver(vtkCommand::StartEvent, this->Observer);
      this->RenderWindow->AddObserver(vtkCommand::EndEvent, this->Observer);
      // this->RenderWindow->AddObserver(vtkCommand::AbortCheckEvent, this->Observer);
    }
  }
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::SetParallelController(vtkMultiProcessController* controller)
{
  if (this->ParallelController == controller)
  {
    return;
  }

  vtkSetObjectBodyMacro(ParallelController, vtkMultiProcessController, controller);

  if (controller)
  {
    // no harm in adding this multiple times.
    controller->AddRMI(::RenderRMI, nullptr, SYNC_RENDER_TAG);
  }
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::AbortRender()
{
  if (this->ParallelRendering && this->ParallelController &&
    this->ParallelController->GetLocalProcessId() == this->RootProcessId)
  {
    // TODO: trigger abort render message.
  }
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::HandleStartRender()
{
  if (!this->RenderWindow || !this->ParallelRendering || !this->ParallelController ||
    (!this->Identifier && this->RenderEventPropagation))
  {
    return;
  }

  if (this->ParallelController->GetLocalProcessId() == this->RootProcessId)
  {
    this->MasterStartRender();
  }
  else
  {
    this->SlaveStartRender();
  }
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::MasterStartRender()
{
  if (this->RenderEventPropagation)
  {
    vtkMultiProcessStream stream;
    stream << this->Identifier;

    std::vector<unsigned char> data;
    stream.GetRawData(data);

    this->ParallelController->TriggerRMIOnAllChildren(
      data.data(), static_cast<int>(data.size()), SYNC_RENDER_TAG);
  }

  RenderWindowInfo windowInfo;
  windowInfo.CopyFrom(this->RenderWindow);

  vtkMultiProcessStream stream;
  windowInfo.Save(stream);
  this->ParallelController->Broadcast(stream, this->RootProcessId);
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::SlaveStartRender()
{
  vtkMultiProcessStream stream;
  this->ParallelController->Broadcast(stream, this->RootProcessId);

  RenderWindowInfo windowInfo;
  windowInfo.Restore(stream);
  windowInfo.CopyTo(this->RenderWindow);
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Identifier: " << this->Identifier << endl;
  os << indent << "ParallelRendering: " << this->ParallelRendering << endl;
  os << indent << "RootProcessId: " << this->RootProcessId << endl;
  os << indent << "RenderEventPropagation: " << this->RenderEventPropagation << endl;

  os << indent << "RenderWindow: ";
  if (this->RenderWindow == nullptr)
  {
    os << "(none)" << endl;
  }
  else
  {
    os << this->RenderWindow << endl;
  }
  if (this->ParallelController == nullptr)
  {
    os << "(none)" << endl;
  }
  else
  {
    os << this->ParallelController << endl;
  }
}

//------------------------------------------------------------------------------
// ********* INFO OBJECT METHODS ***************************
//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::RenderWindowInfo::Save(vtkMultiProcessStream& stream)
{
  stream << 1208 << this->WindowSize[0] << this->WindowSize[1] << this->TileScale[0]
         << this->TileScale[1] << this->TileViewport[0] << this->TileViewport[1]
         << this->TileViewport[2] << this->TileViewport[3] << this->DesiredUpdateRate;
}

//------------------------------------------------------------------------------
bool vtkSynchronizedRenderWindows::RenderWindowInfo::Restore(vtkMultiProcessStream& stream)
{
  int tag;
  stream >> tag;
  if (tag != 1208)
  {
    return false;
  }

  stream >> this->WindowSize[0] >> this->WindowSize[1] >> this->TileScale[0] >>
    this->TileScale[1] >> this->TileViewport[0] >> this->TileViewport[1] >> this->TileViewport[2] >>
    this->TileViewport[3] >> this->DesiredUpdateRate;
  return true;
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::RenderWindowInfo::CopyFrom(vtkRenderWindow* win)
{
  this->WindowSize[0] = win->GetActualSize()[0];
  this->WindowSize[1] = win->GetActualSize()[1];
  this->DesiredUpdateRate = win->GetDesiredUpdateRate();
  win->GetTileScale(this->TileScale);
  win->GetTileViewport(this->TileViewport);
}

//------------------------------------------------------------------------------
void vtkSynchronizedRenderWindows::RenderWindowInfo::CopyTo(vtkRenderWindow* win)
{
  win->SetSize(this->WindowSize[0], this->WindowSize[1]);
  win->SetTileScale(this->TileScale);
  win->SetTileViewport(this->TileViewport);
  win->SetDesiredUpdateRate(this->DesiredUpdateRate);
}
VTK_ABI_NAMESPACE_END
