// curve.cpp
//
// Copyright (C) 2003, 2004 Jason Bevins
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
// License (COPYING.txt) for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// The developer's email is jlbezigvins@gmzigail.com (for great email, take
// off every 'zig'.)
//

#include "interp.h"
#include "misc.h"
#include "module/curve.h"

using namespace noise::module;

Curve::Curve ():
  Module (GetSourceModuleCount ()),
  m_pControlPoints (NULL)
{
  m_controlPointCount = 0;
}

Curve::~Curve ()
{
  delete[] m_pControlPoints;
}

void Curve::AddControlPoint (double inputValue, double outputValue)
{
  // Find the insertion point for the new control point and insert the new
  // point at that position.  The control point array will remain sorted by
  // input value.
  int insertionPos = FindInsertionPos (inputValue);
  InsertAtPos (insertionPos, inputValue, outputValue);
}

void Curve::ClearAllControlPoints ()
{
  delete[] m_pControlPoints;
  m_pControlPoints = NULL;
  m_controlPointCount = 0;
}

int Curve::FindInsertionPos (double inputValue)
{
  int insertionPos;
  for (insertionPos = 0; insertionPos < m_controlPointCount; insertionPos++) {
    if (inputValue < m_pControlPoints[insertionPos].inputValue) {
      // We found the array index in which to insert the new control point.
      // Exit now.
      break;
    } else if (inputValue == m_pControlPoints[insertionPos].inputValue) {
      // Each control point is required to contain a unique input value, so
      // throw an exception.
      throw noise::ExceptionInvalidParam ();
    }
  }
  return insertionPos;
}

double Curve::GetValue (double x, double y, double z) const
{
  assert (m_pSourceModule[0] != NULL);
  assert (m_controlPointCount >= 4);

  // Get the output value from the source module.
  double sourceModuleValue = m_pSourceModule[0]->GetValue (x, y, z);

  // Find the first element in the control point array that has an input value
  // larger than the output value from the source module.
  int indexPos;
  for (indexPos = 0; indexPos < m_controlPointCount; indexPos++) {
    if (sourceModuleValue < m_pControlPoints[indexPos].inputValue) {
      break;
    }
  }

  // Find the four nearest control points so that we can perform cubic
  // interpolation.
  int index0 = ClampValue (indexPos - 2, 0, m_controlPointCount - 1);
  int index1 = ClampValue (indexPos - 1, 0, m_controlPointCount - 1);
  int index2 = ClampValue (indexPos    , 0, m_controlPointCount - 1);
  int index3 = ClampValue (indexPos + 1, 0, m_controlPointCount - 1);

  // If some control points are missing (which occurs if the value from the
  // source module is greater than the largest input value or less than the
  // smallest input value of the control point array), get the corresponding
  // output value of the nearest control point and exit now.
  if (index1 == index2) {
    return m_pControlPoints[index1].outputValue;
  }
  
  // Compute the alpha value used for cubic interpolation.
  double input0 = m_pControlPoints[index1].inputValue;
  double input1 = m_pControlPoints[index2].inputValue;
  double alpha = (sourceModuleValue - input0) / (input1 - input0);

  // Now perform the cubic interpolation given the alpha value.
  return CubicInterp (
    m_pControlPoints[index0].outputValue,
    m_pControlPoints[index1].outputValue,
    m_pControlPoints[index2].outputValue,
    m_pControlPoints[index3].outputValue,
    alpha);
}

void Curve::InsertAtPos (int insertionPos, double inputValue,
  double outputValue)
{
  // Make room for the new control point at the specified position within the
  // control point array.  The position is determined by the input value of
  // the control point; the control points must be sorted by input value
  // within that array.
  ControlPoint* newControlPoints = new ControlPoint[m_controlPointCount + 1];
  for (int i = 0; i < m_controlPointCount; i++) {
    if (i < insertionPos) {
      newControlPoints[i] = m_pControlPoints[i];
    } else {
      newControlPoints[i + 1] = m_pControlPoints[i];
    }
  }
  delete[] m_pControlPoints;
  m_pControlPoints = newControlPoints;
  ++m_controlPointCount;

  // Now that we've made room for the new control point within the array, add
  // the new control point.
  m_pControlPoints[insertionPos].inputValue  = inputValue ;
  m_pControlPoints[insertionPos].outputValue = outputValue;
}
