/*
 * 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 "js-utils.h"
#include "js-idenumeration.h"

/* Procedures */
/**
 * This is the enumeration needed by the for..in statement.
 *
 * See ECMA 12.6.3.
 *
 * IdEnumeration maintains a Hashtable to make sure a given
 * id is enumerated only once across multiple objects in a
 * prototype chain.
 *
 * XXX - ECMA delete doesn't hide properties in the prototype,
 * but js/ref does. This means that the js/ref for..in can
 * avoid maintaining a hash table and instead perform lookups
 * to see if a given property has already been enumerated.
 *
 */
void
JSIdEnumeration::changeObject(JSContext *cx, JSScriptable *m)
{
    obj = m;
    if (obj != NULL) {
        array = m->getIds(cx, &arrayLength);
        if (arrayLength == 0) {
            changeObject(cx, obj->getPrototype(cx));
	}
    }
    index = 0;
}
jsVal_t
JSIdEnumeration::getNext(JSContext *cx)
{
    if (obj == NULL) {
        return JS_VAL_NULL;
    }
    jsVal_t result;

    for (;;) {
        if (index == arrayLength) {
	    JS_DeleteZoneObj(cx, array);
            changeObject(cx, obj->getPrototype(cx));
            if (obj == NULL) {
		array = NULL;
                return JS_VAL_NULL;
	    }
        }
        result = array[index++];
        if (JS_VAL_IS_STRING(result)) {
            if (!obj->has(cx, JS_VAL_TO_STRING(result), obj)) {
                continue;   // must have been deleted
	    }
        } else {
            if (!obj->has(cx, JS_VAL_TO_INT(result), obj)) {
                continue;   // must have been deleted
	    }
        }
        if (!used.containsKey(result)) {
            break;
        }
    }
    return result;
}
jsBool_t
JSIdEnumeration::hasMoreElements()
{
    return next != JS_VAL_NULL;
}
jsVal_t
JSIdEnumeration::nextElement()
{
    jsVal_t result = next;

    // only key used; 'next' as value for convenience
    used.put(next, next);

    next = getNext(cx);
    return result;
}
void
JSIdEnumeration::mark(JSContext *cx)
{
    int i = 0;

    if( array != NULL ){
	while (i < arrayLength ) {
	    JS_MarkVal(cx, array[i++]);
	}
    }
    JSIdEnumHashtable::HashtableKeyEnumerator keys = used.keys();

    while (keys.hasMoreElements()) {
	jsVal_t elem = keys.nextElement();

	JS_MarkVal(cx, elem);
    }
}
void
JSIdEnumeration::destroy(JSContext *cx)
{
    if (array != NULL) {
	JS_DeleteZoneObj(cx, array);
    }
    this->~JSIdEnumeration();
    JSZoneNew::destroy(cx);
}

JSIdEnumeration::JSIdEnumeration(JSContext *cx, JSScriptable *m)
			: used(cx, 27)
{
    arrayLength = 0;
    array = NULL;
    this->cx = cx;
    changeObject(cx, m);
    next = getNext(cx);
}
JSIdEnumeration::~JSIdEnumeration()
{
}

