#include "ScriptSystem_Math.h"

#include "Utilities/MersenneTwister.h"

#include <cmath>

namespace BlueCore
{

static MTRand mtrand;

//------------------------------------------------------------------------------
static SQInteger _sin(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) sin (f ) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _cos(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) cos (f ) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _tan(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) tan (f ) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _sqrt(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) sqrt (f ) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _trunc(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) f - fmod(f, 1.0) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _floor(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) floor (f ) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _ceil(HSQUIRRELVM v)
{
	SQFloat f;
	sq_getfloat (v, 2, &f );
	sq_pushfloat (v, ( SQFloat ) ceil (f ) );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _rand(HSQUIRRELVM v)
{
	sq_pushfloat (v, mtrand.rand() );
	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _seed_rand(HSQUIRRELVM v)
{
	SQInteger seed;
	sq_getinteger(v, 2, &seed );
	mtrand.seed(seed );
	return 0;
}

//------------------------------------------------------------------------------
static void _setup_basic_math (HSQUIRRELVM vm)
{
	sq_pushroottable (vm);

	sq_pushstring (vm, "sin", -1);
	sq_newclosure (vm, _sin, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "cos", -1);
	sq_newclosure (vm, _cos, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "tan", -1);
	sq_newclosure (vm, _tan, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "sqrt", -1);
	sq_newclosure (vm, _sqrt, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "trunc", -1);
	sq_newclosure (vm, _trunc, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "floor", -1);
	sq_newclosure (vm, _floor, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "ceil", -1);
	sq_newclosure (vm, _ceil, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "rand", -1);
	sq_newclosure (vm, _rand, 0);
	sq_setparamscheck (vm, 1, ".");
	sq_newslot (vm, -3, false);

	sq_pushstring (vm, "seed_rand", -1);
	sq_newclosure (vm, _seed_rand, 0);
	sq_setparamscheck (vm, 2, ".n");
	sq_newslot (vm, -3, false);

	// pop the root table
	sq_pop (vm, 1);
}

//------------------------------------------------------------------------------
static SQInteger _vector__add ( HSQUIRRELVM v )
{
	SQFloat ax, ay, az;
	SQFloat bx = 0.0, by = 0.0, bz = 0.0;

	_getvectorvalues ( v, 1, ax, ay, az );
	_getvectorvalues ( v, 2, bx, by, bz );
	_pushvector ( v, ax + bx, ay + by, az + bz );

	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _vector__sub ( HSQUIRRELVM v )
{
	SQFloat ax, ay, az;
	SQFloat bx = 0.0, by = 0.0, bz = 0.0;

	_getvectorvalues ( v, 1, ax, ay, az );
	_getvectorvalues ( v, 2, bx, by, bz );
	_pushvector ( v, ax - bx, ay - by, az - bz );

	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _vector__mul ( HSQUIRRELVM v )
{
	SQFloat x, y, z, f;

	_getvectorvalues ( v, 1, x, y, z );
	sq_getfloat ( v, 2, &f );
	_pushvector ( v, x*f, y*f, z*f );

	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _vector__div ( HSQUIRRELVM v )
{
	SQFloat x, y, z, f;

	_getvectorvalues ( v, 1, x, y, z );
	sq_getfloat ( v, 2, &f );
	_pushvector ( v, x / f, y / f, y / f );

	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _vector_length ( HSQUIRRELVM v )
{
	SQFloat x, y, z;

	_getvectorvalues ( v, 1, x, y, z );

	sq_pushfloat ( v, std::sqrt ( x*x + y*y + z*z ) );

	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _vector_normalize ( HSQUIRRELVM v )
{
	SQFloat x, y, z;

	_getvectorvalues ( v, 1, x, y, z );

	SQFloat l = 1 / std::sqrt ( x * x + y * y + z * z );
	_setvectorvalues ( v, 1, x*l, y*l, z*l );

	return 0;
}

//------------------------------------------------------------------------------
static SQInteger _vector_constructor ( HSQUIRRELVM v )
{
	SQFloat x = 0.0;
	SQFloat y = 0.0;
	SQFloat z = 0.0;

	SQInteger argc = sq_gettop ( v );

	if ( argc > 1 )
		sq_getfloat ( v, 2, &x );

	if ( argc > 2 )
		sq_getfloat ( v, 3, &y );

	if ( argc > 3 )
		sq_getfloat ( v, 4, &z );

	_setvectorvalues ( v, 1, x, y, z );

	return 0;
}

//------------------------------------------------------------------------------
static void _register_vector_class( HSQUIRRELVM _vm )
{
	sq_pushroottable ( _vm );

	// push class
	sq_pushstring ( _vm, "Vector", -1 );
	if ( SQ_SUCCEEDED ( sq_newclass ( _vm, SQFalse ) ) )
	{
		// register variables
		sq_pushstring ( _vm, "x", -1 );
		sq_pushfloat ( _vm, 0.0 );
		sq_newslot ( _vm, -3, false );

		sq_pushstring ( _vm, "y", -1 );
		sq_pushfloat ( _vm, 0.0 );
		sq_newslot ( _vm, -3, false );

		sq_pushstring ( _vm, "z", -1 );
		sq_pushfloat ( _vm, 0.0 );
		sq_newslot ( _vm, -3, false );

		// register constructor
		sq_pushstring ( _vm, "constructor", -1 );
		sq_newclosure ( _vm, _vector_constructor, 0 );
		sq_newslot ( _vm, -3, false );

		// register _add
		sq_pushstring ( _vm, "_add", -1 );
		sq_newclosure ( _vm, _vector__add, 0 );
		sq_newslot ( _vm, -3, false );

		// register _sub
		sq_pushstring ( _vm, "_sub", -1 );
		sq_newclosure ( _vm, _vector__sub, 0 );
		sq_newslot ( _vm, -3, false );

		// register _mul
		sq_pushstring ( _vm, "_mul", -1 );
		sq_newclosure ( _vm, _vector__mul, 0 );
		sq_newslot ( _vm, -3, false );

		// register _div
		sq_pushstring ( _vm, "_div", -1 );
		sq_newclosure ( _vm, _vector__div, 0 );
		sq_newslot ( _vm, -3, false );

		// register length
		sq_pushstring ( _vm, "length", -1 );
		sq_newclosure ( _vm, _vector_length, 0 );
		sq_newslot ( _vm, -3, false );

		// register normalize
		sq_pushstring ( _vm, "normalize", -1 );
		sq_newclosure ( _vm, _vector_normalize, 0 );
		sq_newslot ( _vm, -3, false );

		// create_vector class
		sq_newslot ( _vm, -3, false );
	}
	
	sq_poptop ( _vm );
}

//------------------------------------------------------------------------------
static SQInteger _quaternion_constructor ( HSQUIRRELVM v )
{
	SQFloat w = 0.0;
	SQFloat x = 0.0;
	SQFloat y = 0.0;
	SQFloat z = 0.0;

	SQInteger argc = sq_gettop ( v );

	if ( argc > 1 )
		sq_getfloat ( v, 2, &w );

	if ( argc > 2 )
		sq_getfloat ( v, 3, &x );

	if ( argc > 3 )
		sq_getfloat ( v, 4, &y );

	if ( argc > 4 )
		sq_getfloat ( v, 5, &z );

	_setquaternionvalues ( v, 1, w, x, y, z );

	return 0;
}

//------------------------------------------------------------------------------
static SQInteger _quaternion_apply ( HSQUIRRELVM vm )
{
	SQFloat qw, qx, qy, qz;
	SQFloat vx, vy, vz;

	_getquaternionvalues ( vm, 1, qw, qx, qy, qz );
	_getvectorvalues ( vm, 2, vx, vy, vz );

	SQFloat xx = qx*qx, xy = qx*qy, xz = qx*qz, xw = qx*qw,
			yy = qy*qy, yz = qy*qz, yw = qy*qw,
			zz = qz*qz, zw = qz*qw;
			
	SQFloat rx = 2.0 * (vx * (0.5 - yy - zz) + vy * (xy - zw) + vz * (xz + yw));
	SQFloat ry = 2.0 * (vx * (xy + zw) + vy * (0.5 - xx - zz) + vz * (yz - xw));
	SQFloat rz = 2.0 * (vx * (xz - yw) + vy * (yz + xw) + vz * (0.5 - xx - yy));

	_pushvector ( vm, rx, ry, rz );

	return 1;
}

//------------------------------------------------------------------------------
static SQInteger _quaternion_applyinversed ( HSQUIRRELVM vm )
{
	SQFloat qw, qx, qy, qz;
	SQFloat vx, vy, vz;

	_getquaternionvalues ( vm, 1, qw, qx, qy, qz );
	_getvectorvalues ( vm, 2, vx, vy, vz );

	SQFloat xx = qx*qx, xy = qx*qy, xz = qx*qz, xw = -qx*qw,
			yy = qy*qy, yz = qy*qz, yw = -qy*qw,
			zz = qz*qz, zw = -qz*qw;
			
	SQFloat rx = 2.0 * (vx * (0.5 - yy - zz) + vy * (xy - zw) + vz * (xz + yw));
	SQFloat ry = 2.0 * (vx * (xy + zw) + vy * (0.5 - xx - zz) + vz * (yz - xw));
	SQFloat rz = 2.0 * (vx * (xz - yw) + vy * (yz + xw) + vz * (0.5 - xx - yy));

	_pushvector ( vm, rx, ry, rz );

	return 1;
}

//------------------------------------------------------------------------------
static void _register_quaternion_class (HSQUIRRELVM vm)
{
	sq_pushroottable ( vm );

	// push class
	sq_pushstring ( vm, "Quaternion", -1 );

	if ( SQ_SUCCEEDED ( sq_newclass ( vm, SQFalse ) ) )
	{
		// register variables
		sq_pushstring ( vm, "w", -1 );
		sq_pushfloat ( vm, 0.0 );
		sq_newslot ( vm, -3, false );

		sq_pushstring ( vm, "x", -1 );
		sq_pushfloat ( vm, 0.0 );
		sq_newslot ( vm, -3, false );

		sq_pushstring ( vm, "y", -1 );
		sq_pushfloat ( vm, 0.0 );
		sq_newslot ( vm, -3, false );

		sq_pushstring ( vm, "z", -1 );
		sq_pushfloat ( vm, 0.0 );
		sq_newslot ( vm, -3, false );

		// register constructor
		sq_pushstring ( vm, "constructor", -1 );
		sq_newclosure ( vm, _quaternion_constructor, 0 );
		sq_newslot ( vm, -3, false );

		sq_pushstring ( vm, "apply", -1 );
		sq_newclosure ( vm, _quaternion_apply, 0 );
		sq_newslot ( vm, -3, false );

		sq_pushstring ( vm, "applyInversed", -1 );
		sq_newclosure ( vm, _quaternion_applyinversed, 0 );
		sq_newslot ( vm, -3, false );

		// create_vector class
		sq_newslot ( vm, -3, false );
	}

	sq_poptop ( vm );
}

//------------------------------------------------------------------------------
void setupScriptSystem_Math (ScriptSystem* scriptsystem)
{
	if (scriptsystem == 0)
		return;
	
	HSQUIRRELVM vm = scriptsystem->getVM();
	if (vm)
	{
		_setup_basic_math (vm);
		_register_vector_class(vm);
		_register_quaternion_class(vm);
	}
}

} // namespace BlueCore
