/*
 * TraceCmd.java --
 *
 *	This file implements the Tcl "trace" command.
 *
 * 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: TraceCmd.java,v 1.1.1.1 1998/10/14 21:09:19 cvsadmin Exp $
 *
 */

package tcl.lang;

import java.util.*;

/*
 * The TraceCmd class implements the Command interface for specifying
 * a new Tcl command. The method cmdProc implements the built-in Tcl
 * command "trace" which is used to manupilate variable traces.  See
 * user documentation for more details.
 */

class TraceCmd implements Command {

/*
 * Valid sub-commands for the trace command.
 */

static final private String validCmds[] = {
    "variable",
    "vdelete",
    "vinfo",
};

static final private int OPT_VARIABLE 	= 0;
static final private int OPT_VDELETE   	= 1;
static final private int OPT_VINFO 		= 2;

/*
 * An array for quickly generating the Tcl strings corresponding to
 * the TCL.TRACE_READS, TCL.TRACE_WRITES and TCL.TRACE_UNSETS flags.
 */

private static TclObject opStr[] = initOptStr();

/*
 *----------------------------------------------------------------------
 *
 * initOptStr --
 *
 *	This static method is called when the TraceCmd class is loaded
 *	into the VM. It initializes the opStr array.
 *
 * Results:
 *	Initial value for opStr.
 *
 * Side effects:
 *	The TclObjects stored in opStr are preserve()'ed.
 *
 *----------------------------------------------------------------------
 */

private static TclObject[]
initOptStr()
{
    TclObject strings[] = new TclObject[8];
    strings[0] = TclString.newInstance("error");
    strings[1] = TclString.newInstance("r");
    strings[2] = TclString.newInstance("w");
    strings[3] = TclString.newInstance("rw");
    strings[4] = TclString.newInstance("u");
    strings[5] = TclString.newInstance("ru");
    strings[6] = TclString.newInstance("wu");
    strings[7] = TclString.newInstance("rwu");

    for (int i = 0; i < 8; i++) {
	strings[i].preserve();
    }

    return strings;
}

/*
 *----------------------------------------------------------------------
 *
 * cmdProc --
 *
 *	This procedure is invoked as part of the Command interface to 
 *	process the "trace" Tcl command.  See the user documentation for
 *	details on what it does.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

public void cmdProc(
    Interp interp,	/* Current interpreter. */
    TclObject argv[])	/* Argument list. */
throws 
    TclException 	/* A standard Tcl exception. */
{
    int len;

    if (argv.length < 2) {
	throw new TclNumArgsException(interp, 1, argv, 
		"option arg ?arg ...?");
    }
    int opt = TclIndex.get(interp, argv[1], validCmds, "option", 0);

    switch (opt) {
    case OPT_VARIABLE:
    case OPT_VDELETE:
	if (argv.length != 5) {
	    if (opt == OPT_VARIABLE) {
		throw new TclNumArgsException(interp, 1, argv, 
			"variable name ops command");
	    } else {
		throw new TclNumArgsException(interp, 1, argv, 
			"vdelete name ops command");
	    }
	}

	int flags = 0;
	String ops = argv[3].toString();
	len = ops.length();
    check_ops: {
	    for (int i = 0; i < len; i++) {
		switch (ops.charAt(i)) {
		case 'r':
		    flags |= TCL.TRACE_READS;
		    break;
		case 'w':
		    flags |= TCL.TRACE_WRITES;
		    break;
		case 'u':
		    flags |= TCL.TRACE_UNSETS;
		    break;
		default:
		    flags = 0;
		    break check_ops;
		}
	    }
	}

	if (flags == 0) {
	    throw new TclException(interp, "bad operations \"" + argv[3] +
		    "\": should be one or more of rwu");
	}

	if (opt == OPT_VARIABLE) {
	    CmdTraceProc trace = new CmdTraceProc(argv[4].toString(), flags);
	    interp.varFrame.traceVar(argv[2], trace, flags);
	} else {
	    /*
	     * Search through all of our traces on this variable to
	     * see if there's one with the given command.  If so, then
	     * delete the first one that matches.
	     */
		
	    Vector traces = interp.varFrame.getTraces(argv[2], 0);
	    if (traces != null) {
		len = traces.size();
		for (int i = 0; i < len; i++) {
		    TraceRecord rec = (TraceRecord)traces.elementAt(i);

		    if (rec.trace instanceof CmdTraceProc) {
			CmdTraceProc proc = (CmdTraceProc)(rec.trace);
			if (proc.flags == flags && proc.command.toString().
				equals(argv[4].toString())) {
			    interp.varFrame.untraceVar(argv[2],
				    proc, flags);
			    break;
			}
		    }
		}
	    }
	}
	break;

    case OPT_VINFO:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, 
		    "name");
	}
	Vector traces = interp.varFrame.getTraces(argv[2], 0);
	if (traces != null) {
	    len = traces.size();
	    TclObject list = TclList.newInstance();
	    TclObject cmd = null;
	    list.preserve();

	    try {
		for (int i = 0; i < len; i++) {
		    TraceRecord rec = (TraceRecord)traces.elementAt(i);

		    if (rec.trace instanceof CmdTraceProc) {
			CmdTraceProc proc = (CmdTraceProc)(rec.trace);
			int mode = proc.flags;
			mode &= (TCL.TRACE_READS|TCL.TRACE_WRITES
				|TCL.TRACE_UNSETS);
			mode /= TCL.TRACE_READS;

			cmd = TclList.newInstance();
			TclList.append(interp, cmd, opStr[mode]);
			TclList.append(interp, cmd, TclString.newInstance(
				proc.command));
			TclList.append(interp, list, cmd);
		    }
		}
		interp.setResult(list);
	    } finally {
		list.release();
	    }
	}
	break;
    }
}

} // TraceCmd

/*
 * The CmdTraceProc object holds the information for a specific
 * trace.
 */
class CmdTraceProc implements VarTrace {

/*
 * The command holds the Tcl script that will execute. The flags
 * hold the mode flags that define what conditions to fire under.
 */

String command;
int flags;


/*
 *----------------------------------------------------------------------
 *
 * CmdTraceProc --
 *
 *	This function is a constructor for a CmdTraceProc.  It simply
 *	stores the flags and command used for this trace proc.
 *	details on what it does.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

CmdTraceProc(String cmd, int newFlags) {
    flags = newFlags;
    command = cmd;
}

/*
 *----------------------------------------------------------------------
 *
 * traceProc --
 *
 *	This function gets called when a variable is used in a way that
 *	would cause this particular trace to fire.  It will evaluate
 *	the script associated with this trace.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

public void 
traceProc(
    Interp interp, 	// The current interpreter.
    String part1,	// A Tcl variable or array name.
    String part2,	// Array element name or NULL.
    int flags)		// Mode flags: TCL.TRACE_READS, TCL.TRACE_WRITES or
			// TCL.TRACE_UNSETS.
throws
    TclException 	// A standard Tcl exception.
{
    if (((this.flags & flags) != 0) &&
	    ((flags & TCL.INTERP_DESTROYED) == 0)) {
	StringBuffer sbuf = new StringBuffer(command);

	try {
	    Util.appendElement(interp, sbuf, part1);
	    if (part2 != null) {
		Util.appendElement(interp, sbuf, part2);
	    } else {
		Util.appendElement(interp, sbuf, "");
	    }

	    if ((flags & TCL.TRACE_READS) != 0) {
		Util.appendElement(interp, sbuf, "r");
	    } else if ((flags & TCL.TRACE_WRITES) != 0) {
		Util.appendElement(interp, sbuf, "w");
	    } else if ((flags & TCL.TRACE_UNSETS) != 0) {
		Util.appendElement(interp, sbuf, "u");
	    }
	} catch (TclException e) {
	    throw new TclRuntimeError("unexpected TclException: " + e);
	}

	/*
	 * Execute the command.
	 */

	interp.eval(sbuf.toString(), 0);
    }
}

} //CmdTraceProc
