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

#include "vtkCell.h"
#include "vtkCellData.h"
#include "vtkDataSet.h"
#include "vtkExecutive.h"
#include "vtkFloatArray.h"
#include "vtkIdList.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkTransform.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkProgrammableGlyphFilter);

// Construct object with scaling on, scaling mode is by scalar value,
// scale factor = 1.0, the range is (0,1), orient geometry is on, and
// orientation is by vector. Clamping and indexing are turned off. No
// initial sources are defined.
vtkProgrammableGlyphFilter::vtkProgrammableGlyphFilter()
{
  this->GlyphMethod = nullptr;
  this->GlyphMethodArgDelete = nullptr;
  this->GlyphMethodArg = nullptr;

  this->Point[0] = this->Point[1] = this->Point[2] = 0.0;
  this->PointId = -1;
  this->PointData = nullptr;

  this->ColorMode = VTK_COLOR_BY_INPUT;

  this->SetNumberOfInputPorts(2);
}

vtkProgrammableGlyphFilter::~vtkProgrammableGlyphFilter()
{
  if ((this->GlyphMethodArg) && (this->GlyphMethodArgDelete))
  {
    (*this->GlyphMethodArgDelete)(this->GlyphMethodArg);
  }
}

void vtkProgrammableGlyphFilter::SetSourceConnection(vtkAlgorithmOutput* output)
{
  this->SetInputConnection(1, output);
}

void vtkProgrammableGlyphFilter::SetSourceData(vtkPolyData* pd)
{
  this->SetInputData(1, pd);
}

// Get a pointer to a source object at a specified table location.
vtkPolyData* vtkProgrammableGlyphFilter::GetSource()
{
  if (this->GetNumberOfInputConnections(1) < 1)
  {
    return nullptr;
  }

  return vtkPolyData::SafeDownCast(this->GetExecutive()->GetInputData(1, 0));
}

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

  // get the input and output
  vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* source = vtkPolyData::SafeDownCast(sourceInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkPointData* inputPD = input->GetPointData();
  vtkCellData* inputCD = input->GetCellData();
  vtkPointData* outputPD = output->GetPointData();
  vtkCellData* outputCD = output->GetCellData();
  vtkPoints *newPts, *sourcePts;
  vtkFloatArray *ptScalars = nullptr, *cellScalars = nullptr;
  vtkDataArray *inPtScalars = nullptr, *inCellScalars = nullptr;
  vtkIdType numPts = input->GetNumberOfPoints();
  vtkPointData* sourcePD;
  vtkCellData* sourceCD;
  vtkIdType numSourcePts, numSourceCells, ptOffset = 0, cellId, ptId, id, idx;
  int i, npts;
  vtkIdList* pts;
  vtkIdList* cellPts;
  vtkCell* cell;

  // Initialize
  vtkDebugMacro(<< "Generating programmable glyphs!");

  if (numPts < 1)
  {
    vtkErrorMacro(<< "No input points to glyph");
  }

  pts = vtkIdList::New();
  pts->Allocate(VTK_CELL_SIZE);
  sourcePD = source->GetPointData();
  sourceCD = source->GetCellData();
  numSourcePts = source->GetNumberOfPoints();
  numSourceCells = source->GetNumberOfCells();

  outputPD->CopyScalarsOff(); //'cause we control the coloring process
  outputCD->CopyScalarsOff();

  output->AllocateEstimate(numSourceCells * numPts, 1);
  outputPD->CopyAllocate(sourcePD, numSourcePts * numPts, numSourcePts * numPts);
  outputCD->CopyAllocate(sourceCD, numSourceCells * numPts, numSourceCells * numPts);
  newPts = vtkPoints::New();
  newPts->Allocate(numSourcePts * numPts);

  // figure out how to color the data and setup
  if (this->ColorMode == VTK_COLOR_BY_INPUT)
  {
    if ((inPtScalars = inputPD->GetScalars()))
    {
      ptScalars = vtkFloatArray::New();
      ptScalars->Allocate(numSourcePts * numPts);
    }
    if ((inCellScalars = inputCD->GetScalars()))
    {
      cellScalars = vtkFloatArray::New();
      cellScalars->Allocate(numSourcePts * numPts);
    }
  }

  else
  {
    if (sourcePD->GetScalars())
    {
      ptScalars = vtkFloatArray::New();
      ptScalars->Allocate(numSourcePts * numPts);
    }
    if (sourceCD->GetScalars())
    {
      cellScalars = vtkFloatArray::New();
      cellScalars->Allocate(numSourcePts * numPts);
    }
  }

  // Loop over all points, invoking glyph method and Update(),
  // then append output of source to output of this filter.
  //
  //  this->Updating = 1; // to prevent infinite recursion
  this->PointData = input->GetPointData();
  for (this->PointId = 0; this->PointId < numPts; this->PointId++)
  {
    if (!(this->PointId % 10000))
    {
      this->UpdateProgress((double)this->PointId / numPts);
      if (this->GetAbortExecute())
      {
        break;
      }
    }

    input->GetPoint(this->PointId, this->Point);

    if (this->GlyphMethod)
    {
      (*this->GlyphMethod)(this->GlyphMethodArg);

      // The GlyphMethod may have set the source connection to nullptr
      if (this->GetNumberOfInputConnections(1) == 0)
      {
        source = nullptr;
      }
      else
      {
        // Update the source connection in case the GlyphMethod changed
        // its parameters.
        this->GetInputAlgorithm(1, 0)->Update();
        // The GlyphMethod may also have changed the source.
        sourceInfo = inputVector[1]->GetInformationObject(0);
        source = vtkPolyData::SafeDownCast(sourceInfo->Get(vtkDataObject::DATA_OBJECT()));
      }
    }
    if (source)
    {
      sourcePts = source->GetPoints();
      numSourcePts = source->GetNumberOfPoints();
      numSourceCells = source->GetNumberOfCells();
      sourcePD = source->GetPointData();
      sourceCD = source->GetCellData();

      if (this->ColorMode == VTK_COLOR_BY_SOURCE)
      {
        inPtScalars = sourcePD->GetScalars();
        inCellScalars = sourceCD->GetScalars();
      }

      // Copy all data from source to output.
      for (ptId = 0; ptId < numSourcePts; ptId++)
      {
        id = newPts->InsertNextPoint(sourcePts->GetPoint(ptId));
        outputPD->CopyData(sourcePD, ptId, id);
      }

      for (cellId = 0; cellId < numSourceCells; cellId++)
      {
        cell = source->GetCell(cellId);
        cellPts = cell->GetPointIds();
        npts = cellPts->GetNumberOfIds();
        for (pts->Reset(), i = 0; i < npts; i++)
        {
          pts->InsertId(i, cellPts->GetId(i) + ptOffset);
        }
        id = output->InsertNextCell(cell->GetCellType(), pts);
        outputCD->CopyData(sourceCD, cellId, id);
      }

      // If we're coloring the output with scalars, do that now
      if (ptScalars)
      {
        for (ptId = 0; ptId < numSourcePts; ptId++)
        {
          idx = (this->ColorMode == VTK_COLOR_BY_INPUT ? this->PointId : ptId);
          ptScalars->InsertNextValue(inPtScalars->GetComponent(idx, 0));
        }
      }
      else if (cellScalars)
      {
        for (cellId = 0; cellId < numSourceCells; cellId++)
        {
          idx = (this->ColorMode == VTK_COLOR_BY_INPUT ? this->PointId : cellId);
          cellScalars->InsertNextValue(inCellScalars->GetComponent(idx, 0));
        }
      }

      ptOffset += numSourcePts;

    } // if a source is available
  }   // for all input points

  //  this->Updating = 0;

  pts->Delete();

  output->SetPoints(newPts);
  newPts->Delete();

  if (ptScalars)
  {
    idx = outputPD->AddArray(ptScalars);
    outputPD->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS);
    ptScalars->Delete();
  }

  if (cellScalars)
  {
    idx = outputCD->AddArray(cellScalars);
    outputCD->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS);
    cellScalars->Delete();
  }

  output->Squeeze();

  return 1;
}

// Specify function to be called before object executes.
void vtkProgrammableGlyphFilter::SetGlyphMethod(void (*f)(void*), void* arg)
{
  if (f != this->GlyphMethod || arg != this->GlyphMethodArg)
  {
    // delete the current arg if there is one and a delete meth
    if ((this->GlyphMethodArg) && (this->GlyphMethodArgDelete))
    {
      (*this->GlyphMethodArgDelete)(this->GlyphMethodArg);
    }
    this->GlyphMethod = f;
    this->GlyphMethodArg = arg;
    this->Modified();
  }
}

// Set the arg delete method. This is used to free user memory.
void vtkProgrammableGlyphFilter::SetGlyphMethodArgDelete(void (*f)(void*))
{
  if (f != this->GlyphMethodArgDelete)
  {
    this->GlyphMethodArgDelete = f;
    this->Modified();
  }
}

// Description:
// Return the method of coloring as a descriptive character string.
const char* vtkProgrammableGlyphFilter::GetColorModeAsString()
{
  if (this->ColorMode == VTK_COLOR_BY_INPUT)
  {
    return "ColorByInput";
  }
  else
  {
    return "ColorBySource";
  }
}

int vtkProgrammableGlyphFilter::FillInputPortInformation(int port, vtkInformation* info)
{
  if (port == 0)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
    return 1;
  }
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
  return 1;
}

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

  os << indent << "Color Mode: " << this->GetColorModeAsString() << endl;
  os << indent << "Point Id: " << this->PointId << "\n";
  os << indent << "Point: " << this->Point[0] << ", " << this->Point[1] << ", " << this->Point[2]
     << "\n";
  if (this->PointData)
  {
    os << indent << "PointData: " << this->PointData << "\n";
  }
  else
  {
    os << indent << "PointData: (not defined)\n";
  }

  if (this->GlyphMethod)
  {
    os << indent << "Glyph Method defined\n";
  }
  else
  {
    os << indent << "No Glyph Method\n";
  }
}
VTK_ABI_NAMESPACE_END
