/*
 * Copyright 2011, Nicolas Mansard, LAAS-CNRS
 *
 * This file is part of sot-dyninv.
 * sot-dyninv 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 3 of
 * the License, or (at your option) any later version.
 * sot-dyninv 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 for more details.  You should
 * have received a copy of the GNU Lesser General Public License along
 * with sot-dyninv.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <sot-dyninv/dynamic-integrator.h>
#include <sot/core/debug.hh>
#include <dynamic-graph/factory.h>

#include <sot-dyninv/commands-helper.h>

#include <soth/Algebra.hpp>

/** This class proposes to integrate the acceleration given in input
 * to produce both velocity and acceleration. Initial conditions have to
 * be provided using the setters of position and velocity. The integration
 * has to be explicitely triggered by calling the command 'inc' (increments).
 */

namespace dynamicgraph
{
  namespace sot
  {
    namespace dyninv
    {

      namespace dg = ::dynamicgraph;
      using namespace dg;

      /* --- DG FACTORY ------------------------------------------------------- */
      DYNAMICGRAPH_FACTORY_ENTITY_PLUGIN(DynamicIntegrator,"DynamicIntegrator");

      /* --- CONSTRUCTION ----------------------------------------------------- */
      /* --- CONSTRUCTION ----------------------------------------------------- */
      /* --- CONSTRUCTION ----------------------------------------------------- */
      DynamicIntegrator::
      DynamicIntegrator( const std::string & name )
	: Entity(name)

	,CONSTRUCT_SIGNAL_IN(acceleration,dg::Vector)
	,CONSTRUCT_SIGNAL_IN(dt,double)

	,CONSTRUCT_SIGNAL_OUT(velocity,dg::Vector,sotNOSIGNAL)
	,CONSTRUCT_SIGNAL_OUT(position,dg::Vector,sotNOSIGNAL)
      {
	Entity::signalRegistration( accelerationSIN << dtSIN
				    << velocitySOUT << positionSOUT );

	addCommand("inc",
		   makeCommandVoid0(*this,&DynamicIntegrator::integrateFromSignals,
				    docCommandVoid0("Integrate acc, update v and p.")));

	addCommand("setPosition",
		   makeDirectSetter(*this,&position,
				    docDirectSetter("position","vector")));
	addCommand("setVelocity",
		   makeDirectSetter(*this,&velocity,
				    docDirectSetter("velocity","vector")));

      }

      /* --- SIGNALS ---------------------------------------------------------- */
      /* --- SIGNALS ---------------------------------------------------------- */
      /* --- SIGNALS ---------------------------------------------------------- */

      dg::Vector& DynamicIntegrator::
      velocitySOUT_function( dg::Vector& mlv,int )
      {
	mlv = velocity;
	return mlv;
      }

      dg::Vector& DynamicIntegrator::
      positionSOUT_function( dg::Vector& mlp,int )
      {
	mlp = position;
	return mlp;
      }

      /* --- PROXYS ----------------------------------------------------------- */
      void DynamicIntegrator::
      integrateFromSignals( const int & time )
      {
	const dg::Vector & acc = accelerationSIN(time);
	const double & dt = dtSIN(time);

	integrate( acc,dt, velocity,position );
	velocitySOUT.setReady();
	positionSOUT.setReady();
      }

      void DynamicIntegrator::
      integrateFromSignals( void )
      {
	integrateFromSignals( accelerationSIN.getTime() + 1 );
      }

      void DynamicIntegrator::
      setPosition( const dg::Vector& p )
      {
	position = p;
	positionSOUT.setReady();
      }

      void DynamicIntegrator::
      setVelocity( const dg::Vector& v )
      {
	velocity = v;
	velocitySOUT.setReady();
      }

      void DynamicIntegrator::
      setState( const dg::Vector& p,const dg::Vector& v )
      {
	sotDEBUG(5) << "State: " << p << v << std::endl;
	position = p;
	velocity = v;
	velocitySOUT.setReady();
	positionSOUT.setReady();
      }

      /* --- COMMANDS ---------------------------------------------------------- */
      /* --- COMMANDS ---------------------------------------------------------- */
      /* --- COMMANDS ---------------------------------------------------------- */

      using namespace Eigen;

      namespace DynamicIntegratorStatic
      {
	void
	computeRotationMatrixFromEuler(const Eigen::Vector3d& euler, Eigen::Matrix3d& res) {
	  res = (Eigen::AngleAxisd(euler(2),Eigen::Vector3d::UnitZ())*
		 Eigen::AngleAxisd(euler(1),Eigen::Vector3d::UnitY())*
		 Eigen::AngleAxisd(euler(0),Eigen::Vector3d::UnitX())).toRotationMatrix();
	}


	template< typename D1 >
	Matrix3d skew( const MatrixBase<D1> & v )
	{
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY( MatrixBase<D1> );
	  Matrix3d mat;
	  mat(0,0) =  0   ;	mat(0,1)= -v[2];	mat(0,2)=  v[1];
	  mat(1,0) =  v[2];	mat(1,1)=  0   ;	mat(1,2)= -v[0];
	  mat(2,0) = -v[1];	mat(2,1)=  v[0];	mat(2,2)=  0   ;
	  return mat;
	}

	/*  Convert data expressed at the origin of the joint - typicaly acc and vel
	 * in Djj - to data expressed at the center of the world - typicaly acc
	 * and vel in Amelif - For the waist dq/ddq(1:6) is equivalent to [ v/a
	 * angular | v/a linear ] expressed at the origin of the joint.
	 */
	template< typename D1,typename D2, typename D3 >
	void
	djj2amelif ( Vector3d & angAmelif, Vector3d & linAmelif,
		     const MatrixBase<D1> & angDjj, const MatrixBase<D2> & linDjj,
		     const MatrixBase<D3> & pos, const Matrix3d & /*orient*/ )
	{
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY(Eigen::MatrixBase<D1>);
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY(Eigen::MatrixBase<D2>);
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY(Eigen::MatrixBase<D3>);
	  assert( angDjj.size() == 3 && linDjj.size() == 3 && pos.size() == 3 );

	  angAmelif = angDjj;
	  linAmelif = linDjj + skew(pos)* angDjj;
	  sotDEBUG(20) << "cross = " << skew(pos)* angDjj << std::endl;
	  sotDEBUG(20) << "cross = " << skew(pos) << std::endl;
	  sotDEBUG(20) << "cross = " <<  angDjj << std::endl;
	}

	/*  Convert data expressed at the center of the world - typicaly acc and vel
	 * in Amelif - to data expressed at the origin of the joint - typicaly acc
	 * and vel in Djj - For the waist dq/ddq(1:6) is equivalent to [ v/a
	 * angular | v/a linear ] expressed at the origin of the joint
	 */
	template< typename D1,typename D2, typename D3 >
	void
	amelif2djj ( MatrixBase<D1> & angDjj, MatrixBase<D2> & linDjj,
		     const Vector3d & angAmelif, const Vector3d & linAmelif,
		     const MatrixBase<D3> & pos, const Matrix3d & /*orient*/ )
	{
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY(Eigen::MatrixBase<D1>);
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY(Eigen::MatrixBase<D2>);
	  EIGEN_STATIC_ASSERT_VECTOR_ONLY(Eigen::MatrixBase<D3>);
	  assert( angDjj.size() == 3 && linDjj.size() == 3 && pos.size() == 3 );

	  angDjj = angAmelif;
	  linDjj = linAmelif - skew(pos)* angAmelif;
	}

      }

      /* -------------------------------------------------------------------------- */
      void DynamicIntegrator::
      integrate( const dg::Vector& mlacceleration,
		 const double & dt,
		 dg::Vector & mlvelocity,
		 dg::Vector & mlposition )
      {
	using namespace DynamicIntegratorStatic;
	using soth::MATLAB;
	sotDEBUGIN(15);

	/* --- Convert acceleration, velocity and position to amelif style  ------- */
	const Eigen::VectorXd acceleration(mlacceleration);
	Eigen::VectorXd velocity(mlvelocity);
	Eigen::VectorXd position(mlposition);

	sotDEBUG(1) << "acceleration = " << (MATLAB)acceleration << std::endl;
	sotDEBUG(1) << "velocity = " << (MATLAB)velocity << std::endl;
	sotDEBUG(1) << "position = " << (MATLAB)position << std::endl;

	const Eigen::Vector3d fftrans = position.head<3>();
	const Eigen::Vector3d ffeuler = position.segment<3>(3);
	Eigen::Matrix3d ffrot; computeRotationMatrixFromEuler(ffeuler, ffrot);
	sotDEBUG(15) << "Rff_start = " << (MATLAB)ffrot << std::endl;
	sotDEBUG(15) << "tff_start = " << (MATLAB)fftrans << std::endl;

	Eigen::Vector3d ffvtrans = velocity.head<3>();
	Eigen::Vector3d ffvrot = velocity.segment<3>(3);
	Eigen::Vector3d v_lin,v_ang;
	djj2amelif( v_ang,v_lin,ffvrot,ffvtrans,fftrans,ffrot );
	sotDEBUG(15) << "vff_start = " << (MATLAB)v_lin << std::endl;
	sotDEBUG(15) << "wff_start = " << (MATLAB)v_ang << std::endl;

	const Eigen::Vector3d ffatrans = acceleration.head<3>();
	const Eigen::Vector3d ffarot = acceleration.segment<3>(3);
	Eigen::Vector3d a_lin,a_ang;
	djj2amelif( a_ang,a_lin,ffarot,ffatrans,fftrans,ffrot );
	sotDEBUG(15) << "alff_start = " << (MATLAB)a_lin << std::endl;
	sotDEBUG(15) << "aaff_start = " << (MATLAB)a_ang << std::endl;

	/* --- Integrate velocity ------------------------------------------------- */
	{
	  /* Acceleration, velocity and position of the FF. */
	  Matrix3d finalBodyOrientation;
	  Vector3d finalBodyPosition;

	  double norm_v_ang = v_ang.norm();

	  /* If there's no angular velocity : no rotation. */
	  if (norm_v_ang < 1e-8 )
	    {
	      finalBodyPosition = v_lin*dt + fftrans;
	      finalBodyOrientation = ffrot;
	    }
	  else
	    {
	      const double th  = norm_v_ang * dt;
	      double sth  =  sin(th), cth  =  1.0 - cos(th);
	      Eigen::Vector3d wn  = v_ang / norm_v_ang;
	      Eigen::Vector3d vol = v_lin / norm_v_ang;

	      /* drot = wnX * sin(th) + wnX * wnX * (1 - cos (th)). */
	      const Eigen::Matrix3d w_wedge = skew(wn);

	      Eigen::Matrix3d drot = w_wedge * cth;
	      drot += Eigen::Matrix3d::Identity()*sth;
	      drot = w_wedge * drot;

	      //rot = drot + id
	      Eigen::Matrix3d rot(drot);
	      rot += Eigen::Matrix3d::Identity();
	      sotDEBUG(1) << "Rv = " << (MATLAB)rot << std::endl;

	      /* Update the body rotation for the body. */
	      finalBodyOrientation = rot * ffrot;

	      /* Update the body position for the body
	       * pos = rot * pos - drot * (wn ^ vol) + th* wn *T(wn) * vol */
	      finalBodyPosition = rot * fftrans;

	      // Calculation of "- drot * crossProduct(wn, vol)"
	      VectorXd tmp1 = (w_wedge*vol);
	      VectorXd tmp2 = w_wedge*tmp1;
	      VectorXd tmp3 = w_wedge*tmp2;
	      tmp2 *= sth;
	      tmp3 *= cth;
	      finalBodyPosition -= tmp2;
	      finalBodyPosition -= tmp3;

	      // Calculation of "th * wn * T(wn) * vol"
	      double w0v0 = wn[0u] * vol[0u];
	      double w1v1 = wn[1u] * vol[1u];
	      double w2v2 = wn[2u] * vol[2u];
	      w0v0 += w1v1;
	      w0v0 += w2v2;
	      w0v0 *= th;

	      // pos += wn * wovo
	      finalBodyPosition[0u] += wn[0u] * w0v0;
	      finalBodyPosition[1u] += wn[1u] * w0v0;
	      finalBodyPosition[2u] += wn[2u] * w0v0;
	    }

	  sotDEBUG(1) << "Rff_end = " << (MATLAB)finalBodyOrientation << std::endl;

	  /* --- Convert position --------------------------------------------------- */
	  position.head<3>() = finalBodyPosition;
	  position.segment<3>(3) = (finalBodyOrientation.eulerAngles(2,1,0)).reverse();
	  position.tail( position.size()-6 ) += velocity.tail( position.size()-6 ) * dt;
	}

	/* --- Integrate acceleration --------------------------------------------- */

	v_lin += a_lin*dt;
	v_ang += a_ang*dt;

	Vector3d vdjj_ang,vdjj_lin;
	amelif2djj( vdjj_ang,vdjj_lin,v_ang,v_lin,fftrans,ffrot);
	velocity.head(3) = vdjj_lin; velocity.segment(3,3) = vdjj_ang;
	//amelif2djj( ffvrot,ffvtrans,v_ang,v_lin,fftrans,ffrot);
	velocity.tail( velocity.size()-6 ) += acceleration.tail( acceleration.size()-6 )*dt;

	sotDEBUG(15) << "vff_end = " << (MATLAB)v_lin << std::endl;
	sotDEBUG(15) << "wff_end = " << (MATLAB)v_ang << std::endl;


	sotDEBUG(1) << "velocity = " << (MATLAB)velocity << std::endl;
	sotDEBUG(1) << "position = " << (MATLAB)position << std::endl;
	sotDEBUGOUT(15);
      }

      /* --- ENTITY ----------------------------------------------------------- */
      /* --- ENTITY ----------------------------------------------------------- */
      /* --- ENTITY ----------------------------------------------------------- */

      void DynamicIntegrator::
      display( std::ostream& os ) const
      {
	os << "DynamicIntegrator "<<getName() << ". " << std::endl;
      }
    } // namespace dyninv
  } // namespace sot
} // namespace dynamicgraph