/*

OOJSVector.m

Oolite
Copyright (C) 2004-2013 Giles C Williams and contributors

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.

*/

#import "OOJSVector.h"
#import "OOJavaScriptEngine.h"

#if OOLITE_GNUSTEP
#import <GNUstepBase/GSObjCRuntime.h>
#else
#import <objc/objc-runtime.h>
#endif

#import "OOConstToString.h"
#import "OOJSEntity.h"
#import "OOJSQuaternion.h"


static JSObject *sVectorPrototype;


static BOOL GetThisVector(JSContext *context, JSObject *vectorObj, Vector *outVector, NSString *method)  NONNULL_FUNC;


static JSBool VectorGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value);
static JSBool VectorSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value);
static void VectorFinalize(JSContext *context, JSObject *this);
static JSBool VectorConstruct(JSContext *context, uintN argc, jsval *vp);

// Methods
static JSBool VectorToString(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorToSource(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorAdd(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorSubtract(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorDistanceTo(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorSquaredDistanceTo(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorMultiply(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorDot(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorAngleTo(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorFromCoordinateSystem(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorToCoordinateSystem(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorCross(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorTripleProduct(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorDirection(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorMagnitude(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorSquaredMagnitude(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorRotationTo(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorRotateBy(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorToArray(JSContext *context, uintN argc, jsval *vp);

// Static methods
static JSBool VectorStaticInterpolate(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorStaticRandom(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorStaticRandomDirection(JSContext *context, uintN argc, jsval *vp);
static JSBool VectorStaticRandomDirectionAndLength(JSContext *context, uintN argc, jsval *vp);


static JSClass sVectorClass =
{
	"Vector3D",
	JSCLASS_HAS_PRIVATE,
	
	JS_PropertyStub,		// addProperty
	JS_PropertyStub,		// delProperty
	VectorGetProperty,		// getProperty
	VectorSetProperty,		// setProperty
	JS_EnumerateStub,		// enumerate
	JS_ResolveStub,			// resolve
	JS_ConvertStub,			// convert
	VectorFinalize,			// finalize
	JSCLASS_NO_OPTIONAL_MEMBERS
};


enum
{
	// Property IDs
	kVector_x,
	kVector_y,
	kVector_z
};


static JSPropertySpec sVectorProperties[] =
{
	// JS name					ID							flags
	{ "x",						kVector_x,					OOJS_PROP_READWRITE_CB },
	{ "y",						kVector_y,					OOJS_PROP_READWRITE_CB },
	{ "z",						kVector_z,					OOJS_PROP_READWRITE_CB },
	{ 0 }
};


static JSFunctionSpec sVectorMethods[] =
{
	// JS name					Function					min args
	{ "toSource",				VectorToSource,				0, },
	{ "toString",				VectorToString,				0, },
	{ "add",					VectorAdd,					1, },
	{ "angleTo",				VectorAngleTo,				1, },
	{ "cross",					VectorCross,				1, },
	{ "direction",				VectorDirection,			0, },
	{ "distanceTo",				VectorDistanceTo,			1, },
	{ "dot",					VectorDot,					1, },
	{ "fromCoordinateSystem",	VectorFromCoordinateSystem,	1, },
	{ "magnitude",				VectorMagnitude,			0, },
	{ "multiply",				VectorMultiply,				1, },
	{ "rotateBy",				VectorRotateBy,				1, },
	{ "rotationTo",				VectorRotationTo,			1, },
	{ "squaredDistanceTo",		VectorSquaredDistanceTo,	1, },
	{ "squaredMagnitude",		VectorSquaredMagnitude,		0, },
	{ "subtract",				VectorSubtract,				1, },
	{ "toArray",				VectorToArray,				0, },
	{ "toCoordinateSystem",		VectorToCoordinateSystem,	1, },
	{ "tripleProduct",			VectorTripleProduct,		2, },
	{ 0 }
};


static JSFunctionSpec sVectorStaticMethods[] =
{
	// JS name						Function							min args
	{ "interpolate",				VectorStaticInterpolate,			3, },
	{ "random",						VectorStaticRandom,					0, },
	{ "randomDirection",			VectorStaticRandomDirection, 		0, },
	{ "randomDirectionAndLength",	VectorStaticRandomDirectionAndLength, 0, },
	{ 0 }
};


// *** Public ***

void InitOOJSVector(JSContext *context, JSObject *global)
{
	sVectorPrototype = JS_InitClass(context, global, NULL, &sVectorClass, VectorConstruct, 0, sVectorProperties, sVectorMethods, NULL, sVectorStaticMethods);
}


JSObject *JSVectorWithVector(JSContext *context, Vector vector)
{
	OOJS_PROFILE_ENTER
	
	JSObject				*result = NULL;
	Vector					*private = NULL;
	
	private = malloc(sizeof *private);
	if (EXPECT_NOT(private == NULL))  return NULL;
	
	*private = vector;
	
	result = JS_NewObject(context, &sVectorClass, sVectorPrototype, NULL);
	if (result != NULL)
	{
		if (EXPECT_NOT(!JS_SetPrivate(context, result, private)))  result = NULL;
	}
	
	if (EXPECT_NOT(result == NULL)) free(private);
	
	return result;
	
	OOJS_PROFILE_EXIT
}


BOOL VectorToJSValue(JSContext *context, Vector vector, jsval *outValue)
{
	OOJS_PROFILE_ENTER
	
	JSObject				*object = NULL;
	
	assert(outValue != NULL);
	
	object = JSVectorWithVector(context, vector);
	if (EXPECT_NOT(object == NULL)) return NO;
	
	*outValue = OBJECT_TO_JSVAL(object);
	return YES;
	
	OOJS_PROFILE_EXIT
}


BOOL NSPointToVectorJSValue(JSContext *context, NSPoint point, jsval *outValue)
{
	return VectorToJSValue(context, make_vector(point.x, point.y, 0), outValue);
}


BOOL JSValueToVector(JSContext *context, jsval value, Vector *outVector)
{
	if (EXPECT_NOT(!JSVAL_IS_OBJECT(value)))  return NO;
	
	return JSObjectGetVector(context, JSVAL_TO_OBJECT(value), outVector);
}


#if OO_DEBUG

typedef struct
{
	NSUInteger			vectorCount;
	NSUInteger			entityCount;
	NSUInteger			arrayCount;
	NSUInteger			protoCount;
	NSUInteger			nullCount;
	NSUInteger			failCount;
} VectorStatistics;
static VectorStatistics sVectorConversionStats;


@implementation PlayerEntity (JSVectorStatistics)

// :setM vectorStats PS.callObjC("reportJSVectorStatistics")
// :vectorStats

- (NSString *) reportJSVectorStatistics
{
	VectorStatistics *stats = &sVectorConversionStats;
	
	NSUInteger sum = stats->vectorCount + stats->entityCount + stats->arrayCount + stats->protoCount;
	double convFac = 100.0 / sum;
	if (sum == 0)  convFac = 0;
	
	return [NSString stringWithFormat:
		   @" vector-to-vector conversions: %lu (%g %%)\n"
			" entity-to-vector conversions: %lu (%g %%)\n"
			"  array-to-vector conversions: %lu (%g %%)\n"
			"prototype-to-zero conversions: %lu (%g %%)\n"
			"             null conversions: %lu (%g %%)\n"
			"           failed conversions: %lu (%g %%)\n"
			"                        total: %lu",
			(long)stats->vectorCount, stats->vectorCount * convFac,
			(long)stats->entityCount, stats->entityCount * convFac,
			(long)stats->arrayCount, stats->arrayCount * convFac,
			(long)stats->protoCount, stats->protoCount * convFac,
			(long)stats->nullCount, stats->nullCount * convFac,
			(long)stats->failCount, stats->failCount * convFac,
			(long)sum];
}


- (void) clearJSVectorStatistics
{
	memset(&sVectorConversionStats, 0, sizeof sVectorConversionStats);
}

@end

#define COUNT(FIELD) do { sVectorConversionStats.FIELD++; } while (0)

#else

#define COUNT(FIELD) do {} while (0)

#endif


BOOL JSObjectGetVector(JSContext *context, JSObject *vectorObj, Vector *outVector)
{
	OOJS_PROFILE_ENTER
	
	assert(outVector != NULL);
	
	Vector					*private = NULL;
	jsuint					arrayLength;
	jsval					arrayX, arrayY, arrayZ;
	jsdouble				x, y, z;
	
	// vectorObj can legitimately be NULL, e.g. when JS_NULL is converted to a JSObject *.
	if (EXPECT_NOT(vectorObj == NULL))
	{
		COUNT(nullCount);
		return NO;
	}
	
	// If this is a (JS) Vector...
	private = JS_GetInstancePrivate(context, vectorObj, &sVectorClass, NULL);
	if (EXPECT(private != NULL))
	{
		COUNT(vectorCount);
		*outVector = *private;
		return YES;
	}
	
	// If it's an array...
	if (EXPECT(JS_IsArrayObject(context, vectorObj)))
	{
		// ...and it has exactly three elements...
		if (JS_GetArrayLength(context, vectorObj, &arrayLength) && arrayLength == 3)
		{
			if (JS_LookupElement(context, vectorObj, 0, &arrayX) &&
				JS_LookupElement(context, vectorObj, 1, &arrayY) &&
				JS_LookupElement(context, vectorObj, 2, &arrayZ))
			{
				// ...use the three numbers as [x, y, z]
				if (JS_ValueToNumber(context, arrayX, &x) &&
					JS_ValueToNumber(context, arrayY, &y) &&
					JS_ValueToNumber(context, arrayZ, &z))
				{
					COUNT(arrayCount);
					*outVector = make_vector(x, y, z);
					return YES;
				}
			}
		}
	}
	
	// If it's an entity, use its position.
	if (OOJSIsMemberOfSubclass(context, vectorObj, JSEntityClass()))
	{
		COUNT(entityCount);
		Entity *entity = JS_GetPrivate(context, vectorObj);
		*outVector = [entity position];
		return YES;
	}
	
	/*
		If it's actually a Vector3D but with no private field (this happens for
		Vector3D.prototype)...
		
		NOTE: it would be prettier to do this at the top when we handle normal
		Vector3Ds, but it's a rare case which should be kept off the fast path.
	*/
	if (JS_InstanceOf(context, vectorObj, &sVectorClass, NULL))
	{
		COUNT(protoCount);
		*outVector = kZeroVector;
		return YES;
	}
	
	COUNT(failCount);
	return NO;
	
	OOJS_PROFILE_EXIT
}


static BOOL GetThisVector(JSContext *context, JSObject *vectorObj, Vector *outVector, NSString *method)
{
	if (EXPECT(JSObjectGetVector(context, vectorObj, outVector)))  return YES;
	
	jsval arg = OBJECT_TO_JSVAL(vectorObj);
	OOJSReportBadArguments(context, @"Vector3D", method, 1, &arg, @"Invalid target object", @"Vector3D");
	return NO;
}


BOOL JSVectorSetVector(JSContext *context, JSObject *vectorObj, Vector vector)
{
	OOJS_PROFILE_ENTER
	
	Vector					*private = NULL;
	
	if (EXPECT_NOT(vectorObj == NULL))  return NO;
	
	private = JS_GetInstancePrivate(context, vectorObj, &sVectorClass, NULL);
	if (private != NULL)	// If this is a (JS) Vector...
	{
		*private = vector;
		return YES;
	}
	
	if (JS_InstanceOf(context, vectorObj, &sVectorClass, NULL))
	{
		// Silently fail for the prototype.
		return YES;
	}
	
	return NO;
	
	OOJS_PROFILE_EXIT
}


static BOOL VectorFromArgumentListNoErrorInternal(JSContext *context, uintN argc, jsval *argv, Vector *outVector, uintN *outConsumed, BOOL permitNumberList)
{
	OOJS_PROFILE_ENTER
	
	double				x, y, z;
	
	if (EXPECT_NOT(argc == 0))  return NO;
	assert(argv != NULL && outVector != NULL);
	
	if (outConsumed != NULL)  *outConsumed = 0;
	
	// Is first object a vector, array or entity?
	if (JSVAL_IS_OBJECT(argv[0]))
	{
		if (JSObjectGetVector(context, JSVAL_TO_OBJECT(argv[0]), outVector))
		{
			if (outConsumed != NULL)  *outConsumed = 1;
			return YES;
		}
	}
	
	if (!permitNumberList)  return NO;
	
	// As a special case for VectorConstruct(), look for three numbers.
	if (argc < 3)  return NO;
	
	// Given a string, JS_ValueToNumber() returns YES but provides a NaN number.
	if (EXPECT_NOT(!JS_ValueToNumber(context, argv[0], &x) || isnan(x)))  return NO;
	if (EXPECT_NOT(!JS_ValueToNumber(context, argv[1], &y) || isnan(y)))  return NO;
	if (EXPECT_NOT(!JS_ValueToNumber(context, argv[2], &z) || isnan(z)))  return NO;
	
	// We got our three numbers.
	*outVector = make_vector(x, y, z);
	if (outConsumed != NULL)  *outConsumed = 3;
	
	return YES;
	
	OOJS_PROFILE_EXIT
}


// EMMSTRAN: remove outConsumed, since it can only be 1 except in failure (constructor is an exception, but it uses VectorFromArgumentListNoErrorInternal() directly).
BOOL VectorFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, Vector *outVector, uintN *outConsumed)
{
	if (VectorFromArgumentListNoErrorInternal(context, argc, argv, outVector, outConsumed, NO))  return YES;
	else
	{
		OOJSReportBadArguments(context, scriptClass, function, argc, argv,
							   @"Could not construct vector from parameters",
							   @"Vector, Entity or array of three numbers");
		return NO;
	}
}


BOOL VectorFromArgumentListNoError(JSContext *context, uintN argc, jsval *argv, Vector *outVector, uintN *outConsumed)
{
	return VectorFromArgumentListNoErrorInternal(context, argc, argv, outVector, outConsumed, NO);
}


// *** Implementation stuff ***

static JSBool VectorGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value)
{
	if (!JSID_IS_INT(propID))  return YES;
	
	OOJS_PROFILE_ENTER
	
	Vector				vector;
	GLfloat				fValue;
	
	if (EXPECT_NOT(!JSObjectGetVector(context, this, &vector)))  return NO;
	
	switch (JSID_TO_INT(propID))
	{
		case kVector_x:
			fValue = vector.x;
			break;
		
		case kVector_y:
			fValue = vector.y;
			break;
		
		case kVector_z:
			fValue = vector.z;
			break;
		
		default:
			OOJSReportBadPropertySelector(context, this, propID, sVectorProperties);
			return NO;
	}
	
	return JS_NewNumberValue(context, fValue, value);
	
	OOJS_PROFILE_EXIT
}


static JSBool VectorSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value)
{
	if (!JSID_IS_INT(propID))  return YES;
	
	OOJS_PROFILE_ENTER
	
	Vector				vector;
	jsdouble			dval;
	
	if (EXPECT_NOT(!JSObjectGetVector(context, this, &vector)))  return NO;
	if (EXPECT_NOT(!JS_ValueToNumber(context, *value, &dval)))
	{
		OOJSReportBadPropertyValue(context, this, propID, sVectorProperties, *value);
		return NO;
	}
	
	switch (JSID_TO_INT(propID))
	{
		case kVector_x:
			vector.x = dval;
			break;
		
		case kVector_y:
			vector.y = dval;
			break;
		
		case kVector_z:
			vector.z = dval;
			break;
		
		default:
			OOJSReportBadPropertySelector(context, this, propID, sVectorProperties);
			return NO;
	}
	
	return JSVectorSetVector(context, this, vector);
	
	OOJS_PROFILE_EXIT
}


static void VectorFinalize(JSContext *context, JSObject *this)
{
	OOJS_PROFILE_ENTER
	
	Vector					*private = NULL;
	
	private = JS_GetInstancePrivate(context, this, &sVectorClass, NULL);
	if (private != NULL)
	{
		free(private);
	}
	
	OOJS_PROFILE_EXIT_VOID
}


static JSBool VectorConstruct(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					vector = kZeroVector;
	Vector					*private = NULL;
	JSObject				*this = NULL;
	
	private = malloc(sizeof *private);
	if (EXPECT_NOT(private == NULL))  return NO;
	
	this = JS_NewObject(context, &sVectorClass, NULL, NULL);
	if (EXPECT_NOT(this == NULL))  return NO;
	
	if (argc != 0)
	{
		if (EXPECT_NOT(!VectorFromArgumentListNoErrorInternal(context, argc, OOJS_ARGV, &vector, NULL, YES)))
		{
			free(private);
			OOJSReportBadArguments(context, NULL, NULL, argc, OOJS_ARGV,
								   @"Could not construct vector from parameters",
								   @"Vector, Entity or array of three numbers");
			return NO;
		}
	}
	
	*private = vector;
	
	if (EXPECT_NOT(!JS_SetPrivate(context, this, private)))
	{
		free(private);
		return NO;
	}
	
	OOJS_RETURN_JSOBJECT(this);
	
	OOJS_PROFILE_EXIT
}


// *** Methods ***

// toString() : String
static JSBool VectorToString(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_NATIVE_ENTER(context)
	
	Vector					thisv;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toString"))) return NO;
	
	OOJS_RETURN_OBJECT(VectorDescription(thisv));
	
	OOJS_NATIVE_EXIT
}


// toSource() : String
static JSBool VectorToSource(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_NATIVE_ENTER(context)
	
	Vector					thisv;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toSource"))) return NO;
	
	NSString *str = [NSString stringWithFormat:@"Vector3D(%g, %g, %g)", thisv.x, thisv.y, thisv.z];
	OOJS_RETURN_OBJECT(str);
	
	OOJS_NATIVE_EXIT
}


// add(v : vectorExpression) : Vector3D
static JSBool VectorAdd(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv, result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"add"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"add", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = vector_add(thisv, thatv);
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_PROFILE_EXIT
}


// subtract(v : vectorExpression) : Vector3D
static JSBool VectorSubtract(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv, result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"subtract"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"subtract", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = vector_subtract(thisv, thatv);
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_PROFILE_EXIT
}


// distanceTo(v : vectorExpression) : Number
static JSBool VectorDistanceTo(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv;
	GLfloat					result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"distanceTo"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"distanceTo", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = distance(thisv, thatv);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// squaredDistanceTo(v : vectorExpression) : Number
static JSBool VectorSquaredDistanceTo(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv;
	GLfloat					result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"squaredDistanceTo"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"squaredDistanceTo", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = distance2(thisv, thatv);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// multiply(n : Number) : Vector3D
static JSBool VectorMultiply(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, result;
	double					scalar;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"multiply"))) return NO;
	if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Vector3D", @"multiply", argc, OOJS_ARGV, &scalar, NULL)))  return NO;
	
	result = vector_multiply_scalar(thisv, scalar);
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_PROFILE_EXIT
}


// dot(v : vectorExpression) : Number
static JSBool VectorDot(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv;
	GLfloat					result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"dot"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"dot", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = dot_product(thisv, thatv);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// angleTo(v : vectorExpression) : Number
static JSBool VectorAngleTo(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv;
	GLfloat					result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"angleTo"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"angleTo", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = dot_product(vector_normal(thisv), vector_normal(thatv));
	if (result > 1.0f) result = 1.0f;
	if (result < -1.0f) result = -1.0f;
	// for identical vectors the dot_product sometimes returnes a value > 1.0 because of rounding errors, resulting
	// in an undefined result for the acos.
	result = acos(result);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// cross(v : vectorExpression) : Vector3D
static JSBool VectorCross(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv, result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"cross"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"cross", argc, OOJS_ARGV, &thatv, NULL)))  return NO;
	
	result = true_cross_product(thisv, thatv);
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_PROFILE_EXIT
}


// tripleProduct(v : vectorExpression, u : vectorExpression) : Number
static JSBool VectorTripleProduct(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv, theotherv;
	GLfloat					result;
	uintN					consumed;
	jsval					*argv = OOJS_ARGV;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"tripleProduct"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"tripleProduct", argc, argv, &thatv, &consumed)))  return NO;
	argc -= consumed;
	argv += consumed;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"tripleProduct", argc, argv, &theotherv, NULL)))  return NO;
	
	result = triple_product(thisv, thatv, theotherv);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// direction() : Vector3D
static JSBool VectorDirection(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"direction"))) return NO;
	
	result = vector_normal(thisv);
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_PROFILE_EXIT
}


// magnitude() : Number
static JSBool VectorMagnitude(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv;
	GLfloat					result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"magnitude"))) return NO;
	
	result = magnitude(thisv);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// squaredMagnitude() : Number
static JSBool VectorSquaredMagnitude(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv;
	GLfloat					result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"squaredMagnitude"))) return NO;
	
	result = magnitude2(thisv);
	
	OOJS_RETURN_DOUBLE(result);
	
	OOJS_PROFILE_EXIT
}


// rotationTo(v : vectorExpression [, limit : Number]) : Quaternion
static JSBool VectorRotationTo(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, thatv;
	double					limit;
	BOOL					gotLimit;
	Quaternion				result;
	uintN					consumed;
	jsval					*argv = OOJS_ARGV;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"rotationTo"))) return NO;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"rotationTo", argc, OOJS_ARGV, &thatv, &consumed)))  return NO;
	
	argc -= consumed;
	argv += consumed;
	if (argc != 0)	// limit parameter is optional.
	{
		if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Vector3D", @"rotationTo", argc, argv, &limit, NULL)))  return NO;
		gotLimit = YES;
	}
	else gotLimit = NO;
	
	if (gotLimit)  result = quaternion_limited_rotation_between(thisv, thatv, limit);
	else  result = quaternion_rotation_between(thisv, thatv);
	
	OOJS_RETURN_QUATERNION(result);
	
	OOJS_PROFILE_EXIT
}


// rotateBy(q : quaternionExpression) : Vector3D
static JSBool VectorRotateBy(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv, result;
	Quaternion				q;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"rotateBy"))) return NO;
	if (EXPECT_NOT(!QuaternionFromArgumentList(context, @"Vector3D", @"rotateBy", argc, OOJS_ARGV, &q, NULL)))  return NO;
	
	result = quaternion_rotate_vector(q, thisv);
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_PROFILE_EXIT
}


// toArray() : Array
static JSBool VectorToArray(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					thisv;
	JSObject				*result = NULL;
	jsval					nVal;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toArray"))) return NO;
	
	result = JS_NewArrayObject(context, 0, NULL);
	if (result != NULL)
	{
		// We do this at the top because the return value slot is a GC root.
		OOJS_SET_RVAL(OBJECT_TO_JSVAL(result));
		
		if (JS_NewNumberValue(context, thisv.x, &nVal) && JS_SetElement(context, result, 0, &nVal) &&
			JS_NewNumberValue(context, thisv.y, &nVal) && JS_SetElement(context, result, 1, &nVal) &&
			JS_NewNumberValue(context, thisv.z, &nVal) && JS_SetElement(context, result, 2, &nVal))
		{
			return YES;
		}
		// If we get here, the conversion and stuffing in the previous condition failed.
		OOJS_SET_RVAL(JSVAL_VOID);
	}
	
	return YES;
	
	OOJS_PROFILE_EXIT
}


// toCoordinateSystem(coordScheme : String)
static JSBool VectorToCoordinateSystem(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_NATIVE_ENTER(context)
	
	Vector				thisv;
	NSString			*coordScheme = nil;
	Vector				result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"toCoordinateSystem"))) return NO;
	
	if (EXPECT_NOT(argc < 1 ||
				   (coordScheme = OOStringFromJSValue(context, OOJS_ARGV[0])) == nil))
	{
		OOJSReportBadArguments(context, @"Vector3D", @"toCoordinateSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"coordinate system");
		return NO;
	}
	
	OOJS_BEGIN_FULL_NATIVE(context)
	result = [UNIVERSE legacyPositionFrom:thisv asCoordinateSystem:coordScheme];
	OOJS_END_FULL_NATIVE
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_NATIVE_EXIT
}


// fromCoordinateSystem(coordScheme : String)
static JSBool VectorFromCoordinateSystem(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_NATIVE_ENTER(context)
	
	Vector				thisv;
	NSString			*coordScheme = nil;
	Vector				result;
	
	if (EXPECT_NOT(!GetThisVector(context, OOJS_THIS, &thisv, @"fromCoordinateSystem"))) return NO;
	
	if (EXPECT_NOT(argc < 1 ||
				   (coordScheme = OOStringFromJSValue(context, OOJS_ARGV[0])) == nil))
	{
		OOJSReportBadArguments(context, @"Vector3D", @"fromCoordinateSystem", MIN(argc, 1U), OOJS_ARGV, nil, @"coordinate system");
		return NO;
	}
	
	OOJS_BEGIN_FULL_NATIVE(context)
	NSString *arg = [NSString stringWithFormat:@"%@ %f %f %f", coordScheme, thisv.x, thisv.y, thisv.z];
	result = [UNIVERSE coordinatesFromCoordinateSystemString:arg];
	OOJS_END_FULL_NATIVE
	
	OOJS_RETURN_VECTOR(result);
	
	OOJS_NATIVE_EXIT
}


// *** Static methods ***


// interpolate(v : Vector3D, u : Vector3D, alpha : Number) : Vector3D
static JSBool VectorStaticInterpolate(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	Vector					av, bv;
	double					interp;
	Vector					result;
	uintN					consumed;
	uintN					inArgc = argc;
	jsval					*argv = OOJS_ARGV;
	jsval					*inArgv = argv;
	
	if (EXPECT_NOT(argc < 3))  goto INSUFFICIENT_ARGUMENTS;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"interpolate", argc, argv, &av, &consumed)))  return NO;
	argc -= consumed;
	argv += consumed;
	if (EXPECT_NOT(argc < 2))  goto INSUFFICIENT_ARGUMENTS;
	if (EXPECT_NOT(!VectorFromArgumentList(context, @"Vector3D", @"interpolate", argc, argv, &bv, &consumed)))  return NO;
	argc -= consumed;
	argv += consumed;
	if (EXPECT_NOT(argc < 1))  goto INSUFFICIENT_ARGUMENTS;
	if (EXPECT_NOT(!OOJSArgumentListGetNumber(context, @"Vector3D", @"interpolate", argc, argv, &interp, NULL)))  return NO;
	
	result = OOVectorInterpolate(av, bv, interp);
	
	OOJS_RETURN_VECTOR(result);
	
INSUFFICIENT_ARGUMENTS:
	OOJSReportBadArguments(context, @"Vector3D", @"interpolate", inArgc, inArgv, 
								   @"Insufficient parameters",
								   @"vector expression, vector expression and number");
	return NO;
	
	OOJS_PROFILE_EXIT
}


// random([maxLength : Number]) : Vector3D
static JSBool VectorStaticRandom(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	double					maxLength;
	
	if (argc == 0 || !OOJSArgumentListGetNumberNoError(context, argc, OOJS_ARGV, &maxLength, NULL))  maxLength = 1.0;
	
	OOJS_RETURN_VECTOR(OOVectorRandomSpatial(maxLength));
	
	OOJS_PROFILE_EXIT
}


// randomDirection([scale : Number]) : Vector3D
static JSBool VectorStaticRandomDirection(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	double					scale;
	
	if (argc == 0 || !OOJSArgumentListGetNumberNoError(context, argc, OOJS_ARGV, &scale, NULL))  scale = 1.0;
	
	OOJS_RETURN_VECTOR(vector_multiply_scalar(OORandomUnitVector(), scale));
	
	OOJS_PROFILE_EXIT
}


// randomDirectionAndLength([maxLength : Number]) : Vector3D
static JSBool VectorStaticRandomDirectionAndLength(JSContext *context, uintN argc, jsval *vp)
{
	OOJS_PROFILE_ENTER
	
	double					maxLength;
	
	if (argc == 0 || !OOJSArgumentListGetNumberNoError(context, argc, OOJS_ARGV, &maxLength, NULL))  maxLength = 1.0;
	
	OOJS_RETURN_VECTOR(OOVectorRandomRadial(maxLength));
	
	OOJS_PROFILE_EXIT
}
