package cis75;

import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import java.io.*;

/**
 * <p>Title: CallTrace</p>
 * <p>Description: internal CIS75 tracing facility</p>
 * <p>Copyright: Copyright (c) 2008</p>
 * <p>Company: Bristol Community College</p>
 * @author Igor Kholodov, CIS Instructor
 * @version 1.0
 */
public class CallTrace
{

    public static String strDebugOutput = "debugout.txt";
    private static boolean bTraceActive = true;
    private static String strActivateTrace = "";
    private static int nPreviousTraceLevel = 999;
    private static String strPreviousLocation = "";
    private static Hashtable htStats = new Hashtable();	// maps function name to function stats
    private static Hashtable htFunctionMap = new Hashtable();	// maps function name to fid
    private static Vector vFids = new Vector();								// reverse map from fid to function name

    private static Hashtable htCallHistory = new Hashtable();	// keeps track of calls received
    private static Stack CallStack = new Stack();				// call stack
    private static int fid_generator = 0;	// each function gets unique id
    private static String strTraceLevel = "<-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=?";
    //private static String strTraceLevel = "...........................................................................................................................?";

    public static final void trace2sql(int nEvent, String strLocation)
    {
        /*

	Trace engine usage:
        CallTrace.trace2sql( 4, "Module.Function()" );	// IN
        CallTrace.trace2sql( 0, "Module.Function()" );	// OUT
        CallTrace.trace2sql( 1, "Module.Function()" );	// IN/OUT e.g. stored procedure
        CallTrace.trace2sql( 2, "any text" );			// Console device output
        CallTrace.trace2sql( 3, "any text" );			// Error device output

        // Dump stats, etc.
        CallTrace.trace2sql( 5, "Module.Function()" );	// Dump stats
        CallTrace.trace2sql( 6, "Module.Function()" );	// RESET TRACE - deletes trace history info
        CallTrace.trace2sql( 7, "Module.Function()" );	// Dump fids

        // Activate/deactivate trace:
        CallTrace.trace2sql( 8, "your text" );	// DEACTIVATE TRACE: tracing stops; activated by next "your text" output.
        CallTrace.trace2sql( N, "your text" );	// ACTIVATE TRACE: trace resumes, stack size increased by N.

        // Dump stack
        CallTrace.trace2sql( 9, "Module.Function()" );	// dump current call stack

        Example: the following stack dump is produced by trace

        static final void readDealList() {
            CallTrace.trace2sql( 4, "RunScript.readDealList()" );
            CallTrace.trace2sql( 9, "RunScript.readDealList()" );	//DUMP STACK
            ...
        }


        <-=>RunScript.readDealList()
        <-=->RunScript.readDealList() *** STACK DUMP *** [1, 205, 206, 207]
        <-=- 207	RunScript.readDealList()
        <-=- 206	RunScript.checkDealList()
        <-=- 205	RunScript.getDeal()
        <-=- 1		RunScript.main()
        <-=- *** STACK BOTTOM ***

        */

                int fid = 0;			// function id
        int previous_fid = 0;	// previous function id at a given level
        int pid = 0;			// parent id
        int nTraceLevel = CallStack.size();	// current trace level

        // Special events:
        if (!bTraceActive)
        {
            if (strLocation.equals(strActivateTrace))
            {	// ACTIVATE TRACE
                // activate trace by output
                trace2file(nTraceLevel = nEvent, " *** ACTIVATE TRACE ***");
                bTraceActive = true;
                while (nTraceLevel-- > 0)
                    CallStack.push(new Integer(0));

            }
            return;
        }

        if (nEvent == 5)
        {	// dump stats
            trace2file(nTraceLevel, strLocation + " DUMP STATS" + htStats.toString());
            return;
        }

        if (nEvent == 6)
        {	// reset trace
            trace2file(nTraceLevel, strLocation + " *** RESET TRACE ***");
            htCallHistory.clear();
            return;
        }

        if (nEvent == 7)
        {	// dump fids
            trace2file(nTraceLevel, strLocation + " DUMP FIDs" + htFunctionMap.toString());
            return;
        }

        if (nEvent == 2)	// console output
            System.out.println(strLocation);

        else if (nEvent == 3)	// error output
            System.err.println(strLocation);

        if (nEvent == 8)
        {	// deactivate trace
            strActivateTrace = strLocation;
            trace2file(nTraceLevel, " *** DEACTIVATE TRACE ***");
            bTraceActive = false;
            return;
        }

        if (nEvent == 9)
        {	// dump stack
            trace2file(nTraceLevel, strLocation + " *** STACK DUMP *** " + CallStack.toString());
            for (int iSP = CallStack.size(); iSP >= 0; --iSP)
            {
                if (iSP == 0)
                    trace2file(nTraceLevel, "*** STACK BOTTOM ***", " ");
                else
                {
                    fid = ((Integer)CallStack.elementAt(iSP - 1)).intValue();
                    if (fid == 0)
                        trace2file(nTraceLevel, fid + "\t" + "???", " ");
                    else
                        trace2file(nTraceLevel, fid + "\t" + (String)vFids.elementAt(fid - 1), " ");
                }
            }
            return;
        }

        // End of special events

        else
        {	// inout=1, in=4, out=0
            // obtain fid of the caller:
            if (htFunctionMap.containsKey(strLocation))
                fid = ((Integer)htFunctionMap.get(strLocation)).intValue();
            else
            {
                htFunctionMap.put(strLocation, new Integer(fid = ++fid_generator));
                vFids.addElement(strLocation);
            }

            if (!CallStack.empty())
            {
                pid = ((Integer)CallStack.peek()).intValue();	// get parent function id
                String strHistoryKey = nTraceLevel + "x" + pid;
                if (htCallHistory.containsKey(strHistoryKey))
                    previous_fid = ((Integer)htCallHistory.get(strHistoryKey)).intValue();	// get previous function id

                htCallHistory.put(strHistoryKey, new Integer(fid));
            }

        }

        String strHistoryKey = nTraceLevel + "x" + pid + "x" + previous_fid + "x" + fid;
        boolean bOldItem = htCallHistory.containsKey(strHistoryKey);
        int call_count = 0;			// function call count
        switch (nEvent)
        {
            case 1:
                if (!bOldItem)
                {
                    htCallHistory.put(strHistoryKey, new Integer(fid));
                    trace2file(nTraceLevel, strLocation);
                }
                //else trace2file( nTraceLevel,  strLocation );	// UNCONDITIONAL TRACING

                // in/out - Update statistics
                if (htStats.containsKey(strLocation))
                    call_count = ((Integer)htStats.get(strLocation)).intValue();	// get previous function id
                htStats.put(strLocation, new Integer(++call_count));
                return;
            case 2:
                trace2file(nTraceLevel, strLocation, "|");
                return;
            case 3:
                trace2file(nTraceLevel, strLocation, " *** ");
                return;
            case 0:	// out
                if (!bOldItem)
                    htCallHistory.put(strHistoryKey, new Integer(fid));

                while (!CallStack.empty() && (fid != (pid = ((Integer)CallStack.pop()).intValue())))
                {	// we expect to find this fid on the stack of calls
                    trace2file(nTraceLevel = CallStack.size(), "return; //" + pid);
                    if (pid == 0)	// when trace is activated / deactivated, default stack is populated with an arbitrary number of zero-parents.
                        break;
                }


                // out - Update statistics
                if (htStats.containsKey(strLocation))
                    call_count = ((Integer)htStats.get(strLocation)).intValue();	// get previous function id
                htStats.put(strLocation, new Integer(++call_count));

                return;	// out

            case 4:
                if (nEvent == 4)
                    CallStack.push(new Integer(fid));	// current function id will now be the parent

                if (!bOldItem)
                {
                    htCallHistory.put(strHistoryKey, new Integer(fid));
                    trace2file(nTraceLevel, strLocation);
                }
            //else trace2file( nTraceLevel,  strLocation );	// UNCONDITIONAL TRACING
        }

    }

    public static final void trace2file(int nTraceLevel, String strLocation)
    {
        trace2file(nTraceLevel, strLocation, ">");
    }

    public static final void trace2file(int nTraceLevel, String strLocation, String strPrefix)
    {
        if ((nTraceLevel == nPreviousTraceLevel) && strPreviousLocation.equals(strLocation))
            return;

        nPreviousTraceLevel = nTraceLevel;
        strPreviousLocation = strLocation;

        try
        {
            FileOutputStream fileOut = new FileOutputStream(strDebugOutput, true);
            PrintStream strmOut = new PrintStream(fileOut);
            //strmOut.println( "csx_trace " + nEvent + ", " + strLocation );
            //strmOut.println( "go" );
            if (strTraceLevel.length() > nTraceLevel)
                strmOut.println(strTraceLevel.substring(0, nTraceLevel) + strPrefix + strLocation);
            else
                strmOut.println(strTraceLevel + strLocation);
            strmOut.close();
            fileOut.close();

        }
        catch (java.lang.Exception ex)
        {
            System.out.println("CallTrace.trace2file has failed");
            System.out.println("--" + strLocation + "--");
            ex.printStackTrace();
        }
    }

}	//CallTrace