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

#include "vtkPiecewiseFunction.h"

#include <algorithm>

//------------------------------------------------------------------------------
// Construct a spline with the following defaults:
// ClampValueOff
VTK_ABI_NAMESPACE_BEGIN
vtkSpline::vtkSpline()
{
  this->ComputeTime = 0;
  this->ClampValue = 0;
  this->PiecewiseFunction = vtkPiecewiseFunction::New();
  this->Intervals = nullptr;
  this->Coefficients = nullptr;
  this->LeftConstraint = 1;
  this->LeftValue = 0.0;
  this->RightConstraint = 1;
  this->RightValue = 0.0;
  this->Closed = 0;

  this->ParametricRange[0] = -1;
  this->ParametricRange[1] = -1;
}

//------------------------------------------------------------------------------
vtkSpline::~vtkSpline()
{
  this->PiecewiseFunction->Delete();
  delete[] this->Coefficients;
  delete[] this->Intervals;
}

//------------------------------------------------------------------------------
void vtkSpline::SetParametricRange(double tMin, double tMax)
{
  if (tMin != this->ParametricRange[0] || tMax != this->ParametricRange[1])
  {
    if (tMin >= tMax)
    {
      tMax = tMin + 1;
    }

    this->ParametricRange[0] = tMin;
    this->ParametricRange[1] = tMax;

    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkSpline::GetParametricRange(double tRange[2]) const
{
  if (this->ParametricRange[0] != this->ParametricRange[1])
  {
    tRange[0] = this->ParametricRange[0];
    tRange[1] = this->ParametricRange[1];
  }
  else
  {
    tRange[0] = this->PiecewiseFunction->GetRange()[0];
    tRange[1] = this->PiecewiseFunction->GetRange()[1];
  }
}

//------------------------------------------------------------------------------
double vtkSpline::ComputeLeftDerivative()
{
  double* dptr = this->PiecewiseFunction->GetDataPointer();
  int size = this->PiecewiseFunction->GetSize();
  if (dptr == nullptr || size < 2)
  {
    return 0.0;
  }
  else
  {
    return (dptr[2] - dptr[0]);
  }
}

//------------------------------------------------------------------------------
double vtkSpline::ComputeRightDerivative()
{
  double* dptr = this->PiecewiseFunction->GetDataPointer();
  int size = this->PiecewiseFunction->GetSize();
  if (dptr == nullptr || size < 2)
  {
    return 0.0;
  }
  else
  {
    return (dptr[(size - 1) * 2] - dptr[(size - 2) * 2]);
  }
}

//------------------------------------------------------------------------------
int vtkSpline::GetNumberOfPoints()
{
  return this->PiecewiseFunction->GetSize();
}

//------------------------------------------------------------------------------
// Add a point to the Piecewise Functions containing the data
void vtkSpline::AddPoint(double t, double x)
{
  if (this->ParametricRange[0] != this->ParametricRange[1])
  {
    t = std::min(std::max(t, this->ParametricRange[0]), this->ParametricRange[1]);
  }
  this->PiecewiseFunction->AddPoint(t, x);
}

//------------------------------------------------------------------------------
void vtkSpline::FillFromDataPointer(int nb, double* data)
{
  this->PiecewiseFunction->FillFromDataPointer(nb, data);
}

//------------------------------------------------------------------------------
// Remove a point from the Piecewise Functions.
void vtkSpline::RemovePoint(double t)
{
  if (this->ParametricRange[0] != this->ParametricRange[1])
  {
    t = std::min(std::max(t, this->ParametricRange[0]), this->ParametricRange[1]);
  }
  this->PiecewiseFunction->RemovePoint(t);
}

//------------------------------------------------------------------------------
// Remove all points from the Piecewise Functions.
void vtkSpline::RemoveAllPoints()
{
  this->PiecewiseFunction->RemoveAllPoints();
}

//------------------------------------------------------------------------------
void vtkSpline::DeepCopy(vtkSpline* s)
{
  vtkSpline* spline = vtkSpline::SafeDownCast(s);

  if (spline != nullptr)
  {
    this->ClampValue = s->ClampValue;
    this->LeftConstraint = s->LeftConstraint;
    this->LeftValue = s->LeftValue;
    this->RightConstraint = s->RightConstraint;
    this->RightValue = s->RightValue;
    this->Closed = s->Closed;
    this->PiecewiseFunction->DeepCopy(s->PiecewiseFunction);
  }
}

//------------------------------------------------------------------------------
// Overload standard modified time function. If data is modified,
// then this object is modified as well.
vtkMTimeType vtkSpline::GetMTime()
{
  vtkMTimeType mTime = this->vtkObject::GetMTime();
  vtkMTimeType DataMTime;

  if (this->PiecewiseFunction != nullptr)
  {
    DataMTime = this->PiecewiseFunction->GetMTime();
    mTime = (DataMTime > mTime ? DataMTime : mTime);
  }

  return mTime;
}

//------------------------------------------------------------------------------
int vtkSpline::FindIndex(int size, double t)
{
  int index = 0;
  if (size > 2) // bisection method for speed
  {
    int rightIdx = size - 1;
    int centerIdx = rightIdx - size / 2;
    for (int converged = 0; !converged;)
    {
      if (this->Intervals[index] <= t && t <= this->Intervals[centerIdx])
      {
        rightIdx = centerIdx;
      }
      else // if ( this->Intervals[centerIdx] < t && t <= this->Intervals[rightIdx] )
      {
        index = centerIdx;
      }
      if ((index + 1) == rightIdx)
      {
        converged = 1;
      }
      else
      {
        centerIdx = index + (rightIdx - index) / 2;
      }
    } // while not converged
  }
  return index;
}

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

  os << indent << "Clamp Value: " << (this->ClampValue ? "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 << "Closed: " << (this->Closed ? "On\n" : "Off\n");

  os << indent << "Piecewise Function:\n";
  this->PiecewiseFunction->PrintSelf(os, indent.GetNextIndent());

  os << indent << "Closed: " << (this->Closed ? "On\n" : "Off\n");
}
VTK_ABI_NAMESPACE_END
