package main.parser;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

import main.Addition;
import main.Comparison;
import main.IOperation;
import main.Subtraction;

public class ParsingTree {

	private int tokenCount;
	private HashMap<String, String> tokenMap;
	private BinaryTree<String> binTree;

	public ParsingTree(String expression) {
		tokenCount = 0;
		tokenMap = new HashMap<String, String>();
		expression = expression.trim().replace(" ", "");
		//System.out.println("expression:" + expression);
		String tokenizedExp = tokenize(expression);
		//System.out.println("tokenized expression:" + tokenizedExp);
		//System.out.println(infixToPostfix(tokenizedExp));
		String prefix = infixToPrefix(tokenizedExp);
		//System.out.println("Queue:" + prefix);
		Queue<String> queue = prefixToQueue(prefix);
		buildTree(queue);
		
	}

	public final String compute() {
		return computeRecursively(binTree.getRoot());
	}

	private final String computeRecursively(BinaryTreeNode<String> root) {
		if (root != null) {
			if (isOperator(root.getTreeValue())) {
				String leftValue = computeRecursively(root.getLeft());
				String rightValue = computeRecursively(root.getRight());
				String value = computeOperation(leftValue,rightValue,root.getTreeValue());
				return value;
			} else {
				return tokenMap.get(root.getTreeValue());
			}
		}
		return null;
	}
	
	private String computeOperation(String leftValue,String rightValue,String op){
		IOperation operation;
		if(op.equals("+")){
			operation = new Addition();
			return operation.perform(leftValue, rightValue);
		}else if(op.equals("-")){
			operation = new Subtraction();
			return operation.perform(rightValue, leftValue);
		}else if(op.equals("<") || op.equals(">") || op.equals("=")){
			operation = new Comparison();
			String code = operation.perform(rightValue, leftValue);
			if(op.equals("<")){
				return (code.equals(Comparison.LESS_THAN)) ? "true" : "false";
			}else if(op.equals(">")){
				return (code.equals(Comparison.GREATER_THAN)) ? "true" : "false";
			}else{
				return (code.equals(Comparison.EQUAL_TO)) ? "true" : "false";
			}
		}else{
			return null;
		}
	}

	private boolean isOperator(char op) {
		return op == '+' || op == '-' || op == '*' || op == '(' || op == ')' || op == '>' || op == '<' || op == '=';
	}

	private boolean isOperator(String op) {
		return isOperator(op.charAt(0));
	}

	private String infixToPostfix(String infix) {
		String output = "";
		Stack<PriorityChar> opStack = new Stack<PriorityChar>();
		String valStr = "";
		for (char c : infix.toCharArray()) {

			if (isOperator(c)) {
				output += valStr;
				valStr = "";
				PriorityChar pc = new PriorityChar(c);
				while (!opStack.isEmpty() && opStack.lastElement().getPriority() >= pc.getPriority()
						&& opStack.lastElement().getCh() != '(') {
					PriorityChar p = opStack.pop();
					if (p.getCh() != '(') {
						output += p;
					}
				}

				if (pc.getCh() != ')') {
					opStack.push(pc);
				} else {
					while (true) {
						char op = opStack.pop().getCh();
						if (op != '(')
							output += op;
						else
							break;
					}
				}
			} else {
				valStr += c;
			}

			// System.out.println("read=" + c);
			// System.out.println("process output=" + output);
			// System.out.println("process stack=" + opStack);
			// System.out.println();
		}

		// If there is remaining values in valStr then flushes
		if (valStr.length() > 0) {
			output += valStr;
			valStr = "";
		}

		// System.out.println("mid output=" + output);
		// System.out.println("mid stack=" + opStack);

		while (!opStack.isEmpty()) {
			output += opStack.pop();
		}

		// System.out.println("final output=" + output);

		return output;
	}

	private String infixToPrefix(String infix) {
		String postfix = infixToPostfix(infix);
		Stack<String> revStack = new Stack<String>();
		String token = "";
		for (char c : postfix.toCharArray()) {
			if (token.equals("") && c != 'T') {
				revStack.push(String.valueOf(c));
			} else if (token.equals("") && c == 'T') {
				token += c;
			} else if (!token.equals("") && c == 'T') {
				revStack.push(token);
				token = "T";
			} else if (!token.equals("") && !isOperator(c)) {
				token += c;
			} else {
				if (token.length() > 0) {
					revStack.push(token);
					token = "";
				}
				revStack.push(String.valueOf(c));
			}

		}
		
		
		// Flush Token Buffer
		if (token.length() > 0) {
			revStack.push(token);
			token = "";
		}

		String output = "";
		while (!revStack.isEmpty())
			output += revStack.pop();

		return output;
	}

	private String tokenize(String expression) {
		String value = "";
		String output = "";
		for (char c : expression.toCharArray()) {
			if (value.equals("") && c == '-') {
				value += c;
			} else if (value.equals("") && c == '+') {
			} else {
				if (isOperator(c)) {
					if (value.length() > 0) {
						output += putAndGenToken(value);
						// Clear value buffer
						value = "";
					}
					// Append the operator
					output += c;
				} else {
					// Append the value to value buffer
					value += c;
				}
			}
		}

		if (value.length() > 0) {
			output += putAndGenToken(value);
			value = "";
		}

		return output;
	}

	private String putAndGenToken(String value) {
		String token = "T" + tokenCount;
		tokenCount++;
		tokenMap.put(token, value);
		return token;
	}

	private Queue<String> prefixToQueue(String prefix) {
		Queue<String> queue = new LinkedList<String>();
		String token = "";
		for (char c : prefix.toCharArray()) {
			if (token.equals("") && c != 'T') {
				queue.add(String.valueOf(c));
			} else if (token.equals("") && c == 'T') {
				token += c;
			} else if (!token.equals("") && c == 'T') {
				queue.add(token);
				token = "T";
			} else if (!token.equals("") && !isOperator(c)) {
				token += c;
			} else {
				if (token.length() > 0) {
					queue.add(token);
					token = "";
				}
				queue.add(String.valueOf(c));
			}
		}

		if (token.length() > 0)
			queue.add(token);

		// System.out.println(queue);
		return queue;
	}

	private void buildTree(Queue<String> queue) {
		binTree = new BinaryTree<String>("");
		binTree.getRoot().appendLeftNode(new BinaryTreeNode<String>(""));
		binTree.getRoot().appendRightNode(new BinaryTreeNode<String>(""));
		buildTreeRecursively(queue, binTree.getRoot());
	}

	private BinaryTreeNode<String> buildTreeRecursively(Queue<String> queue, BinaryTreeNode<String> root) {
		while (!queue.isEmpty()) {
			if (root == null)
				root = new BinaryTreeNode<String>("");
			String token = queue.poll();
			root.setTreeValue(token);
			if (isOperator(token)) {
				root.appendLeftNode(new BinaryTreeNode<String>(""));
				root.appendRightNode(new BinaryTreeNode<String>(""));
				buildTreeRecursively(queue, root.getLeft());
				buildTreeRecursively(queue, root.getRight());
			}

			return root;
		}

		return null;
	}

}