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

#include "vtkExtractUserDefinedPiece.h"

#include "vtkCell.h"
#include "vtkCellData.h"
#include "vtkIdList.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkUnsignedCharArray.h"
#include "vtkUnstructuredGrid.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkExtractUserDefinedPiece);

vtkExtractUserDefinedPiece::vtkExtractUserDefinedPiece()
{
  this->ConstantData = nullptr;
  this->ConstantDataLen = 0;
  this->InPiece = nullptr;
}
vtkExtractUserDefinedPiece::~vtkExtractUserDefinedPiece()
{
  delete[] (char*)this->ConstantData;
  this->ConstantData = nullptr;
}
void vtkExtractUserDefinedPiece::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "ConstantData: " << this->ConstantData << indent
     << "ConstantDataLen: " << this->ConstantDataLen << indent << "InPiece: " << this->InPiece
     << "\n";
}
void vtkExtractUserDefinedPiece::SetConstantData(void* data, int len)
{
  this->ConstantData = new char[len];
  this->ConstantDataLen = len;

  memcpy(this->ConstantData, data, len);

  this->Modified();
}

int vtkExtractUserDefinedPiece::GetConstantData(void** data)
{
  *data = this->ConstantData;
  return this->ConstantDataLen;
}

// This is exactly vtkExtractUnstructuredGridPiece::Execute(), with
// the exception that we call ComputeCellTagsWithFunction rather
// than ComputeCellTags.  If ComputeCellTags were virtual, we could
// just override it here.

int vtkExtractUserDefinedPiece::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
  vtkUnstructuredGrid* input =
    vtkUnstructuredGrid::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkUnstructuredGrid* output =
    vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkPointData *pd = input->GetPointData(), *outPD = output->GetPointData();
  vtkCellData *cd = input->GetCellData(), *outCD = output->GetCellData();
  vtkIntArray* cellTags;
  int ghostLevel;
  vtkIdType cellId, newCellId;
  vtkIdList *cellPts, *pointMap;
  vtkIdList* newCellPts = vtkIdList::New();
  vtkIdList* pointOwnership;
  vtkCell* cell;
  vtkPoints* newPoints;
  vtkUnsignedCharArray* cellGhostLevels = nullptr;
  vtkUnsignedCharArray* pointGhostLevels = nullptr;
  vtkIdType i, ptId, newId, numPts;
  int numCellPts;
  double* x;

  // Pipeline update piece will tell us what to generate.
  ghostLevel = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS());

  outPD->CopyAllocate(pd);
  outCD->CopyAllocate(cd);

  if (ghostLevel > 0 && this->CreateGhostCells)
  {
    cellGhostLevels = vtkUnsignedCharArray::New();
    pointGhostLevels = vtkUnsignedCharArray::New();
    cellGhostLevels->Allocate(input->GetNumberOfCells());
    pointGhostLevels->Allocate(input->GetNumberOfPoints());
  }

  // Break up cells based on which piece they belong to.
  cellTags = vtkIntArray::New();
  cellTags->Allocate(input->GetNumberOfCells(), 1000);
  pointOwnership = vtkIdList::New();
  pointOwnership->Allocate(input->GetNumberOfPoints());

  // Cell tags end up being 0 for cells in piece and -1 for all others.
  // Point ownership is the cell that owns the point.

  this->ComputeCellTagsWithFunction(cellTags, pointOwnership, input);

  // Find the layers of ghost cells.
  if (this->CreateGhostCells)
  {
    for (i = 0; i < ghostLevel; i++)
    {
      this->AddGhostLevel(input, cellTags, i + 1);
    }
  }

  // Filter the cells.

  numPts = input->GetNumberOfPoints();
  output->Allocate(input->GetNumberOfCells());
  newPoints = vtkPoints::New();
  newPoints->Allocate(numPts);

  pointMap = vtkIdList::New(); // maps old point ids into new
  pointMap->SetNumberOfIds(numPts);
  for (i = 0; i < numPts; i++)
  {
    pointMap->SetId(i, -1);
  }

  // Filter the cells
  for (cellId = 0; cellId < input->GetNumberOfCells(); cellId++)
  {
    if (cellTags->GetValue(cellId) != -1) // satisfied thresholding
    {
      if (cellGhostLevels)
      {
        cellGhostLevels->InsertNextValue(
          cellTags->GetValue(cellId) > 0 ? vtkDataSetAttributes::DUPLICATECELL : 0);
      }

      cell = input->GetCell(cellId);
      cellPts = cell->GetPointIds();
      numCellPts = cell->GetNumberOfPoints();

      for (i = 0; i < numCellPts; i++)
      {
        ptId = cellPts->GetId(i);
        if ((newId = pointMap->GetId(ptId)) < 0)
        {
          x = input->GetPoint(ptId);
          newId = newPoints->InsertNextPoint(x);
          if (pointGhostLevels)
          {
            pointGhostLevels->InsertNextValue(cellTags->GetValue(pointOwnership->GetId(ptId)) > 0
                ? vtkDataSetAttributes::DUPLICATEPOINT
                : 0);
          }
          pointMap->SetId(ptId, newId);
          outPD->CopyData(pd, ptId, newId);
        }
        newCellPts->InsertId(i, newId);
      }
      newCellId = output->InsertNextCell(cell->GetCellType(), newCellPts);
      outCD->CopyData(cd, cellId, newCellId);
      newCellPts->Reset();
    } // satisfied thresholding
  }   // for all cells

  vtkDebugMacro(<< "Extracted " << output->GetNumberOfCells() << " number of cells.");

  // now clean up / update ourselves
  pointMap->Delete();
  newCellPts->Delete();

  if (cellGhostLevels)
  {
    cellGhostLevels->SetName(vtkDataSetAttributes::GhostArrayName());
    output->GetCellData()->AddArray(cellGhostLevels);
    cellGhostLevels->Delete();
    cellGhostLevels = nullptr;
  }
  if (pointGhostLevels)
  {
    pointGhostLevels->SetName(vtkDataSetAttributes::GhostArrayName());
    output->GetPointData()->AddArray(pointGhostLevels);
    pointGhostLevels->Delete();
    pointGhostLevels = nullptr;
  }
  output->SetPoints(newPoints);
  newPoints->Delete();

  output->Squeeze();
  cellTags->Delete();
  pointOwnership->Delete();

  return 1;
}
void vtkExtractUserDefinedPiece::ComputeCellTagsWithFunction(
  vtkIntArray* tags, vtkIdList* pointOwnership, vtkUnstructuredGrid* input)
{
  int j;
  vtkIdType idx, numCells, ptId;
  vtkIdList* cellPtIds;

  numCells = input->GetNumberOfCells();

  cellPtIds = vtkIdList::New();
  // Clear Point ownership.
  for (idx = 0; idx < input->GetNumberOfPoints(); ++idx)
  {
    pointOwnership->SetId(idx, -1);
  }

  // Brute force division.
  for (idx = 0; idx < numCells; ++idx)
  {
    if (this->InPiece(idx, input, this->ConstantData))
    {
      tags->SetValue(idx, 0);
    }
    else
    {
      tags->SetValue(idx, -1);
    }
    // Fill in point ownership mapping.
    input->GetCellPoints(idx, cellPtIds);
    for (j = 0; j < cellPtIds->GetNumberOfIds(); ++j)
    {
      ptId = cellPtIds->GetId(j);
      if (pointOwnership->GetId(ptId) == -1)
      {
        pointOwnership->SetId(ptId, idx);
      }
    }
  }

  cellPtIds->Delete();
}
VTK_ABI_NAMESPACE_END
