/*
 * Created on 31.01.2004 15:14:03
 *
 * Multimediale Algorithmen und Datenstrukturen Assessments
 */
package mauda.plugin;

import mauda.*;
import mauda.operation.*;

import java.util.*;

/**
 * Evaluator for formulas from Knowledge-Bases. Every PlugIn
 * must have a class, who extends this class.
 * 
 * @author Markus Krebs
 */
public class KBFormulaEvaluator {
	
	private HashSet known;

	/**
	 * Contains the currently filled Formula.
	 * (for debugging-purposes)
	 */	
	public String filledFormula;
	
	protected Exercise exercise;
	
	private boolean finishOperationState;

	/**
	 * Creates a KBFormulaEvaluator
	 * @param exercise Exercise
	 */
	public KBFormulaEvaluator(Exercise exercise) {
		this.exercise = exercise;
		
		known = new HashSet();
		known.add("AND");
		known.add("NOT");
		known.add("TRUE");
		known.add("FALSE");
		known.add("=");
		known.add("!=");
		known.add(">");
		known.add("<");
		//known.add(">=");
		//known.add("<=");
		
		finishOperationState = false;
	}
	
	/**
	 * Sets following state for the FormulaEvaluator:<br>
	 * The student wants to process the next Operation,
	 * because he thinks the current is complete. E.g.: He has
	 * pressed "Finish Operation...".<br>
	 * In this case the knowledge-base have to proof for missing
	 * suboperations, which is a special-state of the Formula-
	 * Evaluator.<br>
	 * Internally that means, that following variables in the 
	 * formulas are set:
	 * <ul>
	 * <li><i>correct</i> = FALSE</li>
	 * <li><i>firstincorrect</i> = FALSE</li>
	 * <li><i>missingsubop</i> = TRUE</li>
	 * </ul>
	 * 
	 * @param b true if want to switch in finish-operation-state, false otherwise
	 */
	// Angabe ob der Anwender Finish Operation gedrückt hat
	// => Feedback-Anzeige für MISSING-SUBOPERATIONS
	// Müssen gesondert behandelt werden.
	// bei TRUE:	correct = FALSE
	//				firstincorrect = FALSE
	//				missingsubop = TRUE
	// Somit werden nur die Formeln mit missingsubop verwendet
	public void setFinishOperationState(boolean b) {
		finishOperationState = b;
	}
	/**
	 * Gets the finish-operation-state
	 * @return true if in finish-operation-state, false otherwise
	 */
	public boolean isFinishOperationState() {
		return finishOperationState;
	}
	
	/**
	 * Evaluates a delivered formula
	 * @param formula Formual to evaluate
	 * @return true if formula is true, false otherwise
	 */
	public boolean evaluate(String formula) {
		//System.out.println("FORMULA: "+formula);
		String filledFormula = "";
		StringTokenizer st = new StringTokenizer(formula);
		while(st.hasMoreTokens()) {
			String token = st.nextToken();
			//System.out.println("  Processing: "+token);
			// kennt der Parser das token nicht, so ist es eine
			// Variable, und muss gefüllt werden
			if(!known.contains(token)) {
				token = getConstant(token.toLowerCase());
				//if(token==null) return false;
			}
			filledFormula += token+" ";
		}
		//System.out.println("     --> "+filledFormula);
		//return false;
		this.filledFormula = filledFormula;
		return evaluateFilledFormula(filledFormula);
	}
	/**
	 * Gets the value of constant.<br>
	 * E.g.: <code>subop[current].id</code> can be evaluated to
	 * <code>cut</code>.
	 * @param c the constant
	 * @return evaluated constant
	 */
	public String getConstant(String c) {
		StringTokenizer st = new StringTokenizer(c,".",false);
		String[] tokens = new String[st.countTokens()];
		int counter=0;
		while(st.hasMoreTokens()) tokens[counter++] = st.nextToken();
		
		OperationRecorder or = exercise.getOperationRecorder();
		String partialFilledConstant = "";
		for(int i=0; i<tokens.length; i++) {
			String token = tokens[i];
			//System.out.println("   TOKEN = "+token);
			// TOKEN: subop[nr].? ------------------------------------------
			if(token.equals("correct")) {
				if(finishOperationState) return "FALSE";
				SubOperation ao = or.getSubOperation(or.getCurrentOperationNr(), or.getCurrentSubOperationNr());
				SubOperation co = or.getCorrectSubOperation(or.getCurrentOperationNr(), or.getCurrentSubOperationNr());
				if(ao==null||co==null) token = "FALSE";
				else if(ao.equals(co)) token = "TRUE";
				else token = "FALSE";
			} else if(token.equals("firstincorrect")) {
				if(finishOperationState) return "FALSE";
				SubOperation ao = or.getSubOperation(or.getCurrentOperationNr(), or.getCurrentSubOperationNr());
				SubOperation co = or.getCorrectSubOperation(or.getCurrentOperationNr(), or.getCurrentSubOperationNr());
				if(ao==null) token = "FALSE";
				// wenn es keine korrekte SubOperation gibt ==> MISSING_SUBOPERATIONS
				else if(co==null||!ao.equals(co)) token = "TRUE";
				else token = "FALSE";
			} else if(token.matches("missingsubop")) {
				if(finishOperationState) return "TRUE";
				else return "FALSE";				
			} else if(token.matches("subop\\[.*\\]")||token.matches("correctsubop\\[.*\\]")) {
				boolean correct = token.matches("correctsubop\\[.*\\]");
				// Zahl innerhalb der eckigen Klammern holen
				int subopnr = getPositionNr(or.getCurrentSubOperationNr(),token);
				if(subopnr<0) return null;
				if(i==tokens.length-1) {
					// Auf subop muss ein Token folgen, ansonsten fehlerhafte Formel
					System.out.println("ERROR in Formula. Missing command!");
					return null;
				} else {
					SubOperation so = null;
					int offset = -1;
					int copnr = or.getCurrentOperationNr();
					if(correct) so = or.getCorrectSubOperation(copnr, subopnr);
					else {
						so = or.getSubOperation(copnr, subopnr);
						offset = or.getOffset(copnr, subopnr);
					} 
					if(so==null) return null; 
					if(tokens[i+1].equals("id")) {
						token = so.getID().toLowerCase();
						i++;
					} else if(tokens[i+1].equals("param1")) {
						if(so.getParameter1() == SubOperation.NULL) return null; 
						int p1 = so.getParameter1();
						token = p1+""; i++;
						if(i!=tokens.length-1) {
							if(!correct) token += "["+offset+"]";
							else {
								System.out.println("ERROR in Formula. Command on correctsubop not allowed.");
								return null;
							} 
						} 
					} else if(tokens[i+1].equals("param2")) {
						if(so.getParameter2() == SubOperation.NULL) return null; 
						int p2 = so.getParameter2();
						token = p2+""; i++;
						if(i!=tokens.length-1) {
							if(!correct) token += "["+offset+"]";
							else {
								System.out.println("ERROR in Formula. Command on correctsubop not allowed.");
								return null;
							} 
						} 
					} else if(tokens[i+1].equals("count")) {
						int count = 1;
						SubOperation so2;
						for(int j=0; j<subopnr; j++) {
							if(correct) so2 = or.getCorrectSubOperation(or.getCurrentOperationNr(), j);
							else so2 = or.getSubOperation(or.getCurrentOperationNr(), j);
							if(so.getID().equals(so2.getID())) count++;
						}
						token = count+""; i++;
					} else {
						System.out.println("ERROR in Formula. Unknown command: "+tokens[i+1]);
					}
				}
			// TOKEN: op[nr].? -------------------------------------------------------
			} else if(token.matches("op\\[.*\\]")) {
				// Zahl innerhalb der eckigen Klammern holen
				int nr = getPositionNr(or.getCurrentOperationNr(), token);
				if(nr<0) return null;
				Operation op = or.getOperation(nr);
				if(op==null) return null;
				if(i==tokens.length-1) {
					// Auf subop muss ein Token folgen, ansonsten fehlerhafte Formel
					System.err.println("ERROR in Formula. Missing command!");
					return null;
				} else if(tokens[i+1].equals("id")) {
					token = op.getID().toLowerCase();
					i++;
				} else if(tokens[i+1].equals("param1")) {
					if(op.getParameter1()==Operation.NULL) return null; 
					int p1 = op.getParameter1();
					token = p1+""; i++;
				} else if(tokens[i+1].equals("param2")) {
					if(op.getParameter2()==Operation.NULL) return null; 
					int p2 = op.getParameter2();
					token = p2+""; i++;
				} else {
					System.err.println("ERROR in Formula. Unknown command: "+tokens[i+1]);
				}
				
			}
			partialFilledConstant += token;
			if(i<tokens.length-1) partialFilledConstant += ".";
		}
		return getSpecialConstant(partialFilledConstant);
	}
	/**
	 * Gets a plugin-specific constant like:<br>
	 * <code>subop[current].param2.pre.isroot<code><br>
	 * This expression cannot completely evaluated by this
	 * evaluator, because he understands pre.isroot not. Therefore
	 * the expression <code>subop[current].param2</code> will be 
	 * evaluated by this evaluator e.g. in <code>2</code>, and then
	 * <code>2[5].pre.isroot</code> will be transmitted to the
	 * plugin-specific evaluatore, who can evaluate this formula in
	 * e.g. <code>TRUE</code>. The number in brackets is needed to
	 * identify from which data-structure in the history the
	 * information should retrieved.
	 * 
	 * @param sc constant
	 * @return evaluated constant
	 */
	public String getSpecialConstant(String sc) {
		return sc;
	}
	
	// Gibt die Verschiebung zu current zurück
	// current gibt aktuelle Position an (Angabe für current)
	protected int getPositionNr(int current, String opToken) {
		OperationRecorder or = exercise.getOperationRecorder();
		int i1 = opToken.indexOf("[");
		int i2 = opToken.indexOf("]",i1+1);
		String pos = opToken.substring(i1+1,i2);
		//System.out.println("  pos='"+pos+"'");
		if(pos.startsWith("current")) {
			if(pos.equals("current")) return current;
			String z = pos.substring(7);	// 7=Länge von "current"
			if(z.startsWith("+")) z = z.substring(1); 
			return current+Integer.parseInt(z);
		} else if(pos.matches("\\d+")) {
			return Integer.parseInt(pos);
		} 
		return 0;			
	}
	/**
	 * Evaluates a comletely filled formula. E.g.:
	 * <code>TRUE AND NOT TRUE AND NOT newfheapmeld = cut 
	 * @param formula Filled formula
	 * @return true if formula is true, false otherwise
	 */
	public boolean evaluateFilledFormula(String formula) {
		// Test-Ausgabe:
		//System.out.println("FilledFormula: "+formula);
		if(formula.indexOf("null")>=0) return false;
		
		StringTokenizer st = new StringTokenizer(formula);
		String[] tokens = new String[st.countTokens()];
		int c = 0;
		while(st.hasMoreTokens()) {
			tokens[c] = st.nextToken();
			c++;
		}
		c = 0;
		boolean negate=false;
		while(c<tokens.length) {
			String t1 = tokens[c];
			if(t1.equals("NOT")) {
				negate = true;
				c++;
				t1 = tokens[c];
			} else negate = false;
			boolean result = true;
			if(t1.equals("TRUE")) {
				result = true;
				c += 2; // "TRUE AND" überspringen
			} else if(t1.equals("FALSE")) {
				result = false;
				c += 2; // "FALSE AND" überspringen	
			} else if(!known.contains(t1)) {
				String comparator = tokens[c+1];
				String t2 = tokens[c+2];
				// 2 Zahlen mit < vergleichen
				if(comparator.equals("<")) {
					int v1 = Integer.parseInt(t1);
					int v2 = Integer.parseInt(t2);
					if(v1>=v2) result = false;
					c+=4;
				// 2 Zahlen mit > vergleichen
				} else if(comparator.equals(">")) {
					int v1 = Integer.parseInt(t1);
					int v2 = Integer.parseInt(t2);
					if(v1<=v2) result = false;
					c+=4;					
				// 2 Strings vergleichen (= bzw. !=)	
				} else if(comparator.equals("=")) {
					if(!t1.equals(t2)) result = false;
					c+=4;
				} else if(comparator.equals("!=")) {
					if(t1.equals(t2)) result = false;
					c+=4;
				}
			} else {
				System.out.println("ERROR: Cant evaluate Formula!");
				System.out.println("Setting them to false.");
				System.out.println("--> "+formula);
				return false;
			}
			//System.out.println(negate+" "+result);
			if(negate&&result||!negate&&!result) return false;
		}
		return true;
	}
}
