/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (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.mozilla.org/NPL/

 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.

 * The Original Code is Rhino code, released
 * May 6, 1999.

 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */
/* Header file */
#include <stdio.h>
#include <math.h>
#include <limits.h>
#ifdef VX
#include <private/mathP.h>
#endif
#include "js-utils.h"
#include "js-iterating.h"
#include "js-objectadapter.h"
#include "js-stringbuffer.h"
#include "js-scriptruntime.h"
#include "js-functionobject.h"
#include "js-evaluatorexception.h"
#include "js-exceptionhandler.h"
#include "js-nativestring.h"
#include "js-nativearray.h"

/* Internal data */
const jsString_t	JSNativeArray::className = "Array";

/* Procedures */
void
JSNativeArray::finishInit(JSContext *cx, JSScriptable *scope,
			JSFunctionObject *ctor, JSScriptable *proto)
{
    class ArrayAdapter: public JSObjectAdapter {
	public:
	    JS_OA_DefineGetter(length, JSNativeArray)
	    JS_OA_DefineSetter(length, JSNativeArray)
    } ; 
    static jsFunction_t functions[] = {
#define Item(name, length)	{ #name,  jsFunction_##name, length }
	Item(toString, -1),	Item(join, -1),
	Item(reverse, -1),	Item(sort, -1),
	Item(push, -1),		Item(pop, -1),
	Item(shift, -1),	Item(unshift, -1),
	Item(splice, -1),	Item(concat, -1),
	Item(slice, -1),
#undef  Item
    } ;
    static jsProperty_t properties[] = {
#define Item(name, clazz)						\
	{								\
	    #name, JS_PA_Permanent | JS_PA_DontEnum,			\
	    &clazz::name##_getter, &clazz::name##_setter		\
	}
	Item(length, ArrayAdapter),
#undef Item
    } ;
 
    ((JSScriptableObject *) proto)->defineFunctionProperties(cx, className,
					TableSize(functions), functions,
					JS_PA_DontEnum);
    ((JSScriptableObject *) proto)->defineProperties(cx, 
					TableSize(properties), properties);

    static const struct {
	jsString_t	name;
	short		value;		
    } specialLengths[] = {
	{ "reverse",	0 },	{ "toString",	0 },
    } ;

    for (int i = 0; i < (int) TableSize(specialLengths); ++i) {
	jsVal_t value;

	(void) proto->get(cx, specialLengths[i].name, proto, &value);
	((JSFunctionObject *) JS_VAL_TO_OBJECT(value))
					->setLength(specialLengths[i].value);
    }
}
/**  
 * See ECMA 15.4.1,2
 */ 
jsVal_t
JSNativeArray::jsConstructor(JSContext *cx, int numArgs, jsVal_t *args,
			JSFunction *ctorObj, jsBool_t inNewExpr)
{
    if (!inNewExpr) {
	// FunctionObject.construct will set up parent, proto
	JSFunctionObject *ctor = (JSFunctionObject *) ctorObj;

	return JS_OBJECT_TO_VAL(
			ctor->construct(cx, ctor->getParentScope(cx),
					numArgs, args));
    }
    if (numArgs == 0) {
	return JS_OBJECT_TO_VAL(new (cx) JSNativeArray());
    }
 
    // Only use 1 arg as first element for version 1.2; for
    // any other version (including 1.3) follow ECMA and use it as
    // a length.
    if (cx->getLanguageVersion() == cx->VERSION_1_2) {
	return JS_OBJECT_TO_VAL(new (cx) JSNativeArray(cx, args, numArgs));
    } else {   
	if ((numArgs > 1) || !JS_VAL_IS_NUMBER(args[0]))  {
	    return JS_OBJECT_TO_VAL(new (cx) JSNativeArray(cx, args, numArgs));
	} else {
	    uint32_t len = JSScriptRuntime::toUint32(cx, args[0]);
	    double d = JSScriptRuntime::toNumber(cx, args[0]);

	    if (len != d) {
		cx->exceptionHandler->throwEx(
                        cx,
			JSContext::reportRuntimeError(cx,
				JSContext::getMessage(cx,
						"msg.arraylength.bad")));
	    }
	    return JS_OBJECT_TO_VAL(new (cx) JSNativeArray(cx, len));
	}
    }
}
jsVal_t
JSNativeArray::jsFunction_toString(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    return toStringHelper(cx, thisObj, 
			cx->getLanguageVersion() == cx->VERSION_1_2);
}
/**
 * See ECMA 15.4.4.3
 */ 
jsVal_t
JSNativeArray::jsFunction_join(JSContext *cx, JSScriptable *thisObj,
			int numArgs, jsVal_t *args, JSFunction *funObj)
{
    JSStringBuffer result(cx);
    jsString_t separator;

    double length = getLengthProperty(cx, thisObj);

    // if no args, use "," as separator
    if (numArgs < 1) {
	separator = ",";
    } else {
	separator = JSScriptRuntime::toString(cx, args[0]);
    }
    for (int64_t i = 0; i < (int64_t) length; i++) {
	if (i > 0) {
	    result.append(separator);
	}
	jsVal_t temp = getElem(cx, thisObj, i);

	if (temp == JS_VAL_NULL || temp == JS_VAL_VOID) {
	    continue;
	}
  	jsString_t s = JSScriptRuntime::toString(cx, temp);

	result.append(s);
	JS_DeleteZoneObj(cx, s);
    }
    if (numArgs >= 1) {
	JS_DeleteZoneObj(cx, separator);
    }
    return JS_STRING_TO_VAL(JS_NewGCString(cx, result.toString()));
}
/**
 * See ECMA 15.4.4.4
 */  
jsVal_t
JSNativeArray::jsFunction_reverse(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    int64_t len = (int64_t) getLengthProperty(cx, thisObj);
    int64_t half = len / 2;

    for (int64_t i = 0; i < half; i++) {
	int64_t j = len - i - 1;   
	jsVal_t temp1 = getElem(cx, thisObj, i);
	jsVal_t temp2 = getElem(cx, thisObj, j);

	setElem(cx, thisObj, i, temp2);
	setElem(cx, thisObj, j, temp1);
    }
    return JS_OBJECT_TO_VAL(thisObj);
}
/**
 * See ECMA 15.4.4.5
 */ 
jsVal_t
JSNativeArray::jsFunction_sort(JSContext *cx, JSScriptable *thisObj,
			int numArgs, jsVal_t *args, JSFunction *funObj)
{
    int64_t length = (int64_t) getLengthProperty(cx, thisObj);
    jsVal_t compare;

    if (numArgs > 0 && args[0] != JS_VAL_VOID) {
	// sort with given compare function
	compare = args[0];
    } else {
	// sort with default compare
	compare = JS_VAL_NULL;
    }

    // OPT: Would it make sense to use the extended sort for very small
    // arrays?

    // Should we use the extended sort function, or the faster one?
    if (length >= INT_MAX) {
	qsort_extended(cx, compare, thisObj, 0, length - 1);
    } else {
	// copy the JS array into a working array, so it can be
	// sorted cheaply.
	jsVal_t *working = (jsVal_t *) JS_NewZoneObj(cx,
					sizeof (*working) * (int) length);
	int i; // avoid compile error on vxWorks

	for (i = 0; i < length; i++) {
	    working[i] = getElem(cx, thisObj, i);
	}

	qsort(cx, compare, working, 0, (int) length - 1,
		(JSNativeFunction *) funObj);

	// copy the working array back into thisObj
	for (i = 0; i < length; i++) {
	    setElem(cx, thisObj, i, working[i]);
	}
	JS_DeleteZoneObj(cx, working);
    }
    return JS_OBJECT_TO_VAL(thisObj);
}
/**  
 * Non-ECMA methods.
 */  
jsVal_t
JSNativeArray::jsFunction_push(JSContext *cx, JSScriptable *thisObj,
			int numArgs, jsVal_t *args, JSFunction *funObj)
{   
    double length = getLengthProperty(cx, thisObj);

    for (int i = 0; i < numArgs; i++) {
	setElem(cx, thisObj, (int64_t) length + i, args[i]);
    }

    length += numArgs;
    JSScriptRuntime::setProp(cx, JS_OBJECT_TO_VAL(thisObj), "length",
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length)), thisObj);

    /*
     * If JS1.2, follow Perl4 by returning the last thing pushed.
     * Otherwise, return the new array length.
     */
    if (cx->getLanguageVersion() == JSContext::VERSION_1_2) {
	// if JS1.2 && no arguments, return undefined.
	return numArgs == 0
	    ? JSContext::getUndefinedValue()
	    : args[numArgs - 1];
    } else {
	return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length));
    }
}
jsVal_t
JSNativeArray::jsFunction_pop(JSContext *cx, JSScriptable *thisObj,
			int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t result;
    double length = getLengthProperty(cx, thisObj);

    if (length > 0) {
	length--;

	// Get the to-be-deleted property's value.
	result = getElem(cx, thisObj, (int64_t) length);

	// We don't need to delete the last property, because
	// setLength does that for us.
    } else {
	result = JSContext::getUndefinedValue();
    }
    // necessary to match js even when length < 0; js pop will give a
    // length property to any target it is called on.
    JSScriptRuntime::setProp(cx, JS_OBJECT_TO_VAL(thisObj), "length",
			JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length)), thisObj);
    return result;
}
jsVal_t
JSNativeArray::jsFunction_shift(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t result;
    double length = getLengthProperty(cx, thisObj);

    if (length > 0) {
	int64_t i = 0;

	length--;
 
	// Get the to-be-deleted property's value.
	result = getElem(cx, thisObj, i);
 
	/*
	 * Slide down the array above the first element.  Leave i
	 * set to point to the last element.
	 */
	if (length > 0) {
	    for (i = 1; i <= (int64_t) length; i++) {
	        jsVal_t temp = getElem(cx, thisObj, i);

	        setElem(cx, thisObj, i - 1, temp);
	    }
	}
	// We don't need to delete the last property, because
	// setLength does that for us.
    } else {
	result = JSContext::getUndefinedValue();
    }      
    JSScriptRuntime::setProp(cx, JS_OBJECT_TO_VAL(thisObj), "length",
			JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length)), thisObj);
    return result;
}
jsVal_t
JSNativeArray::jsFunction_unshift(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    double length = getLengthProperty(cx, thisObj);
    int argc = numArgs;
 
    if (numArgs > 0) {
	/*  Slide up the array to make room for args at the bottom */
	if (length > 0) {
	    for (int64_t last = (int64_t) length - 1; last >= 0; last--) {
	        jsVal_t temp = getElem(cx, thisObj, last);

	        setElem(cx, thisObj, last + argc, temp);
	    }
	}
 
	/* Copy from argv to the bottom of the array. */
	for (int i = 0; i < numArgs; i++) {
	    setElem(cx, thisObj, i, args[i]);
	}
 
	/* Follow Perl by returning the new array length. */
	length += (double) numArgs;
	JSScriptRuntime::setProp(cx, JS_OBJECT_TO_VAL(thisObj), "length",
			JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length)), thisObj);
    }
    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length));
}
jsVal_t
JSNativeArray::jsFunction_splice(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    /* create an empty Array to return. */
    JSScriptable *scope = getTopLevelScope(cx, (JSNativeFunction *) funObj);
    jsVal_t result
		= JS_OBJECT_TO_VAL(
			JSScriptRuntime::newObject(cx, scope, "Array",
						0, NULL));
    int argc = numArgs;

    if (argc == 0) {
	return result;
    }
    cx->pushLocalVal(result);

    double length = getLengthProperty(cx, thisObj);
 
    /* Convert the first argument into a starting index. */
    double begin = JSScriptRuntime::toInteger(cx, args[0]);
    double end;
    double delta;
    double count;
 
    if (begin < 0.0) {
	begin += length;
	if (begin < 0.0) {
	    begin = 0.0;
	}
    } else if (begin > length) {
	begin = length;
    }
    argc--;
 
    /* Convert the second argument from a count into a fencepost index. */
    delta = length - begin;
 
    if (numArgs == 1) {
	count = delta;
	end = length;
    } else {
	count = JSScriptRuntime::toInteger(cx, args[1]);
	if (count < 0) {
	    count = 0;
	} else if (count > delta) {
	    count = delta;
	}
	end = begin + count;
 
	argc--;
    }
 
    int64_t lbegin = (int64_t) begin;
    int64_t lend = (int64_t) end;
 
    /* If there are elements to remove, put them into the return value. */
    if (count > 0) {
	if (count == 1
	    && (cx->getLanguageVersion() == JSContext::VERSION_1_2)) {
	    /*
	     * JS lacks "list context", whereby in Perl one turns the
	     * single scalar that's spliced out into an array just by
	     * assigning it to @single instead of $single, or by using it
	     * as Perl push's first argument, for instance.
	     *
	     * JS1.2 emulated Perl too closely and returned a non-Array for
	     * the single-splice-out case, requiring callers to test and
	     * wrap in [] if necessary.  So JS1.3, default, and other
	     * versions all return an array of length 1 for uniformity.
	     */
	    result = getElem(cx, thisObj, lbegin);
	} else {
	    for (int64_t last = lbegin; last < lend; last++) {
	        JSScriptable *resultArray = JS_VAL_TO_OBJECT(result);

	        jsVal_t temp = getElem(cx, thisObj, last);

	        setElem(cx, resultArray, last - lbegin, temp);
	    }
	}
    } else if (count == 0
	       && cx->getLanguageVersion() == JSContext::VERSION_1_2) {
	/* Emulate C JS1.2; if no elements are removed, return undefined. */            result = JSContext::getUndefinedValue();
    }
 
    /* Find the direction (up or down) to copy and make way for argv. */
    delta = argc - count;
 
    if (delta > 0) {
	for (int64_t last = (int64_t) length - 1; last >= lend; last--) {
	    jsVal_t temp = getElem(cx, thisObj, last);

	    setElem(cx, thisObj, last + (int64_t) delta, temp);
	}
    } else if (delta < 0) {
	for (int64_t last = lend; last < length; last++) {
	    jsVal_t temp = getElem(cx, thisObj, last);

	    setElem(cx, thisObj, last + (int64_t) delta, temp);
	}
    }
 
    /* Copy from argv into the hole to complete the splice. */
    int argoffset = numArgs - argc;

    for (int i = 0; i < argc; i++) {
	setElem(cx, thisObj, lbegin + i, args[i + argoffset]);
    }
 
    /* Update length in case we deleted elements from the end. */
    JSScriptRuntime::setProp(cx, JS_OBJECT_TO_VAL(thisObj), "length",
			JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, length + delta)),
			thisObj);
    cx->popLocalVals(1);
    return result;
}
/*
 * Python-esque sequence operations.
 */  
jsVal_t
JSNativeArray::jsFunction_concat(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    /* Concat tries to keep the definition of an array as general
     * as possible; if it finds that an object has a numeric
     * 'length' property, then it treats that object as an array.
     * This treats string atoms and string objects differently; as
     * string objects have a length property and are accessible by
     * index, they get exploded into arrays when added, while
     * atomic strings are just added as strings.
     */

    // create an empty Array to return.
    JSScriptable *scope = getTopLevelScope(cx, (JSNativeFunction *) funObj);
    JSScriptable *result = JSScriptRuntime::newObject(cx, scope, "Array",
						0, NULL);
    double length;
    int64_t slot = 0;

    cx->pushLocalVal(JS_OBJECT_TO_VAL(result));
    /* Put the target in the result array; only add it as an array
     * if it looks like one.
     */
    if (hasLengthProperty(cx, JS_OBJECT_TO_VAL(thisObj))) {
	length = getLengthProperty(cx, thisObj);

	// Copy from the target object into the result
	for (slot = 0; slot < length; slot++) {
	    jsVal_t temp = getElem(cx, thisObj, slot);

	    setElem(cx, result, slot, temp);
	}
    } else {
	setElem(cx, result, slot++, JS_OBJECT_TO_VAL(thisObj));
    }
 
    /* Copy from the arguments into the result.  If any argument
     * has a numeric length property, treat it as an array and add
     * elements separately; otherwise, just copy the argument.
     */
    for (int i = 0; i < numArgs; i++) {
	if (hasLengthProperty(cx, args[i])) {
	    // hasLengthProperty => instanceOf Scriptable.
	    JSScriptable *arg = (JSScriptable *) JS_VAL_TO_OBJECT(args[i]);

	    length = getLengthProperty(cx, arg);
	    for (int64_t j = 0; j < length; j++, slot++) {
	        jsVal_t temp = getElem(cx, arg, j);

	        setElem(cx, result, slot, temp);
	    }
	} else {
	    setElem(cx, result, slot++, args[i]);
	}
    }
    cx->popLocalVals(1);
    return JS_OBJECT_TO_VAL(result);
}
jsVal_t
JSNativeArray::jsFunction_slice(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    JSScriptable *scope = getTopLevelScope(cx, (JSNativeFunction *) funObj);
    JSScriptable *result
		= JSScriptRuntime::newObject(cx, scope, "Array", 0, NULL);
    double length = getLengthProperty(cx, thisObj);
 
    double begin = 0;
    double end = length;
 
    cx->pushLocalVal(JS_OBJECT_TO_VAL(result));
    if (numArgs > 0) {
	begin = JSScriptRuntime::toInteger(cx, args[0]);
	if (begin < 0) {
	    begin += length;
	    if (begin < 0) {
	        begin = 0;
	    }
	} else if (begin > length) {
	    begin = length;
	}
 
	if (numArgs > 1) {
	    end = JSScriptRuntime::toInteger(cx, args[1]);
	    if (end < 0) {
	        end += length;
	        if (end < 0) {
	            end = 0;
		}
	    } else if (end > length) {
	        end = length;
	    }
	}
    }
 
    int64_t lbegin = (int64_t) begin;
    int64_t lend = (int64_t) end;
    for (int64_t slot = lbegin; slot < lend; slot++) {
	jsVal_t temp = getElem(cx, thisObj, slot);

	setElem(cx, result, slot - lbegin, temp);
    }
 
    cx->popLocalVals(1);
    return JS_OBJECT_TO_VAL(result);
}
jsVal_t
JSNativeArray::toStringHelper(JSContext *cx, JSScriptable *thisObj,
			jsBool_t toSource)
{
    /* It's probably redundant to handle long lengths in this
     * function; StringBuffers are limited to 2^31 in java.
     */

    int64_t length = (int64_t) getLengthProperty(cx, thisObj);

    JSStringBuffer result(cx);

    if (cx->iterating == NULL) {
	cx->iterating = new (cx) JSIterating(cx, 31);
    }
    jsBool_t iterating = cx->iterating->get(thisObj) == TRUE;
 
    // whether to return '4,unquoted,5' or '[4, "quoted", 5]'
    jsString_t separator;
 
    if (toSource) {
        result.append("[");
        separator = ", ";
    } else {
        separator = ",";
    }      
    jsBool_t haslast = FALSE;
    int64_t i = 0;

    if (!iterating) {
        for (i = 0; i < length; i++) {
            if (i > 0) {
                result.append(separator);
	    }
            jsVal_t elem = getElem(cx, thisObj, i);

            if (elem == JS_VAL_NULL || elem == JS_VAL_VOID) {
                haslast = FALSE;
                continue;
            }
            haslast = TRUE;

            if (JS_VAL_IS_STRING(elem)) {
                if (toSource) {
                    result.append("\"");

		    jsString_t s = JSScriptRuntime::escapeString(cx, 
						JS_VAL_TO_STRING(elem));
                    result.append(s);
		    JS_DeleteZoneObj(cx, s);
                    result.append("\"");
                } else {
                    result.append(JS_VAL_TO_STRING(elem));
                }
            } else {
                /* wrap changes to cx.iterating in a try/finally
                 * so that the reference always gets removed, and
                 * we don't leak memory.  Good place for weak
                 * references, if we had them.  */
		JSException *ex;

		if ((ex = enterTry(cx)) == NULL) {
                    cx->iterating->put(thisObj, TRUE);

		    jsString_t s = JSScriptRuntime::toString(cx, elem);

                    result.append(s);
		    JS_DeleteZoneObj(cx, s);
		    cx->exceptionHandler->exitTry();
		}
                cx->iterating->remove(thisObj);
            }      
        }
    }
 
    if (toSource) {
        //for [,,].length behavior; we want toString to be symmetric.
        if (!haslast && i > 0) {
            result.append(", ]");
        } else {
            result.append("]");
	}
    }
    return JS_STRING_TO_VAL(JS_NewGCString(cx, result.toString()));
}
 
double
JSNativeArray::getLengthProperty(JSContext *cx, JSScriptable *obj)
{
    jsVal_t val;

    // These will both give numeric lengths within Uint32 range.
    if (obj->isInstanceOf(JSNativeString::className)) {
	(void) ((JSNativeString *) obj)->jsGet_length(cx, &val);

	return JS_VAL_TO_NUMBER(val);
    }
    if (obj->isInstanceOf(JSNativeArray::className)) {
	((JSNativeArray *) obj)->jsGet_length(cx, &val);

	return JS_VAL_TO_NUMBER(val);
    }
    return (double) JSScriptRuntime::toUint32(cx,
			JSScriptRuntime::getProp(cx, JS_OBJECT_TO_VAL(obj),
						"length", obj));
}
jsBool_t
JSNativeArray::hasLengthProperty(JSContext *cx, jsVal_t obj)
{
    if (!JS_VAL_IS_OBJECT(obj) || obj ==JS_VAL_VOID) {
        return FALSE;
    }
    JSScriptable *sobj = JS_VAL_TO_OBJECT(obj);
    
    if (sobj->isInstanceOf(JSNativeString::className)
	|| sobj->isInstanceOf(JSNativeArray::className)) {
        return TRUE;
    }
    // XXX some confusion as to whether or not to walk to get the length
    // property.  Pending review of js/[new ecma submission] treatment
    // of 'arrayness'.
 
    jsVal_t property = JSScriptRuntime::getProp(cx, JS_OBJECT_TO_VAL(sobj),
						"length", sobj);

    return JS_VAL_IS_NUMBER(property);
}
jsVal_t
JSNativeArray::getElem(JSContext *cx, JSScriptable *target, int64_t index)
{
    if (index > INT_MAX) {
	double id[4];		// jsChar_t id[32]; use double to align

	sprintf((jsChar_t *) id, "%lld", index);
	return JSScriptRuntime::getElem(cx, JS_OBJECT_TO_VAL(target),
					JS_STRING_TO_VAL((jsChar_t *) id),
					target);
    } else {
	return JSScriptRuntime::getElem(cx, target, (int) index);
    }
}
void
JSNativeArray::setElem(JSContext *cx, JSScriptable *target, int64_t index,
			jsVal_t value)
{
    if (index > INT_MAX) {
	double id[4];		// jsChar_t id[32]; use double to align

	sprintf((jsChar_t *) id, "%lld", index);
	JSScriptRuntime::setElem(cx, JS_OBJECT_TO_VAL(target),
			JS_STRING_TO_VAL((jsChar_t *) id), value, target);
    } else {
	JSScriptRuntime::setElem(cx, target, (int) index, value);
    }
}
double
JSNativeArray::qsortCompare(JSContext *cx, jsVal_t jsCompare, jsVal_t x,
			jsVal_t y, JSScriptable *scope)
{
    jsVal_t undef = JS_VAL_VOID;

    // sort undefined to end
    if (undef == x || undef == y) {
	if (undef != x)
	    return -1;
	if (undef != y)
	    return 1;
	return 0;
    }

    if (jsCompare == JS_VAL_NULL) {
	// if no compare function supplied, sort lexicographically
	jsString_t a = JSScriptRuntime::toString(cx, x);
	jsString_t b = JSScriptRuntime::toString(cx, y);

	int result = strcmp(a, b);
	
	JS_DeleteZoneObj(cx, a);
	JS_DeleteZoneObj(cx, b);
	return (double) result;
    } else {
	// assemble args and call supplied JS compare function
	// OPT: put this argument creation in the caller and reuse it.
	// XXX what to do when compare function returns NaN?  ECMA states
	// that it's then not a 'consistent compararison function'... but
	// then what do we do?  Back out and start over with the generic
	// compare function when we see a NaN?  Throw an error?
	jsVal_t args[2] = { x, y };

	// for now, just ignore it:
	double d = JSScriptRuntime::toNumber(cx,
			JSScriptRuntime::call(cx, jsCompare, JS_VAL_NULL,
					TableSize(args), args, scope));

	return (d == d) ? d : 0;
    }
}
void
JSNativeArray::qsort(JSContext *cx, jsVal_t jsCompare, jsVal_t *working,
		int lo, int hi, JSScriptable *scope)
{
    jsVal_t pivot;
    int i, j;
    int a, b;

    while (lo < hi) {
	i = lo;
	j = hi;
	a = i;
	pivot = working[a];
	while (i < j) {
	    for(;;) {
	        b = j;
	        if (qsortCompare(cx, jsCompare, working[j], pivot, 
	                         scope) <= 0)
	            break;
	        j--;
	    }
	    working[a] = working[b];
	    while (i < j && qsortCompare(cx, jsCompare, working[a],
	                                 pivot, scope) <= 0)
	    {
	        i++;
	        a = i;
	    }
	    working[b] = working[a];
	}
	working[a] = pivot;
	if (i - lo < hi - i) {
	    qsort(cx, jsCompare, working, lo, i - 1, scope);
	    lo = i + 1;
	} else {
	    qsort(cx, jsCompare, working, i + 1, hi, scope);
	    hi = i - 1;
	}
    }
}
// A version that knows about long indices and doesn't use
// a working array.  Probably will never be used.
void
JSNativeArray::qsort_extended(JSContext *cx, jsVal_t jsCompare,
			JSScriptable *target, int64_t lo, int64_t hi)
{
    jsVal_t pivot;
    int64_t i, j;
    int64_t a, b;

    while (lo < hi) {
	i = lo;
	j = hi;
	a = i;
	pivot = getElem(cx, target, a);
	while (i < j) {
	    for(;;) {
	        b = j;
	        if (qsortCompare(cx, jsCompare, getElem(cx, target, j),
	                         pivot, target) <= 0)
	            break;
	        j--;
	    }
	    setElem(cx, target, a, getElem(cx, target, b));
	    while (i < j && qsortCompare(cx, jsCompare,
	                                 getElem(cx, target, a), 
	                                 pivot, target) <= 0)
	    {
	        i++;
	        a = i;
	    }
	    setElem(cx, target, b, getElem(cx, target, a));
	}
	setElem(cx, target, a, pivot);
	if (i - lo < hi - i) {
	    qsort_extended(cx, jsCompare, target, lo, i - 1);
	    lo = i + 1;
	} else {
	    qsort_extended(cx, jsCompare, target, i + 1, hi);
	    hi = i - 1;
	}
    }
}
jsBool_t
JSNativeArray::denseIsSet(int index, uint32_t *denseBits)
{
    int numBits = sizeof (*denseBits) * 8;

    return ((denseBits[index / numBits] & (0x1 << (index % numBits))) != 0);
}
void
JSNativeArray::denseClr(int index, uint32_t *denseBits)
{
    int numBits = sizeof (*denseBits) * 8;

    denseBits[index / numBits] &= ~(0x1 << (index % numBits));
}
void
JSNativeArray::denseSet(int index, uint32_t *denseBits)
{
    int numBits = sizeof (*denseBits) * 8;

    denseBits[index / numBits] |= 0x1 << (index % numBits);
}

void
JSNativeArray::defineClass(JSContext *cx, JSScriptable *scope)
{
    JSScriptable *proto = new (cx) JSNativeArray();

    JSScriptableObject::defineClass(cx, scope, proto, jsConstructor,
				finishInit);
}
jsBool_t
JSNativeArray::jsGet_length(JSContext *cx, jsVal_t *val)
{
    *val = JSScriptRuntime::toValue(cx, (double) length);
    return TRUE;
}
void
JSNativeArray::jsSet_length(JSContext *cx, jsVal_t val)
{
    /* XXX do we satisfy this?
     * 15.4.5.1 [[Put]](P, V):
     * 1. Call the [[CanPut]] method of A with name P.
     * 2. If Result(1) is false, return.
     * ?
     */ 

    if (!JS_VAL_IS_NUMBER(val)) {
        cx->exceptionHandler->throwEx(
                cx,
                JSContext::reportRuntimeError(cx,
                    JSContext::getMessage(cx, "msg.arraylength.bad")));
    }

    int64_t longVal = JSScriptRuntime::toUint32(cx, val);
    double d = JSScriptRuntime::toNumber(cx, val);

    if (longVal != d) {
        cx->exceptionHandler->throwEx(
                cx,
                JSContext::reportRuntimeError(cx,
                    JSContext::getMessage(cx, "msg.arraylength.bad")));
    }

    if (longVal < length) {
        // remove all properties between longVal and length
        if (length - longVal > 0x1000) {
            // assume that the representation is sparse
	    int numIds;
            jsVal_t *e = getIds(cx, &numIds); // will only find in object itself

            for (int i = 0; i < numIds; i++) {
                if (JS_VAL_IS_STRING(e[i])) {
                    // > MAXINT will appear as string
                    jsString_t id = JS_VAL_TO_STRING(e[i]);
                    double d = JSScriptRuntime::toNumber(cx, id);

#ifdef VX
		    if (!isNan(d) && d < length) {
#else
		    if (!isnan(d) && d < length) {
#endif
			JSScriptableObject::del(cx, id);
		    }
                    continue;
                }
                int index = JS_VAL_TO_INT(e[i]);

                if (index >= longVal) {
                    del(cx, index);
		}
            }
        } else {
            // assume a dense representation
            for (int64_t i = longVal; i < length; i++) {
                // only delete if defined in the object itself
                if (hasElem(cx, this, i)) {
                    JSScriptRuntime::del(cx, JS_OBJECT_TO_VAL(this),
				JS_DOUBLE_TO_VAL(
					JS_NewGCDouble(cx, (double) i)));
		}
            }
        }
    }
    length = longVal;
}
jsBool_t
JSNativeArray::hasElem(JSContext *cx, JSScriptable *target, int64_t index)
{
    if (index > INT_MAX) {
	jsChar_t id[32];

	sprintf(id, "%lld", index);
	return target->has(cx, id, target);
    } else {
	return target->has(cx, (int) index, target);
    }
}
jsBool_t
JSNativeArray::isInstanceOf(const jsString_t className)
{
    if (className == JSNativeArray::className) return TRUE;
    return JSScriptableObject::isInstanceOf(className);
}
jsString_t
JSNativeArray::getClassName(JSContext *cx)
{
    return className;
}
jsBool_t
JSNativeArray::get(JSContext *cx, int index, JSScriptable *start,
		jsVal_t *value)
{
    if (dense != NULL && 0 <= index && index < denseLength) {
	if (denseIsSet(index, denseBits)) {
  	    *value = dense[index];
	    return TRUE;
	}
	return FALSE;
    }
    return JSScriptableObject::get(cx, index, start, value);
}
jsBool_t
JSNativeArray::has(JSContext *cx, int index, JSScriptable *start)
{
    if (dense != NULL && 0 <= index && index < denseLength) {
        return denseIsSet(index, denseBits);
    }
    return JSScriptableObject::has(cx, index, start);
}
void
JSNativeArray::put(JSContext *cx, jsString_t id, JSScriptable *start,
		jsVal_t value)
{
    // only set the array length if given an array index (ECMA 15.4.0)

    // try to get an array index from id
    double d = JSScriptRuntime::toNumber(cx, id);

    if (JSScriptRuntime::toUint32(d) == d
	&& strcmp(JSScriptRuntime::numberToString(cx, d, 10), id) == 0
	&& length <= d && d != 4294967295.0) {
        length = (int64_t) d + 1;
    }
    JSScriptableObject::put(cx, id, start, value);
}
void
JSNativeArray::put(JSContext *cx, int index, JSScriptable *start,
		jsVal_t value)
{
    // only set the array length if given an array index (ECMA 15.4.0)
 
    if (length <= index) {
        // avoid overflowing index!
        length = (int64_t) index + 1;
    }
 
    if (dense != NULL && 0 <= index && index < denseLength) {
        dense[index] = value;
	denseSet(index, denseBits);
        return;
    }
    JSScriptableObject::put(cx, index, start, value);
}
void
JSNativeArray::del(JSContext *cx, int index)
{
    if (dense != NULL && 0 <= index && index < denseLength) {
        dense[index] = JS_VAL_VOID;
	denseClr(index, denseBits);
        return;
    }
    JSScriptableObject::del(cx, index);
}
 
jsVal_t *
JSNativeArray::getIds(JSContext *cx, int *numIds)
{
    int numSuperIds;
    jsVal_t *superIds = JSScriptableObject::getIds(cx, &numSuperIds);

    if (dense == NULL) {
	*numIds = numSuperIds;
        return superIds;
    }
    int count = 0;
    int last = denseLength;
    int i; // avoid compile error on vxWorks

    if (last > length) {
        last = (int) length;
    }
    for (i = last - 1; i >= 0; i--) {
        if (denseIsSet(i, denseBits)) {
            count++;
	}
    }
    count += numSuperIds;
    *numIds = count;
    if (count == 0) {
	return NULL;
    }

    jsVal_t *result = (jsVal_t *) JS_NewZoneObj(cx, sizeof (*result) * count);

    memcpy(result, superIds, sizeof (*result) * numSuperIds);
    for (i = last - 1; i >= 0; i--) {
        if (denseIsSet(i, denseBits)) {
            result[--count] = JSScriptRuntime::toValue(cx, i);
	}
    }
    if (superIds != NULL) {
	JS_DeleteZoneObj(cx, superIds);
    }

    return result;
}
jsVal_t
JSNativeArray::getDefaultValue(JSContext *cx, jsType_t hint)
{
    if (hint == JS_T_Number) {
        if (cx->getLanguageVersion() == JSContext::VERSION_1_2) {
            return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, (double) length));
	}
    }
    return JSScriptableObject::getDefaultValue(cx, hint);
}
void
JSNativeArray::mark(JSContext *cx)
{
    JSGCHeap *gcHeap = &cx->runtime->gcHeap;

    if (gcHeap->isMarkObj(this)) return;

    JSScriptableObject::mark(cx);
    if (dense != NULL) {
	gcHeap->markObj(dense);
	for (int i = 0; i < denseLength; ++i) {
	    JS_MarkVal(cx, dense[i]);
	}
    }
    if (denseBits != NULL) {
	gcHeap->markObj(denseBits);
    }
}

JSNativeArray::JSNativeArray(JSContext *cx, jsVal_t *array, int arrayLength)
{
#define Roundup(a, u)           ((((a) + ((u) - 1)) / (u)) * (u))
    /* gc safe */
    dense = NULL;
    denseBits = NULL;

    denseLength = arrayLength;
    dense = (jsVal_t *) JS_NewGCObj(cx, sizeof (*dense) * denseLength);
    memcpy(dense, array,  sizeof (*dense) * denseLength);

    int numBits = sizeof (*denseBits) * 8;
    int size = Roundup(denseLength, numBits) / numBits;

    denseBits = (uint32_t *) JS_NewGCObj(cx, sizeof (*denseBits) * size);
    memset(denseBits, 0xff, sizeof (*denseBits) * size);
    length = (int64_t) arrayLength;
#undef Roundup
}
JSNativeArray::JSNativeArray(JSContext *cx, int64_t length)
{
#define Roundup(a, u)           ((((a) + ((u) - 1)) / (u)) * (u))
    int intLength = (int) length;

    /* gc safe */
    dense = NULL;
    denseBits = NULL;
    if (intLength == length && intLength > 0) {
        if (intLength > maximumDenseLength) {
            intLength = maximumDenseLength;
	}
	denseLength = intLength;
	dense = (jsVal_t *) JS_NewGCObj(cx, sizeof (*dense) * intLength);
	for (int i = 0; i < intLength; ++i) {
	    dense[i] = JS_VAL_VOID;
	}
	int numBits = sizeof (*denseBits) * 8;
 	int size = Roundup(intLength, numBits) / numBits;

	denseBits = (uint32_t *) JS_NewGCObj(cx, sizeof (*denseBits) * size);
	memset(denseBits, 0, sizeof (*denseBits) * size);
    } else {
	denseLength = 0;
    }
    this->length = length;
#undef Roundup
}
JSNativeArray::JSNativeArray()
{
    denseLength = 0;
    dense = NULL;
    denseBits = NULL;
    length = 0;
}

