// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkGraphAnnotationLayersFilter.h"

#include "vtkAnnotation.h"
#include "vtkAnnotationLayers.h"
#include "vtkAppendPolyData.h"
#include "vtkCellData.h"
#include "vtkConvexHull2D.h"
#include "vtkDataSetAttributes.h"
#include "vtkDoubleArray.h"
#include "vtkGraph.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkRenderer.h"
#include "vtkSelection.h"
#include "vtkSelectionNode.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkUnsignedCharArray.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkGraphAnnotationLayersFilter);

//------------------------------------------------------------------------------
vtkGraphAnnotationLayersFilter::vtkGraphAnnotationLayersFilter()
{
  this->SetNumberOfInputPorts(2);
  this->SetNumberOfOutputPorts(2);

  this->HullAppend = vtkSmartPointer<vtkAppendPolyData>::New();
  this->OutlineAppend = vtkSmartPointer<vtkAppendPolyData>::New();
  this->ConvexHullFilter = vtkSmartPointer<vtkConvexHull2D>::New();
}

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

//------------------------------------------------------------------------------
int vtkGraphAnnotationLayersFilter::FillInputPortInformation(int port, vtkInformation* info)
{
  if (port == 0)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkGraph");
    return 1;
  }
  else if (port == 1)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkAnnotationLayers");
    return 1;
  }
  return 0;
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::OutlineOn()
{
  this->ConvexHullFilter->OutlineOn();
}

void vtkGraphAnnotationLayersFilter::OutlineOff()
{
  this->ConvexHullFilter->OutlineOff();
}

void vtkGraphAnnotationLayersFilter::SetOutline(bool b)
{
  this->ConvexHullFilter->SetOutline(b);
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::SetScaleFactor(double scale)
{
  this->ConvexHullFilter->SetScaleFactor(scale);
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::SetHullShapeToBoundingRectangle()
{
  this->ConvexHullFilter->SetHullShape(vtkConvexHull2D::BoundingRectangle);
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::SetHullShapeToConvexHull()
{
  this->ConvexHullFilter->SetHullShape(vtkConvexHull2D::ConvexHull);
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::SetMinHullSizeInWorld(double size)
{
  this->ConvexHullFilter->SetMinHullSizeInWorld(size);
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::SetMinHullSizeInDisplay(int size)
{
  this->ConvexHullFilter->SetMinHullSizeInDisplay(size);
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::SetRenderer(vtkRenderer* renderer)
{
  this->ConvexHullFilter->SetRenderer(renderer);
}

//------------------------------------------------------------------------------
vtkMTimeType vtkGraphAnnotationLayersFilter::GetMTime()
{
  if (this->ConvexHullFilter)
  {
    return this->ConvexHullFilter->GetMTime();
  }
  else
  {
    return this->MTime;
  }
}

//------------------------------------------------------------------------------
int vtkGraphAnnotationLayersFilter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // Get the input and output.
  vtkInformation* inGraphInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* inLayersInfo = inputVector[1]->GetInformationObject(0);

  vtkGraph* graph = vtkGraph::SafeDownCast(inGraphInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPoints* inputPoints = graph->GetPoints();
  vtkAnnotationLayers* layers =
    vtkAnnotationLayers::SafeDownCast(inLayersInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkInformation* outInfo0 = outputVector->GetInformationObject(0);
  vtkInformation* outInfo1 = outputVector->GetInformationObject(1);

  vtkPolyData* outputHull = vtkPolyData::SafeDownCast(outInfo0->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* outputOutline =
    vtkPolyData::SafeDownCast(outInfo1->Get(vtkDataObject::DATA_OBJECT()));

  this->HullAppend->RemoveAllInputs();
  this->OutlineAppend->RemoveAllInputs();

  unsigned numberOfAnnotations = layers->GetNumberOfAnnotations();

  // Generate one hull/polydata per selection node
  vtkIdType hullId = 0;
  for (unsigned annotationId = 0; annotationId < numberOfAnnotations; ++annotationId)
  {
    vtkAnnotation* annotation = layers->GetAnnotation(annotationId);
    if (annotation->GetInformation()->Get(vtkAnnotation::ENABLE()) == 0)
    {
      continue;
    }

    vtkSelection* selection = annotation->GetSelection();
    unsigned numberOfSelectionNodes = selection->GetNumberOfNodes();

    for (unsigned selectionNodeId = 0; selectionNodeId < numberOfSelectionNodes; ++selectionNodeId)
    {
      vtkSmartPointer<vtkPoints> hullPoints = vtkSmartPointer<vtkPoints>::New();

      hullId++;
      vtkSelectionNode* selectionNode = selection->GetNode(selectionNodeId);
      if (selectionNode->GetFieldType() != vtkSelectionNode::VERTEX)
      {
        continue;
      }
      vtkIdTypeArray* vertexIds =
        vtkArrayDownCast<vtkIdTypeArray>(selectionNode->GetSelectionList());

      // Get points from graph
      vtkIdType numberOfNodePoints = vertexIds->GetNumberOfTuples();
      if (numberOfNodePoints == 0)
      {
        continue;
      }
      for (vtkIdType i = 0; i < numberOfNodePoints; ++i)
      {
        hullPoints->InsertNextPoint(inputPoints->GetPoint(vertexIds->GetValue(i)));
      }

      // Create filled polygon
      vtkSmartPointer<vtkPolyData> hullPolyData = vtkSmartPointer<vtkPolyData>::New();
      hullPolyData->SetPoints(hullPoints);
      ConvexHullFilter->SetInputData(hullPolyData);
      ConvexHullFilter->Update();
      hullPolyData->ShallowCopy(ConvexHullFilter->GetOutput());

      // Add data arrays to the polydata
      vtkIdType representativeVertex = vertexIds->GetValue(0);
      vtkIdType numberOfCells = hullPolyData->GetNumberOfCells();

      vtkUnsignedCharArray* outColors = vtkUnsignedCharArray::New();
      outColors->SetNumberOfComponents(4);
      outColors->SetName("Hull color");
      double* color = annotation->GetInformation()->Get(vtkAnnotation::COLOR());
      double opacity = annotation->GetInformation()->Get(vtkAnnotation::OPACITY());
      unsigned char outColor[4] = { static_cast<unsigned char>(color[0] * 255),
        static_cast<unsigned char>(color[1] * 255), static_cast<unsigned char>(color[2] * 255),
        static_cast<unsigned char>(opacity * 255) };
      for (vtkIdType i = 0; i < numberOfCells; ++i)
      {
        outColors->InsertNextTypedTuple(outColor);
      }
      hullPolyData->GetCellData()->AddArray(outColors);
      outColors->Delete();

      vtkIdTypeArray* hullIds = vtkIdTypeArray::New();
      hullIds->SetName("Hull id");
      for (vtkIdType i = 0; i < numberOfCells; ++i)
      {
        hullIds->InsertNextValue(hullId);
      }
      hullPolyData->GetCellData()->AddArray(hullIds);
      hullIds->Delete();

      vtkStringArray* hullName = vtkStringArray::New();
      hullName->SetName("Hull name");
      for (vtkIdType i = 0; i < numberOfCells; ++i)
      {
        hullName->InsertNextValue(annotation->GetInformation()->Get(vtkAnnotation::LABEL()));
      }
      hullPolyData->GetCellData()->AddArray(hullName);
      hullName->Delete();

      vtkDoubleArray* hullCentreVertex = vtkDoubleArray::New();
      hullCentreVertex->SetName("Hull point");
      hullCentreVertex->SetNumberOfComponents(3);
      for (vtkIdType i = 0; i < numberOfCells; ++i)
      {
        hullCentreVertex->InsertNextTuple(inputPoints->GetPoint(representativeVertex));
      }
      hullPolyData->GetCellData()->AddArray(hullCentreVertex);
      hullCentreVertex->Delete();

      this->HullAppend->AddInputData(hullPolyData);

      if (this->ConvexHullFilter->GetOutline())
      {
        vtkSmartPointer<vtkPolyData> outlinePolyData = vtkSmartPointer<vtkPolyData>::New();
        outlinePolyData->ShallowCopy(ConvexHullFilter->GetOutput(1));
        this->OutlineAppend->AddInputData(outlinePolyData);
      }
    } // Next selection node.
  }   // Next annotation.

  // Send data to output
  if (this->HullAppend->GetNumberOfInputConnections(0) > 0)
  {
    this->HullAppend->Update();
    outputHull->ShallowCopy(this->HullAppend->GetOutput());
  }
  if (this->OutlineAppend->GetNumberOfInputConnections(0) > 0)
  {
    this->OutlineAppend->Update();
    outputOutline->ShallowCopy(this->OutlineAppend->GetOutput());
  }
  return 1;
}

//------------------------------------------------------------------------------
void vtkGraphAnnotationLayersFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "ConvexHull2D: ";
  if (this->ConvexHullFilter)
  {
    os << endl;
    this->ConvexHullFilter->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << "(none)" << endl;
  }
}
VTK_ABI_NAMESPACE_END
