/*
 * Shell.java --
 *
 *	Implements the start up shell for Tcl.
 *
 * Copyright (c) 1997 Cornell University.
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * RCS: @(#) $Id: Shell.java,v 1.1.1.1 1998/10/14 21:09:18 cvsadmin Exp $
 */

package tcl.lang;

import java.util.*;
import java.io.*;

/*
 * The Shell class is similar to the Tclsh program: you can use it to
 * execute a Tcl script or enter Tcl command interactively at the
 * command prompt.
 */

public class Shell {

/*
 *----------------------------------------------------------------------
 *
 * main --
 *
 *	Main program for tclsh and most other Tcl-based applications.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	This procedure initializes the Tcl world and then starts
 *	interpreting commands; almost anything could happen, depending
 *	on the script being interpreted.
 *
 *----------------------------------------------------------------------
 */

public static void 
main(
    String args[]) 	// Array of command-line argument strings.
{
    String fileName = null;

    /*
     * Create the interpreter. This will also create the built-in
     * Tcl commands.
     */

    Interp interp = new Interp();

    /*
     * Make command-line arguments available in the Tcl variables "argc"
     * and "argv".  If the first argument doesn't start with a "-" then
     * strip it off and use it as the name of a script file to process.
     * We also set the argv0 and tcl_interactive vars here.
     */

    if ((args.length > 0) && !(args[0].startsWith("-"))) {
	fileName = args[0];
    }

    TclObject argv = TclList.newInstance();
    argv.preserve();
    try {
	int i = 0;
	int argc = args.length;
	if (fileName == null) {
	    interp.setVar("argv0", "tcl.lang.Shell", TCL.GLOBAL_ONLY);
	    interp.setVar("tcl_interactive", "1", TCL.GLOBAL_ONLY);
	} else {
	    interp.setVar("argv0", fileName, TCL.GLOBAL_ONLY);
	    interp.setVar("tcl_interactive", "0", TCL.GLOBAL_ONLY);
	    i++;
	    argc--;
	}
	for (; i < args.length; i++) {
	    TclList.append(interp, argv, 
		    TclString.newInstance(args[i]));
	}
	interp.setVar("argv", argv, TCL.GLOBAL_ONLY);
	interp.setVar("argc", java.lang.Integer.toString(argc),
		TCL.GLOBAL_ONLY);
    } catch (TclException e) {
	throw new TclRuntimeError("unexpected TclException: " + e);
    } finally {
	argv.release();
    }

    /*
     * Normally we would do application specific initialization here.
     * However, that feature is not currently supported.
     */

    /*
     * If a script file was specified then just source that file
     * and quit.
     */

    if (fileName != null) {
	try {
	    interp.evalFile(fileName);
	} catch (TclException e) {
	    int code = e.getCompletionCode();
	    if (code == TCL.RETURN) {
		code = interp.updateReturnInfo();
		if (code != TCL.OK) {
		    System.err.println("command returned bad code: " + code);
		}
	    } else if (code == TCL.ERROR) {
		System.err.println(interp.getResult().toString());
	    } else {
		System.err.println("command returned bad code: " + code);
	    }
	}

	/*
	 * Note that if the above interp.evalFile() returns the main
	 * thread will exit.  This may bring down the VM and stop
	 * the execution of Tcl.
	 *
	 * If the script needs to handle events, it must call
	 * vwait or do something similar.
	 *
	 * Note that the script can create AWT widgets. This will
	 * start an AWT event handling thread and keep the VM up. However,
	 * the interpreter thread (the same as the main thread) would
	 * have exited and no Tcl scripts can be executed.
	 */
    }

    if (fileName == null) {
	/*
	 * We are running in interactive mode. Start the ConsoleThread
	 * that loops, grabbing stdin and passing it to the interp.
	 */

	ConsoleThread consoleThread = new ConsoleThread(interp);
	consoleThread.setDaemon(true);
	consoleThread.start();

	/*
	 * Loop forever to handle user input events in the command line.
	 */

	Notifier notifier = interp.getNotifier();
	while (true) {
	    /*
	     * process events forevet until "exit" is called.
	     */

	    notifier.doOneEvent(TCL.ALL_EVENTS);
	}
    } else {
	System.exit(0);
    }
}
} // end Shell


/*
 *----------------------------------------------------------------------
 *
 * ConsoleThread --
 *
 * This class implements the Console Thread: it is started by
 * tcl.lang.Shell if the user gives no initial script to evaluate, or
 * when the -console option is specified. The console thread loops
 * forever, reading from the standard input, executing the user input
 * and writing the result to the standard output.
 *
 *----------------------------------------------------------------------
 */

class ConsoleThread extends Thread {

/*
 * Interpreter associated with this console thread.
 */

Interp interp;

/*
 * Temporarily holds refcount on the results of the interactive
 * commands so that an object command returned the java::* calls
 * can be used even when they are not saved in variables.
 */

Vector historyObjs;

/*
 * Collect the user input in this buffer until it forms a complete Tcl
 * command.
 */

StringBuffer sbuf;

/*
 * Used to for interactive input/output
 */

private Channel in;
private Channel out;
private Channel err;

/*
 *----------------------------------------------------------------------
 *
 * ConsoleThread --
 *
 *	Create a ConsoleThread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Member fields are initialized.
 *
 *----------------------------------------------------------------------
 */

ConsoleThread(
    Interp i)			// Initial value for interp.
{
    interp = i;
    sbuf = new StringBuffer();
    historyObjs = new Vector();

    out = new StdChannel("stdout");
    err = new StdChannel("stderr");
}

/*
 *----------------------------------------------------------------------
 *
 * run --
 *
 *	Called by the JVM to start the execution of the console thread.
 *	It loops forever to handle user inputs.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	This method never returns. During its execution, some
 *	TclObjects may be locked inside the historyObjs vector.
 *	Remember to free them at "appropriate" times!
 *
 *----------------------------------------------------------------------
 */

public synchronized void
run()
{
    put(out, "% ");

    while (true) {
	/*
	 * Loop forever to collect user inputs in a StringBuffer.
	 * When we have a complete command, then execute it and print
	 * out the results.
	 *
	 * The loop is broken under two conditions: (1) when EOF is
	 * received inside getLine(). (2) when the "exit" command is
	 * executed in the script.
	 */

	TclObject prompt;

        sbuf.append(getLine());

	/*
	 * We have a complete command now. Execute it.
	 */

	if (Interp.commandComplete(sbuf.toString())) {
	    ConsoleEvent evt = new ConsoleEvent(interp, sbuf.toString());
	    interp.getNotifier().queueEvent(evt, TCL.QUEUE_TAIL);
	    evt.sync();

	    if (evt.evalResult != null) {
		String s = evt.evalResult.toString();
		if (s.length() > 0) {
		    putLine(out, s);
		}

		/*
		 * The "history limit" controls how many TclObject's
		 * we record from the results of the Console inputs.
		 */

		int limit = 0;
//		int limit = 10;

		try {
		    TclObject histLimit = interp.getVar("historyLimit",
			    TCL.GLOBAL_ONLY | TCL.DONT_THROW_EXCEPTION);
		    if (histLimit != null) {
			limit = TclInteger.get(interp, histLimit);
		    }
		} catch (TclException e) {
		    /*
		     * histLimit doesn't exist or contains an invalid integer.
		     * Ignore it.
		     */
		}

		/*
		 * This cleans up all intermediate objects that
		 * are no longer referenced in Tcl. This check is
		 * needed to clean up object references in the
		 * string->object translation table.
		 */

		if (limit > 0) {
		    historyObjs.addElement(evt.evalResult);
		} else {
		    evt.evalResult.release();
		}

		if (historyObjs.size() > limit && historyObjs.size() > 0) {
		    TclObject cobj =(TclObject)historyObjs.elementAt(0);
		    historyObjs.removeElementAt(0);
		    cobj.release();
		}
	    } else {
		TclException e = evt.evalException;
		int code = e.getCompletionCode();

		check_code: {
		    if (code == TCL.RETURN) {
			code = interp.updateReturnInfo();
			if (code == TCL.OK) {
			    break check_code;
			}
		    }

		    switch (code) {
		    case TCL.ERROR:
			putLine(err, interp.getResult().toString());
			break;
		    case TCL.BREAK:
			putLine(err, "invoked \"break\" outside of a loop");
			break;
		    case TCL.CONTINUE:
			putLine(err, "invoked \"continue\" outside of a loop");
			break;
		    default:
			putLine(err, "command returned bad code: " + code);
		    }
		}
	    }

	    sbuf.setLength(0);
	    try {
		prompt = interp.getVar("tcl_prompt1", TCL.GLOBAL_ONLY);
	    } catch (TclException e) {
		prompt = null;
	    }
	    if (prompt != null) {
		try {
		    interp.eval(prompt.toString(), TCL.GLOBAL_ONLY);
		} catch (TclException e) {
		    put(out, "% ");
		}
	    } else {
		put(out, "% ");
	    }
	} else {

	    /*
	     * We don't have a complete command yet. Print out a level 2
	     * prompt message and wait for further inputs.
	     */

	    try {
		prompt = interp.getVar("tcl_prompt2", TCL.GLOBAL_ONLY);
	    } catch (TclException e) {
		prompt = null;
	    }
	    if (prompt != null) {
		try {
		    interp.eval(prompt.toString(), TCL.GLOBAL_ONLY);
		} catch (TclException e) {
		    put(out, "> ");
		}
	    } else {
		put(out, "> ");
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * getLine --
 *
 *	Gets a new line from System.in.
 *
 * Result:
 *	The new line of user input, including the trailing carriage
 *	return character.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private String
getLine()
{
    StringBuffer sbuf = new StringBuffer();

    /*
     * On Unix platforms, System.in.read() will block the delivery of
     * of AWT events. We must make sure System.in.available() is larger
     * than zero before attempting to read from System.in. Since
     * there is no asynchronous IO in Java, we must poll the System.in
     * every 100 milliseconds.
     */

    boolean sysInBlocks = false;

    try {
	/*
	 * There is no way to tell whether System.in will block AWT
	 * threads, so we assume it does block if we can use
	 * System.in.available().
	 */

	System.in.available();
	sysInBlocks = true;
    } catch (Exception e) {
	/*
	 * System.in.available() causes an exception -- it's probably
	 * no supported on this platform (e.g. MS Java SDK). We assume
	 * sysInBlocks is false and let the user suffer ...
	 */

	sysInBlocks = false;
    }

    if (sysInBlocks) {
	try {
	    /*
	     * Wait until there are inputs from System.in. On Unix,
	     * this usually means the user has pressed the return key.
	     */

	    while (System.in.available() == 0) {
		Thread.currentThread().sleep(100);
	    }
	} catch (InterruptedException e) {
	    System.exit(0);
	} catch (EOFException e) {
	    System.exit(0);
	} catch (IOException e) {
	    e.printStackTrace();
	    System.exit(0);
	}
    }

    /* 
     * Loop until user presses return or EOF is reached.
     */
    char c2 = ' ';

    while (true) {
	char c = ' ';

	try {
	    int i = System.in.read();
	    if (i == -1) {
		if (sbuf.length() == 0) {
		    System.exit(0);
		} else {
		    return sbuf.toString();
		}
	    }
	    c = (char)i;

	    /*
	     * Temporary hack until Channel drivers are complete.  Convert
	     * the Windows \r\n to \n.
	     */

	    if (c == '\r') {
	        i = System.in.read();
	        if (i == -1) {
		    if (sbuf.length() == 0) {
		        System.exit(0);
		    } else {
		        return sbuf.toString();
		    }
		}
		c2 = (char)i; 
		if (c2 == '\n') {
		  c = c2;
		} else {
		  sbuf.append(c);
		  c = c2;
		}
	    }
	} catch (IOException e) {
	    /*
	     * IOException shouldn't happen when reading from
	     * System.in. The only exceptional state is the EOF event,
	     * which is indicated by a return value of -1.
	     */

	    e.printStackTrace();
	    System.exit(0);
	}

	sbuf.append(c);
	if (c == '\n') {
	    return sbuf.toString();
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * putLine --
 *
 *	Prints a string into the given channel with a trailing carriage
 *	return.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private void
putLine(
    Channel chan, 		// The channel to print to.
    String s)			// The string to print.
{
    put(chan, s);
    put(chan, "\n");
}

/*
 *----------------------------------------------------------------------
 *
 * put --
 *
 *	Prints a string into the given channel without a trailing
 *	carriage return.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

private
void put(
    Channel chan,  		// The channel to print to.
    String s)			// The string to print.
{
    try {
	out.write(interp, s);
    } catch (Exception  e) {
	throw new TclRuntimeError("unexpected Exception " + e);
    }
}
} // end ConsoleThread
