// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkTessellatedBoxSource.h"
#include "vtkCellArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkStreamingDemandDrivenPipeline.h"

#include <cassert>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkTessellatedBoxSource);

//------------------------------------------------------------------------------
vtkTessellatedBoxSource::vtkTessellatedBoxSource()
{
  this->Bounds[0] = -0.5;
  this->Bounds[1] = 0.5;
  this->Bounds[2] = -0.5;
  this->Bounds[3] = 0.5;
  this->Bounds[4] = -0.5;
  this->Bounds[5] = 0.5;

  this->Level = 0;
  this->DuplicateSharedPoints = 0;
  this->Quads = 0;
  this->OutputPointsPrecision = SINGLE_PRECISION;

  this->SetNumberOfInputPorts(0); // this is a source.
}

//------------------------------------------------------------------------------
vtkTessellatedBoxSource::~vtkTessellatedBoxSource() = default;

// Duplicate point method.
// Each face of the box is defined by the 3 points: an origin, a point along
// a first axis, a point along a second axis. The axes are oriented
// counterclockwise.
// Point id use voxel numbering, not hexahedron numbering.
// voxelPointId=boundingBoxQuads[faceId][facePointId]
// facePointId=0 : origin
// facePointId=1 : point along the first axis
// facePointId=2 : point along the second axis
static int boundingBoxQuads[6][3] = { { 0, 4, 2 }, // -x face
  { 5, 1, 7 },                                     // +x face
  { 0, 1, 4 },                                     // -y face
  { 6, 7, 2 },                                     // +y face
  { 1, 0, 3 },                                     // -z face
  { 4, 5, 6 } };                                   // +z face

// Minimal number of points method.
// Each edge of the box is defined by two vertices in increasing id order.
// vertexId=edges[edge][lowestId=0, highestId=1]
static int edges[12][2] = { { 0, 1 }, // 0
  { 0, 2 },                           // 1
  { 0, 4 },                           // 2
  { 1, 3 },                           // 3
  { 1, 5 },                           // 4
  { 2, 3 },                           // 5
  { 2, 6 },                           // 6
  { 3, 7 },                           // 7
  { 4, 5 },                           // 8
  { 4, 6 },                           // 9
  { 5, 7 },                           // 10
  { 6, 7 } };                         // 11

// Minimal number of points method.
// signed (edge id+1)=faces[face][edge]
// +1 because we cannot encode -0....
static int faces[6][4] = { { 3, 10, -7, -2 }, // 0: -x face
  { -5, 4, 8, -11 },                          // 1: +x face
  { 1, 5, -9, -3 },                           // 2: -y face
  { 12, -8, -6, 7 },                          // 3: +y face
  { -1, 2, 6, -4 },                           // 4: -z face
  { 9, 11, -12, -10 } };                      // 5: +z face

//------------------------------------------------------------------------------
// Description:
// Called by the superclass. Actual creation of the points and cells
// happens here.
int vtkTessellatedBoxSource::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  // get the info object
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the output
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // get the bounds
  double* bounds = this->Bounds;

  vtkPoints* points = vtkPoints::New();

  // Set the desired precision for the points in the output.
  if (this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION)
  {
    points->SetDataType(VTK_DOUBLE);
  }
  else
  {
    points->SetDataType(VTK_FLOAT);
  }

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

  // Always create a new vtkCellArray, otherwise it uses the this->Dummy of
  // vtkPolyData...
  vtkCellArray* polys = vtkCellArray::New();
  output->SetPolys(polys);
  polys->Delete();

  if (this->DuplicateSharedPoints)
  {
    this->DuplicateSharedPointsMethod(bounds, points, polys);
  }
  else
  {
    this->MinimalPointsMethod(bounds, points, polys);
  }
  return 1;
}

void vtkTessellatedBoxSource::DuplicateSharedPointsMethod(
  double* bounds, vtkPoints* points, vtkCellArray* polys)
{
  int numberOfPoints = (this->Level + 2) * (this->Level + 2) * 6;
  int changed = points->GetNumberOfPoints() != numberOfPoints;

  if (changed)
  {
    // Topology changed.
    points->SetNumberOfPoints(numberOfPoints);
    polys->Initialize();
  }

  // Iterate over the 6 faces.
  double facePoints[3][3]; // 3 points of 3 coordinates

  int face = 0;
  vtkIdType firstPointId = 0;
  while (face < 6)
  {
    int i = 0;
    while (i < 3)
    {
      int pointId = boundingBoxQuads[face][i];
      facePoints[i][0] = bounds[pointId & 1];
      facePoints[i][1] = bounds[2 + ((pointId >> 1) & 1)];
      facePoints[i][2] = bounds[4 + ((pointId >> 2) & 1)];
      ++i;
    }
    this->BuildFace(points, polys, firstPointId, facePoints, changed);
    firstPointId += (this->Level + 2) * (this->Level + 2);
    ++face;
  }
}

//------------------------------------------------------------------------------
void vtkTessellatedBoxSource::MinimalPointsMethod(
  double* bounds, vtkPoints* points, vtkCellArray* polys)
{
  int numberOfInternalPointsPerEdge = this->Level;
  int numberOfInternalPointsPerFace = this->Level * this->Level;
  int numberOfPoints = 8 + 12 * numberOfInternalPointsPerEdge + 6 * numberOfInternalPointsPerFace;

  int changed = points->GetNumberOfPoints() != numberOfPoints;
  if (changed)
  {
    // Topology changed.
    points->SetNumberOfPoints(numberOfPoints);
    polys->Initialize();
  }

  // Compute point coordinates.

  // First the 8 vertices
  // Then the 12 * (level) internal edge points (static list)
  // Then the 6 * (level*level) internal face points (static list)

  // The 8 vertices, voxel numbering: id=4*k+2*j+i
  int i = 0;
  double p[3];
  while (i < 8)
  {
    p[0] = bounds[i & 1];
    p[1] = bounds[2 + ((i >> 1) & 1)];
    p[2] = bounds[4 + ((i >> 2) & 1)];
    points->SetPoint(i, p);
    ++i;
  }

  // The 12*(level) internal edge points
  // edges are describe by there lowest vertex Id and highest vertex Id.
  // Numbering start from edges with vertex 0 for lowest Id, in increasing
  // order.
  int currentPointId = i;
  double edgeDirection[3];
  if (this->Level > 0)
  {
    int e = 0;
    while (e < 12)
    {
      assert("check: valid_currentPointId" && (currentPointId == (8 + e * this->Level)));
      double firstPoint[3];
      double lastPoint[3];

      points->GetPoint(edges[e][0], firstPoint);
      points->GetPoint(edges[e][1], lastPoint);
      i = 0;
      while (i < 3)
      {
        edgeDirection[i] = (lastPoint[i] - firstPoint[i]) / (this->Level + 1);
        p[i] = firstPoint[i];
        ++i;
      }

      int j = 1;
      while (j <= this->Level)
      {
        i = 0;
        while (i < 3)
        {
          p[i] += edgeDirection[i];
          ++i;
        }
        points->SetPoint(currentPointId, p);
        ++currentPointId;
        ++j;
      }
      ++e;
    }

    assert("check: valid_currentPointId" && (currentPointId == (8 + 12 * this->Level)));

    int f = 0;
    while (f < 6)
    {
      assert("check: valid_currentPointId" &&
        (currentPointId == (8 + 12 * this->Level + f * this->Level * this->Level)));
      double faceDirections[2][3];
      int facePointId[3];
      e = faces[f][0];
      if (e < 0)
      {
        e = -e;
        facePointId[0] = edges[e - 1][1];
        facePointId[1] = edges[e - 1][0];
      }
      else
      {
        assert("check: not_null_edge" && e > 0);
        facePointId[0] = edges[e - 1][0];
        facePointId[1] = edges[e - 1][1];
      }
      e = faces[f][3];
      if (e < 0)
      {
        e = -e;
        facePointId[2] = edges[e - 1][1];
      }
      else
      {
        facePointId[2] = edges[e - 1][0];
      }

      double facePoints[3][3];

      i = 0;
      while (i < 3)
      {
        int pointId = facePointId[i];
        facePoints[i][0] = bounds[pointId & 1];
        facePoints[i][1] = bounds[2 + ((pointId >> 1) & 1)];
        facePoints[i][2] = bounds[4 + ((pointId >> 2) & 1)];
        ++i;
      }

      int j = 0;
      while (j < 2)
      {
        i = 0;
        while (i < 3)
        {
          faceDirections[j][i] = (facePoints[j + 1][i] - facePoints[0][i]) / (this->Level + 1);
          ++i;
        }
        ++j;
      }

      // internal face points
      j = 1;
      while (j < (this->Level + 1))
      {
        i = 1;
        while (i < (this->Level + 1))
        {
          int comp = 0;
          while (comp < 3)
          {
            p[comp] =
              facePoints[0][comp] + i * faceDirections[0][comp] + j * faceDirections[1][comp];
            ++comp;
          }
          points->SetPoint(currentPointId, p);
          ++currentPointId;
          ++i;
        }
        ++j;
      }

      ++f;
    }
  }
  assert("check: valid_currentPointId" &&
    (currentPointId == (8 + 12 * this->Level + 6 * this->Level * this->Level)));

  // Faces
  vtkIdType poly[4]; // fit a triangle or a quad depending on the Quads flag.
  int f = 0;
  while (f < 6)
  {
    // Create the cells. Two triangle per subquad.
    int j = 0;
    while (j < (this->Level + 1))
    {
      i = 0;
      while (i < (this->Level + 1))
      {
        if (this->Quads)
        {
          // (i,j)
          poly[0] = this->LocalFacePointCoordinatesToPointId(f, i, j);
          // (i+1,j)
          poly[1] = this->LocalFacePointCoordinatesToPointId(f, i + 1, j);
          // (i+1,j+1)
          poly[2] = this->LocalFacePointCoordinatesToPointId(f, i + 1, j + 1);
          // (i,j+1)
          poly[3] = this->LocalFacePointCoordinatesToPointId(f, i, j + 1);
          polys->InsertNextCell(4, poly);
        }
        else
        {
          // First triangle
          // (i,j)
          poly[0] = this->LocalFacePointCoordinatesToPointId(f, i, j);
          // (i+1,j)
          poly[1] = this->LocalFacePointCoordinatesToPointId(f, i + 1, j);
          // (i+1,j+1)
          poly[2] = this->LocalFacePointCoordinatesToPointId(f, i + 1, j + 1);
          polys->InsertNextCell(3, poly);

          // Second triangle
          // poly[0]=(i,j), same than for the first triangle
          // (i+1,j+1)
          poly[1] = poly[2];
          // (i,j+1)
          poly[2] = this->LocalFacePointCoordinatesToPointId(f, i, j + 1);
          polys->InsertNextCell(3, poly);
        }
        ++i;
      }
      ++j;
    }
    ++f;
  }
}

//------------------------------------------------------------------------------
// Description:
// Compute the pointId of point (i,j) of face f.
// Used by MinimalPointsMethod().
// \pre valid_face: f>=0 && f<6
// \pre valid_i: i>=0 && i<=(this->Level+1)
// \pre valid_j: j>=0 && j<=(this->Level+1)
vtkIdType vtkTessellatedBoxSource::LocalFacePointCoordinatesToPointId(int f, int i, int j)
{
  assert("pre: valid_face" && f >= 0 && f < 6);
  assert("pre: valid_i" && i >= 0 && i <= (this->Level + 1));
  assert("pre: valid_j" && j >= 0 && j <= (this->Level + 1));

  vtkIdType result;

  int e;

  // vertex point?
  if (i == 0 && j == 0)
  {
    e = faces[f][0];
    if (e < 0)
    {
      e = -e;
      result = edges[e - 1][1];
    }
    else
    {
      result = edges[e - 1][0];
    }
  }
  else
  {
    if (i == this->Level + 1 && j == 0)
    {
      e = faces[f][0];
      if (e < 0)
      {
        e = -e;
        result = edges[e - 1][0];
      }
      else
      {
        result = edges[e - 1][1];
      }
    }
    else
    {
      if (i == this->Level + 1 && j == this->Level + 1)
      {
        e = faces[f][1];
        if (e < 0)
        {
          e = -e;
          result = edges[e - 1][0];
        }
        else
        {
          result = edges[e - 1][1];
        }
      }
      else
      {
        if (i == 0 && j == this->Level + 1)
        {
          e = faces[f][3];
          if (e < 0)
          {
            e = -e;
            result = edges[e - 1][1];
          }
          else
          {
            result = edges[e - 1][0];
          }
        }
        else
        {
          // internal edge point?
          if (i == 0)
          {
            e = faces[f][3];
            if (e < 0)
            {
              e = -e;
              result = 8 + (e - 1) * this->Level + j - 1;
            }
            else
            {
              result = 8 + (e - 1) * this->Level + this->Level - j;
            }
          }
          else
          {
            if (i == this->Level + 1)
            {
              e = faces[f][1];
              if (e < 0)
              {
                e = -e;
                result = 8 + (e - 1) * this->Level + this->Level - j;
              }
              else
              {
                result = 8 + (e - 1) * this->Level + j - 1;
              }
            }
            else
            {
              if (j == 0)
              {
                e = faces[f][0];
                if (e < 0)
                {
                  e = -e;
                  result = 8 + (e - 1) * this->Level + this->Level - i;
                }
                else
                {
                  result = 8 + (e - 1) * this->Level + i - 1;
                }
              }
              else
              {
                if (j == this->Level + 1)
                {
                  e = faces[f][2];
                  if (e < 0)
                  {
                    e = -e;
                    result = 8 + (e - 1) * this->Level + i - 1;
                  }
                  else
                  {
                    result = 8 + (e - 1) * this->Level + this->Level - i;
                  }
                }
                else
                {
                  // internal face point.
                  result =
                    (8 + (12 + f * this->Level) * this->Level) + (j - 1) * this->Level + (i - 1);
                }
              }
            }
          }
        }
      }
    }
  }
  assert("post: valid_result" && result >= 0 &&
    result < (8 + 12 * this->Level + 6 * this->Level * this->Level));
  return result;
}

//------------------------------------------------------------------------------
// Description:
// Build one of the face of the box with some level of tessellation.
// facePoints[0] is the lower-left point
// facePoints[1] is the point along the first axis
// facePoints[2] is the point along the second axis
// \pre positive_id: firstPointId>=0
// \pre points_exists: points!=0
// \pre polys_exists: polys!=0
void vtkTessellatedBoxSource::BuildFace(vtkPoints* points, vtkCellArray* polys,
  vtkIdType firstPointId, double facePoints[3][3], int changed)
{
  assert("pre: positive_id" && firstPointId >= 0);
  assert("pre: points_exists" && points != nullptr);
  assert("pre: polys_exists" && polys != nullptr);

  double direction[2][3];

  int j = 0;
  while (j < 2)
  {
    int i = 0;
    while (i < 3)
    {
      direction[j][i] = (facePoints[j + 1][i] - facePoints[0][i]) / (this->Level + 1);
      ++i;
    }
    ++j;
  }

  // Create the point positions.

  double p[3];

  j = 0;
  while (j <= (this->Level + 1))
  {
    int i = 0;
    while (i <= (this->Level + 1))
    {
      int comp = 0;
      while (comp < 3)
      {
        p[comp] = facePoints[0][comp] + i * direction[0][comp] + j * direction[1][comp];
        ++comp;
      }
      points->SetPoint(firstPointId + j * (this->Level + 2) + i, p);

      ++i;
    }
    ++j;
  }

  if (changed)
  {
    vtkIdType poly[4]; // fit a triangle or a quad depending on the Quads flag.

    // Create the cells. Two triangle per subquad.
    j = 0;
    while (j < (this->Level + 1))
    {
      int i = 0;
      while (i < (this->Level + 1))
      {
        if (this->Quads)
        {
          // (i,j)
          poly[0] = firstPointId + j * (this->Level + 2) + i;
          // (i+1,j)
          poly[1] = firstPointId + j * (this->Level + 2) + i + 1;
          // (i+1,j+1)
          poly[2] = firstPointId + (j + 1) * (this->Level + 2) + i + 1;
          // (i,j+1)
          poly[3] = firstPointId + (j + 1) * (this->Level + 2) + i;
          polys->InsertNextCell(4, poly);
        }
        else
        {
          // First triangle
          // (i,j)
          poly[0] = firstPointId + j * (this->Level + 2) + i;
          // (i+1,j)
          poly[1] = firstPointId + j * (this->Level + 2) + i + 1;
          // (i+1,j+1)
          poly[2] = firstPointId + (j + 1) * (this->Level + 2) + i + 1;
          polys->InsertNextCell(3, poly);

          // Second triangle
          // (i,j)
          poly[0] = firstPointId + j * (this->Level + 2) + i;
          // (i+1,j+1)
          poly[1] = firstPointId + (j + 1) * (this->Level + 2) + i + 1;
          // (i,j+1)
          poly[2] = firstPointId + (j + 1) * (this->Level + 2) + i;
          polys->InsertNextCell(3, poly);
        }
        ++i;
      }
      ++j;
    }
  }
}

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

  os << indent << "Bounds: (" << this->Bounds[0];
  int i = 1;
  while (i < 6)
  {
    os << ", " << this->Bounds[i];
    ++i;
  }
  os << ")" << endl;
  os << indent << "Level: " << this->Level << endl;

  os << indent << "DuplicateSharedPoints: ";
  if (this->DuplicateSharedPoints)
  {
    os << "true";
  }
  else
  {
    os << "false";
  }

  os << indent << "Quads: ";
  if (this->Quads)
  {
    os << "true";
  }
  else
  {
    os << "false";
  }
  os << endl;
  os << indent << "Output Points Precision: " << this->OutputPointsPrecision << endl;
}
VTK_ABI_NAMESPACE_END
