/*
 * 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 */
#ifdef VX
#include <vxWorks.h>
#include <taskLib.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include "js-new.h"
#include "js-scriptruntime.h"
#include "js-exceptionhandler.h"
#include "js-api.h"
#include "js-shell.h"

//#include "ewb-dbg.h"

/* Class data */
JSShell *JSShell::global;
jsBool_t JSShell::quitting = FALSE;
const jsString_t JSShell::className = "global";

int EwbSetLog = 0;
int EwbTracePointExt = 0;
int ewbPrintOn = 0;
int EwbTracePointEnt = 0;
int EwbSetTrace = 0;
int ewbJsErrorDetails = 0;
int ewbTimeTrace = 0;

/* Procedures */
/**
 * Print the string values of its arguments.
 *
 * This method is defined as a JavaScript function.
 * Note that its arguments are of the "varargs" form, which
 * allows it to handle an arbitrary number of arguments
 * supplied to the JavaScript function.
 *
 */
jsVal_t JSShell::print(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    for (int i = 0; i < numArgs; i++) {
        if (i > 0) {
            printf(" ");
        }

        // Convert the arbitrary JavaScript value into a string form.
        jsString_t s = cx->toString(args[i]);

        printf("%s", s);
    }
    printf("\n");
    return JS_VAL_VOID;
}

/**
  * Get and set the language version.
  *
  * This method is defined as a JavaScript function.
  */
jsVal_t JSShell::version(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    double result = (double)cx->getLanguageVersion();

    if (numArgs > 0) {
        double d = cx->toNumber(args[0]);

        cx->setLanguageVersion((int)d);
    }
    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, result));
}

/**
  * Load and execute a set of JavaScript source files.
  *
  * This method is defined as a JavaScript function.
  *
  */
jsVal_t JSShell::load(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj)
{
    for (int i = 0; i < numArgs; i++) {
        processSource(cx, cx->toString(args[i]));
    }
    return JS_VAL_VOID;
}

/**
 * Print a usage message.
 */
void JSShell::p(jsString_t s)
{
    printf("%s\n", s);
}

void JSShell::usage(jsString_t s)
{
    char buf[256];

    sprintf(buf, "Didn't understand \"%s\".", s);
    p(buf);
    p("Valid arguments are:");
    p("-version 100|110|120|130");
    exit(1);
}

/**
  * Parse arguments.
  */
char **JSShell::processOptions(JSContext * cx, int argc, char **argv, int *pargc)
{
    for (int i = 0; i < argc; i++) {
        char *arg = argv[i];

        if (arg[0] != '-') {
            *pargc = argc - i;
            return argv + i;
        }
        if (strcmp(arg, "-version") == 0) {
            if (++i == argc) {
                usage(arg);
            }
            double d = JSScriptRuntime::toNumber(cx, argv[i]);

            if (d != d) {
                usage(arg);
            }
            cx->setLanguageVersion((int)d);
            continue;
        }
        usage(arg);
    }
    *pargc = 0;
    return NULL;
}

/**
 * Evaluate JavaScript source.
 *
 * @param cx the current context
 * @param filename the name of the file to compile, or null
 *                 for interactive mode.
 */
void JSShell::processSource(JSContext * cx, jsString_t filename)
{
    if (filename == NULL) {
        jsString_t sourceName = "<stdin>";
        int lineno = 1;
        jsBool_t hitEOF = FALSE;

        JSContext::exit();
        do {
            int startline = lineno;
            JSException *ex;

            printf("js> ");
            JSContext::enter(cx);
            if ((ex = enterTry(cx)) == NULL) {
                jsString_t source = NULL;
                // Collect lines of source to compile.

                while (TRUE) {
                    char newline[1024];

                    char *result = gets(newline);

                    if (result == NULL) {
                        hitEOF = TRUE;
                        break;
                    }
                    if (result[0] == '\x1b') {
                        if (source != NULL) {
                            JS_DeleteZoneObj(cx, (void *)source);
                        }
                        goto Exit;
                    }
                    int sourceLength = source != NULL ? strlen(source) : 0;
                    int newlineLength = strlen(newline);
                    jsString_t newSource = (jsString_t) JS_NewZoneObj(cx,
                            sizeof(*newSource)
                            * (sourceLength + newlineLength + 1));

                    if (newSource != NULL) {
                        memcpy(newSource, source, sourceLength);
                        memcpy(newSource + sourceLength, newline, newlineLength);
                        newSource[sourceLength + newlineLength] = '\0';
                        if (source != NULL) {
                            JS_DeleteZoneObj(cx, (void *)source);
                        }
                        source = newSource;
                    }
                    else {
                        printf("JSShell::processSource: 0\n");
                        break;
                    }
                    lineno++;
                    if (cx->stringIsCompilableUnit(source)) {
                        break;
                    }
                }
                if (source != NULL) {
                    jsVal_t result = cx->evaluateString(global, source,
                            sourceName, startline);
                    if (result != JS_VAL_VOID) {
                        jsString_t s = JSScriptRuntime::toString(cx, result);

                        printf("%s\n", s);
                        JS_DeleteZoneObj(cx, s);
                    }
                    JS_DeleteZoneObj(cx, (void *)source);
                }
              Exit:
                cx->exceptionHandler->exitTry();
            }
            JSContext::exit();
            if (quitting) {
                // The user executed the quit() function.
                break;
            }
        } while (!hitEOF);
        JSContext::enter(cx);
        printf("\n");
    }
    else {
        struct stat buf;
        jsString_t string;
        int fd;

        stat(filename, &buf);
        string = (jsString_t) JS_NewZoneObj(cx, buf.st_size + sizeof(*string));
        fd = open(filename, O_RDONLY, 0644);
        if (fd >= 0) {
            read(fd, string, buf.st_size);
            string[buf.st_size] = '\0';
            close(fd);

            JSException *ex;

            if ((ex = enterTry(cx)) == NULL) {
                // Here we evalute the entire contents of the file as
                // a script. Text is printed only if the print() function
                // is called.
                cx->evaluateString(global, string, filename, 1);
                cx->exceptionHandler->exitTry();
            }
            JS_DeleteZoneObj(cx, string);
        }
        else {
            printf("Couldn't open file %s.\n", filename);
        }
    }
}

void JSShell::defineClass(JSContext * cx)
{
    class ShellAdapter {
      public:
        static jsVal_t jsFunction_quit(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj) {
            JSShell::quit();
            return JS_VAL_VOID;
        } static jsVal_t jsFunction_help(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj) {
            JSShell::help(JSScriptRuntime::toString(cx, args[0]));
            return JS_VAL_VOID;
        }
        static jsVal_t jsFunction_gc(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj) {
            JSShell::gc(cx);
            return JS_VAL_VOID;
        }
        static jsVal_t jsFunction_sleep(JSContext * cx, JSScriptable * thisObj, int numArgs, jsVal_t * args, JSFunction * funObj) {
            if (numArgs > 0) {
                JSShell::sleep((int)JSScriptRuntime::toNumber(cx, args[0]));
            }
            return JS_VAL_VOID;
        }
    };

    static JSScriptableObject::jsFunction_t functions[] = {
#define Item(name, length)      { #name,  name, length }
#define ItemAdapter(name, clazz, length)        \
                                { #name,  &clazz::jsFunction_##name, length }
        Item(print, -1),
        ItemAdapter(quit, ShellAdapter, 0),
        Item(version, -1),
        Item(load, -1),
        ItemAdapter(help, ShellAdapter, 1),
        ItemAdapter(gc, ShellAdapter, 0),
        ItemAdapter(sleep, ShellAdapter, 0),
#undef  ItemAdapter
#undef  Item
    };

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

/**
 * Print a help message.
 *
 * This method is defined as a JavaScript function.
 */
void JSShell::help(jsString_t s)
{
    p("");
    p("Command                Description");
    p("=======                ===========");
    p("help()                 Display usage and help messages. ");
    p("load(['foo.js', ...])  Load JavaScript source files named by ");
    p("                       string arguments. ");
    p("print([expr ...])      Evaluate and print expressions. ");
    p("quit()                 Quit the shell. ");
    p("version([number])      Get or set the JavaScript version number.");
    p("gc()		      Execute gc.");
    p("sleep(milliseconds)    sleep in milliseconds.");
    p("");
}

/**
  * Quit the shell.
  *
  * This only affects the interactive mode.
  *
  * This method is defined as a JavaScript function.
  */
void JSShell::quit()
{
    quitting = TRUE;
}

void JSShell::gc(JSContext * cx)
{
    cx->runtime->gcHeap.gc();
}

#ifdef VX
extern "C" int sysClkRateGet();
#endif
void JSShell::sleep(int milliseconds)
{
#ifdef VX
    taskDelay(milliseconds * sysClkRateGet() / 1000);
#else
    struct timespec timespec;

    timespec.tv_sec = milliseconds / 1000;
    timespec.tv_nsec = (milliseconds % 1000) * 1000;
    nanosleep(&timespec, NULL);
#endif
}

/**
  * Main entry point.
  *
  * Process arguments as would a normal Java program. Also
  * create a new Context and associate it with the current thread.
  * Then set up the execution environment and begin to
  * execute scripts.
  */
#ifdef VX
void JSShell::shell(int argc, char **argv)
#else
void JSShell::main(int argc, char **argv)
#endif
{
#ifdef VX
    quitting = FALSE;
#endif
    argc -= 1;
    argv += 1;

    Initialize_JSInterpreter();

    JSRuntime *runtime = new(NULL) JSRuntime(1024 * 2000 /* 400 *//* 800 */ );

    JSContext *cx = new(runtime) JSContext(runtime);

    JSContext::enter(cx);

    // A bit of shorthand: since Shell extends ScriptableObject,
    // we can make it the global object.
    global = new(cx) JSShell();
    cx->runtime->addRoot(global);

    // Initialize the standard objects (Object, Function, etc.)
    // This must be done before scripts can be executed.
    cx->initStandardObjects(global);

    // Define some global functions particular to the shell. Note
    // that these functions are not part of ECMA.

    global->defineClass(cx);

    argv = processOptions(cx, argc, argv, &argc);

    // Set up "arguments" in the global scope to contain the command
    // line arguments after the name of the script to execute
    jsVal_t *array = NULL;
    int arrayLength = 0;

    if (argc > 0) {
        int i = argc;
        char **p = argv;

        array = (jsVal_t *) JS_NewZoneObj(cx, sizeof(*array) * argc);
        while (--i >= 0) {
            array[arrayLength] = JS_STRING_TO_VAL(JS_NewGCString(cx, *p));
            cx->pushLocalVal(array[arrayLength]);
            arrayLength += 1;
            p += 1;
        }
    }

    JSScriptable *argsObj = cx->newArray(global, array, arrayLength);
    cx->popLocalVals(arrayLength);

    global->defineProperty(cx, "arguments", JS_OBJECT_TO_VAL(argsObj), JS_PA_DontEnum);

    processSource(cx, argc == 0 ? NULL : argv[0]);

    cx->runtime->removeRoot(global);
    JSContext::exit();

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

/**
 * Return name of this class, the global object.
 *
 * This method must be implemented in all concrete classes
 * extending ScriptableObject.
 *
 * @see com.netscape.javascript.Scriptable#getClassName
 */
jsString_t JSShell::getClassName(JSContext * cx)
{
    return className;
}

#ifdef VX
int run_shell(char *filename)
{
    int argc;
    char *argv[2];

    argc = 1;
    argv[argc - 1] = "run_shell";
    if (filename != NULL) {
        argc += 1;
        argv[argc - 1] = filename;
    }
    JSShell::shell(argc, argv);
    return 0;
}
#else
int main(int argc, char **argv)
{
    JSShell::main(argc, argv);
    return 0;
}
#endif
