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

#include "vtkDoubleArray.h"
#include "vtkLine.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkQuadraticEdge.h"
#include "vtkTriangle.h"

#include <algorithm> //std::copy
#include <array>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkBiQuadraticTriangle);

//------------------------------------------------------------------------------
// Construct the line with two points.
vtkBiQuadraticTriangle::vtkBiQuadraticTriangle()
{
  this->Edge = vtkQuadraticEdge::New();
  this->Face = vtkTriangle::New();
  this->Scalars = vtkDoubleArray::New();
  this->Scalars->SetNumberOfTuples(3);

  this->Points->SetNumberOfPoints(7);
  this->PointIds->SetNumberOfIds(7);
  for (int i = 0; i < 7; i++)
  {
    this->Points->SetPoint(i, 0.0, 0.0, 0.0);
    this->PointIds->SetId(i, 0);
  }
}

//------------------------------------------------------------------------------
vtkBiQuadraticTriangle::~vtkBiQuadraticTriangle()
{
  this->Edge->Delete();
  this->Face->Delete();
  this->Scalars->Delete();
}

//------------------------------------------------------------------------------
vtkCell* vtkBiQuadraticTriangle::GetEdge(int edgeId)
{
  edgeId = std::max(edgeId, 0);
  edgeId = std::min(edgeId, 2);
  int p = (edgeId + 1) % 3;

  // load point id's
  this->Edge->PointIds->SetId(0, this->PointIds->GetId(edgeId));
  this->Edge->PointIds->SetId(1, this->PointIds->GetId(p));
  this->Edge->PointIds->SetId(2, this->PointIds->GetId(edgeId + 3));

  // load coordinates
  this->Edge->Points->SetPoint(0, this->Points->GetPoint(edgeId));
  this->Edge->Points->SetPoint(1, this->Points->GetPoint(p));
  this->Edge->Points->SetPoint(2, this->Points->GetPoint(edgeId + 3));

  return this->Edge;
}

//------------------------------------------------------------------------------
// order picked carefully for parametric coordinate conversion
static vtkIdType LinearTris[6][3] = {
  { 0, 3, 6 },
  { 6, 3, 4 },
  { 6, 4, 5 },
  { 0, 6, 5 },
  { 3, 1, 4 },
  { 5, 4, 2 },
};

int vtkBiQuadraticTriangle::EvaluatePosition(const double x[3], double closestPoint[3], int& subId,
  double pcoords[3], double& minDist2, double weights[])
{
  double pc[3], dist2, pc0, pc1;
  int ignoreId, i, returnStatus = 0, status;
  double tempWeights[3];
  double closest[3];

  pc0 = pc1 = 0;

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

  // six linear triangles are used
  for (minDist2 = VTK_DOUBLE_MAX, i = 0; i < 6; i++)
  {
    this->Face->Points->SetPoint(0, pts + 3 * LinearTris[i][0]);
    this->Face->Points->SetPoint(1, pts + 3 * LinearTris[i][1]);
    this->Face->Points->SetPoint(2, pts + 3 * LinearTris[i][2]);

    status = this->Face->EvaluatePosition(x, closest, ignoreId, pc, dist2, tempWeights);
    if (status != -1 && ((dist2 < minDist2) || ((dist2 == minDist2) && (returnStatus == 0))))
    {
      returnStatus = status;
      minDist2 = dist2;
      subId = i;
      pc0 = pc[0];
      pc1 = pc[1];
      if (closestPoint)
      {
        for (int j = 0; j < 3; j++)
        {
          closestPoint[j] = closest[j];
        }
      }
    }
  }

  // adjust parametric coordinates
  if (returnStatus != -1)
  {
    if (subId == 0)
    {
      pcoords[0] = pc0 / 2.0 + pc1 / 3.0;
      pcoords[1] = pc1 / 3.0;
    }
    else if (subId == 1)
    {
      pcoords[0] = (1.0 / 3.0) + pc0 / 6.0 + pc1 / 6.0;
      pcoords[1] = (1.0 / 3.0) + pc0 / (-3.0) + pc1 / 6.0;
    }
    else if (subId == 2)
    {
      pcoords[0] = (1.0 / 3.0) + pc0 / 6.0 + pc1 / (-3.0);
      pcoords[1] = (1.0 / 3.0) + pc0 / 6.0 + pc1 / 6.0;
    }
    else if (subId == 3)
    {
      pcoords[0] = pc0 / 3.0;
      pcoords[1] = pc0 / 3.0 + pc1 * 0.5;
    }
    else if (subId == 4)
    {
      pcoords[0] = pc0 * 0.5 + 0.5;
      pcoords[1] = 0.5 * pc1;
    }
    else if (subId == 5)
    {
      pcoords[0] = 0.5 * pc0;
      pcoords[1] = 0.5 + 0.5 * pc1;
    }
    pcoords[2] = 0.0;
    vtkBiQuadraticTriangle::InterpolationFunctions(pcoords, weights);
  }

  return returnStatus;
}

//------------------------------------------------------------------------------
void vtkBiQuadraticTriangle::EvaluateLocation(
  int& vtkNotUsed(subId), const double pcoords[3], double x[3], double* weights)
{
  // 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);

  int i;
  const double *a0, *a1, *a2, *a3, *a4, *a5, *a6;
  a0 = pts;
  a1 = pts + 3;
  a2 = pts + 6;
  a3 = pts + 9;
  a4 = pts + 12;
  a5 = pts + 15;
  a6 = pts + 18;

  vtkBiQuadraticTriangle::InterpolationFunctions(pcoords, weights);

  for (i = 0; i < 3; i++)
  {
    x[i] = a0[i] * weights[0] + a1[i] * weights[1] + a2[i] * weights[2] + a3[i] * weights[3] +
      a4[i] * weights[4] + a5[i] * weights[5] + a6[i] * weights[6];
  }
}

//------------------------------------------------------------------------------
int vtkBiQuadraticTriangle::CellBoundary(int subId, const double pcoords[3], vtkIdList* pts)
{
  return this->Face->CellBoundary(subId, pcoords, pts);
}

//------------------------------------------------------------------------------
void vtkBiQuadraticTriangle::Contour(double value, vtkDataArray* cellScalars,
  vtkIncrementalPointLocator* locator, vtkCellArray* verts, vtkCellArray* lines,
  vtkCellArray* polys, vtkPointData* inPd, vtkPointData* outPd, vtkCellData* inCd, vtkIdType cellId,
  vtkCellData* outCd)
{
  for (int i = 0; i < 6; i++)
  {
    this->Face->Points->SetPoint(0, this->Points->GetPoint(LinearTris[i][0]));
    this->Face->Points->SetPoint(1, this->Points->GetPoint(LinearTris[i][1]));
    this->Face->Points->SetPoint(2, this->Points->GetPoint(LinearTris[i][2]));

    if (outPd)
    {
      this->Face->PointIds->SetId(0, this->PointIds->GetId(LinearTris[i][0]));
      this->Face->PointIds->SetId(1, this->PointIds->GetId(LinearTris[i][1]));
      this->Face->PointIds->SetId(2, this->PointIds->GetId(LinearTris[i][2]));
    }

    this->Scalars->SetTuple(0, cellScalars->GetTuple(LinearTris[i][0]));
    this->Scalars->SetTuple(1, cellScalars->GetTuple(LinearTris[i][1]));
    this->Scalars->SetTuple(2, cellScalars->GetTuple(LinearTris[i][2]));

    this->Face->Contour(
      value, this->Scalars, locator, verts, lines, polys, inPd, outPd, inCd, cellId, outCd);
  }
}

//------------------------------------------------------------------------------
// Line-line intersection. Intersection has to occur within [0,1] parametric
// coordinates and with specified tolerance.
int vtkBiQuadraticTriangle::IntersectWithLine(
  const double* p1, const double* p2, double tol, double& t, double* x, double* pcoords, int& subId)
{
  int subTest, i;
  subId = 0;

  for (i = 0; i < 6; i++)
  {
    this->Face->Points->SetPoint(0, this->Points->GetPoint(LinearTris[i][0]));
    this->Face->Points->SetPoint(1, this->Points->GetPoint(LinearTris[i][1]));
    this->Face->Points->SetPoint(2, this->Points->GetPoint(LinearTris[i][2]));

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

  return 0;
}

//------------------------------------------------------------------------------
int vtkBiQuadraticTriangle::TriangulateLocalIds(int vtkNotUsed(index), vtkIdList* ptIds)
{
  ptIds->SetNumberOfIds(18);
  constexpr std::array<vtkIdType, 18> localPtIds{ 0, 3, 6, 6, 3, 4, 6, 4, 5, 0, 6, 5, 3, 1, 4, 5, 4,
    2 };
  std::copy(localPtIds.begin(), localPtIds.end(), ptIds->begin());
  return 1;
}

//------------------------------------------------------------------------------
void vtkBiQuadraticTriangle::Derivatives(
  int vtkNotUsed(subId), const double pcoords[3], const double* values, int dim, double* derivs)
{
  double v0[2], v1[2], v2[2], v3[2], v4[2], v5[2], v6[2];        // Local coordinates of Each point.
  double v10[3], v20[3], lenX;                                   // Reesentation of local Axis
  double x0[3], x1[3], x2[3], x3[3], x4[3], x5[3], x6[3];        // Points of the model
  double n[3], vec20[3], vec30[3], vec40[3], vec50[3], vec60[3]; // Normal and vector of each point.
  double *J[2], J0[2], J1[2];                                    // Jacobian Matrix
  double *JI[2], JI0[2], JI1[2];                                 // Inverse of the Jacobian Matrix
  double funcDerivs[14], sum[2], dBydx, dBydy;                   // Derivated values

  // Project points of BiQuadTriangle into a 2D system
  this->Points->GetPoint(0, x0);
  this->Points->GetPoint(1, x1);
  this->Points->GetPoint(2, x2);
  this->Points->GetPoint(3, x3);
  this->Points->GetPoint(4, x4);
  this->Points->GetPoint(5, x5);
  this->Points->GetPoint(6, x6);
  vtkTriangle::ComputeNormal(x0, x1, x2, n);

  for (int i = 0; i < 3; i++) // Compute the vector for each point
  {
    v10[i] = x1[i] - x0[i];
    vec20[i] = x2[i] - x0[i];
    vec30[i] = x3[i] - x0[i];
    vec40[i] = x4[i] - x0[i];
    vec50[i] = x5[i] - x0[i];
    vec60[i] = x6[i] - x0[i];
  }

  vtkMath::Cross(n, v10, v20); // creates local y' axis

  if ((lenX = vtkMath::Normalize(v10)) <= 0.0 || vtkMath::Normalize(v20) <= 0.0) // degenerate
  {
    for (int j = 0; j < dim; j++)
    {
      for (int i = 0; i < 3; i++)
      {
        derivs[j * dim + i] = 0.0;
      }
    }
    return;
  }

  v0[0] = v0[1] = 0.0; // convert points to 2D (i.e., local system)
  v1[0] = lenX;
  v1[1] = 0.0;

  v2[0] = vtkMath::Dot(vec20, v10);
  v2[1] = vtkMath::Dot(vec20, v20);

  v3[0] = vtkMath::Dot(vec30, v10);
  v3[1] = vtkMath::Dot(vec30, v20);

  v4[0] = vtkMath::Dot(vec40, v10);
  v4[1] = vtkMath::Dot(vec40, v20);

  v5[0] = vtkMath::Dot(vec50, v10);
  v5[1] = vtkMath::Dot(vec50, v20);

  v6[0] = vtkMath::Dot(vec60, v10);
  v6[1] = vtkMath::Dot(vec60, v20);

  vtkBiQuadraticTriangle::InterpolationDerivs(pcoords, funcDerivs);

  // Compute Jacobian and inverse Jacobian
  J[0] = J0;
  J[1] = J1;
  JI[0] = JI0;
  JI[1] = JI1;

  J[0][0] = v0[0] * funcDerivs[0] + v1[0] * funcDerivs[1] + v2[0] * funcDerivs[2] +
    v3[0] * funcDerivs[3] + v4[0] * funcDerivs[4] + v5[0] * funcDerivs[5] + v6[0] * funcDerivs[6];
  J[0][1] = v0[1] * funcDerivs[0] + v1[1] * funcDerivs[1] + v2[1] * funcDerivs[2] +
    v3[1] * funcDerivs[3] + v4[1] * funcDerivs[4] + v5[1] * funcDerivs[5] + v6[1] * funcDerivs[6];
  J[1][0] = v0[0] * funcDerivs[7] + v1[0] * funcDerivs[8] + v2[0] * funcDerivs[9] +
    v3[0] * funcDerivs[10] + v4[0] * funcDerivs[11] + v5[0] * funcDerivs[12] +
    v6[0] * funcDerivs[13];
  J[1][1] = v0[1] * funcDerivs[7] + v1[1] * funcDerivs[8] + v2[1] * funcDerivs[9] +
    v3[1] * funcDerivs[10] + v4[1] * funcDerivs[11] + v5[1] * funcDerivs[12] +
    v6[1] * funcDerivs[13];

  // Compute inverse Jacobian, return if Jacobian is singular
  if (!vtkMath::InvertMatrix(J, JI, 2))
  {
    for (int j = 0; j < dim; j++)
    {
      for (int i = 0; i < 3; i++)
      {
        derivs[j * dim + i] = 0.0;
      }
    }
    return;
  }

  // Loop over "dim" derivative values. For each set of values,
  // compute derivatives
  // in local system and then transform into modelling system.
  // First compute derivatives in local x'-y' coordinate system
  for (int j = 0; j < dim; j++)
  {
    sum[0] = sum[1] = 0.0;
    for (int i = 0; i < 7; i++) // loop over interp. function derivatives
    {
      sum[0] += funcDerivs[i] * values[dim * i + j];
      sum[1] += funcDerivs[7 + i] * values[dim * i + j];
    }
    dBydx = sum[0] * JI[0][0] + sum[1] * JI[0][1];
    dBydy = sum[0] * JI[1][0] + sum[1] * JI[1][1];

    // Transform into global system (dot product with global axes)
    derivs[3 * j] = dBydx * v10[0] + dBydy * v20[0];
    derivs[3 * j + 1] = dBydx * v10[1] + dBydy * v20[1];
    derivs[3 * j + 2] = dBydx * v10[2] + dBydy * v20[2];
  }
}

//------------------------------------------------------------------------------
// Clip this quadratic triangle using the scalar value provided. Like
// contouring, except that it cuts the triangle to produce other quads
// and triangles.
void vtkBiQuadraticTriangle::Clip(double value, vtkDataArray* cellScalars,
  vtkIncrementalPointLocator* locator, vtkCellArray* polys, vtkPointData* inPd, vtkPointData* outPd,
  vtkCellData* inCd, vtkIdType cellId, vtkCellData* outCd, int insideOut)
{
  for (int i = 0; i < 6; i++)
  {
    this->Face->Points->SetPoint(0, this->Points->GetPoint(LinearTris[i][0]));
    this->Face->Points->SetPoint(1, this->Points->GetPoint(LinearTris[i][1]));
    this->Face->Points->SetPoint(2, this->Points->GetPoint(LinearTris[i][2]));

    this->Face->PointIds->SetId(0, this->PointIds->GetId(LinearTris[i][0]));
    this->Face->PointIds->SetId(1, this->PointIds->GetId(LinearTris[i][1]));
    this->Face->PointIds->SetId(2, this->PointIds->GetId(LinearTris[i][2]));

    this->Scalars->SetTuple(0, cellScalars->GetTuple(LinearTris[i][0]));
    this->Scalars->SetTuple(1, cellScalars->GetTuple(LinearTris[i][1]));
    this->Scalars->SetTuple(2, cellScalars->GetTuple(LinearTris[i][2]));

    this->Face->Clip(
      value, this->Scalars, locator, polys, inPd, outPd, inCd, cellId, outCd, insideOut);
  }
}

//------------------------------------------------------------------------------
// Compute maximum parametric distance to cell
double vtkBiQuadraticTriangle::GetParametricDistance(const double pcoords[3])
{
  int i;
  double pDist, pDistMax = 0.0;
  double pc[3];

  pc[0] = pcoords[0];
  pc[1] = pcoords[1];
  pc[2] = 1.0 - pcoords[0] - pcoords[1];

  for (i = 0; i < 3; i++)
  {
    if (pc[i] < 0.0)
    {
      pDist = -pc[i];
    }
    else if (pc[i] > 1.0)
    {
      pDist = pc[i] - 1.0;
    }
    else // inside the cell in the parametric direction
    {
      pDist = 0.0;
    }
    pDistMax = std::max(pDist, pDistMax);
  }

  return pDistMax;
}

//------------------------------------------------------------------------------
// Compute interpolation functions. The first three nodes are the triangle
// vertices; the next three nodes are mid-edge nodes; the last node is the mid-cell node.
void vtkBiQuadraticTriangle::InterpolationFunctions(const double pcoords[3], double weights[7])
{
  double r = pcoords[0];
  double s = pcoords[1];

  weights[0] = 1.0 - 3.0 * (r + s) + 2.0 * (r * r + s * s) + 7.0 * r * s - 3.0 * r * s * (r + s);
  weights[1] = r * (-1.0 + 2.0 * r + 3.0 * s - 3.0 * s * (r + s));
  weights[2] = s * (-1.0 + 3.0 * r + 2.0 * s - 3.0 * r * (r + s));
  weights[3] = 4.0 * r * (1.0 - r - 4.0 * s + 3.0 * s * (r + s));
  weights[4] = 4.0 * r * s * (-2.0 + 3.0 * (r + s));
  weights[5] = 4.0 * s * (1.0 - 4.0 * r - s + 3.0 * r * (r + s));
  weights[6] = 27.0 * r * s * (1.0 - r - s);
}

//------------------------------------------------------------------------------
// Derivatives in parametric space.
void vtkBiQuadraticTriangle::InterpolationDerivs(const double pcoords[3], double derivs[14])
{
  double r = pcoords[0];
  double s = pcoords[1];

  // r-derivatives
  derivs[0] = -3.0 + 4.0 * r + 7.0 * s - 6.0 * r * s - 3.0 * s * s;
  derivs[1] = -1.0 + 4.0 * r + 3.0 * s - 6.0 * r * s - 3.0 * s * s;
  derivs[2] = 3.0 * s * (1.0 - s - 2.0 * r);
  derivs[3] = 4.0 * (1.0 - 2.0 * r - 4.0 * s + 6.0 * r * s + 3.0 * s * s);
  derivs[4] = 4.0 * s * (-2.0 + 6.0 * r + 3.0 * s);
  derivs[5] = 4.0 * s * (-4.0 + 6.0 * r + 3.0 * s);
  derivs[6] = 27.0 * s * (1.0 - 2.0 * r - s);

  // s-derivatives
  derivs[7] = -3.0 + 7.0 * r + 4.0 * s - 6.0 * r * s - 3.0 * r * r;
  derivs[8] = 3.0 * r * (1.0 - r - 2.0 * s);
  derivs[9] = -1.0 + 3.0 * r + 4.0 * s - 6.0 * r * s - 3.0 * r * r;
  derivs[10] = 4.0 * r * (-4.0 + 3.0 * r + 6.0 * s);
  derivs[11] = 4.0 * r * (-2.0 + 3.0 * r + 6.0 * s);
  derivs[12] = 4.0 * (1.0 - 4.0 * r - 2.0 * s + 6.0 * r * s + 3.0 * r * r);
  derivs[13] = 27.0 * r * (1.0 - r - 2.0 * s);
}

//------------------------------------------------------------------------------
static double vtkBiQTriangleCellPCoords[21] = {
  0.0, 0.0, 0.0,                //
  1.0, 0.0, 0.0,                //
  0.0, 1.0, 0.0,                //
  0.5, 0.0, 0.0,                //
  0.5, 0.5, 0.0,                //
  0.0, 0.5, 0.0,                //
  (1.0 / 3.0), (1.0 / 3.0), 0.0 //
};
double* vtkBiQuadraticTriangle::GetParametricCoords()
{
  return vtkBiQTriangleCellPCoords;
}

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

  os << indent << "Edge: " << this->Edge << endl;
  os << indent << "Face: " << this->Face << endl;
  os << indent << "Scalars: " << this->Scalars << endl;
}
VTK_ABI_NAMESPACE_END
