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

#include "vtkAppendPolyData.h"
#include "vtkCellArray.h"
#include "vtkContourFilter.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkStructuredPoints.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkVoxelContoursToSurfaceFilter);

vtkVoxelContoursToSurfaceFilter::vtkVoxelContoursToSurfaceFilter()
{
  this->MemoryLimitInBytes = 10000000;
  this->Spacing[0] = 1.0;
  this->Spacing[1] = 1.0;
  this->Spacing[2] = 1.0;
  this->LineList = new double[4 * 1000];
  this->LineListLength = 0;
  this->LineListSize = 1000;
  this->SortedXList = nullptr;
  this->SortedYList = nullptr;
  this->WorkingList = nullptr;
  this->IntersectionList = nullptr;
  this->SortedListSize = 0;
}

vtkVoxelContoursToSurfaceFilter::~vtkVoxelContoursToSurfaceFilter()
{
  delete[] this->LineList;
  delete[] this->SortedXList;
  delete[] this->SortedYList;
  delete[] this->WorkingList;
  delete[] this->IntersectionList;
}

void vtkVoxelContoursToSurfaceFilter::AddLineToLineList(double x1, double y1, double x2, double y2)
{
  // Do we need to increase the size of our list?
  if (this->LineListLength >= this->LineListSize)
  {
    // Double the space we had before
    double* newList = new double[this->LineListSize * 4 * 2];
    memcpy(newList, this->LineList, 4 * this->LineListSize * sizeof(double));
    delete[] this->LineList;
    this->LineList = newList;
    this->LineListSize *= 2;
  }

  // Now we are sure we have space - add the line
  this->LineList[4 * this->LineListLength + 0] = x1;
  this->LineList[4 * this->LineListLength + 1] = y1;
  this->LineList[4 * this->LineListLength + 2] = x2;
  this->LineList[4 * this->LineListLength + 3] = y2;
  this->LineListLength++;
}

void vtkVoxelContoursToSurfaceFilter::SortLineList()
{
  int i, j;
  double tmp[4];
  double tmpval;

  if (!this->LineListLength)
  {
    return;
  }

  // Make sure we have enough space in our sorted list
  if (this->SortedListSize < this->LineListLength)
  {
    delete[] this->SortedXList;
    delete[] this->SortedYList;
    delete[] this->WorkingList;
    delete[] this->IntersectionList;

    this->SortedXList = new double[4 * this->LineListLength];
    this->SortedYList = new double[4 * this->LineListLength];
    this->SortedListSize = this->LineListLength;

    // Create the space we'll need for our working list of indices
    // The is the list of lines that we are currently considering
    // for intersections. Lines move in then out of the list as
    // we pass the first then the second endpoint. This will be
    // used during the CastXLines and CastYLines methods, and is
    // the same size as the number of lines.
    this->WorkingList = new int[this->LineListLength];

    // Create the space we'll need for the intersection list
    // There can't be more intersections than there are possible
    // lines. Although it is highly doubtful we'll actually use
    // all this space, it isn't much and it makes the code simpler
    // not to have to worry about exceeding the bounds. This will be
    // used during the CastXLines and CastYLines methods, and is
    // the same size as the number of lines.
    this->IntersectionList = new double[this->LineListLength];
  }

  // Copy the lines into the lists
  memcpy(this->SortedXList, this->LineList, 4 * this->LineListLength * sizeof(double));
  memcpy(this->SortedYList, this->LineList, 4 * this->LineListLength * sizeof(double));

  // Now sort on x and y
  // Use a simple bubble sort - will improve if necessary
  for (i = 0; i < this->LineListLength; i++)
  {
    // swap x entry if necessary to keep min x the first endpoint
    if (this->SortedXList[4 * i + 0] > this->SortedXList[4 * i + 2])
    {
      tmpval = this->SortedXList[4 * i];
      this->SortedXList[4 * i] = this->SortedXList[4 * i + 2];
      this->SortedXList[4 * i + 2] = tmpval;
      tmpval = this->SortedXList[4 * i + 1];
      this->SortedXList[4 * i + 1] = this->SortedXList[4 * i + 3];
      this->SortedXList[4 * i + 3] = tmpval;
    }

    // swap y entry if necessary to keep min y the first endpoint
    if (this->SortedYList[4 * i + 1] > this->SortedYList[4 * i + 3])
    {
      tmpval = this->SortedYList[4 * i];
      this->SortedYList[4 * i] = this->SortedYList[4 * i + 2];
      this->SortedYList[4 * i + 2] = tmpval;
      tmpval = this->SortedYList[4 * i + 1];
      this->SortedYList[4 * i + 1] = this->SortedYList[4 * i + 3];
      this->SortedYList[4 * i + 3] = tmpval;
    }

    // Sort x list
    for (j = i; j > 0; j--)
    {
      if (this->SortedXList[j * 4] < this->SortedXList[(j - 1) * 4])
      {
        memcpy(tmp, this->SortedXList + j * 4, 4 * sizeof(double));
        memcpy(this->SortedXList + j * 4, this->SortedXList + (j - 1) * 4, 4 * sizeof(double));
        memcpy(this->SortedXList + (j - 1) * 4, tmp, 4 * sizeof(double));
      }
      else
      {
        break;
      }
    }

    // Sort y list
    for (j = i; j > 0; j--)
    {
      if (this->SortedYList[j * 4 + 1] < this->SortedYList[(j - 1) * 4 + 1])
      {
        memcpy(tmp, this->SortedYList + j * 4, 4 * sizeof(double));
        memcpy(this->SortedYList + j * 4, this->SortedYList + (j - 1) * 4, 4 * sizeof(double));
        memcpy(this->SortedYList + (j - 1) * 4, tmp, 4 * sizeof(double));
      }
      else
      {
        break;
      }
    }
  }
}

void vtkVoxelContoursToSurfaceFilter::CastLines(
  float* slicePtr, double gridOrigin[3], int gridSize[3], int type)
{
  double axis1, axis2;
  double d1, d2;
  int index;
  int i, j;
  double tmp;
  double* line;
  float* currSlicePtr;
  int currSlice;
  int currentIntersection;
  double sign;
  double* sortedList;
  double low1, low2, high1, high2;
  int increment1, increment2;
  int offset1, offset2, offset3, offset4;

  // this is the x direction
  if (type == 0)
  {
    low1 = gridOrigin[0];
    high1 = gridOrigin[0] + (double)gridSize[0];
    low2 = gridOrigin[1];
    high2 = gridOrigin[1] + (double)gridSize[1];
    increment1 = gridSize[0];
    increment2 = 1;
    sortedList = this->SortedXList;
    offset1 = 0;
    offset2 = 2;
    offset3 = 1;
    offset4 = 3;
  }
  // This is the y direction
  else
  {
    low1 = gridOrigin[1];
    high1 = gridOrigin[1] + (double)gridSize[1];
    low2 = gridOrigin[0];
    high2 = gridOrigin[0] + (double)gridSize[0];
    increment1 = 1;
    increment2 = gridSize[0];
    sortedList = this->SortedYList;
    offset1 = 1;
    offset2 = 3;
    offset3 = 0;
    offset4 = 2;
  }

  // Initialize the working list to nothing. We will start
  // looking at index = 0 for the next line to add to the
  // working list
  this->WorkingListLength = 0;
  index = 0;

  // Loop through the x or y lines
  for (axis1 = low1, currSlice = 0; axis1 < high1; axis1 += 1.0, currSlice++)
  {
    // Initialize the intersection list to nothing
    this->IntersectionListLength = 0;

    // Add lines to the working list if necessary
    for (; index < this->LineListLength; index++)
    {
      if (sortedList[4 * index + offset1] < axis1)
      {
        this->WorkingList[this->WorkingListLength] = index;
        this->WorkingListLength++;
      }
      else
      {
        break;
      }
    }

    // Do the intersections, removing lines from the
    // working list if necessary
    for (i = 0; i < this->WorkingListLength; i++)
    {
      line = sortedList + 4 * this->WorkingList[i];

      // Yes, it intersects, add it to the intersection list
      if (line[offset1] < axis1 && line[offset2] > axis1)
      {
        // Compute the intersection distance
        // For x lines this is y = y1 + (y2 - y1)*((x - x1)/(x2 - x1))
        // For y lines this is x = x1 + (x2 - x1)*((y - y1)/(y2 - y1))
        this->IntersectionList[this->IntersectionListLength] = line[offset3] +
          (line[offset4] - line[offset3]) *
            ((axis1 - line[offset1]) / (line[offset2] - line[offset1]));

        // Make sure this distance is sorted
        for (j = this->IntersectionListLength; j > 0; j--)
        {
          if (this->IntersectionList[j] < this->IntersectionList[j - 1])
          {
            tmp = this->IntersectionList[j];
            this->IntersectionList[j] = this->IntersectionList[j - 1];
            this->IntersectionList[j - 1] = tmp;
          }
          else
          {
            break;
          }
        }
        this->IntersectionListLength++;
      }
      // No, it doesn't intersect, remove it from the working list
      else
      {
        for (j = i; j < (this->WorkingListLength - 1); j++)
        {
          this->WorkingList[j] = this->WorkingList[j + 1];
        }
        this->WorkingListLength--;
        i--;
      }
    }

    // Now we have all the intersections for the x or y line, in sorted
    // order. Use them to fill in distances (as long as there are
    // any)
    if (this->IntersectionListLength)
    {
      currSlicePtr = slicePtr + currSlice * increment2;
      currentIntersection = 0;
      // We are starting outside which has a negative distance
      sign = -1.0;
      for (axis2 = low2; axis2 < high2; axis2 += 1.0)
      {
        while (currentIntersection < this->IntersectionListLength &&
          this->IntersectionList[currentIntersection] < axis2)

        {
          currentIntersection++;

          // Each time we cross a line we are moving across an
          // inside/outside boundary
          sign *= -1.0;
        }
        // We are now positioned at an x or y value between currentIntersection
        // and currentIntersection - 1 (except at boundaries where we are
        // before intersection 0 or after the last intersection)

        if (currentIntersection == 0)
        {
          d1 = axis2 - this->IntersectionList[currentIntersection];
          *currSlicePtr = (*currSlicePtr > d1) ? (*currSlicePtr) : (d1);
        }
        else if (currentIntersection == this->IntersectionListLength)
        {
          d1 = this->IntersectionList[currentIntersection - 1] - axis2;
          *currSlicePtr = (*currSlicePtr > d1) ? (*currSlicePtr) : (d1);
        }
        else
        {
          d1 = axis2 - this->IntersectionList[currentIntersection - 1];
          d2 = this->IntersectionList[currentIntersection] - axis2;
          d1 = (d1 < d2) ? (d1) : (d2);
          if (type == 0)
          {
            *currSlicePtr = sign * d1;
          }
          else
          {
            *currSlicePtr = (sign * (*currSlicePtr) < d1) ? (*currSlicePtr) : (sign * d1);
          }
        }

        currSlicePtr += increment1;
      }
    }
  }
}

void vtkVoxelContoursToSurfaceFilter::PushDistances(
  float* volumePtr, int gridSize[3], int chunkSize)
{
  int i, j, k;
  float* vptr;

  // Push distances along x (both ways) and y (both ways) on each slice
  for (k = 0; k < chunkSize; k++)
  {
    // Do the x rows
    for (j = 0; j < gridSize[1]; j++)
    {
      vptr = volumePtr + k * gridSize[0] * gridSize[1] + j * gridSize[0];
      vptr++;

      // first one way
      for (i = 1; i < gridSize[0]; i++)
      {
        if (*vptr > 0 && *(vptr - 1) + 1 < *(vptr))
        {
          *vptr = *(vptr - 1) + 1;
        }
        else if (*vptr < 0 && *(vptr - 1) - 1 > *(vptr))
        {
          *vptr = *(vptr - 1) - 1;
        }
        vptr++;
      }

      vptr -= 2;
      i -= 2;

      // then the other
      for (; i >= 0; i--)
      {
        if (*vptr > 0 && *(vptr + 1) + 1 < *(vptr))
        {
          *vptr = *(vptr + 1) + 1;
        }
        else if (*vptr < 0 && *(vptr + 1) - 1 > *(vptr))
        {
          *vptr = *(vptr + 1) - 1;
        }
      }
    }

    // Do the y columns
    for (i = 0; i < gridSize[0]; i++)
    {
      vptr = volumePtr + k * gridSize[0] * gridSize[1] + i;

      vptr += gridSize[0];

      // first one way
      for (j = 1; j < gridSize[1]; j++)
      {
        if (*vptr > 0 && *(vptr - gridSize[0]) + 1 < *(vptr))
        {
          *vptr = *(vptr - gridSize[0]) + 1;
        }
        else if (*vptr < 0 && *(vptr - gridSize[0]) - 1 > *(vptr))
        {
          *vptr = *(vptr - gridSize[0]) - 1;
        }
        vptr += gridSize[0];
      }

      vptr -= 2 * gridSize[0];
      j -= 2;

      // then the other
      for (; j >= 0; j--)
      {
        if (*vptr > 0 && *(vptr + gridSize[0]) + 1 < *(vptr))
        {
          *vptr = *(vptr + gridSize[0]) + 1;
        }
        else if (*vptr < 0 && *(vptr + gridSize[0]) - 1 > *(vptr))
        {
          *vptr = *(vptr + gridSize[0]) - 1;
        }
      }
    }
  }
}

// Append data sets into single unstructured grid
int vtkVoxelContoursToSurfaceFilter::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
  vtkPolyData* input = vtkPolyData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkCellArray* inputPolys = input->GetPolys();
  int gridSize[3];
  double gridOrigin[3];
  double contourBounds[6];
  int chunkSize;
  int currentSlice, lastSlice, currentIndex;
  int i, j;
  int numberOfInputCells;
  int currentInputCellIndex;
  vtkIdType npts = 0;
  const vtkIdType* pts = nullptr;
  double point1[3], point2[3];
  double currentZ;
  vtkStructuredPoints* volume;
  float *volumePtr, *slicePtr;
  vtkContourFilter* contourFilter;
  vtkPolyData* contourOutput;
  vtkAppendPolyData* appendFilter;

  vtkDebugMacro(<< "Creating surfaces from contours");

  // Get the bounds of the input contours
  input->GetBounds(contourBounds);

  if (contourBounds[0] > contourBounds[1])
  { // empty input
    return 1;
  }

  // From the bounds, compute the grid size, and origin

  // The origin of the grid should be (-0.5, -0.5, 0.0) away from the
  // lower bounds of the contours. This is because we want the grid
  // to lie halfway between integer endpoint locations of the line
  // segments on each plane. Also, we want an extra plane on each end
  // for capping
  gridOrigin[0] = contourBounds[0] - 0.5;
  gridOrigin[1] = contourBounds[2] - 0.5;
  gridOrigin[2] = contourBounds[4] - 1.0;

  // The difference between the bounds, plus one to account a
  // sample on the first and last location, plus one to account
  // for the larger grid size ( the 0.5 unit border ) On Z, we
  // want to sample exactly on the contours so we don't need to
  // add the extra 1, but we have added two extra planes so we
  // need another 2.
  gridSize[0] = (int)(contourBounds[1] - contourBounds[0] + 2);
  gridSize[1] = (int)(contourBounds[3] - contourBounds[2] + 2);
  gridSize[2] = (int)(contourBounds[5] - contourBounds[4] + 3);

  // How many slices in a chunk? This will later be decremented
  // by one to account for the fact that the last slice in the
  // previous chuck is copied to the first slice in the next chunk.
  // Stay within memory limit. There are 4 bytes per double.
  chunkSize = this->MemoryLimitInBytes / (gridSize[0] * gridSize[1] * 4);
  if (chunkSize > gridSize[2])
  {
    chunkSize = gridSize[2];
  }

  currentSlice = 0;
  currentZ = contourBounds[4] - 1.0;
  currentIndex = 0;
  lastSlice = gridSize[2] - 1;
  numberOfInputCells = inputPolys->GetNumberOfCells();
  currentInputCellIndex = 0;

  volume = vtkStructuredPoints::New();
  volume->SetDimensions(gridSize[0], gridSize[1], chunkSize);
  volume->SetSpacing(this->Spacing);
  volume->AllocateScalars(VTK_FLOAT, 1);
  vtkDataArray* volumeArrayDA = volume->GetPointData()->GetScalars();
  vtkFloatArray* volumeArray = vtkArrayDownCast<vtkFloatArray>(volumeArrayDA);
  assert(volumeArray);
  volumePtr = volumeArray->GetPointer(0);

  contourFilter = vtkContourFilter::New();
  contourFilter->SetInputData(volume);
  contourFilter->SetNumberOfContours(1);
  contourFilter->SetValue(0, 0.0);
  contourFilter->SetContainerAlgorithm(this);

  appendFilter = vtkAppendPolyData::New();
  appendFilter->SetContainerAlgorithm(this);

  inputPolys->InitTraversal();
  inputPolys->GetNextCell(npts, pts);

  while (currentSlice <= lastSlice)
  {
    if (this->CheckAbort())
    {
      break;
    }
    // Make sure the origin of the volume is in the right
    // place so that the appended polydata all matches up
    // nicely.
    volume->SetOrigin(gridOrigin[0], gridOrigin[1],
      gridOrigin[2] + this->Spacing[2] * (currentSlice - (currentSlice != 0)));

    for (i = currentIndex; i < chunkSize; i++)
    {
      slicePtr = volumePtr + i * gridSize[0] * gridSize[1];

      // Clear out the slice memory - set it all to a large negative
      // value indicating no surfaces are nearby, and we assume we
      // are outside of any surface
      for (j = 0; j < gridSize[0] * gridSize[1]; j++)
      {
        *(slicePtr + j) = -9.99e10;
      }

      // If we are past the end, don't do anything
      if (currentSlice > lastSlice)
      {
        continue;
      }

      this->LineListLength = 0;

      // Read in the lines for the contours on this slice
      while (currentInputCellIndex < numberOfInputCells)
      {
        // Check if we are still on the right z slice
        input->GetPoint(pts[0], point1);
        if (point1[2] != currentZ)
        {
          break;
        }

        // This contour is on the right z slice - add the lines
        // to our list
        for (j = 0; j < npts; j++)
        {
          input->GetPoint(pts[j], point1);
          input->GetPoint(pts[(j + 1) % npts], point2);
          this->AddLineToLineList(point1[0], point1[1], point2[0], point2[1]);
        }

        inputPolys->GetNextCell(npts, pts);
        currentInputCellIndex++;
      }

      // Sort the contours in x and y
      this->SortLineList();

      // Cast lines in x and y filling in distance
      this->CastLines(slicePtr, gridOrigin, gridSize, 0);
      this->CastLines(slicePtr, gridOrigin, gridSize, 1);

      // Move on to the next slice
      currentSlice++;
      currentIndex++;
      currentZ += 1.0;
    }

    this->PushDistances(volumePtr, gridSize, chunkSize);

    // Update the contour filter and grab the output
    // Make a new output for it, then grab the output and
    // add it to the append filter, then delete the output
    // which is ok since it was registered by the appendFilter
    contourOutput = vtkPolyData::New();
    contourFilter->Update();
    contourOutput->ShallowCopy(contourFilter->GetOutput());
    appendFilter->AddInputData(contourOutput);
    contourOutput->Delete();

    if (currentSlice <= lastSlice)
    {
      // Copy last slice to first slice
      memcpy(volumePtr, volumePtr + (chunkSize - 1) * gridSize[0] * gridSize[1],
        sizeof(float) * gridSize[0] * gridSize[1]);

      // reset currentIndex to 1
      currentIndex = 1;
    }
  }

  appendFilter->Update();

  // Grab the appended data as the output to this filter
  output->SetPoints(appendFilter->GetOutput()->GetPoints());
  output->SetVerts(appendFilter->GetOutput()->GetVerts());
  output->SetLines(appendFilter->GetOutput()->GetLines());
  output->SetPolys(appendFilter->GetOutput()->GetPolys());
  output->SetStrips(appendFilter->GetOutput()->GetStrips());
  output->GetPointData()->PassData(appendFilter->GetOutput()->GetPointData());

  contourFilter->Delete();
  appendFilter->Delete();
  volume->Delete();

  return 1;
}

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

  os << indent << "Memory Limit (in bytes): " << this->MemoryLimitInBytes << endl;

  os << indent << "Spacing: " << this->Spacing[0] << " " << this->Spacing[1] << " "
     << this->Spacing[2] << endl;
}
VTK_ABI_NAMESPACE_END
