/*
 * 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-objectadapter.h"
#include "js-stringreader.h"
#include "js-scriptruntime.h"
#include "js-evaluatorexception.h"
#include "js-exceptionhandler.h"
#include "js-runtimeexception.h"
#include "js-functionobject.h"
#include "js-nativescript.h"

/* Internal data */
const jsString_t JSNativeScript::className = "Script";

/* Procedures */
void JSNativeScript::finishInit(JSContext * cx, JSScriptable * scope, JSFunctionObject * ctor, JSScriptable * proto)
{
    class ScriptAdapter:public JSObjectAdapter {
      public:
        static jsVal_t compile(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj) {
            JSScriptable *scriptable;

            scriptable = getScriptable(cx, thisObj, JSNativeScript::className, funObj);

            JSNativeScript *_thisObj = (JSNativeScript *) scriptable;
            jsString_t source = JSScriptRuntime::toString(cx, args[0]);
            jsVal_t result = _thisObj->jsFunction_compile(cx, source);

            JS_DeleteZoneObj(cx, source);
            return result;
        } static jsVal_t exec(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj) {
            JSScriptable *scriptable;

            scriptable = getScriptable(cx, thisObj, JSNativeScript::className, funObj);

            JSNativeScript *_thisObj = (JSNativeScript *) scriptable;

            return _thisObj->jsFunction_exec(cx);
        }

        JS_OA_DefineGetter(name, JSNativeScript)
    };
    static jsFunction_t functions[] = {
#define	ItemAdapter(name, clazz, length)	\
			{ #name,  clazz::name, length }
#define	Item(name, length)	{ #name,  jsFunction_##name, length }
        ItemAdapter(compile, ScriptAdapter, 1),
        ItemAdapter(exec, ScriptAdapter, 0),
        Item(toString, -1),
#undef	Item
#undef	ItemAdapter
    };
    static jsProperty_t properties[] = {
#define ItemG(name, clazz)					      \
	{							       \
	    #name, JS_PA_ReadOnly | JS_PA_Permanent | JS_PA_DontEnum , \
	    &clazz::name##_getter, NULL				 \
	}
        ItemG(name, ScriptAdapter)
#undef ItemG
    };

    ((JSScriptableObject *) proto)->defineFunctionProperties(cx, className, TableSize(functions), functions, JS_PA_DontEnum);
    ((JSScriptableObject *) proto)->defineProperties(cx, TableSize(properties), properties);
}
jsVal_t JSNativeScript::jsConstructor(JSContext * cx, int numArgs, jsVal_t * args, JSFunction * ctorObj, jsBool_t inNewExpr)
{
    jsString_t source = numArgs == 0 ? (jsString_t) "" : JSScriptRuntime::toString(cx, args[0]);
    JSScriptable *scope = cx->ctorScope;
    if (scope == NULL) {
        scope = (JSNativeFunction *) ctorObj;
    }
    JSScript *script = compile(cx, scope, source);

    if (numArgs != 0)
        JS_DeleteZoneObj(cx, source);
    return JS_OBJECT_TO_VAL((JSNativeScript *) script);
}

jsVal_t JSNativeScript::jsFunction_toString(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    JSScript *thisScript = ((JSNativeScript *) thisObj)->script;

    if (thisScript == NULL) {
        thisScript = (JSNativeScript *) thisObj;
    }
    JSScriptable *scope = getTopLevelScope(cx, thisObj);

    jsString_t s = cx->decompileScript(thisScript, scope, 0);
    jsVal_t result = JS_STRING_TO_VAL(JS_NewGCString(cx, s));

    JS_DeleteZoneObj(cx, s);
    return result;
}

JSScript *JSNativeScript::compile(JSContext * cx, JSScriptable * scope, jsString_t source)
{
    JSStringReader reader(source);
    JSException *ex;

    if ((ex = enterTry(cx)) == NULL) {
        int linep;
        jsString_t filename = JSContext::getSourcePosition(cx, &linep);

        if (filename == NULL) {
            filename = "<Script object>";
            linep = 1;
        }
        JSScript *result = cx->compileReader(scope, &reader, filename, linep);

        cx->exceptionHandler->exitTry();
        return result;
    }
    else {
        cx->exceptionHandler->throwEx(new(cx) JSRuntimeException("Unexpected IOException"));
    }
    /* NOT REACHED */
    return NULL;
}

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

    JSScriptableObject::defineClass(cx, scope, proto, jsConstructor, finishInit);
}

jsVal_t JSNativeScript::jsFunction_compile(JSContext * cx, jsString_t source)
{
    script = compile(cx, NULL, source);
    return JS_OBJECT_TO_VAL(this);
}

jsVal_t JSNativeScript::jsFunction_exec(JSContext * cx)
{
    cx->exceptionHandler->throwEx(JSContext::reportRuntimeError(cx, JSContext::getMessage(cx, "msg.cant.call.indirect %s", "exec")));
    /* NOT REACHED */
    return JS_VAL_VOID;
}

jsBool_t JSNativeScript::jsGet_name(JSContext * cx, jsVal_t * value)
{
    *value = JS_STRING_TO_VAL(JS_NewGCString(cx, ""));
    return TRUE;
}

/**
  * Initialize script.
  *   
  * Does nothing here, but scripts will override with code
  * to initialize contained functions, regexp literals, etc.
  */
void JSNativeScript::initScript(JSContext * cx, JSScriptable * scope)
{
}
jsBool_t JSNativeScript::isInstanceOf(const jsString_t className)
{
    if (className == JSNativeScript::className)
        return TRUE;
    return JSNativeFunction::isInstanceOf(className);
}

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

jsVal_t JSNativeScript::exec(JSContext * cx, JSScriptable * scope)
{
    return script == NULL ? JS_VAL_VOID : script->exec(cx, scope);
}

jsVal_t JSNativeScript::call(JSContext * cx, JSScriptable * scope, JSScriptable * thisObj, int numArgs, jsVal_t * args)
{
    return exec(cx, scope);
}

JSScriptable *JSNativeScript::construct(JSContext * cx, JSScriptable * scope, int numArgs, jsVal_t * args)
{
    jsString_t message = JSContext::getMessage(cx, "msg.script.is.not.constructor");
    cx->exceptionHandler->throwEx(JSContext::reportRuntimeError(cx, message));
    /* NOT REACHED */
    return NULL;
}

void JSNativeScript::mark(JSContext * cx)
{
    JSGCHeap *gcHeap = &cx->runtime->gcHeap;

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

    JSScriptableObject::mark(cx);
    if (script != NULL) {
        ((JSNativeScript *) script)->mark(cx);
    }
}

JSNativeScript::JSNativeScript()
{
    script = NULL;
}
