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

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkParametricSpline);

//------------------------------------------------------------------------------
vtkParametricSpline::vtkParametricSpline()
{
  this->MinimumU = 0;
  this->MaximumU = 1.0;
  this->JoinU = 0;

  this->Points = nullptr;

  this->XSpline = vtkCardinalSpline::New();
  this->YSpline = vtkCardinalSpline::New();
  this->ZSpline = vtkCardinalSpline::New();

  this->Closed = 0;
  this->LeftConstraint = 1;
  this->LeftValue = 0.0;
  this->RightConstraint = 1;
  this->RightValue = 0.0;
  this->ParameterizeByLength = 1;

  this->InitializeTime = 0;
}

//------------------------------------------------------------------------------
vtkParametricSpline::~vtkParametricSpline()
{
  if (this->Points)
  {
    this->Points->Delete();
  }
  if (this->XSpline)
  {
    this->XSpline->Delete();
  }
  if (this->YSpline)
  {
    this->YSpline->Delete();
  }
  if (this->ZSpline)
  {
    this->ZSpline->Delete();
  }
}

//------------------------------------------------------------------------------
void vtkParametricSpline::SetNumberOfPoints(vtkIdType numPts)
{
  if (!this->Points)
  {
    vtkPoints* pts = vtkPoints::New(VTK_DOUBLE);
    this->SetPoints(pts);
    pts->Delete();
  }
  this->Points->SetNumberOfPoints(numPts);
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkParametricSpline::SetPoint(vtkIdType index, double x, double y, double z)
{
  if (this->Points)
  {
    this->Points->SetPoint(index, x, y, z);
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkParametricSpline::SetPoints(vtkPoints* pts)
{
  if (pts != this->Points)
  {
    if (this->Points != nullptr)
    {
      this->Points->Delete();
    }
    this->Points = pts;
    if (this->Points != nullptr)
    {
      this->Points->Register(this);
    }
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkParametricSpline::SetXSpline(vtkSpline* s)
{
  if (s != this->XSpline)
  {
    if (this->XSpline != nullptr)
    {
      this->XSpline->Delete();
    }
    this->XSpline = s;
    if (this->XSpline != nullptr)
    {
      this->XSpline->Register(this);
    }
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkParametricSpline::SetYSpline(vtkSpline* s)
{
  if (s != this->YSpline)
  {
    if (this->YSpline != nullptr)
    {
      this->YSpline->Delete();
    }
    this->YSpline = s;
    if (this->YSpline != nullptr)
    {
      this->YSpline->Register(this);
    }
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkParametricSpline::SetZSpline(vtkSpline* s)
{
  if (s != this->ZSpline)
  {
    if (this->ZSpline != nullptr)
    {
      this->ZSpline->Delete();
    }
    this->ZSpline = s;
    if (this->ZSpline != nullptr)
    {
      this->ZSpline->Register(this);
    }
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkParametricSpline::Evaluate(double U[3], double Pt[3], double*)
{
  // make sure everything has been set up
  if (this->InitializeTime < this->GetMTime())
  {
    if (!this->Initialize())
    {
      return;
    }
  }

  double t = std::min(std::max(U[0], 0.0), 1.0);
  if (this->Closed)
  {
    t *= this->ClosedLength;
  }
  else
  {
    t *= this->Length;
  }

  if (this->Length == 0 && this->Points && this->Points->GetNumberOfPoints() > 0)
  {
    this->Points->GetPoint(0, Pt);
    return;
  }

  // Evaluate the spline at the parameter t
  Pt[0] = this->XSpline->Evaluate(t);
  Pt[1] = this->YSpline->Evaluate(t);
  Pt[2] = this->ZSpline->Evaluate(t);
}

//------------------------------------------------------------------------------
double vtkParametricSpline::EvaluateScalar(double u[3], double*, double*)
{
  // make sure everything has been set up
  if (this->InitializeTime < this->GetMTime())
  {
    if (!this->Initialize())
    {
      return 0.0;
    }
  }

  return u[0]; // simply parametric value
}

//------------------------------------------------------------------------------
// Configure the splines for evaluation
int vtkParametricSpline::Initialize()
{
  // Check to make sure splines are available
  if (!this->XSpline || !this->YSpline || !this->ZSpline)
  {
    vtkErrorMacro("Please specify splines");
    return 0;
  }
  if (!this->Points)
  {
    vtkErrorMacro("Please specify points");
    return 0;
  }

  // Make sure that the splines are consistent with this instance
  this->XSpline->SetClosed(this->GetClosed());
  this->XSpline->SetLeftConstraint(this->GetLeftConstraint());
  this->XSpline->SetRightConstraint(this->GetRightConstraint());
  this->XSpline->SetLeftValue(this->GetLeftValue());
  this->XSpline->SetRightValue(this->GetRightValue());

  this->YSpline->SetClosed(this->GetClosed());
  this->YSpline->SetLeftConstraint(this->GetLeftConstraint());
  this->YSpline->SetRightConstraint(this->GetRightConstraint());
  this->YSpline->SetLeftValue(this->GetLeftValue());
  this->YSpline->SetRightValue(this->GetRightValue());

  this->ZSpline->SetClosed(this->GetClosed());
  this->ZSpline->SetLeftConstraint(this->GetLeftConstraint());
  this->ZSpline->SetRightConstraint(this->GetRightConstraint());
  this->ZSpline->SetLeftValue(this->GetLeftValue());
  this->ZSpline->SetRightValue(this->GetRightValue());

  // Construct the splines, parameterized by length
  vtkIdType i;
  double xPrev[3], x[3], len;
  vtkIdType npts = this->Points->GetNumberOfPoints();

  if (npts < 1)
  {
    vtkErrorMacro("Please specify at least one point.");
    return 0;
  }
  if (npts < 2)
  {
    this->Length = 0;
    this->ClosedLength = 0;
    // If number of points == 1, then we simply generate a single point.
    return 1;
  }

  if (this->ParameterizeByLength)
  {
    this->Points->GetPoint(0, xPrev);
    this->Length = 0.0;
    for (i = 1; i < npts; ++i)
    {
      this->Points->GetPoint(i, x);
      len = sqrt(vtkMath::Distance2BetweenPoints(x, xPrev));
      // No need to complain if there are coincident points, we indeed handle
      // them correctly.
      // if ( len <= 0.0 )
      //  {
      //  vtkErrorMacro("Spline must have non-coincident points");
      //  return 0; //failure
      //  }
      this->Length += len;
      xPrev[0] = x[0];
      xPrev[1] = x[1];
      xPrev[2] = x[2];
    }
    if (this->Length <= 0.0)
    {
      // Zero length spline is same a single point.
      this->ClosedLength = 0.0;
      return 1;
    }
    if (this->Closed)
    {
      this->Points->GetPoint(0, x);
      this->ClosedLength = this->Length + sqrt(vtkMath::Distance2BetweenPoints(x, xPrev));
    }
  }
  else
  {
    this->Length = npts - 1;
    if (this->Closed)
    {
      this->ClosedLength = npts;
    }
  }

  this->XSpline->RemoveAllPoints();
  this->YSpline->RemoveAllPoints();
  this->ZSpline->RemoveAllPoints();

  // Specify the parametric range that the spline can take
  if (!this->Closed)
  {
    this->XSpline->SetParametricRange(0.0, this->Length);
    this->YSpline->SetParametricRange(0.0, this->Length);
    this->ZSpline->SetParametricRange(0.0, this->Length);
  }
  else
  {
    this->XSpline->SetParametricRange(0.0, this->ClosedLength);
    this->YSpline->SetParametricRange(0.0, this->ClosedLength);
    this->ZSpline->SetParametricRange(0.0, this->ClosedLength);
  }

  // Now we insert points into the splines with the parametric coordinate
  // based on (polyline) length. We keep track of the parametric coordinates
  // of the points for later point interpolation.
  if (this->ParameterizeByLength)
  {
    this->Points->GetPoint(0, xPrev);
    for (len = 0.0, i = 0; i < npts; ++i)
    {
      this->Points->GetPoint(i, x);
      len += sqrt(vtkMath::Distance2BetweenPoints(x, xPrev));

      this->XSpline->AddPoint(len, x[0]);
      this->YSpline->AddPoint(len, x[1]);
      this->ZSpline->AddPoint(len, x[2]);

      xPrev[0] = x[0];
      xPrev[1] = x[1];
      xPrev[2] = x[2];
    }
  }
  else
  {
    for (i = 0; i < npts; ++i)
    {
      this->Points->GetPoint(i, x);
      this->XSpline->AddPoint(i, x[0]);
      this->YSpline->AddPoint(i, x[1]);
      this->ZSpline->AddPoint(i, x[2]);
    }
  }

  this->InitializeTime = this->GetMTime();
  return 1;
}

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

  os << indent << "Points: ";
  if (this->Points)
  {
    os << this->Points << "\n";
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "X Spline: ";
  if (this->XSpline)
  {
    os << this->XSpline << "\n";
  }
  else
  {
    os << "(none)\n";
  }
  os << indent << "Y Spline: ";
  if (this->YSpline)
  {
    os << this->YSpline << "\n";
  }
  else
  {
    os << "(none)\n";
  }
  os << indent << "Z Spline: ";
  if (this->ZSpline)
  {
    os << this->ZSpline << "\n";
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Closed: " << (this->Closed ? "On\n" : "Off\n");
  os << indent << "Left Constraint: " << this->LeftConstraint << "\n";
  os << indent << "Right Constraint: " << this->RightConstraint << "\n";
  os << indent << "Left Value: " << this->LeftValue << "\n";
  os << indent << "Right Value: " << this->RightValue << "\n";
  os << indent << "Parameterize by length: " << (this->ParameterizeByLength ? "On\n" : "Off\n");
}
VTK_ABI_NAMESPACE_END
