/////////////////////////////////////////////////////////////////////
// Created by Dan Andersson & Robin Engman 2013
/////////////////////////////////////////////////////////////////////

#include "RigidBody.h"
#include "Utilities.h"

using namespace ::Oyster::Collision3D;
using namespace ::Oyster::Physics3D;
using namespace ::Oyster::Math3D;

RigidBody::RigidBody( const Box &b, Float m )
	: box(b), angularMomentum(0.0f), linearMomentum(0.0f), impulseTorqueSum(0.0f), impulseForceSum(0.0f)
{ // by Dan Andersson
	if( m != 0.0f )
	{
		this->mass = m;
	}
	else
	{
		this->mass = ::Utility::Value::numeric_limits<Float>::epsilon();
	}

	this->momentOfInertiaTensor = Float4x4::identity;
}

RigidBody & RigidBody::operator = ( const RigidBody &body )
{ // by Dan Andersson
	this->box = body.box;
	this->angularMomentum = body.angularMomentum;
	this->linearMomentum = body.linearMomentum;
	this->impulseTorqueSum = body.impulseTorqueSum;
	this->impulseForceSum = body.impulseForceSum;
	this->mass = body.mass;
	this->momentOfInertiaTensor = body.momentOfInertiaTensor;
	return *this;
}

void RigidBody::Update_LeapFrog( Float deltaTime )
{ // by Dan Andersson: Euler leap frog update when Runga Kutta is not needed
	
	// updating the linear
	// dv = dt * a = dt * F / m
	// ds = dt * avg_v
	Float3 deltaLinearVelocity = this->impulseForceSum;
	deltaLinearVelocity *= (deltaTime / this->mass);
	Float3 deltaPos = deltaTime * ::Utility::Value::AverageWithDelta( Formula::LinearVelocity(this->mass, this->linearMomentum), deltaLinearVelocity );

	// updating the angular
	// dw = dt * a = dt * ( I^-1 * T )
	// rotation = dt * avg_w
	Float4x4 inversedMomentOfInertiaTensor = this->momentOfInertiaTensor.GetInverse();
	Float3 deltaAngularVelocity = Formula::AngularImpulseAcceleration( inversedMomentOfInertiaTensor, this->impulseTorqueSum ); // I^-1 * T
	deltaAngularVelocity *= deltaTime; 
	Float3 rotationAxis = ::Utility::Value::AverageWithDelta( Formula::AngularVelocity(inversedMomentOfInertiaTensor,this->angularMomentum), deltaAngularVelocity );
	
	Float deltaRadian = rotationAxis.Dot( rotationAxis );
	if( deltaRadian != 0.0f )
	{ // branch depending if there is rotation
		deltaRadian = ::std::sqrt( deltaRadian );
		rotationAxis /= deltaRadian;

		// using rotationAxis, deltaRadian and deltaPos to create a matrix to update the orientation matrix
		UpdateOrientationMatrix( deltaPos, RotationMatrix(deltaRadian, rotationAxis), this->box.orientation );

		/** @todo TODO: ISSUE! how is momentOfInertiaTensor related to the orientation of the RigidBody? */
	}
	else
	{ // no rotation, only use deltaPos to translate the RigidBody
		this->box.center += deltaPos;
	}

	// update movements and clear impulses
	this->linearMomentum += Formula::LinearMomentum( this->mass, deltaLinearVelocity );
	this->impulseForceSum = Float3::null;
	this->angularMomentum += Formula::AngularMomentum( this->momentOfInertiaTensor, deltaAngularVelocity );
	this->impulseTorqueSum = Float3::null;
}

void RigidBody::ApplyImpulseForce( const Float3 &f )
{ // by Dan Andersson
	this->impulseForceSum += f;
}

void RigidBody::ApplyImpulseForceAt_Local( const Float3 &localForce, const Float3 &localOffset )
{ // by Dan Andersson
	if( localOffset != Float3::null )
	{
		this->impulseForceSum += VectorProjection( localForce, localOffset );
		this->impulseTorqueSum += Formula::ImpulseTorque( localForce, localOffset );
	}
	else
	{
		this->impulseForceSum += localForce;
	}
}

void RigidBody::ApplyImpulseForceAt_World( const Float3 &worldForce, const Float3 &worldPos )
{ // by Dan Andersson
	Float4x4 view = this->GetView();
	this->ApplyImpulseForceAt_Local( (view * Float4(worldForce, 0.0f)).xyz,
									 (view * Float4(worldPos, 1.0f)).xyz ); // should not be any disform thus result.w = 1.0f
}

void RigidBody::ApplyLinearImpulseAcceleration( const Float3 &a )
{ // by Dan Andersson
	this->impulseForceSum += Formula::ImpulseForce( this->mass, a );
}

void RigidBody::ApplyLinearImpulseAccelerationAt_Local( const Float3 &localImpulseLinearAcc, const Float3 &localOffset )
{ // by Dan Andersson
	if( localOffset != Float3::null )
	{
		this->impulseForceSum += Formula::ImpulseForce( this->mass, VectorProjection(localImpulseLinearAcc, localOffset) );

		// tanAcc = angularAcc x localPosition
		// angularAcc = localPosition x tanAcc = localPosition x linearAcc
		// T = I * angularAcc
		this->impulseTorqueSum += Formula::ImpulseTorque( this->momentOfInertiaTensor, Formula::AngularImpulseAcceleration(localImpulseLinearAcc, localOffset) );
	}
	else
	{
		this->impulseForceSum += Formula::ImpulseForce( this->mass, localImpulseLinearAcc );
	}
}

void RigidBody::ApplyLinearImpulseAccelerationAt_World( const Float3 &worldImpulseLinearAcc, const Float3 &worldPos )
{ // by Dan Andersson
	Float4x4 view = this->GetView();
	this->ApplyLinearImpulseAccelerationAt_Local( (view * Float4(worldImpulseLinearAcc, 0.0f)).xyz,
												  (view * Float4(worldPos, 1.0f)).xyz ); // should not be any disform thus result.w = 1.0f
}

void RigidBody::ApplyImpulseTorque( const Float3 &t )
{ // by Dan Andersson
	this->impulseTorqueSum += t;
}

void RigidBody::ApplyAngularImpulseAcceleration( const Float3 &a )
{ // by Dan Andersson
	this->impulseTorqueSum += Formula::ImpulseTorque( this->momentOfInertiaTensor, a );
}

Float4x4 & RigidBody::AccessOrientation()
{ // by Dan Andersson
	return this->box.orientation;
}

const Float4x4 & RigidBody::AccessOrientation() const
{ // by Dan Andersson
	return this->box.orientation;
}

Float3 & RigidBody::AccessBoundingReach()
{ // by Dan Andersson
	return this->box.boundingOffset;
}

const Float3 & RigidBody::AccessBoundingReach() const
{ // by Dan Andersson
	return this->box.boundingOffset;
}

Float3 & RigidBody::AccessCenter()
{ // by Dan Andersson
	return this->box.center;
}

const Float3 & RigidBody::AccessCenter() const
{ // by Dan Andersson
	return this->box.center;
}

const Float4x4 & RigidBody::GetMomentOfInertia() const
{ // by Dan Andersson
	return this->momentOfInertiaTensor;
}

const Float & RigidBody::GetMass() const
{ // by Dan Andersson
	return this->mass;
}

const Float4x4 & RigidBody::GetOrientation() const
{ // by Dan Andersson
	return this->box.orientation;
}

Float4x4 RigidBody::GetView() const
{ // by Dan Andersson
	return InverseOrientationMatrix( this->box.orientation );
}

const Float3 & RigidBody::GetBoundingReach() const
{ // by Dan Andersson
	return this->box.boundingOffset;
}

Float3 RigidBody::GetSize() const
{ // by Dan Andersson
	return 2.0f * this->box.boundingOffset;
}

const Float3 & RigidBody::GetCenter() const
{ // by Dan Andersson
	return this->box.center;
}

const Float3 & RigidBody::GetImpulsTorque() const
{ // by Dan Andersson
	return this->impulseTorqueSum;
}

const Float3 & RigidBody::GetAngularMomentum() const
{ // by Dan Andersson
	return this->angularMomentum;
}

Float3 RigidBody::GetAngularImpulseAcceleration() const
{ // by Dan Andersson
	return Formula::AngularImpulseAcceleration( this->momentOfInertiaTensor.GetInverse(), this->impulseTorqueSum );
}

Float3 RigidBody::GetAngularVelocity() const
{ // by Dan Andersson
	return Formula::AngularVelocity( this->momentOfInertiaTensor.GetInverse(), this->angularMomentum );
}

const Float3 & RigidBody::GetImpulseForce() const
{ // by Dan Andersson
	return this->impulseForceSum;
}

const Float3 & RigidBody::GetLinearMomentum() const
{ // by Dan Andersson
	return this->linearMomentum;
}

Float3 RigidBody::GetLinearImpulseAcceleration() const
{ // by Dan Andersson
	return Formula::LinearImpulseAcceleration( this->mass, this->impulseForceSum );
}

Float3 RigidBody::GetLinearVelocity() const
{ // by Dan Andersson
	return Formula::LinearVelocity( this->mass, this->linearMomentum );
}

Float3 RigidBody::GetTangentialImpulseForceAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	return Formula::TangentialImpulseForce( this->impulseTorqueSum, localPos );
}

Float3 RigidBody::GetTangentialImpulseForceAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	return this->GetTangentialImpulseForceAt_Local( (this->GetView() * Float4(worldPos, 1.0f)).xyz ); // should not be any disform thus result.w = 1.0f
}

Float3 RigidBody::GetTangentialLinearMomentumAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	return Formula::TangentialLinearMomentum( this->angularMomentum, localPos );
}

Float3 RigidBody::GetTangentialLinearMomentumAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	return this->GetTangentialLinearMomentumAt_Local( (this->GetView() * Float4(worldPos, 1.0f)).xyz ); // should not be any disform thus result.w = 1.0f
}

Float3 RigidBody::GetTangentialImpulseAccelerationAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	return Formula::TangentialImpulseAcceleration( this->momentOfInertiaTensor.GetInverse(), this->impulseTorqueSum, localPos );
}

Float3 RigidBody::GetTangentialImpulseAccelerationAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	return this->GetTangentialImpulseAccelerationAt_Local( (this->GetView() * Float4(worldPos, 1.0f)).xyz ); // should not be any disform thus result.w = 1.0f
}

Float3 RigidBody::GetTangentialLinearVelocityAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	return Formula::TangentialLinearVelocity( this->momentOfInertiaTensor.GetInverse(), this->angularMomentum, localPos );
}

Float3 RigidBody::GetTangentialLinearVelocityAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	return this->GetTangentialLinearVelocityAt_Local( (this->GetView() * Float4(worldPos, 1.0f)).xyz ); // should not be any disform thus result.w = 1.0f
}

Float3 RigidBody::GetImpulseForceAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	return this->impulseForceSum + Formula::TangentialImpulseForce( this->impulseForceSum, localPos );
}

Float3 RigidBody::GetImpulseForceAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	Float4 localForce = Float4( this->GetImpulseForceAt_Local((this->GetView() * Float4(worldPos, 1.0f)).xyz), 0.0f ); // should not be any disform thus result.w = 1.0f
	return (this->box.orientation * localForce).xyz; // should not be any disform thus result.w = 0.0f
}

Float3 RigidBody::GetLinearMomentumAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	// Reminder! Momentum is a world value.
	return Float3::null; // TODO: 
}

Float3 RigidBody::GetLinearMomentumAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	// Reminder! Momentum is a world value.
	Float4 localMomentum = Float4( this->GetLinearMomentumAt_Local((this->GetView() * Float4(worldPos, 1.0f)).xyz), 0.0f ); // should not be any disform thus result.w = 1.0f
	return (this->box.orientation * localMomentum).xyz; // should not be any disform thus result.w = 0.0f

	// TODO: angularMomentum is a local value!!
	return this->linearMomentum + Formula::TangentialLinearMomentum( this->angularMomentum, worldPos );
}

Float3 RigidBody::GetImpulseAccelerationAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	// Reminder! Acceleration is a world value.
	Float4 worldAccel = Float4( this->GetImpulseAccelerationAt_Local((this->box.orientation * Float4(localPos, 1.0f)).xyz), 0.0f ); // should not be any disform thus result.w = 1.0f
	return (this->GetView() * worldAccel).xyz; // should not be any disform thus result.w = 0.0f
}

Float3 RigidBody::GetImpulseAccelerationAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	// Reminder! Acceleration is a world value.
	return Formula::LinearImpulseAcceleration( this->mass, this->impulseForceSum )
		 + Formula::TangentialImpulseAcceleration( this->momentOfInertiaTensor.GetInverse(), this->impulseTorqueSum, worldPos );
}

Float3 RigidBody::GetLinearVelocityAt_Local( const Float3 &localPos ) const
{ // by Dan Andersson
	// Reminder! Velocity is a world value.
	Float4 worldV = Float4( this->GetLinearVelocityAt_Local((this->box.orientation * Float4(localPos, 1.0f)).xyz), 0.0f ); // should not be any disform thus result.w = 1.0f
	return (this->GetView() * worldV).xyz; // should not be any disform thus result.w = 0.0f
}

Float3 RigidBody::GetLinearVelocityAt_World( const Float3 &worldPos ) const
{ // by Dan Andersson
	// Reminder! Velocity is a world value.
	return Formula::LinearVelocity( this->mass, this->linearMomentum )
		 + Formula::TangentialLinearVelocity( this->momentOfInertiaTensor.GetInverse(), this->angularMomentum, worldPos );
}

void RigidBody::SetMomentOfInertia( const Float4x4 &i )
{ // by Dan Andersson
	if( i.GetDeterminant() != 0.0f ) // insanitycheck! momentOfInertiaTensor must be invertable
	{
		this->momentOfInertiaTensor = i;
	}
}

void RigidBody::SetMass_KeepVelocity( const Float &m )
{ // by Dan Andersson
	if( m != 0.0f ) // insanitycheck! mass must be invertable
	{
		Float3 velocity = Formula::LinearVelocity( this->mass, this->linearMomentum );
		this->mass = m;
		this->linearMomentum = Formula::LinearMomentum( this->mass, velocity );
	}
}

void RigidBody::SetMass_KeepMomentum( const Float &m )
{ // by Dan Anderson
	if( m != 0.0f ) // insanitycheck! mass must be invertable
	{
		this->mass = m;
	}
}

void RigidBody::SetOrientation( const Float4x4 &o )
{ // by Dan Andersson
	this->box.orientation = o;
}

void RigidBody::SetSize( const Float3 &widthHeight )
{ // by Dan Andersson
	this->box.boundingOffset = 0.5f * widthHeight;
}

void RigidBody::SetCenter( const Float3 &p )
{ // by Dan Andersson
	this->box.center = p;
}

void RigidBody::SetImpulseTorque( const Float3 &t )
{ // by Dan Andersson
	this->impulseTorqueSum = t;
}

void RigidBody::SetAngularMomentum( const Float3 &h )
{ // by Dan Andersson
	this->angularMomentum = h;
}

void RigidBody::SetAngularImpulseAcceleration( const Float3 &a )
{ // by Dan Andersson
	this->impulseTorqueSum = Formula::ImpulseTorque( this->momentOfInertiaTensor, a );
}

void RigidBody::SetAngularVelocity( const Float3 &w )
{ // by Dan Andersson
	this->angularMomentum = Formula::AngularMomentum( this->momentOfInertiaTensor, w );
}

void RigidBody::SetImpulseForce( const Float3 &f )
{ // by Dan Andersson
	this->impulseForceSum = f;
}

void RigidBody::SetLinearMomentum( const Float3 &g )
{ // by Dan Andersson
	this->linearMomentum = g;
}

void RigidBody::SetLinearImpulseAcceleration( const Float3 &a )
{ // by Dan Andersson
	this->impulseForceSum = Formula::ImpulseForce( this->mass, a );
}

void RigidBody::SetLinearVelocity( const Float3 &v )
{ // by Dan Andersson
	this->linearMomentum = Formula::LinearMomentum( this->mass, v );
}

void RigidBody::SetImpulseForceAt_Local( const Float3 &localForce, const Float3 &localPos )
{ // by Dan Andersson
	// Reminder! Impulse force and torque is world values.
	Float3 worldForce = ( this->box.orientation * Float4(localForce, 0.0f) ).xyz,
		   worldPos = ( this->box.orientation * Float4(localPos, 1.0f) ).xyz;
	this->SetImpulseForceAt_World( worldForce, worldPos );

}

void RigidBody::SetImpulseForceAt_World( const Float3 &worldForce, const Float3 &worldPos )
{ // by Dan Andersson
	// Reminder! Impulse force and torque is world values.
	this->impulseForceSum = VectorProjection( worldForce, worldPos );
	this->impulseTorqueSum = Formula::ImpulseTorque( worldForce, worldPos );
}

void RigidBody::SetLinearMomentumAt_Local( const Float3 &localG, const Float3 &localPos )
{ // by Dan Andersson
	// Reminder! Linear and angular momentum is world values.
	Float3 worldG = ( this->box.orientation * Float4(localG, 0.0f) ).xyz,
		   worldPos = ( this->box.orientation * Float4(localPos, 1.0f) ).xyz;
	this->SetLinearMomentumAt_World( worldG, worldPos );
}

void RigidBody::SetLinearMomentumAt_World( const Float3 &worldG, const Float3 &worldPos )
{ // by Dan Andersson
	// Reminder! Linear and angular momentum is world values.
	this->linearMomentum = VectorProjection( worldG, worldPos );
	this->angularMomentum = Formula::AngularMomentum( worldG, worldPos );
}

void RigidBody::SetImpulseAccelerationAt_Local( const Float3 &a, const Float3 &pos )
{ // by Dan Andersson

}

void RigidBody::SetImpulseAccelerationAt_World( const Float3 &a, const Float3 &pos )
{ // by 

}

void RigidBody::SetLinearVelocityAt_Local( const Float3 &v, const Float3 &pos )
{ // by 

}

void RigidBody::SetLinearVelocityAt_World( const Float3 &v, const Float3 &pos )
{ // by 

}