package TauIL.interpreter;

import TauIL.absyn.*;
import TauIL.util.InstList;

import java.io.PrintStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Vector;

/**
 * The TauIL interpreter is used to interpret an {@link AbstractSyntaxTree} which is the
 * intermediate representation generated by the TauIL {@link TauIL.parser.Parser}. A TauIL interpreter
 * instance has methods to change the abstract syntax on the fly and therefore can be used 
 * on multiple syntax trees without the need to instantiate a new instance.
 */
public class Interpreter {
    // Abstract syntax tree currently being interpreted.
    private AbstractSyntaxTree ast;

    // Vector of selective insturmentation lists for use by TAU insturmentor.
    private Vector list;

    // Global and local inclusion and exclusion lists.
    private Vector [] global_exclude = new Vector[Entity.MAX_ENTITY_VALUE + 1], 
	global_include = new Vector[Entity.MAX_ENTITY_VALUE + 1];

    private Vector [] local_exclude = new Vector[Entity.MAX_ENTITY_VALUE + 1], 
	local_include = new Vector[Entity.MAX_ENTITY_VALUE + 1];

    // Pointers are used by the interpreter to actually refer to the
    // global and local lists.

    // *----------- Pointers -------------*

    // These pointers are set to point at the appropriate global or local 
    // list based on point in interpretation.
    private Vector [] include = new Vector[Entity.MAX_ENTITY_VALUE + 1], 
	exclude = new Vector[Entity.MAX_ENTITY_VALUE + 1];

    // These pointers are set to point at the appropriate list based on
    // the target directive.
    private Vector [] condition_list = new Vector[Entity.MAX_ENTITY_VALUE + 1], 
	anti_list = new Vector[Entity.MAX_ENTITY_VALUE + 1];

    // This pointer is set to point at the appropriate list base on whether
    // conditions or anti_conditions are being interpreted.
    private Vector [] current_list = new Vector[Entity.MAX_ENTITY_VALUE + 1];

    // *---------- End Pointers ------------*

    // Current insturmentation tree node and current data source.
    private Directive data_source;
    private Instrumentation scenario;

    // Data source object for analytical data evaluation.
    private DataSource data;

    // Insturmentation target.
    private int target = InstList.EXCLUDE;

    // Insturmentation count.
    private int count = 0;

    // Debug and verbose mode flags.
    private boolean debug = false;
    private boolean verbose = false;

    // Used to quiet paraprof output.
    private File empty_file = new File("tauIL_dev_null");
    private PrintStream dev_null;

    /**
     * Creates a new TauIL interpreter instance.
     *
     */
    public Interpreter() {
	debug("Interpreter : Interpreter()");

	// Initialize inclusion and exclusion lists.
	for (int i = 0; i <= Entity.MAX_ENTITY_VALUE; i++) {
	    global_exclude[i] = new Vector();
	    global_include[i] = new Vector();

	    local_include[i] = new Vector();
	    local_exclude[i] = new Vector();
	}

	list = new Vector();

	ast = null;
    }

    /**
     * Creates a new TauIL interpreter instance with the given {@link AbstractSyntaxTree}.
     *
     * @param ast TauIL {@link AbstractSyntaxTree} to be interpreted by the TauIL interpreter.
     */
    public Interpreter(AbstractSyntaxTree ast) {
	this();

	debug("Interpreter : Interpreter(AbstractSyntaxTree)");

	this.ast = ast;
    }

    /**
     * Sets the {@link AbstractSyntaxTree} to be interpreted by the TauIL interpreter.
     *
     * @param ast a TauIL {@link AbstractSyntaxTree} to be interpreted by the TauIL interpreter.
     */
    public void setAST(AbstractSyntaxTree ast) {
	debug("Interpreter : setAST(AbstractSyntaxTree)");

	this.ast = ast;
    }

    /**
     * Retrieves the current {@link AbstractSyntaxTree} that the interpreter contains.
     *
     * @return a TauIL {@link AbstractSyntaxTree}.
     */
    public AbstractSyntaxTree getAST() {
	debug("Interpreter : getAST()");

	return ast;
    }

    /**
     * Gets the number of selective insturmentation lists created.
     *
     * @return number of generated insturmentation lists.
     */
    public int numberInstLists() {
	return list.size();
    }

    /**
     * Retrieves the generated selective insturmentation lists.
     *
     * @return a Vector of {@link InstList} objects.
     */
    public Vector getInstLists() {
	return list;
    }

    /**
     * Toggles debuging mode on or off.
     *
     * @param debug a true value enables debugging and false disables.
     */
    public void setDebugMode(boolean debug) {
	this.debug = debug;
    }

    /**
     * Toggles verbose output on or off.
     *
     * @param verbose a true values enables verbose output while false
     *        quiets the interpreter.
     */
    public void setVerboseMode(boolean verbose) {
	this.verbose = verbose;
    }

    /**
     * Interprets the current {@link AbstractSyntaxTree}.
     */
    public void interpret() throws Exception {
	debug("Interpreter : interpret()");

	// Throw error if a syntax tree hasn't been specified.
	if (ast == null) 
	    throw new Error("AST is null!");

	// Set up file for output redirect.
	if (empty_file.exists())
	    empty_file.delete();

	empty_file.createNewFile();
	dev_null = new PrintStream(new FileOutputStream(empty_file));

	// Set the include and exclude pointers to the global lists.
	include = global_include;
	exclude = global_exclude;

	debug("Interpreting global declarations...");
	interpretList((SyntaxList) ast.declarations);

	// Set the include and exclude pointers too the local lists.
	include = local_include;
	exclude = local_exclude;

	debug("Interpreting insturmentation scenarios...");
	interpretList((SyntaxList) ast.root);

	dev_null.close();
	empty_file.delete();
    }

    private void interpretList(SyntaxList list) {
	debug("Interpreter : interpretList(SyntaxList)");

	// Create a new list manager.
	ListManager iterator = new ListManager(list);

	// Iterate through the list and interpert each element.
	while (iterator.hasNext())
	    interpretAS(iterator.next());
    }

    private void interpretAS(AbstractSyntax absyn) {
	debug("Interpreter : interpretAS(AbstractSyntax)");

	if (absyn == null) return;
	else if (absyn instanceof SyntaxElement) interpretElem((SyntaxElement) absyn);
	else if (absyn instanceof SyntaxAttribute) throw new Error(absyn + " : Syntax attributes can not be interpreted!");
	else throw new Error(absyn + " : Unrecogonized abstract syntax element!");
    }

    private void interpretElem(SyntaxElement elem) {
	debug("Interpreter : interpretElem(SyntaxElement)");

	if (elem == null) return;
	else if (elem instanceof Declaration) interpret((Declaration) elem);
	else if (elem instanceof Statement) interpret((Statement) elem);
	else if (elem instanceof Directive) interpret((Directive) elem);
	else if (elem instanceof Instrumentation) interpret((Instrumentation) elem);

	/*
	else if (elem instanceof InsturmentationList) interpretList((InsturmentationList) elem);
	else throw new Error("Unrecogonized syntax element : " + elem);
	*/
    }

    private void interpret(Declaration dec) {
	debug("Interpreter : interpret(Declaration)");

	if (dec == null) return;
	else if (dec instanceof IncludeDec) interpret((IncludeDec) dec);
	else throw new Error(dec + " : Unrecognized declaration class!");
    }

    private void interpret(Statement elem) {
	debug("Interpreter : interpret(Statement)");

	if (elem instanceof MultiStatement) interpret((MultiStatement) elem);
	else if (elem instanceof OperatorStatement) throw new Error(elem + " : Operator statments must be contained within a mulitistatment!");
	
    }

    private void interpret(Directive direct) {
	debug("Interpreter : interpret(Directive)");

	switch(direct.directive) {
	case Directive.TARGET :
	    if (direct.flag == Directive.INCLUDE) {
		condition_list = local_include;
		anti_list = local_exclude;
		target = InstList.INCLUDE;
	    }
	    break;
	case Directive.TYPE :
	    break;
	case Directive.USE :
	    data_source = direct;
	    break;
	default :
	    throw new Error(direct.directive + " : Unrecognized directive type!");
	}
    }

    private void interpret(IncludeDec dec) {
	debug("Interpreter : interpret(IncludeDec)");

	// Temporary pointer.
	Vector temp;

	// Set temporary pointer to the appropriate list offset.
	switch(dec.include_flag) {
	case IncludeDec.INCLUDE :
	    temp = include[dec.entity_flag];
	    break;
	case IncludeDec.EXCLUDE :
	    temp = exclude[dec.entity_flag];
	    break;
	default :
	    throw new Error(dec.include_flag + " : Unrecognized include declaration type!");
	}

	// Iterate through the entity list and add each entity to the
	// temporary pointer list.
	ListManager iterator = new ListManager(dec.list);
	while (iterator.hasNext()) {
	    Entity ent = (Entity) iterator.next();
	    temp.add(ent.name);
	}
    }

    private void interpret(Instrumentation instr) {
	debug("Interpreter : interpret(Instrumentation)");

	scenario = instr;

	resetLocalLists();

	// Set default condition and anti_condition lists.
	condition_list = local_exclude;
	anti_list = local_include;

	// Set default target.
	target = InstList.EXCLUDE;

	// Based on data source type set default source information.
	switch(instr.type) {
	case Instrumentation.PROFILE :
	    data_source = new Directive(Directive.USE, Directive.FILE, "pprof.dat");
	    break;
	case Instrumentation.STATIC :
	    throw new Error(instr.type + " : Sorry, no support for static data sources at this time!");
	case Instrumentation.RUNTIME :
	    throw new Error(instr.type + " : Sorry, no support for runtime data sources at this time!");
	default :
	    throw new Error(instr.type + " : Unrecognized data source type!");
	}

	debug("Interpreting instrumentation directives...");
	interpretList(instr.directives);

	// Load data source.
	loadDataSource();

	debug("Interpreting instrumentation declarations...");
	interpretList(instr.declarations);

	debug("Interpreting instrumentation conditions...");
	current_list = condition_list;
	interpretList(instr.conditions);

	debug("Interpreting instrumentation anti-conditions...");
	current_list = anti_list;
	interpretList(instr.anti_conditions);

	// --------------- incomplete -------------------------------

	Vector files = new Vector(condition_list[Entity.FILE]);
	Vector events = new Vector(condition_list[Entity.EVENT]);

	files.removeAll(anti_list[Entity.FILE]);
	events.removeAll(anti_list[Entity.EVENT]);

	list.add(new InstList(instr.fname, files, events, target));

	// ----------------------------------------------------------

	// Increment scenario count.
	count++;
    }

    private void interpret(MultiStatement list) {
	debug("Interpreter : interpret(MultiStatement)");
	info("__________________________________________________________________________________");
	info(list.generateSyntax());
	info("");

	ListManager iterator = new ListManager(list);

	while (data.hasNext()) {
	    boolean match = true;

	    data.next();
	    iterator.reset();

	    while(iterator.hasNext() && match)
		match = interpret((OperatorStatement) iterator.next());

	    if (match) {
		String name = data.getEventName();

		info("\t" + name);

		if (current_list[Entity.EVENT].indexOf(name) == -1)
		    current_list[Entity.EVENT].add(name);
	    }
	}

	data.reset();

    }

    private boolean interpret(OperatorStatement eval) {
	debug("Interpreter : interpret(OperatorStatement)");
	
	ProfileDataSource pds = (ProfileDataSource) data;

	if (pds.isTimeMetric() && (eval.field.metric == Field.COUNTER)) {
	    System.out.println("Counter metrics in rule, but profile contains timer metrics!");
	    return false;
	} else if (!pds.isTimeMetric() && (eval.field.metric == Field.TIMER)) {
	    System.out.println("Timer metrics in rule, but profile contains counter metrics!");
	    return false;
	}

	double value;

	switch(eval.field.field) {
	case Field.NUMCALLS :
	    value = pds.getNumCalls();
	    break;
	case Field.NUMSUBRS :
	    value = pds.getNumSubRS();
	    break;
	case Field.PERCENT :
	    value = pds.getPercent();
	    break;
	case Field.USEC :
	    value = pds.getUsec();
	    break;
	case Field.COUNT :
	    value = pds.getCount();
	    break;
	case Field.CUMUSEC :
	    value = pds.getCumUsec();
	    break;
	case Field.TOTCOUNT :
	    value = pds.getTotCount();
	    break;
	case Field.STDDEV :
	    value = pds.getStdDev();
	    break;
	case Field.USECS_CALL :
	    value = pds.getUsecsPerCall();
	    break;
	case Field.COUNTS_CALL :
	    value = pds.getCountsPerCall();
	    break;
	default :
	    throw new Error(eval.field.field + " : Unrecognized field!");
	}

	return evaluate(value, eval.op, eval.val.value.doubleValue());
    }

    private boolean evaluate(double data_val, Operator op, double test_val) {
	debug("Interpreter : evaluate(double, Operator, double)");
	
	switch (op.op) {
	case Operator.EQ :
	    return (data_val == test_val);
	case Operator.LT :
	    return (data_val < test_val);
	case Operator.GT :
	    return (data_val > test_val);
	case Operator.GTEQ :
	    return (data_val >= test_val);
	case Operator.LTEQ :
	    return (data_val <= test_val);
	case Operator.NEQ :
	    return (data_val != test_val);
	default :
	    return false;
	}
    }

    private void loadDataSource() {
	debug("Interpreter : loadDataSource()");

	switch(data_source.flag) {
	case Directive.FILE :
	    break;
	case Directive.DB :
	    throw new Error(data_source.flag + " : Database data sources are not supported at this time!");
	default :
	    throw new Error(data_source.flag + " : Unrecognized data source use type!");
	}

	switch(scenario.type) {
	case Instrumentation.PROFILE :
	    data = new ProfileDataSource(data_source.arg);
	    break;
	case Instrumentation.STATIC :
	case Instrumentation.RUNTIME :
	}

	PrintStream temp = System.out;
	System.setOut(dev_null);

	data.load();

	System.setOut(temp);
    }

    private void resetLocalLists() {
	debug("Interpreter : resetLocalLists()");

	for (int i = 0; i <= Entity.MAX_ENTITY_VALUE; i++) {
	    local_exclude[i].clear();
	    local_include[i].clear();

	    local_exclude[i].addAll(global_exclude[i]);
	    local_include[i].addAll(global_include[i]);
	}
    }

    private void debug(String text) {
	if (debug)
	    System.out.println(text);
    }

    private void info(String text) {
	if (verbose)
	    System.out.println(text);
    }
}
