/*
 * Copyright (C) 2012-2016 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/
#include "gazebo/math/RotationSpline.hh"

using namespace gazebo;
using namespace math;

RotationSpline::RotationSpline()
: autoCalc(true)
{
}

RotationSpline::~RotationSpline()
{
}

void RotationSpline::AddPoint(const Quaternion &_p)
{
  this->points.push_back(_p);
  if (this->autoCalc)
    this->RecalcTangents();
}

Quaternion RotationSpline::Interpolate(double _t, bool _useShortestPath)
{
  // Work out which segment this is in
  double fSeg = _t * (this->points.size() - 1);
  unsigned int segIdx = (unsigned int)fSeg;

  // Apportion t
  _t = fSeg - segIdx;

  return this->Interpolate(segIdx, _t, _useShortestPath);
}

Quaternion RotationSpline::Interpolate(unsigned int _fromIndex, double _t,
                                       bool _useShortestPath)
{
  // Bounds check
  assert (_fromIndex < this->points.size() && "fromIndex out of bounds");

  if ((_fromIndex + 1) == this->points.size())
  {
    // Duff request, cannot blend to nothing
    // Just return source
    return this->points[_fromIndex];
  }

  // Fast special cases
  if (math::equal(_t, 0.0))
    return this->points[_fromIndex];
  else if (math::equal(_t, 1.0))
    return this->points[_fromIndex + 1];

  // double interpolation
  // Use squad using tangents we've already set up
  Quaternion &p = this->points[_fromIndex];
  Quaternion &q = this->points[_fromIndex+1];
  Quaternion &a = this->tangents[_fromIndex];
  Quaternion &b = this->tangents[_fromIndex+1];

  // NB interpolate to nearest rotation
  return Quaternion::Squad(_t, p, a, b, q, _useShortestPath);
}

void RotationSpline::RecalcTangents()
{
  // ShoeMake (1987) approach
  // Just like Catmull-Rom really, just more gnarly
  // And no, I don't understand how to derive this!
  //
  // let p = point[i], pInv = p.Inverse
  // tangent[i] = p * exp(-0.25 *
  // (log(pInv * point[i+1]) + log(pInv * point[i-1])))
  //
  // Assume endpoint tangents are parallel with line with neighbour

  unsigned int i, numPoints;
  bool isClosed;

  numPoints = this->points.size();

  if (numPoints < 2)
  {
    // Can't do anything yet
    return;
  }

  this->tangents.resize(numPoints);

  if (this->points[0] == this->points[numPoints-1])
    isClosed = true;
  else
    isClosed = false;

  Quaternion invp, part1, part2, preExp;
  for (i = 0; i < numPoints; ++i)
  {
    Quaternion &p = this->points[i];
    invp = p.GetInverse();

    if (i == 0)
    {
      // special case start
      part1 = (invp * this->points[i+1]).GetLog();
      if (isClosed)
      {
        // Use numPoints-2 since numPoints-1 == end == start == this one
        part2 = (invp * this->points[numPoints-2]).GetLog();
      }
      else
      {
        part2 = (invp * p).GetLog();
      }
    }
    else if (i == numPoints-1)
    {
      // special case end
      if (isClosed)
      {
        // Wrap to [1] (not [0], this is the same as end == this one)
        part1 = (invp * this->points[1]).GetLog();
      }
      else
      {
        part1 = (invp * p).GetLog();
      }
      part2 = (invp * this->points[i-1]).GetLog();
    }
    else
    {
      part1 = (invp * this->points[i+1]).GetLog();
      part2 = (invp * this->points[i-1]).GetLog();
    }

    preExp = (part1 + part2) * -0.25;
    this->tangents[i] = p * preExp.GetExp();
  }
}

const Quaternion& RotationSpline::GetPoint(unsigned int _index) const
{
  assert (_index < this->points.size() && "Point index is out of bounds!!");

  return this->points[_index];
}

unsigned int RotationSpline::GetNumPoints() const
{
  return this->points.size();
}

void RotationSpline::Clear()
{
  this->points.clear();
  this->tangents.clear();
}

void RotationSpline::UpdatePoint(unsigned int _index,
                                 const Quaternion &_value)
{
  assert (_index < this->points.size() && "Point index is out of bounds!!");

  this->points[_index] = _value;
  if (this->autoCalc)
    this->RecalcTangents();
}

void RotationSpline::SetAutoCalculate(bool _autoCalc)
{
  this->autoCalc = _autoCalc;
}


