/*
 * 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.
 */
/*
 *  JavaScript Interpreter
 *  The JavaScript Interpreter is used for executing JavaScript programs
 *  in EWB that control user events on buttons, restrict inputs on textlines
 *  and so on.
 *
 *  Developmened by: Institute of Artificial Intelligence & Robotics
 *		   of Xi'an Jiaotong University
 *
 *  Create by:      Tian Wenqiang
 *  Last modified:  2001-11-13 19:46
 */

/*
   JavaScript Interpreter Application Programming Interface implement file
*/

#include <stdio.h>
#include "js-api.h"
#include "js-new.h"
#include "js-context.h"
#include "js-scriptruntime.h"
#include "js-nativehost.h"
#include "js-nativescript.h"
#include "js-stringreader.h"
#include "js-script.h"
#include "js-hosterrorreporter.h"
#include "js-exceptionhandler.h"
#include "js-config.h"
#include "js-interpreter.h"

#include "js-gcheap.h"
/* #include "ewb-mem.h" */
#include "js-mem.h"

#if defined CONF_WPAD
#include "js-dbg.h"
#endif

extern int ewbCompileMemShow;

/* Check object type */
/* Procedures */
/* Type operations ***********************************************************/
jsBool_t JS_IsObject(jsVal_t value)
{
	return JS_VAL_IS_OBJECT(value);
}

jsBool_t JS_IsInteger(jsVal_t value)
{
	return JS_VAL_IS_INT(value);
}

jsBool_t JS_IsDouble(jsVal_t value)
{
	return JS_VAL_IS_DOUBLE(value);
}

jsBool_t JS_IsString(jsVal_t value)
{
	return JS_VAL_IS_STRING(value);
}

jsBool_t JS_IsBoolean(jsVal_t value)
{
	return JS_VAL_IS_BOOLEAN(value);
}

/* Convert JS value to a specified type value */
jsBool_t JS_ValueToBoolean(jsContext_t context, jsVal_t val)
{
	JSContext *cx = (JSContext *) context;

	return JSScriptRuntime::toBoolean(cx, val);
}

jsInt_t JS_ValueToInteger(jsContext_t context, jsVal_t val)
{
	JSContext *cx = (JSContext *) context;

	return (jsInt_t) JSScriptRuntime::toInteger(cx, val);
}

jsString_t JS_ValueToString(jsContext_t context, jsVal_t val)
{
	JSContext *cx = (JSContext *) context;

	if (JS_VAL_IS_STRING(val))
		return JS_VAL_TO_STRING(val);

	jsString_t s = JSScriptRuntime::toString(cx, val);
	jsString_t result = JS_NewGCString(cx, s);

	JS_DeleteZoneObj(cx, s);
	return result;
}

jsDouble_t JS_ValueToDouble(jsContext_t context, jsVal_t val)
{
	JSContext *cx = (JSContext *) context;

	return JSScriptRuntime::toNumber(cx, val);
}

jsObject_t JS_ValueToObject(jsContext_t context, jsObject_t scope, jsVal_t val)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;

	return JSScriptRuntime::toObject(cx, _scope, val);
}

/* Convert a specified type value to JS value */
jsVal_t JS_ObjectToValue(jsContext_t context, jsObject_t obj)
{
	return JS_OBJECT_TO_VAL(obj);
}

jsVal_t JS_IntegerToValue(jsContext_t context, jsInt_t i)
{
	JSContext *cx = (JSContext *) context;

	return JSScriptRuntime::toValue(cx, (double)i);
}

jsVal_t JS_BooleanToValue(jsContext_t context, jsBool_t b)
{
	return JS_BOOLEAN_TO_VAL(b);
}

jsVal_t JS_DoubleToValue(jsContext_t context, jsDouble_t d)
{
	JSContext *cx = (JSContext *) context;

	return JSScriptRuntime::toValue(cx, d);
}

jsVal_t JS_StringToValue(jsContext_t context, jsString_t s)
{
	JSContext *cx = (JSContext *) context;

	return JS_STRING_TO_VAL(JS_NewGCString(cx, s));
}


/* Runtime and Context *******************************************************/

/* Initialize
Initialize all modules in the JavaScript Interpreter.*/
void Initialize_JSInterpreter()
{
	Initialize_JSConfig();
}

/* Runtime
JS_NewRuntime creates a JavaScript Runtime and JS_DeleteRuntime
destroys a JavaScript Runtime.*/
jsRuntime_t JS_NewRuntime(long maxBytes)
{
	JSRuntime *rt = new JSRuntime(maxBytes);

	return (jsRuntime_t) rt;
}

void JS_DeleteRuntime(jsRuntime_t runtime)
{
	JSRuntime *rt = (JSRuntime *) runtime;

	delete rt;
}

/* Context
JS_NewContext creates a JavaScript execution context and JS_DeleteContext
destroys a JavaScript execution context.*/
jsContext_t JS_NewContext(jsRuntime_t runtime)
{
	JSRuntime *_runtime = (JSRuntime *) runtime;
	JSContext *cx = new(_runtime) JSContext(_runtime);

	return (jsContext_t) cx;
}

void JS_DeleteContext(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;

	cx->destroy(cx->runtime);
}

void JS_CleanupContext(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;

	cx->cleanup();
}

/* Bind a thread and a context
JS_EnterContext binds a thread and a context.
User must execute this procedure before compiling or eveluating JavaScript.*/
void JS_EnterContext(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;
	//JsZoneResetMax();
	JSContext::enter(cx);
}

/* JS_ExitContext unbinds a thread and a context.
User must execute this procedure after compiling and eveluating JavaScript.
Since the status that multiple contexts are binded influences starting GC,
user must execute the function as soon as possible after compiling or
eveluating JavaScript. */
void JS_ExitContext(jsContext_t context)
{
	JsZoneResetMax();
	JSContext::exit();
}

/* Initialize Standard objects
The function adds standard objects to scope object such as Math,
Date and so on. */
void JS_InitStandardObjects(jsContext_t context, jsObject_t scope)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;

	cx->initStandardObjects((JSScriptableObject *) _scope);
}

/* Object ********************************************************************/
/* Object
JS_NewObject creates a new object according to arguments.
User must call JS_DefineProperties and JS_DefineFunctions functions as
necessary. */
jsObject_t JS_NewGlobalObject(jsContext_t context, jsClass_t * clazz)
{
	JSContext *cx = (JSContext *) context;
	JSNativeHost *result = new(cx) JSNativeHost(clazz);

	return (jsObject_t) result;
}

/*JS_ConstructObject create a new object with proto object and call construct
  with args.  The proto object is created by JS_DefineClass.*/
jsObject_t JS_NewPlaneObject(jsContext_t context, jsObject_t scope, jsObject_t proto)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;
	JSScriptable *_proto = (JSScriptable *) proto;

	JSNativeHost *result = new(cx) JSNativeHost(_proto);

	result->setParentScope(cx, _scope);
	if (_proto != NULL) {
		result->setPrototype(cx, _proto);
	}
	else {
		result->setPrototype(cx, JSScriptableObject::getObjectPrototype(cx, _scope));
	}
	return (jsObject_t) result;
}

/*JS_ConstructObjectByName create a new object with name, sucn as "Array",
  "Date" and call construct with args.*/
jsObject_t JS_NewObject(jsContext_t context, jsObject_t scope, jsString_t constructor, int numArgs, jsVal_t * args)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;

	return cx->newObject(_scope, constructor, numArgs, args);
}

/* Define Class
   The function creates a new object with native constructor under the scope.
 The returned object is used as proto of JS_NewObject   to create new object.
*/
jsObject_t JS_DefineClass(jsContext_t context, jsObject_t scope, jsObject_t parent_proto, jsClass_t * clazz, jsConstructor_t constructor, int numProperties, jsProperty_t * properties, int numFunctions, jsFunction_t * functions, int numCtorProperties, jsProperty_t * ctorProperties, int numCtorFunctions, jsFunction_t * ctorFunctions)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;
	JSScriptable *_parent_proto = (JSScriptable *) parent_proto;
	JSNativeHost *result = new(cx) JSNativeHost(clazz);

	cx->pushLocalVal(JS_OBJECT_TO_VAL(result)); // gc safe 20061124

	if (constructor != NULL) {
		JSScriptableObject::defineClass(cx, _scope, result, constructor);
	}
	else {
		result->setPrototype(cx, JSScriptableObject::getObjectPrototype(cx, _scope));
		((JSScriptableObject *) _scope)
				->defineProperty(cx, result->getClassName(cx), JS_OBJECT_TO_VAL(result), JS_PA_DontEnum);
	}
	result->defineProperties(cx, numProperties, properties);
	result->defineFunctionProperties(cx, result->getClassName(cx), numFunctions, functions);

	if (constructor != NULL) {
		JSScriptable *ctor = result->getParentScope(cx);

		((JSScriptableObject *) ctor)->defineProperties(cx, numCtorProperties, ctorProperties);
		((JSScriptableObject *) ctor)->defineFunctionProperties(cx, ctor->getClassName(cx), numCtorFunctions, ctorFunctions);
	}
	if (_parent_proto != NULL) {
		result->setPrototype(cx, _parent_proto);
	}
	cx->popLocalVals(1);    // gc safe 20061124
	return (jsObject_t) result;
}

/* Set object properties
JS_DefineFunctions adds function properties to an object.
JS_DefineProperties adds properties to an object.
*/
void JS_DefineFunctions(jsContext_t context, jsObject_t object, int numFunctions, jsFunction_t * functions)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	((JSScriptableObject *) _object)->defineFunctionProperties(cx, _object->getClassName(cx), numFunctions, functions);
}
void JS_DefineProperties(jsContext_t context, jsObject_t object, int numProperties, jsProperty_t * properties)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	((JSScriptableObject *) _object)->defineProperties(cx, numProperties, properties);
}

/* Compile and execute JavaScript
JS_CompileString compiles the string that is JavaScrit program.
JS_ExecuteScript executes the script that is compiled by JS_CompileString.
JS_EvaluateString compiles the string and executes the script.
JS_CallFunction call a function.
*/
jsScript_t JS_CompileString(jsContext_t context, jsObject_t scope, jsString_t string, jsString_t sourceName, int lineno)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;

	JSStringReader in(string);
	JSException *ex;

	cx->pushLocalVal(JS_OBJECT_TO_VAL(_scope));

	if ((ex = enterTry(cx)) == NULL) {
		JSScript *script = cx->compileReader(_scope, &in, sourceName, lineno);

		if (script != NULL) {
			cx->runtime->addRoot((JSNativeScript *) script);
			cx->exceptionHandler->exitTry();
			cx->popLocalVals(1);
			if (ewbCompileMemShow && JsZoneGetMax() > 0) {
				printf("compile %s used %d bytes.\n", sourceName, JsZoneGetMax());
				JsZoneResetMax();
			}
			return (jsScript_t) script;
		}
		cx->exceptionHandler->exitTry();
	}
#if defined CONF_WPAD
	else {
		ApdJsCompileErrorNotify(context, ex->toString(cx));
	}
#endif
	cx->popLocalVals(1);
	if (ewbCompileMemShow && JsZoneGetMax() > 0) {
		printf("compile %s used %d bytes.\n", sourceName, JsZoneGetMax());
		JsZoneResetMax();
	}
	return NULL;
}

jsVal_t JS_ExecuteScript(jsContext_t context, jsObject_t scope, jsScript_t script)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;
	JSScript *_script = (JSScript *) script;
	JSException *ex;

	if ((ex = enterTry(cx)) == NULL) {
		cx->pushLocalVal(JS_OBJECT_TO_VAL(_scope));
		jsVal_t result = _script->exec(cx, _scope);
		cx->popLocalVals(1);
		cx->exceptionHandler->exitTry();
		if (ewbCompileMemShow && JsZoneGetMax() > 0) {
			printf("execute used %d bytes.\n", JsZoneGetMax());
			JsZoneResetMax();
		}
		return result;
	}
#if defined CONF_WPAD
	else {
		ApdJsExecuteErrorNotify(context, ex->toString(cx));
	}
#endif
	cx->popLocalVals(1);
	if (ewbCompileMemShow && JsZoneGetMax() > 0) {
		printf("execute used %d bytes.\n", JsZoneGetMax());
		JsZoneResetMax();
	}
	return JS_VAL_NULL;
}

jsVal_t JS_EvaluateString(jsContext_t context, jsObject_t scope, jsString_t string, jsString_t sourceName, int lineno)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;
	jsVal_t tmp = cx->evaluateString(_scope, string, sourceName, lineno);
	if (ewbCompileMemShow && JsZoneGetMax() > 0) {
		printf("evaluate %s used %d bytes.\n", sourceName, JsZoneGetMax());
		JsZoneResetMax();
	}
	return tmp;

}

void JS_DeleteScript(jsContext_t context, jsScript_t script)
{
	JSContext *cx = (JSContext *) context;
	JSScript *_script = (JSScript *) script;

	cx->runtime->removeRoot((JSNativeScript *) _script);
}
jsVal_t JS_CallFunction(jsContext_t context, jsObject_t function, jsObject_t thisObject, int numArgs, jsVal_t * args)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_function = (JSScriptable *) function;
	JSScriptable *_thisObject = (JSScriptable *) thisObject;
	JSException *ex;
	jsVal_t tmp;

	if ((ex = enterTry(cx)) == NULL) {  // ichino 20040519
		tmp = ((JSNativeFunction *) _function)
				->call(cx, _function, _thisObject, numArgs, args);
		if (ewbCompileMemShow && JsZoneGetMax() > 0) {
			printf("callfunc used %d bytes.\n", JsZoneGetMax());
			JsZoneResetMax();
		}
		return tmp;
		cx->exceptionHandler->exitTry();
	}
    return JS_VAL_VOID;
}

/* Object property operation
JS_GetTopLevelProp returns property of top level scope.  The function traverses scope chain,
get top level scope and search property.
JS_GetPrototype returns prototype of an object.
JS_PutProperty put an object to scope as a named object.
*/
jsObject_t JS_GetClassPrototype(jsContext_t context, jsObject_t scope, const jsString_t className)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;

	return (jsObject_t) JSScriptableObject::getClassPrototype(cx, _scope, className);
}

jsVal_t JS_GetTopLevelProp(jsContext_t context, jsObject_t scope, jsString_t id)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_scope = (JSScriptable *) scope;
	jsVal_t v;

	(void)JSScriptRuntime::getTopLevelProp(cx, _scope, id, &v);
	return v;
}

jsObject_t JS_GetPrototype(jsContext_t context, jsObject_t object)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	return (jsObject_t) _object->getPrototype(cx);
}

void JS_PutProperty(jsContext_t context, jsObject_t object, jsString_t name, jsVal_t val)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	_object->put(cx, name, _object, val);
}

jsObject_t JS_GetParentScope(jsContext_t context, jsObject_t object)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	return (jsObject_t) _object->getParentScope(cx);
}

/* Object closure
JS_GetContextClosure gets extensive data from a context.
JS_SetObjectContextClosure sets extensive data to a context.
JS_GetObjectClosure gets extensive data from an object.
JS_SetObjectClosure sets extensive data to an object.*/
void *JS_GetContextClosure(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;

	return cx->getClosure();
}
void JS_SetContextClosure(jsContext_t context, void *closure)
{
	JSContext *cx = (JSContext *) context;

	cx->setClosure(closure);
}

void *JS_GetObjectClosure(jsObject_t object)
{
	JSScriptable *_object = (JSScriptable *) object;

	if (_object->isInstanceOf(JSNativeHost::className)) {
		return ((JSNativeHost *) _object)->getClosure();
	}
	return NULL;
}
void JS_SetObjectClosure(jsObject_t object, void *closure)
{
	JSScriptable *_object = (JSScriptable *) object;

	((JSNativeHost *) _object)->setClosure(closure);
}

/* Default class
The function gets a default class.  The default class is used
in user defined class to call the default procedure. */
jsClass_t *JS_DefaultClass()
{
	return JSNativeHost::defaultClass();
}


/* GC ************************************************************************/

/* Root object
JS_AddRoot adds an object as a root object of GC.
JS_RemoveRoot removes an object from root objects of GC.*/
void JS_AddRoot(jsContext_t context, jsObject_t object)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	cx->runtime->addRoot(_object);
}

void JS_RemoveRoot(jsContext_t context, jsObject_t object)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

	cx->runtime->removeRoot(_object);
}

void JS_GC(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;
	cx->runtime->gcHeap.gc();
}
void JS_GCMemFreeShow(jsContext_t context, int mode)
{
	JSContext *cx = (JSContext *) context;
	cx->runtime->gcHeap.ShowGCFreeBlock(mode);
}
void JS_GCMemFreeShowDetail(jsContext_t context, int mode)
{
	JSContext *cx = (JSContext *) context;
	cx->runtime->gcHeap.ShowGCFreeBlockDetail(mode);
}

int JS_GCGetMemFree(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;
	return cx->runtime->gcHeap.GetGCFreeBlock();
}

int JS_GCGetMaxUse(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;
	return cx->runtime->gcHeap.GetGCMaxUseBlock();
}

void JS_GCResetMaxUse(jsContext_t context)
{
	JSContext *cx = (JSContext *) context;
	cx->runtime->gcHeap.ResetGCMaxUseBlock();
}

void JS_ResetByteCodeMaxUse()
{
	JS_ResetRuntimeMaxSize();
}

/* Others ********************************************************************/
/* Branch callback
The function sets branch callback procedure in executing JavaScript
to prevent infinite loop.  The callback is called on backword branch. */
void JS_SetBranchCallback(jsContext_t context, jsBranchProc_t branch)
{
#ifdef BRANCH
	JSContext *cx = (JSContext *) context;

	cx->setBranchCallback(branch);
#endif
}

/* Error callback
The function enables to define and use own error reporting mechanism in
appilications.*/
void JS_SetErrorReporter(jsContext_t context, jsErrorReporter_t * reporter)
{
	JSContext *cx = (JSContext *) context;
	JSHostErrorReporter *errorReporter = new(cx->runtime) JSHostErrorReporter(reporter);
	JSErrorReporter *result = cx->setErrorReporter(errorReporter);

	if (result != NULL) {
		result->destroy(cx->runtime);
	}
}

#ifdef MEASURMENT
void JS_BytecodeCountShow()
{
	JSInterpreter::bytecodeCountShow();
}

void JS_BytecodeCountReset()
{
	JSInterpreter::bytecodeCountReset();
}
#endif

#ifdef STATS
void JS_printGCRoots()
{
	JSContext *cx;
	JSRuntime *_runtime;
	cx = JSContext::getCurrentContext();
	_runtime = _runtime->keepedRuntime;

	printf("num of GCRoots: %d\n", _runtime->numRoots);
	for (int i = 0; i < _runtime->numRoots; ++i) {
		printf("GCRoots type: %s %x\n", _runtime->roots[i]->getClassName(cx), _runtime->roots[i]);
	}
}
#endif

void JS_ShowICodeMemSize()
{
	JS_ShowRuntimeMem();
}

void JS_RemoveAllProperties(jsContext_t context, jsObject_t object)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

    NOT_USED( cx );
	((JSScriptableObject *) _object)->removeAllProperties();
}

void JS_ResetAllProperties(jsContext_t context, jsObject_t object)
{
	JSContext *cx = (JSContext *) context;
	JSScriptable *_object = (JSScriptable *) object;

    NOT_USED( cx );
	((JSScriptableObject *) _object)->removeAllProperties();

	JS_InitStandardObjects(context, object);
}

int JS_SetMaxBytes(size_t size)
{
	//JSGCHeap heap_t;
	//heap_t.Allocator.setMaxBytes(size);
    return 0;
}

#if defined(SUNOS5) || defined(TARGET_OS_LINUX)
void JS_ShowGCMemforSun()
{
	showGCMemforSun();
}

void JS_ChangeGCMemSize(int gc, int bytecode, int compile)
{
	changeGCMemSize(gc, bytecode, compile);
}
#endif
