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

#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkDataObjectTreeIterator.h"
#include "vtkDataSet.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkNew.h"
#include "vtkPolyData.h"

#include <set>

VTK_ABI_NAMESPACE_BEGIN
class vtkOutlineFilter::vtkIndexSet : public std::set<unsigned int>
{
};

vtkStandardNewMacro(vtkOutlineFilter);

//------------------------------------------------------------------------------
vtkOutlineFilter::vtkOutlineFilter()
{
  this->GenerateFaces = 0;
  this->CompositeStyle = vtkOutlineFilter::ROOT_AND_LEAFS;
  this->OutputPointsPrecision = SINGLE_PRECISION;
  this->Indices = new vtkOutlineFilter::vtkIndexSet();
}

//------------------------------------------------------------------------------
vtkOutlineFilter::~vtkOutlineFilter()
{
  delete this->Indices;
}

//------------------------------------------------------------------------------
void vtkOutlineFilter::AddIndex(unsigned int index)
{
  if (this->Indices->find(index) == this->Indices->end())
  {
    this->Indices->insert(index);
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkOutlineFilter::RemoveIndex(unsigned int index)
{
  if (this->Indices->find(index) != this->Indices->end())
  {
    this->Indices->erase(index);
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkOutlineFilter::RemoveAllIndices()
{
  if (!this->Indices->empty())
  {
    this->Indices->clear();
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkOutlineFilter::AppendOutline(
  vtkPoints* points, vtkCellArray* lines, vtkCellArray* faces, double bounds[6])
{
  // Since points have likely been inserted before, keep track of the point
  // offsets
  vtkIdType offset[8];

  // Insert points
  double x[3];
  x[0] = bounds[0];
  x[1] = bounds[2];
  x[2] = bounds[4];
  offset[0] = points->InsertNextPoint(x);
  x[0] = bounds[1];
  x[1] = bounds[2];
  x[2] = bounds[4];
  offset[1] = points->InsertNextPoint(x);
  x[0] = bounds[0];
  x[1] = bounds[3];
  x[2] = bounds[4];
  offset[2] = points->InsertNextPoint(x);
  x[0] = bounds[1];
  x[1] = bounds[3];
  x[2] = bounds[4];
  offset[3] = points->InsertNextPoint(x);
  x[0] = bounds[0];
  x[1] = bounds[2];
  x[2] = bounds[5];
  offset[4] = points->InsertNextPoint(x);
  x[0] = bounds[1];
  x[1] = bounds[2];
  x[2] = bounds[5];
  offset[5] = points->InsertNextPoint(x);
  x[0] = bounds[0];
  x[1] = bounds[3];
  x[2] = bounds[5];
  offset[6] = points->InsertNextPoint(x);
  x[0] = bounds[1];
  x[1] = bounds[3];
  x[2] = bounds[5];
  offset[7] = points->InsertNextPoint(x);

  // Produce topology, either lines or quad faces
  vtkIdType pts[4];

  // Always generate wire edges. I think this is dumb but historical....
  pts[0] = offset[0];
  pts[1] = offset[1];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[2];
  pts[1] = offset[3];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[4];
  pts[1] = offset[5];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[6];
  pts[1] = offset[7];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[0];
  pts[1] = offset[2];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[1];
  pts[1] = offset[3];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[4];
  pts[1] = offset[6];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[5];
  pts[1] = offset[7];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[0];
  pts[1] = offset[4];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[1];
  pts[1] = offset[5];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[2];
  pts[1] = offset[6];
  lines->InsertNextCell(2, pts);
  pts[0] = offset[3];
  pts[1] = offset[7];
  lines->InsertNextCell(2, pts);

  if (this->GenerateFaces)
  {
    pts[0] = offset[1];
    pts[1] = offset[0];
    pts[2] = offset[2];
    pts[3] = offset[3];
    faces->InsertNextCell(4, pts);
    pts[0] = offset[0];
    pts[1] = offset[1];
    pts[2] = offset[5];
    pts[3] = offset[4];
    faces->InsertNextCell(4, pts);
    pts[0] = offset[2];
    pts[1] = offset[0];
    pts[2] = offset[4];
    pts[3] = offset[6];
    faces->InsertNextCell(4, pts);
    pts[0] = offset[3];
    pts[1] = offset[2];
    pts[2] = offset[6];
    pts[3] = offset[7];
    faces->InsertNextCell(4, pts);
    pts[0] = offset[1];
    pts[1] = offset[3];
    pts[2] = offset[7];
    pts[3] = offset[5];
    faces->InsertNextCell(4, pts);
    pts[0] = offset[7];
    pts[1] = offset[6];
    pts[2] = offset[4];
    pts[3] = offset[5];
    faces->InsertNextCell(4, pts);
  }
}

//------------------------------------------------------------------------------
int vtkOutlineFilter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the input and output. Differentiate between composite and typical datasets.
  vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkCompositeDataSet* compInput =
    vtkCompositeDataSet::SafeDownCast(inInfo->Get(vtkCompositeDataSet::DATA_OBJECT()));
  if (input == nullptr && compInput == nullptr)
  {
    vtkErrorMacro(<< "Invalid or missing input");
    return 0;
  }
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkDebugMacro(<< "Creating outline");

  // Each outline is passed down to the core generation function
  vtkNew<vtkPoints> pts;
  // Set the desired precision for the points in the output.
  if (this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION)
  {
    pts->SetDataType(VTK_DOUBLE);
  }
  else
  {
    pts->SetDataType(VTK_FLOAT);
  }

  vtkNew<vtkCellArray> lines;
  vtkNew<vtkCellArray> faces;

  // If vtkDataSet input just create one bounding box. Composites may require multiple.
  double bds[6];
  if (input)
  {
    // dataset bounding box
    input->GetBounds(bds);
    this->AppendOutline(pts.GetPointer(), lines.GetPointer(), faces.GetPointer(), bds);
  }
  else
  {
    // Root bounding box
    if (this->CompositeStyle == ROOT_LEVEL || this->CompositeStyle == ROOT_AND_LEAFS)
    {
      compInput->GetBounds(bds);
      this->AppendOutline(pts.GetPointer(), lines.GetPointer(), faces.GetPointer(), bds);
    }

    // Leafs
    if (this->CompositeStyle == LEAF_DATASETS || this->CompositeStyle == ROOT_AND_LEAFS)
    {
      vtkCompositeDataIterator* iter = compInput->NewIterator();
      for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
      {
        if (this->CheckAbort())
        {
          break;
        }
        vtkDataSet* ds = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject());
        if (ds)
        {
          ds->GetBounds(bds);
          this->AppendOutline(pts.GetPointer(), lines.GetPointer(), faces.GetPointer(), bds);
        }
      }
      iter->Delete();
    }

    // Specified flat indices
    if (this->CompositeStyle == SPECIFIED_INDEX)
    {
      vtkCompositeDataIterator* iter = compInput->NewIterator();
      vtkDataObjectTreeIterator* treeIter = vtkDataObjectTreeIterator::SafeDownCast(iter);
      if (treeIter)
      {
        treeIter->VisitOnlyLeavesOff();
      }
      unsigned int index;
      for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
      {
        if (this->CheckAbort())
        {
          break;
        }
        vtkDataSet* ds = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject());
        if (ds)
        {
          index = iter->GetCurrentFlatIndex();
          if (this->Indices->find(index) != this->Indices->end())
          {
            ds->GetBounds(bds);
            this->AppendOutline(pts.GetPointer(), lines.GetPointer(), faces.GetPointer(), bds);
          }
        } // if a dataset
      }
      iter->Delete();
    }
  }

  // Specify output
  output->SetPoints(pts.GetPointer());
  output->SetLines(lines.GetPointer());

  if (this->GenerateFaces)
  {
    output->SetPolys(faces.GetPointer());
  }

  this->CheckAbort();

  return 1;
}

//------------------------------------------------------------------------------
int vtkOutlineFilter::FillInputPortInformation(int, vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
  info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkCompositeDataSet");
  return 1;
}

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

  os << indent << "Generate Faces: " << (this->GenerateFaces ? "On\n" : "Off\n");
  os << indent << "Composite Style: " << this->CompositeStyle << endl;
  os << indent << "Output Points Precision: " << this->OutputPointsPrecision << "\n";
  os << indent
     << "Composite indices: " << (!this->Indices->empty() ? "(Specified)\n" : "(Not specified)\n");
}
VTK_ABI_NAMESPACE_END
