/*=========================================================================

Program:   Visualization Toolkit
Module:    vtkSCurveSpline.cxx

Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/*-------------------------------------------------------------------------
  Copyright 2009 Sandia Corporation.
  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  the U.S. Government retains certain rights in this software.
  -------------------------------------------------------------------------*/
#include "vtkSCurveSpline.h"

#include "vtkObjectFactory.h"
#include "vtkPiecewiseFunction.h"
#include <algorithm> // for std::min()/std::max()
#include <cassert>

vtkStandardNewMacro(vtkSCurveSpline);

//----------------------------------------------------------------------------
// Construct a SCurve Spline.
vtkSCurveSpline::vtkSCurveSpline()
{
  this->NodeWeight = 0.0;
}

//----------------------------------------------------------------------------
// Evaluate a 1D Spline
double vtkSCurveSpline::Evaluate (double t)
{
  int index;
  double *intervals;
  double *coefficients;

  // check to see if we need to recompute the spline
  if (this->ComputeTime < this->GetMTime())
  {
    this->Compute ();
  }

  // make sure we have at least 2 points
  int size = this->PiecewiseFunction->GetSize();

  if (size < 2)
  {
    return 0.0;
  }

  intervals = this->Intervals;
  coefficients = this->Coefficients;

  if ( this->Closed )
  {
    size = size + 1;
  }

  // clamp the function at both ends
  if (t < intervals[0])
  {
    t = intervals[0];
  }
  if (t > intervals[size - 1])
  {
    t = intervals[size - 1];
  }

  // find pointer to cubic spline coefficient using bisection method
  index = this->FindIndex(size,t);

  // calculate offset within interval
  t = (t - intervals[index]);

  // normalize to unit width
  t /= intervals[index+1] - intervals[index];

  // apply weighting function
  if (this->NodeWeight > 0.0)
  {
    double shift = t * (t * (t * (-4*this->NodeWeight)
                             + (6*this->NodeWeight)))
      - this->NodeWeight;
    // clamp t
    t = std::max(std::min(t+shift,1.0),0.0);
  }

  // evaluate intervals value y
  return (t * (t * (t * *(coefficients + index * 3 + 2) // a
                    + *(coefficients + index * 3 + 1))) // b
          + *(coefficients + index * 3)); // d
}

//----------------------------------------------------------------------------
// Compute SCurve Splines for each dependent variable
void vtkSCurveSpline::Compute ()
{
  double *ts, *xs;
  //  double *work;
  double *coefficients;
  double *dependent;
  int size;
  int i;

  // Make sure the function is up to date.
  //this->PiecewiseFunction->Update();

  // get the size of the independent variables
  size = this->PiecewiseFunction->GetSize ();

  if(size < 2)
  {
    vtkErrorMacro("Cannot compute a spline with less than 2 points. # of points is: " << size);
    return;
  }

  // copy the independent variables. Note that if the spline
  // is closed the first and last point are assumed repeated -
  // so we add and extra point
  delete [] this->Intervals;

  if ( !this->Closed )
  {
    this->Intervals = new double[size];
    ts = this->PiecewiseFunction->GetDataPointer ();
    for (i = 0; i < size; i++)
    {
      this->Intervals[i] = *(ts + 2*i);
    }

    // allocate memory for work arrays
    //    work = new double[size];

    // allocate memory for coefficients
    delete [] this->Coefficients;
    this->Coefficients = new double [3*size];

    // allocate memory for dependent variables
    dependent = new double [size];

    // get start of coefficients for this dependent variable
    coefficients = this->Coefficients;

    // get the dependent variable values
    xs = this->PiecewiseFunction->GetDataPointer () + 1;
    for (int j = 0; j < size; j++)
    {
      *(dependent + j) = *(xs + 2*j);
    }

    for (int k = 0; k < size-1; k++)
    {
      *(coefficients + 3*k) = dependent[k]; // d
      *(coefficients + 3*k+1) = 3*(dependent[k+1]-dependent[k]); // b
      *(coefficients + 3*k+2) = -2*(dependent[k+1]-dependent[k]); // a
    }
    *(coefficients + 3*(size-1)) = dependent[size-1];
    *(coefficients + 3*(size-1)+1) = dependent[size-1];
    *(coefficients + 3*(size-1)+2) = dependent[size-1];
  }

  else //add extra "fictitious" point to close loop
  {
    size = size + 1;
    this->Intervals = new double[size];
    ts = this->PiecewiseFunction->GetDataPointer ();
    for (i = 0; i < size-1; i++)
    {
      this->Intervals[i] = *(ts + 2*i);
    }
    if ( this->ParametricRange[0] != this->ParametricRange[1] )
    {
      this->Intervals[size-1] = this->ParametricRange[1];
    }
    else
    {
      this->Intervals[size-1] = this->Intervals[size-2] + 1.0;
    }

    // allocate memory for work arrays
    //    work = new double[size];

    // allocate memory for coefficients
    delete [] this->Coefficients;
    //this->Coefficients = new double [4*size];
    this->Coefficients = new double [3*size];

    // allocate memory for dependent variables
    dependent = new double [size];

    // get start of coefficients for this dependent variable
    coefficients = this->Coefficients;

    // get the dependent variable values
    xs = this->PiecewiseFunction->GetDataPointer () + 1;
    for (int j = 0; j < size-1; j++)
    {
      *(dependent + j) = *(xs + 2*j);
    }
    dependent[size-1] = *xs;

    for (int k = 0; k < size-1; k++)
    {
      *(coefficients + 3*k) = dependent[k]; // d
      *(coefficients + 3*k+1) = 3*(dependent[k+1]-dependent[k]); // b
      *(coefficients + 3*k+2) = -2*(dependent[k+1]-dependent[k]); // a
    }
    *(coefficients + 3*(size-1)) = dependent[size-1];
    *(coefficients + 3*(size-1)+1) = dependent[size-1];
    *(coefficients + 3*(size-1)+2) = dependent[size-1];
  }

  // free the work array and dependent variable storage
  //delete [] work;
  delete [] dependent;

  // update compute time
  this->ComputeTime = this->GetMTime();
}

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

  if ( spline != nullptr )
  {
    //nothing to do
  }

  // Now do superclass
  this->vtkSpline::DeepCopy(s);
}

//----------------------------------------------------------------------------
void vtkSCurveSpline::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << "NodeWeight: " << this->NodeWeight << endl;
}
