/*
 * 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 "js-iterating.h"
#include "js-stringbuffer.h"
#include "js-scriptruntime.h"
#include "js-exceptionhandler.h"
#include "js-functionobject.h"
#include "js-nativeobject.h"

/* Internal data */
const jsString_t JSNativeObject::className = "Object";

/* Procedures */
void JSNativeObject::finishInit(JSContext * cx, JSScriptable * scope, JSFunctionObject * ctor, JSScriptable * proto)
{
    static jsFunction_t functions[] = {
#define	Item(name, length)	{ #name,  jsFunction_##name, length }
        Item(toString, -1), Item(valueOf, -1),
        Item(hasOwnProperty, -1), Item(propertyIsEnumerable, -1),
        Item(isPrototypeOf, -1),
#undef	Item
    };

    ((JSScriptableObject *) proto)->defineFunctionProperties(cx, className, TableSize(functions), functions, JS_PA_DontEnum);

    jsVal_t value;

    (void)proto->get(cx, "valueOf", proto, &value);
    ((JSFunctionObject *) JS_VAL_TO_OBJECT(value))->setLength(0);
}
jsVal_t JSNativeObject::jsConstructor(JSContext * cx, int numArgs, jsVal_t * args, JSFunction * ctorObj, jsBool_t inNewExpr)
{
    if (!inNewExpr) {
        return JS_OBJECT_TO_VAL(ctorObj->construct(cx, ((JSNativeFunction *) ctorObj)->getParentScope(cx), numArgs, args));
    }
    if (numArgs == 0 || args[0] == JS_VAL_NULL || args[0] == JS_VAL_VOID) {
        return JS_OBJECT_TO_VAL(new(cx) JSNativeObject());
    }
    return JS_OBJECT_TO_VAL(JSScriptRuntime::toObject(cx, ((JSNativeFunction *) ctorObj)->getParentScope(cx), args[0]));
}

jsVal_t JSNativeObject::jsFunction_toString(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    if (cx->getLanguageVersion() != JSContext::VERSION_1_2) {
        jsChar_t buf[128];

        sprintf(buf, "[object %s]", thisObj->getClassName(cx));
        return JS_STRING_TO_VAL(JS_NewGCString(cx, buf));
    }
    return toSource(cx, thisObj, numArgs, args, funObj);
}

jsVal_t JSNativeObject::jsFunction_valueOf(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    return JS_OBJECT_TO_VAL(thisObj);
}

jsVal_t JSNativeObject::jsFunction_hasOwnProperty(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    if (numArgs != 0) {
        jsString_t s = JSScriptRuntime::toString(cx, args[0]);

        if (thisObj->has(cx, s, thisObj)) {
            JS_DeleteZoneObj(cx, s);
            return JS_VAL_TRUE;
        }
        JS_DeleteZoneObj(cx, s);
    }
    return JS_VAL_FALSE;
}

jsVal_t JSNativeObject::jsFunction_propertyIsEnumerable(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    JSException *ex;

    if ((ex = enterTry(cx)) == NULL) {
        if (numArgs != 0) {
            jsString_t name = JSScriptRuntime::toString(cx, args[0]);

            if (thisObj->has(cx, name, thisObj)) {
                int a = ((JSScriptableObject *) thisObj)
                        ->getAttributes(cx, name, thisObj);
                if ((a & JS_PA_DontEnum) == 0) {
                    JS_DeleteZoneObj(cx, name);
                    cx->exceptionHandler->exitTry();

                    return JS_VAL_TRUE;
                }
            }
            JS_DeleteZoneObj(cx, name);
        }
        cx->exceptionHandler->exitTry();
    }
    return JS_VAL_FALSE;
}

jsVal_t JSNativeObject::jsFunction_isPrototypeOf(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    if (numArgs != 0 && JS_VAL_IS_OBJECT(args[0])) {
        JSScriptable *v = JS_VAL_TO_OBJECT(args[0]);

        do {
            v = v->getPrototype(cx);
            if (v == thisObj) {
                return JS_VAL_TRUE;
            }
        } while (v != NULL);
    }
    return JS_VAL_FALSE;
}

jsVal_t JSNativeObject::toSource(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    JSScriptable *m = thisObj;

    if (cx->iterating == NULL) {
        cx->iterating = new(cx) JSIterating(cx, 31);
    }

    if (cx->iterating->get(m) == TRUE) {
        return JS_STRING_TO_VAL(JS_NewGCString(cx, "{}"));  // stop recursion
    }
    else {
        JSStringBuffer result(cx, "{");
        int numIds;
        jsVal_t *ids = m->getIds(cx, &numIds);

        for (int i = 0; i < numIds; i++) {
            if (i > 0) {
                result.append(", ");
            }

            jsVal_t id = ids[i];
            jsString_t idString = JSScriptRuntime::toString(cx, id);
            jsVal_t p;

            JS_VAL_IS_STRING(id)
                    ? (void)m->get(cx, JS_VAL_TO_STRING(id), m, &p)
                    : (void)m->get(cx, (int)JSScriptRuntime::toNumber(cx, id), m, &p);
            if (JS_VAL_IS_STRING(p)) {
                jsString_t s = JSScriptRuntime::toString(cx, p);
                jsString_t escapeString = JSScriptRuntime::escapeString(cx, s);

                result.append(idString);
                result.append(":\"");
                result.append(escapeString);
                result.append("\"");
                JS_DeleteZoneObj(cx, escapeString);
                JS_DeleteZoneObj(cx, s);
            }
            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) {
                    jsString_t s = JSScriptRuntime::toString(cx, p);

                    cx->iterating->put(m, TRUE);    // stop recursion.
                    result.append(idString);
                    result.append(":");
                    result.append(s);
                    JS_DeleteZoneObj(cx, s);
                    cx->exceptionHandler->exitTry();
                }
                cx->iterating->remove(m);
            }
            JS_DeleteZoneObj(cx, idString);
        }
        result.append("}");
        return JS_STRING_TO_VAL(JS_NewGCString(cx, result.toString()));
    }
}
void JSNativeObject::defineClass(JSContext * cx, JSScriptable * scope)
{
    JSScriptable *proto = new(cx) JSNativeObject();

    JSScriptableObject::defineClass(cx, scope, proto, jsConstructor, finishInit);
}
jsBool_t JSNativeObject::isInstanceOf(const jsString_t className)
{
    if (className == JSNativeObject::className)
        return TRUE;
    return JSScriptableObject::isInstanceOf(className);
}

jsString_t JSNativeObject::getClassName(JSContext * cx)
{
    return JSNativeObject::className;
}
