/*
 * 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-scriptruntime.h"
#include "js-nativefunction.h"
#include "js-propertyexception.h"
#include "js-notafunctionexception.h"
#include "js-exceptionhandler.h"
#include "js-flattenedobjectp.h"

/* Procedures */
JSScriptable *JSFlattenedObject::getBase(JSContext * cx, JSScriptable * obj, jsString_t s)
{
    JSScriptable *m = obj;

    while (m != NULL) {
        if (m->has(cx, s, obj)) {
            return m;
        }
        m = m->getPrototype(cx);
    }
    return NULL;
}

JSScriptable *JSFlattenedObject::getBase(JSContext * cx, JSScriptable * obj, int index)
{
    JSScriptable *m = obj;

    while (m != NULL) {
        if (m->has(cx, index, obj)) {
            return m;
        }
        m = m->getPrototype(cx);
    }
    return NULL;
}

jsBool_t JSFlattenedObject::hasProperty(JSContext * cx, jsVal_t id)
{
    jsString_t stringId = JSScriptRuntime::toString(cx, id);
    jsString_t s = JSScriptRuntime::getStringId(cx, JS_STRING_TO_VAL(stringId));
    jsBool_t result;

    if (s == NULL) {
        result = getBase(cx, obj, JSScriptRuntime::getIntId(cx, JS_STRING_TO_VAL(stringId))) != NULL;
    }
    else {
        result = getBase(cx, obj, s) != NULL;
        JS_DeleteZoneObj(cx, s);
    }
    JS_DeleteZoneObj(cx, stringId);

    return result;
}

jsVal_t JSFlattenedObject::getProperty(JSContext * cx, jsVal_t id)
{
    JSScriptable *m = obj;
    jsString_t s = JSScriptRuntime::getStringId(cx, id);
    int index = (s == NULL) ? JSScriptRuntime::getIntId(cx, id) : 0;
    jsVal_t result;

    for (;;) {
        jsVal_t ok = (s == NULL) ? m->get(cx, index, obj, &result) : m->get(cx, s, obj, &result);
        if (ok)
            break;

        m = m->getPrototype(cx);
        if (m == NULL) {
            result = JS_VAL_VOID;
            break;
        }
    }
    if (s != NULL)
        JS_DeleteZoneObj(cx, s);

    return result;
}

void JSFlattenedObject::putProperty(JSContext * cx, jsVal_t id, jsVal_t value)
{
    jsString_t s = JSScriptRuntime::getStringId(cx, id);
    JSScriptable *x;

    if (s == NULL) {
        int index = JSScriptRuntime::getIntId(cx, id);

        x = getBase(cx, obj, index);
        if (x == NULL) {
            x = obj;
        }
        x->put(cx, index, obj, value);
        return;
    }
    x = getBase(cx, obj, s);
    if (x == NULL) {
        x = obj;
    }
    x->put(cx, s, obj, value);
    JS_DeleteZoneObj(cx, s);
}

jsBool_t JSFlattenedObject::deleteProperty(JSContext * cx, jsVal_t id)
{
    jsString_t s = JSScriptRuntime::getStringId(cx, id);

    if (s == NULL) {
        int index = JSScriptRuntime::getIntId(cx, id);
        JSScriptable *base = getBase(cx, obj, index);

        if (base == NULL) {
            return TRUE;
        }
        base->del(cx, index);
        return !base->has(cx, index, base);
    }
    JSScriptable *base;

    base = getBase(cx, obj, s);
    if (base == NULL) {
        return TRUE;
    }
    base->del(cx, s);

    jsBool_t result = !base->has(cx, s, base);

    JS_DeleteZoneObj(cx, s);

    return result;
}

jsVal_t *JSFlattenedObject::getIds(JSContext * cx, int *numIds)
{
    JSFGetIdsHashtable h(&cx->allocator);
    JSScriptable *m = obj;
    int count = 0;

    while (m != NULL) {
        int n;
        jsVal_t *e = m->getIds(cx, &n);

        for (int i = 0; i < n; i++) {
            cx->pushLocalVal(e[i]);
            count += 1;
            h.put(e[i], 0);
        }
        JS_DeleteZoneObj(cx, e);
        m = m->getPrototype(cx);
    }
    JSFGetIdsHashtable::HashtableKeyEnumerator keys = h.keys();
    jsVal_t *result = (jsVal_t *) JS_NewZoneObj(cx, sizeof(*result) * h.size());
    int index = 0;
    while (keys.hasMoreElements()) {
        jsVal_t elem = keys.nextElement();

        result[index++] = elem;
    }
    cx->popLocalVals(count);
    *numIds = h.size();

    return result;
}

jsVal_t JSFlattenedObject::call(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args)
{
    if (!obj->isInstanceOf(JSNativeFunction::className)) {
        cx->exceptionHandler->throwEx(new(cx) JSNotAFunctionException());
    }
    return JSScriptRuntime::call(cx, JS_OBJECT_TO_VAL(obj), JS_OBJECT_TO_VAL(thisObj), numArgs, args, obj);
}

JSScriptable *JSFlattenedObject::construct(JSContext * cx, int numArgs, jsVal_t * args)
{
    if (!obj->isInstanceOf(JSNativeFunction::className)) {
        cx->exceptionHandler->throwEx(new(cx) JSNotAFunctionException());
    }
    return JSScriptRuntime::newObject(cx, JS_OBJECT_TO_VAL(obj), numArgs, args, NULL);
}

jsVal_t JSFlattenedObject::callMethod(JSContext * cx, jsVal_t id, int numArgs, jsVal_t * args)
{
    if (!hasProperty(cx, id)) {
        cx->exceptionHandler->throwEx(new(cx) JSPropertyException(JSContext::getMessage(cx, "msg.prop.not.found")));
    }
    jsVal_t o = getProperty(cx, id);

    if (JS_VAL_IS_OBJECT(o)) {
        return call(cx, obj, numArgs, args);
    }
    cx->exceptionHandler->throwEx(new(cx) JSNotAFunctionException());
    /* NOT REACHED */
    return JS_VAL_VOID;
}

JSFlattenedObject::JSFlattenedObject(JSScriptable * object)
{
    this->obj = object;
}
