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

#include "vtkCellArray.h"
#include "vtkDoubleArray.h"
#include "vtkIncrementalPointLocator.h"
#include "vtkLine.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkTriangle.h"

namespace
{
//------------------------------------------------------------------------------
[[maybe_unused]] constexpr const char* TriangleStripTopology = R"(
   TriangleStrip topology:

          2----4----6--- ...
         /|\   |\   |
        / | \  | \  |
       /  |  \ |  \ |
      /   |   \|   \|
     0----1----3----5---- ...
)";
}

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkTriangleStrip);

//------------------------------------------------------------------------------
vtkTriangleStrip::vtkTriangleStrip()
{
  this->Line = vtkSmartPointer<vtkLine>::New();
  this->Triangle = vtkSmartPointer<vtkTriangle>::New();
}

//------------------------------------------------------------------------------
int vtkTriangleStrip::EvaluatePosition(const double x[3], double closestPoint[3], int& subId,
  double pcoords[3], double& minDist2, double weights[])
{
  double pc[3], dist2;
  int ignoreId;
  double tempWeights[3], activeWeights[3];
  double closest[3];

  pcoords[2] = 0.0;

  activeWeights[0] = 0.0;
  activeWeights[1] = 0.0;
  activeWeights[2] = 0.0;

  int returnStatus = 0;
  minDist2 = VTK_DOUBLE_MAX;
  int i;
  for (i = 0; i < this->Points->GetNumberOfPoints() - 2; i++)
  {
    weights[i] = 0.0;
    this->Triangle->Points->SetPoint(0, this->Points->GetPoint(i));
    this->Triangle->Points->SetPoint(1, this->Points->GetPoint(i + 1));
    this->Triangle->Points->SetPoint(2, this->Points->GetPoint(i + 2));
    int status = this->Triangle->EvaluatePosition(x, closest, ignoreId, pc, dist2, tempWeights);
    if (status != -1 && ((dist2 < minDist2) || ((dist2 == minDist2) && (returnStatus == 0))))
    {
      returnStatus = status;
      if (closestPoint)
      {
        closestPoint[0] = closest[0];
        closestPoint[1] = closest[1];
        closestPoint[2] = closest[2];
      }
      subId = i;
      pcoords[0] = pc[0];
      pcoords[1] = pc[1];
      minDist2 = dist2;
      activeWeights[0] = tempWeights[0];
      activeWeights[1] = tempWeights[1];
      activeWeights[2] = tempWeights[2];
    }
  }

  weights[i] = 0.0;
  weights[i + 1] = 0.0;

  weights[subId] = activeWeights[0];
  weights[subId + 1] = activeWeights[1];
  weights[subId + 2] = activeWeights[2];

  return returnStatus;
}

//------------------------------------------------------------------------------
void vtkTriangleStrip::EvaluateLocation(
  int& subId, const double pcoords[3], double x[3], double* weights)
{
  static constexpr int idx[2][3] = { { 0, 1, 2 }, { 1, 0, 2 } };
  const int order = subId % 2;

  // Efficient point access
  const auto pointsArray = vtkDoubleArray::FastDownCast(this->Points->GetData());
  if (!pointsArray)
  {
    vtkErrorMacro(<< "Points should be double type");
    return;
  }
  const double* pts = pointsArray->GetPointer(0);

  const double* pt1 = pts + 3 * (subId + idx[order][0]);
  const double* pt2 = pts + 3 * (subId + idx[order][1]);
  const double* pt3 = pts + 3 * (subId + idx[order][2]);
  const double u3 = 1.0 - pcoords[0] - pcoords[1];

  std::fill_n(weights, this->Points->GetNumberOfPoints(), 0.0);
  weights[subId] = u3;
  weights[subId + 1] = pcoords[0];
  weights[subId + 2] = pcoords[1];

  for (int i = 0; i < 3; i++)
  {
    x[i] = pt1[i] * weights[subId] + pt2[i] * weights[subId + 1] + pt3[i] * weights[subId + 2];
  }
}

//------------------------------------------------------------------------------
int vtkTriangleStrip::CellBoundary(int subId, const double pcoords[3], vtkIdList* pts)
{
  static constexpr int idx[2][3] = { { 0, 1, 2 }, { 1, 0, 2 } };
  int order;

  order = subId % 2;

  this->Triangle->PointIds->SetId(0, this->PointIds->GetId(subId + idx[order][0]));
  this->Triangle->PointIds->SetId(1, this->PointIds->GetId(subId + idx[order][1]));
  this->Triangle->PointIds->SetId(2, this->PointIds->GetId(subId + idx[order][2]));
  return this->Triangle->CellBoundary(0, pcoords, pts);
}

//------------------------------------------------------------------------------
void vtkTriangleStrip::Contour(double value, vtkDataArray* cellScalars,
  vtkIncrementalPointLocator* locator, vtkCellArray* verts, vtkCellArray* lines,
  vtkCellArray* polys, vtkPointData* inPd, vtkPointData* outPd, vtkCellData* inCd, vtkIdType cellId,
  vtkCellData* outCd)
{
  int numTris = this->Points->GetNumberOfPoints() - 2;
  vtkDataArray* triScalars = cellScalars->NewInstance();
  triScalars->SetNumberOfComponents(cellScalars->GetNumberOfComponents());
  triScalars->SetNumberOfTuples(3);

  for (int i = 0; i < numTris; i++)
  {
    this->Triangle->Points->SetPoint(0, this->Points->GetPoint(i));
    this->Triangle->Points->SetPoint(1, this->Points->GetPoint(i + 1));
    this->Triangle->Points->SetPoint(2, this->Points->GetPoint(i + 2));

    if (outPd)
    {
      this->Triangle->PointIds->SetId(0, this->PointIds->GetId(i));
      this->Triangle->PointIds->SetId(1, this->PointIds->GetId(i + 1));
      this->Triangle->PointIds->SetId(2, this->PointIds->GetId(i + 2));
    }

    triScalars->SetTuple(0, cellScalars->GetTuple(i));
    triScalars->SetTuple(1, cellScalars->GetTuple(i + 1));
    triScalars->SetTuple(2, cellScalars->GetTuple(i + 2));

    this->Triangle->Contour(
      value, triScalars, locator, verts, lines, polys, inPd, outPd, inCd, cellId, outCd);
  }
  triScalars->Delete();
}

//------------------------------------------------------------------------------
vtkCell* vtkTriangleStrip::GetEdge(int edgeId)
{
  int id1, id2;

  if (edgeId == 0)
  {
    id1 = 0;
    id2 = 1;
  }
  else if (edgeId == (this->GetNumberOfPoints() - 1))
  {
    id1 = edgeId - 1;
    id2 = edgeId;
  }
  else
  {
    id1 = edgeId - 1;
    id2 = edgeId + 1;
  }

  this->Line->PointIds->SetId(0, this->PointIds->GetId(id1));
  this->Line->PointIds->SetId(1, this->PointIds->GetId(id2));
  this->Line->Points->SetPoint(0, this->Points->GetPoint(id1));
  this->Line->Points->SetPoint(1, this->Points->GetPoint(id2));

  return this->Line;
}

//------------------------------------------------------------------------------
//
// Intersect sub-triangles
//
int vtkTriangleStrip::IntersectWithLine(const double p1[3], const double p2[3], double tol,
  double& t, double x[3], double pcoords[3], int& subId)
{
  int subTest, numTris = this->Points->GetNumberOfPoints() - 2;

  for (subId = 0; subId < numTris; subId++)
  {
    this->Triangle->Points->SetPoint(0, this->Points->GetPoint(subId));
    this->Triangle->Points->SetPoint(1, this->Points->GetPoint(subId + 1));
    this->Triangle->Points->SetPoint(2, this->Points->GetPoint(subId + 2));

    if (this->Triangle->IntersectWithLine(p1, p2, tol, t, x, pcoords, subTest))
    {
      return 1;
    }
  }

  return 0;
}

//------------------------------------------------------------------------------
int vtkTriangleStrip::TriangulateLocalIds(int vtkNotUsed(index), vtkIdList* ptIds)
{
  int numTris = this->Points->GetNumberOfPoints() - 2;
  ptIds->SetNumberOfIds(3 * numTris);
  static constexpr int idx[2][3] = { { 0, 1, 2 }, { 1, 0, 2 } };
  for (int subId = 0; subId < numTris; subId++)
  {
    int order = subId % 2;
    for (int i = 0; i < 3; i++)
    {
      ptIds->SetId(subId * 3 + i, subId + idx[order][i]);
    }
  }
  return 1;
}

//------------------------------------------------------------------------------
void vtkTriangleStrip::Derivatives(
  int subId, const double pcoords[3], const double* values, int dim, double* derivs)
{
  this->Triangle->Points->SetPoint(0, this->Points->GetPoint(subId));
  this->Triangle->Points->SetPoint(1, this->Points->GetPoint(subId + 1));
  this->Triangle->Points->SetPoint(2, this->Points->GetPoint(subId + 2));

  this->Triangle->Derivatives(0, pcoords, values + dim * subId, dim, derivs);
}

//------------------------------------------------------------------------------
// Given a triangle strip, decompose it into (triangle) polygons. The
// polygons are appended to the end of the list of polygons.
void vtkTriangleStrip::DecomposeStrip(int npts, const vtkIdType* pts, vtkCellArray* polys)
{
  int p1 = pts[0];
  int p2 = pts[1];
  for (int i = 0; i < (npts - 2); i++)
  {
    int p3 = pts[i + 2];
    polys->InsertNextCell(3);
    if ((i % 2)) // flip ordering to preserve consistency
    {
      polys->InsertCellPoint(p2);
      polys->InsertCellPoint(p1);
      polys->InsertCellPoint(p3);
    }
    else
    {
      polys->InsertCellPoint(p1);
      polys->InsertCellPoint(p2);
      polys->InsertCellPoint(p3);
    }
    p1 = p2;
    p2 = p3;
  }
}

//------------------------------------------------------------------------------
void vtkTriangleStrip::Clip(double value, vtkDataArray* cellScalars,
  vtkIncrementalPointLocator* locator, vtkCellArray* tris, vtkPointData* inPd, vtkPointData* outPd,
  vtkCellData* inCd, vtkIdType cellId, vtkCellData* outCd, int insideOut)
{
  int numTris = this->Points->GetNumberOfPoints() - 2;
  int id1, id2, id3;
  vtkDataArray* triScalars = cellScalars->NewInstance();
  triScalars->SetNumberOfComponents(cellScalars->GetNumberOfComponents());
  triScalars->SetNumberOfTuples(3);

  for (int i = 0; i < numTris; i++)
  {
    if (i % 2)
    {
      id1 = i + 2;
      id2 = i + 1;
      id3 = i;
    }
    else
    {
      id1 = i;
      id2 = i + 1;
      id3 = i + 2;
    }

    this->Triangle->Points->SetPoint(0, this->Points->GetPoint(id1));
    this->Triangle->Points->SetPoint(1, this->Points->GetPoint(id2));
    this->Triangle->Points->SetPoint(2, this->Points->GetPoint(id3));

    this->Triangle->PointIds->SetId(0, this->PointIds->GetId(id1));
    this->Triangle->PointIds->SetId(1, this->PointIds->GetId(id2));
    this->Triangle->PointIds->SetId(2, this->PointIds->GetId(id3));

    triScalars->SetTuple(0, cellScalars->GetTuple(id1));
    triScalars->SetTuple(1, cellScalars->GetTuple(id2));
    triScalars->SetTuple(2, cellScalars->GetTuple(id3));

    this->Triangle->Clip(
      value, triScalars, locator, tris, inPd, outPd, inCd, cellId, outCd, insideOut);
  }

  triScalars->Delete();
}

//------------------------------------------------------------------------------
// Return the center of the point cloud in parametric coordinates.
int vtkTriangleStrip::GetParametricCenter(double pcoords[3])
{
  pcoords[0] = pcoords[1] = 0.333333;
  pcoords[2] = 0.0;
  return ((this->Points->GetNumberOfPoints() - 2) / 2);
}

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

  os << indent << "Line:\n";
  this->Line->PrintSelf(os, indent.GetNextIndent());
  os << indent << "Triangle:\n";
  this->Triangle->PrintSelf(os, indent.GetNextIndent());
}
VTK_ABI_NAMESPACE_END
