// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkSMPreselectionPipeline.h"

#include "vtkCallbackCommand.h"
#include "vtkCommand.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkProcessModule.h"
#include "vtkSMInputProperty.h"
#include "vtkSMIntVectorProperty.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxy.h"
#include "vtkSMProxyManager.h"
#include "vtkSMRenderViewProxy.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSettings.h"
#include "vtkSMSourceProxy.h"

//----------------------------------------------------------------------------
vtkSMPreselectionPipeline::vtkSMPreselectionPipeline()
  : ExtractInteractiveSelection(nullptr)
  , SelectionRepresentation(nullptr)
  , PreviousView(nullptr)
  , PreviousRepresentation(nullptr)
  , ColorObserver(nullptr)
  , ConnectionObserver(nullptr)
{
  this->ConnectionObserver = vtkCallbackCommand::New();
  this->ConnectionObserver->SetCallback(&vtkSMPreselectionPipeline::ClearCache);
  this->ConnectionObserver->SetClientData(this);

  vtkProcessModule* pm = vtkProcessModule::GetProcessModule();
  pm->AddObserver(vtkCommand::ConnectionClosedEvent, this->ConnectionObserver);
}

//----------------------------------------------------------------------------
vtkSMPreselectionPipeline::~vtkSMPreselectionPipeline()
{
  this->ClearCache();
  if (this->ConnectionObserver)
  {
    this->ConnectionObserver->Delete();
  }
}

//----------------------------------------------------------------------------
void vtkSMPreselectionPipeline::ClearCache(vtkObject*, unsigned long, void* clientdata, void*)
{
  vtkSMPreselectionPipeline* This = static_cast<vtkSMPreselectionPipeline*>(clientdata);
  This->ClearCache();
}

//----------------------------------------------------------------------------
void vtkSMPreselectionPipeline::ClearCache()
{
  if (this->ExtractInteractiveSelection)
  {
    this->ExtractInteractiveSelection->Delete();
    this->ExtractInteractiveSelection = nullptr;
  }
  if (this->SelectionRepresentation)
  {
    this->SelectionRepresentation->Delete();
    this->SelectionRepresentation = nullptr;
  }
  this->PreviousView = nullptr;
  this->PreviousRepresentation = nullptr;
  if (this->ColorObserver)
  {
    this->ColorObserver->Delete();
    this->ColorObserver = nullptr;
  }
}

//----------------------------------------------------------------------------
vtkSMSourceProxy* vtkSMPreselectionPipeline::ConnectPVExtractSelection(
  vtkSMSourceProxy* source, unsigned int sourceOutputPort, vtkSMSourceProxy* selection)
{
  vtkSMSessionProxyManager* proxyManager =
    vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();
  vtkSMSourceProxy* extract = nullptr;
  if (!this->ExtractInteractiveSelection)
  {
    extract =
      vtkSMSourceProxy::SafeDownCast(proxyManager->NewProxy("filters", "PVExtractSelection"));
    vtkSMIntVectorProperty* HTG2UG =
      vtkSMIntVectorProperty::SafeDownCast(extract->GetProperty("HyperTreeGridToUnstructuredGrid"));
    HTG2UG->SetElement(0, 1);
  }
  else
  {
    extract = this->ExtractInteractiveSelection;
  }

  // set the selection
  vtkSMInputProperty* inputProperty =
    vtkSMInputProperty::SafeDownCast(extract->GetProperty("Selection"));
  inputProperty->RemoveAllProxies();
  inputProperty->AddInputConnection(selection, 0);
  // set the source
  inputProperty = vtkSMInputProperty::SafeDownCast(extract->GetProperty("Input"));
  inputProperty->RemoveAllProxies();
  inputProperty->AddInputConnection(source, sourceOutputPort);
  extract->UpdateVTKObjects();

  if (!this->ExtractInteractiveSelection)
  {
    this->ExtractInteractiveSelection = extract;
  }
  return extract;
}

//----------------------------------------------------------------------------
vtkSMProxy* vtkSMPreselectionPipeline::CreateSelectionRepresentation(
  vtkSMSourceProxy* extract, vtkSMRenderViewProxy* view)
{
  if (!this->SelectionRepresentation)
  {
    vtkSMSessionProxyManager* proxyManager =
      vtkSMProxyManager::GetProxyManager()->GetActiveSessionProxyManager();
    vtkSMProxy* representation = proxyManager->NewProxy("representations",
      view ? view->GetSelectionRepresentationProxyName() : "SelectionRepresentation");
    vtkSMSettings* settings = vtkSMSettings::GetInstance();
    settings->GetProxySettings(representation);

    vtkSMPropertyHelper(representation, "Input").Set(extract);
    vtkSMPropertyHelper(representation, "Visibility").Set(extract != nullptr);
    double color[] = { 0.5, 0, 1 };
    vtkSMProxy* colorPalette = proxyManager->GetProxy("settings", "ColorPalette");
    if (!this->ColorObserver)
    {
      // setup a callback to set the interactive selection color
      this->ColorObserver = vtkCallbackCommand::New();
      this->ColorObserver->SetCallback(&vtkSMPreselectionPipeline::OnColorModified);
      this->ColorObserver->SetClientData(this);
    }
    if (colorPalette)
    {
      vtkSMProperty* colorProperty = colorPalette->GetProperty("InteractiveSelectionColor");
      colorProperty->AddObserver(vtkCommand::ModifiedEvent, this->ColorObserver);
      vtkSMPropertyHelper(colorPalette, "InteractiveSelectionColor").Get(color, 3);
    }
    vtkSMPropertyHelper(representation, "Color").Set(color, 3);
    representation->UpdateVTKObjects();
    this->SelectionRepresentation = representation;
  }
  else
  {
    vtkSMPropertyHelper(this->SelectionRepresentation, "Input").Set(extract);
    vtkSMPropertyHelper(this->SelectionRepresentation, "Visibility").Set(extract != nullptr);
    this->SelectionRepresentation->UpdateVTKObjects();
  }
  return this->SelectionRepresentation;
}

//----------------------------------------------------------------------------
vtkSMProxy* vtkSMPreselectionPipeline::GetSelectionRepresentation() const
{
  return this->SelectionRepresentation;
}

//----------------------------------------------------------------------------
vtkSMProxy* vtkSMPreselectionPipeline::GetOrCreateSelectionRepresentation()
{
  vtkSMProxy* proxyISelectionRepresentation = this->GetSelectionRepresentation();
  if (!proxyISelectionRepresentation)
  {
    proxyISelectionRepresentation = this->CreateSelectionRepresentation(nullptr);
  }
  return proxyISelectionRepresentation;
}

//----------------------------------------------------------------------------
void vtkSMPreselectionPipeline::OnColorModified(
  vtkObject* source, unsigned long, void* clientdata, void*)
{
  vtkSMPreselectionPipeline* This = static_cast<vtkSMPreselectionPipeline*>(clientdata);
  vtkSMProperty* property = vtkSMProperty::SafeDownCast(source);
  double color[] = { 0, 0, 0 };
  vtkSMPropertyHelper(property).Get(color, 3);
  vtkSMPropertyHelper(This->SelectionRepresentation, "Color").Set(color, 3);
  This->SelectionRepresentation->UpdateVTKObjects();
}

//----------------------------------------------------------------------------
void vtkSMPreselectionPipeline::Hide(vtkSMRenderViewProxy* view)
{
  if (this->SelectionRepresentation && view)
  {
    vtkSMPropertyHelper(this->SelectionRepresentation, "Visibility").Set(0);
    this->SelectionRepresentation->UpdateVTKObjects();
    view->StillRender();
  }
}

//----------------------------------------------------------------------------
void vtkSMPreselectionPipeline::Show(
  vtkSMSourceProxy* representation, vtkSMSourceProxy* selection, vtkSMRenderViewProxy* view)
{
  if (representation && view)
  {
    if (this->PreviousRepresentation != representation)
    {
      this->CopyLabels(representation);
      this->PreviousRepresentation = representation;
    }
    vtkSMPropertyHelper representationHelper(representation, "Input", true);
    vtkSMSourceProxy* source = vtkSMSourceProxy::SafeDownCast(representationHelper.GetAsProxy());
    unsigned int sourceOutputPort = representationHelper.GetOutputPort();
    vtkSMSourceProxy* extract =
      this->ConnectPVExtractSelection(source, sourceOutputPort, selection);

    // Clear Cache when server is not the same, can happen in multi server mode
    if (this->SelectionRepresentation &&
      view->GetSession() != this->SelectionRepresentation->GetSession())
    {
      this->ClearCache();
    }
    // Clear Cache when then render view type has changed
    if (this->PreviousView && strcmp(this->PreviousView->GetXMLName(), view->GetXMLName()) != 0)
    {
      this->ClearCache();
    }

    this->CreateSelectionRepresentation(extract, view);
    if (this->PreviousView)
    {
      vtkSMPropertyHelper(this->PreviousView, "Representations")
        .Remove(this->SelectionRepresentation);
      this->PreviousView->UpdateVTKObjects();
    }
    auto selectionRepresentation = representation->GetSubProxy("SelectionRepresentation");
    view->CopySelectionRepresentationProperties(
      selectionRepresentation, this->SelectionRepresentation);
    vtkSMPropertyHelper(view, "Representations").Add(this->SelectionRepresentation);
    view->UpdateVTKObjects();
    view->StillRender();
    this->PreviousView = view;
  }
  else
  {
    this->PreviousRepresentation = nullptr;
    this->Hide(view);
  }
}

void vtkSMPreselectionPipeline::CopyLabels(vtkSMProxy* representation)
{
  vtkSMProxy* iSelectionRepresentation = this->GetSelectionRepresentation();
  if (iSelectionRepresentation)
  {
    const char* selectionArrayNames[] = { "SelectionCellFieldDataArrayName",
      "SelectionPointFieldDataArrayName" };
    const char* selectionVisibilityNames[] = { "SelectionCellLabelVisibility",
      "SelectionPointLabelVisibility" };
    const char* iSelectionArrayNames[] = { "CellFieldDataArrayName", "PointFieldDataArrayName" };
    const char* iSelectionVisibilityNames[] = { "CellLabelVisibility", "PointLabelVisibility" };
    for (int i = 0; i < 2; ++i)
    {
      const char* iSelectionVisibilityName = iSelectionVisibilityNames[i];
      const char* iSelectionArrayName = iSelectionArrayNames[i];
      const char* selectionVisibilityName = selectionVisibilityNames[i];
      const char* selectionArrayName = selectionArrayNames[i];
      int visibility = 0;
      const char* selectionArray = "";
      if (representation)
      {
        visibility = vtkSMPropertyHelper(representation, selectionVisibilityName, true).GetAsInt();
        selectionArray =
          vtkSMPropertyHelper(representation, selectionArrayName, true).GetAsString();
      }
      vtkSMPropertyHelper(iSelectionRepresentation, iSelectionVisibilityName, true).Set(visibility);
      vtkSMPropertyHelper(iSelectionRepresentation, iSelectionArrayName, true).Set(selectionArray);
      iSelectionRepresentation->UpdateVTKObjects();
    }
  }
}

//----------------------------------------------------------------------------
void vtkSMPreselectionPipeline::PrintSelf(ostream& os, vtkIndent indent)
{
  (void)os;
  (void)indent;
}
