// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkImagePointDataIterator.h"
#include "vtkAlgorithm.h"
#include "vtkDataArray.h"
#include "vtkImageData.h"
#include "vtkImageStencilData.h"
#include "vtkPointData.h"

#include <algorithm>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
class vtkImageStencilIteratorFriendship
{
public:
  static int* GetExtentListLengths(vtkImageStencilData* stencil)
  {
    return stencil->ExtentListLengths;
  }

  static int** GetExtentLists(vtkImageStencilData* stencil) { return stencil->ExtentLists; }
};

//------------------------------------------------------------------------------
vtkImagePointDataIterator::vtkImagePointDataIterator()
{
  this->Id = 0;
  this->SpanEnd = 0;
  this->RowEnd = 0;
  this->SliceEnd = 0;
  this->End = 0;

  this->RowEndIncrement = 0;
  this->RowIncrement = 0;
  this->SliceEndIncrement = 0;
  this->SliceIncrement = 0;

  this->Extent[0] = 0;
  this->Extent[1] = 0;
  this->Extent[2] = 0;
  this->Extent[3] = 0;
  this->Extent[4] = 0;
  this->Extent[5] = 0;

  this->Index[0] = 0;
  this->Index[1] = 0;
  this->Index[2] = 0;
  this->StartY = 0;

  this->HasStencil = false;
  this->InStencil = false;
  this->SpanSliceEndIncrement = 0;
  this->SpanSliceIncrement = 0;
  this->SpanIndex = 0;
  this->SpanCountPointer = nullptr;
  this->SpanListPointer = nullptr;

  this->Algorithm = nullptr;
  this->Count = 0;
  this->Target = 0;
  this->ThreadId = 0;
}

//------------------------------------------------------------------------------
void vtkImagePointDataIterator::Initialize(vtkImageData* image, const int extent[6],
  vtkImageStencilData* stencil, vtkAlgorithm* algorithm, int threadId)
{
  const int* dataExtent = image->GetExtent();
  if (extent == nullptr)
  {
    extent = dataExtent;
  }

  // Save the extent (will be adjusted if there is a stencil).
  bool emptyExtent = false;
  for (int i = 0; i < 6; i += 2)
  {
    this->Extent[i] = std::max(extent[i], dataExtent[i]);
    this->Extent[i + 1] = std::min(extent[i + 1], dataExtent[i + 1]);
    if (this->Extent[i] > this->Extent[i + 1])
    {
      emptyExtent = true;
    }
  }

  // Compute the increments for marching through the data.
  this->RowIncrement = dataExtent[1] - dataExtent[0] + 1;
  this->SliceIncrement = this->RowIncrement * (dataExtent[3] - dataExtent[2] + 1);

  int rowSpan, sliceSpan, volumeSpan;

  if (!emptyExtent)
  {
    // Compute the span of the image region to be covered.
    rowSpan = this->Extent[1] - this->Extent[0] + 1;
    sliceSpan = this->Extent[3] - this->Extent[2] + 1;
    volumeSpan = this->Extent[5] - this->Extent[4] + 1;
    this->Id = (this->Extent[0] - dataExtent[0]) +
      (this->Extent[2] - dataExtent[2]) * this->RowIncrement +
      (this->Extent[4] - dataExtent[4]) * this->SliceIncrement;

    // Compute the end increments (continuous increments).
    this->RowEndIncrement = this->RowIncrement - rowSpan;
    this->SliceEndIncrement =
      this->RowEndIncrement + this->SliceIncrement - this->RowIncrement * sliceSpan;
  }
  else
  {
    // Extent is empty, IsAtEnd() will immediately return "true"
    rowSpan = 0;
    sliceSpan = 0;
    volumeSpan = 0;
    this->Id = 0;
    this->RowEndIncrement = 0;
    this->SliceEndIncrement = 0;
    for (int i = 0; i < 6; i += 2)
    {
      this->Extent[i] = dataExtent[i];
      this->Extent[i + 1] = dataExtent[i] - 1;
    }
  }

  // Get the end pointers for row, slice, and volume.
  this->SpanEnd = this->Id + rowSpan;
  this->RowEnd = this->Id + rowSpan;
  this->SliceEnd = this->Id + (this->RowIncrement * sliceSpan - this->RowEndIncrement);
  this->End = this->Id + (this->SliceIncrement * volumeSpan - this->SliceEndIncrement);

  // For keeping track of the current x,y,z index.
  this->Index[0] = this->Extent[0];
  this->Index[1] = this->Extent[2];
  this->Index[2] = this->Extent[4];

  // For resetting the Y index after each slice.
  this->StartY = this->Index[1];

  // Code for when a stencil is provided.
  if (stencil)
  {
    this->HasStencil = true;
    this->InStencil = false;

    this->SpanIndex = 0;
    int stencilExtent[6];
    stencil->GetExtent(stencilExtent);

    // The stencil has a YZ array of span lists, we need increments
    // to get to the next Z position in the YZ array.
    this->SpanSliceIncrement = 0;
    this->SpanSliceEndIncrement = 0;

    if (stencilExtent[3] >= stencilExtent[2] && stencilExtent[5] >= stencilExtent[4])
    {
      this->SpanSliceIncrement = stencilExtent[3] - stencilExtent[2] + 1;
      int botOffset = this->Extent[2] - stencilExtent[2];
      if (botOffset >= 0)
      {
        this->SpanSliceEndIncrement += botOffset;
      }
      int topOffset = stencilExtent[3] - this->Extent[3];
      if (topOffset >= 0)
      {
        this->SpanSliceEndIncrement += topOffset;
      }
    }

    // Find the offset to the start position within the YZ array.
    vtkIdType startOffset = 0;

    int yOffset = this->Extent[2] - stencilExtent[2];
    if (yOffset < 0)
    {
      this->Extent[2] = stencilExtent[2];
      // starting before start of stencil: subtract the increment that
      // will be added in NextSpan() upon entry into stencil extent
      startOffset -= 1;
    }
    else
    {
      // starting partway into the stencil, so add an offset
      startOffset += yOffset;
    }

    if (stencilExtent[3] <= this->Extent[3])
    {
      this->Extent[3] = stencilExtent[3];
    }

    int zOffset = this->Extent[4] - stencilExtent[4];
    if (zOffset < 0)
    {
      this->Extent[4] = stencilExtent[4];
      // starting before start of stencil: subtract the increment that
      // will be added in NextSpan() upon entry into stencil extent
      if (yOffset >= 0)
      {
        startOffset -= 1 + this->SpanSliceEndIncrement;
      }
    }
    else
    {
      // starting partway into the stencil, so add an offset
      startOffset += zOffset * this->SpanSliceIncrement;
    }

    if (stencilExtent[5] <= this->Extent[5])
    {
      this->Extent[5] = stencilExtent[5];
    }

    if (this->Extent[2] <= this->Extent[3] && this->Extent[4] <= this->Extent[5])
    {
      this->SpanCountPointer =
        vtkImageStencilIteratorFriendship::GetExtentListLengths(stencil) + startOffset;

      this->SpanListPointer =
        vtkImageStencilIteratorFriendship::GetExtentLists(stencil) + startOffset;

      // Get the current position within the span list for the current row
      if (yOffset >= 0 && zOffset >= 0)
      {
        // If starting within stencil extent, check stencil immediately
        this->InStencil = true;
        this->SetSpanState(this->Extent[0]);
      }
    }
    else
    {
      this->SpanCountPointer = nullptr;
      this->SpanListPointer = nullptr;
      this->InStencil = false;
    }
  }
  else
  {
    this->HasStencil = false;
    this->InStencil = true;
    this->SpanSliceEndIncrement = 0;
    this->SpanSliceIncrement = 0;
    this->SpanIndex = 0;
    this->SpanCountPointer = nullptr;
    this->SpanListPointer = nullptr;
  }

  if (algorithm)
  {
    this->Algorithm = algorithm;
    vtkIdType maxCount = sliceSpan;
    maxCount *= volumeSpan;
    this->Target = maxCount / 50 + 1;
    this->Count = this->Target * 50 - (maxCount / this->Target) * this->Target + 1;
    this->ThreadId = threadId;
  }
  else
  {
    this->Algorithm = nullptr;
    this->Target = 0;
    this->Count = 0;
    this->ThreadId = 0;
  }
}

//------------------------------------------------------------------------------
void vtkImagePointDataIterator::SetSpanState(int idX)
{
  // Find the span that includes idX
  bool inStencil = false;
  int* spans = *this->SpanListPointer;
  int n = *this->SpanCountPointer;
  int i;
  for (i = 0; i < n; i++)
  {
    if (spans[i] > idX)
    {
      break;
    }
    inStencil = !inStencil;
  }

  // Set the primary span state variables
  this->SpanIndex = i;
  this->InStencil = inStencil;

  // Clamp the span end to MaxX+1
  int endIdX = this->Extent[1] + 1;
  if (i < n && spans[i] <= this->Extent[1])
  {
    endIdX = spans[i];
  }

  // Compute the pointers for idX and endIdX
  vtkIdType rowStart = this->RowEnd - (this->RowIncrement - this->RowEndIncrement);

  this->Id = rowStart + (idX - this->Extent[0]);
  this->SpanEnd = rowStart + (endIdX - this->Extent[0]);
}

//------------------------------------------------------------------------------
void vtkImagePointDataIterator::NextSpan()
{
  if (this->SpanEnd == this->RowEnd)
  {
    int spanIncr = 1;

    if (this->SpanEnd != this->SliceEnd)
    {
      // Move to the next row
      this->Id = this->RowEnd + this->RowEndIncrement;
      this->RowEnd += this->RowIncrement;
      this->SpanEnd = this->RowEnd;
      this->Index[1]++;
    }
    else if (this->SpanEnd != this->End)
    {
      // Move to the next slice
      this->Id = this->SliceEnd + this->SliceEndIncrement;
      this->SliceEnd += this->SliceIncrement;
      this->RowEnd = this->Id + (this->RowIncrement - this->RowEndIncrement);
      this->SpanEnd = this->RowEnd;
      this->Index[1] = this->StartY;
      this->Index[2]++;
      spanIncr += this->SpanSliceEndIncrement;
    }
    else
    {
      // reached End
      this->Id = this->End;
      return;
    }

    // Start of next row
    this->Index[0] = this->Extent[0];

    if (this->HasStencil)
    {
      if ((this->Index[1] >= this->Extent[2]) && (this->Index[1] <= this->Extent[3]) &&
        (this->Index[2] >= this->Extent[4]) && (this->Index[2] <= this->Extent[5]))
      {
        this->SpanCountPointer += spanIncr;
        this->SpanListPointer += spanIncr;
        this->SetSpanState(this->Extent[0]);
      }
      else
      {
        this->InStencil = false;
      }
    }

    if (this->Algorithm)
    {
      this->ReportProgress();
    }
  }
  else
  {
    // Move to the next span in the current row
    this->Id = this->SpanEnd;
    int spanCount = *this->SpanCountPointer;
    int endIdX = this->Extent[1] + 1;
    this->Index[0] = endIdX;
    if (this->SpanIndex < spanCount)
    {
      int tmpIdX = (*this->SpanListPointer)[this->SpanIndex];
      if (tmpIdX < endIdX)
      {
        this->Index[0] = tmpIdX;
      }
    }

    // Get the index to the start of the span after the next
    this->SpanIndex++;
    if (this->SpanIndex < spanCount)
    {
      int tmpIdX = (*this->SpanListPointer)[this->SpanIndex];
      if (tmpIdX < endIdX)
      {
        endIdX = tmpIdX;
      }
    }

    // Compute the end of the span
    this->SpanEnd =
      this->RowEnd - (this->RowIncrement - this->RowEndIncrement) + (endIdX - this->Extent[0]);

    // Flip the state
    this->InStencil = !this->InStencil;
  }
}

//------------------------------------------------------------------------------
void* vtkImagePointDataIterator::GetVoidPointer(
  vtkDataArray* array, vtkIdType i, int* pixelIncrement)
{
  int n = array->GetNumberOfComponents();
  if (pixelIncrement)
  {
    *pixelIncrement = n;
  }
  return array->GetVoidPointer(i * n);
}

//------------------------------------------------------------------------------
void* vtkImagePointDataIterator::GetVoidPointer(
  vtkImageData* image, vtkIdType i, int* pixelIncrement)
{
  return vtkImagePointDataIterator::GetVoidPointer(
    image->GetPointData()->GetScalars(), i, pixelIncrement);
}

//------------------------------------------------------------------------------
void vtkImagePointDataIterator::ReportProgress()
{
  if (this->Count % this->Target == 0)
  {
    if (this->Algorithm->GetAbortExecute())
    {
      this->Id = this->End;
      this->SpanEnd = this->End;
      this->RowEnd = this->End;
      this->SliceEnd = this->End;
    }
    else if (this->ThreadId == 0)
    {
      this->Algorithm->UpdateProgress(0.02 * (this->Count / this->Target));
    }
  }
  this->Count++;
}
VTK_ABI_NAMESPACE_END
