/*
 * Encog(tm) Core v3.2 - Java Version
 * http://www.heatonresearch.com/encog/
 * https://github.com/encog/encog-java-core
 
 * Copyright 2008-2013 Heaton Research, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *   
 * For more information on Heaton Research copyrights, licenses 
 * and trademarks visit:
 * http://www.heatonresearch.com/copyright
 */
package org.encog.parse.expression.common;

import org.encog.ml.ea.exception.EACompileError;
import org.encog.ml.prg.EncogProgram;
import org.encog.ml.prg.ProgramNode;
import org.encog.ml.prg.expvalue.ExpressionValue;
import org.encog.ml.prg.extension.BasicTemplate;
import org.encog.ml.prg.extension.NodeType;
import org.encog.ml.prg.extension.ProgramExtensionTemplate;
import org.encog.ml.prg.extension.StandardExtensions;
import org.encog.util.SimpleParser;
import org.encog.util.datastruct.StackObject;

/**
 * This class is used to process a common format equation (in-fix) into the tree
 * format that Encog uses. To do this I make use of the shunting yard algorithm.
 * 
 * One important note on definitions. I consider an operator to be simply a
 * special type of function. Really, an operator is just a shorthand for writing
 * certain types of functions. Therefore I do not distinguish between functions
 * and operators in this implementation.
 * 
 * References:
 * 
 * http://en.wikipedia.org/wiki/Shunting-yard_algorithm
 **/
public class ParseCommonExpression {

	private final EncogProgram holder;
	private SimpleParser parser;
	private ProgramNode rootNode;
	private boolean unary;
	private ProgramExtensionTemplate LEFT_PAREN = new BasicTemplate(
			ProgramExtensionTemplate.NO_PREC, "(", NodeType.None, false, 0) {
		@Override
		public ExpressionValue evaluate(ProgramNode actual) {
			return null;
		}
	};

	private StackObject<ProgramExtensionTemplate> functionStack = new StackObject<ProgramExtensionTemplate>(
			100);
	private StackObject<ProgramNode> outputStack = new StackObject<ProgramNode>(
			100);

	public ParseCommonExpression(final EncogProgram theHolder) {
		this.holder = theHolder;
	}

	/**
	 * Push a leaf onto the output stack.
	 * 
	 * @param leaf
	 *            The leaf to push onto the output stack.
	 **/
	private void outputQueue(ProgramNode leaf) {
		outputStack.push(leaf);
	}

	private void outputQueue(ProgramExtensionTemplate opp) {
		if (opp == this.LEFT_PAREN) {
			throw new EACompileError("Unmatched parentheses");
		}

		ProgramNode[] args = new ProgramNode[opp.getChildNodeCount()];

		for (int i = args.length - 1; i >= 0; i--) {
			if (this.outputStack.isEmpty()) {
				throw new EACompileError("Not enough arguments");
			}
			args[i] = this.outputStack.pop();
		}

		this.rootNode = this.holder.getFunctions().factorProgramNode(opp,
				this.holder, args);
		outputStack.push(rootNode);
	}

	private void functionQueue(ProgramExtensionTemplate f) {

		// while there is an operator token, o2, at the top of the stack, and
		// either o1 is left-associative and o1 has precedence less than or
		// equal to that of o2,
		// or o1 has precedence less than that of o2,

		while (!this.functionStack.isEmpty()
				&& this.functionStack.peek().getNodeType() != NodeType.None
				&& ((f.getNodeType() == NodeType.OperatorLeft && f
						.getPrecedence() >= this.functionStack.peek()
						.getPrecedence()) || f.getPrecedence() > this.functionStack
						.peek().getPrecedence())) {
			outputQueue(this.functionStack.pop());
		}

		functionStack.push(f);
	}

	private void handleNumeric() {
		double value, exponent;
		char sign = '+';
		boolean isFloat = false;
		boolean neg = false;

		value = 0.0;
		exponent = 0;

		// should we just make this negative, due to an unary minus?
		if (!this.functionStack.isEmpty()
				&& this.functionStack.peek() == StandardExtensions.EXTENSION_NEG) {
			this.functionStack.pop();
			neg = true;
		}

		// whole number part

		while (Character.isDigit(this.parser.peek())) {
			value = (10.0 * value) + (this.parser.readChar() - '0');
		}

		// Optional fractional
		if (this.parser.peek() == '.') {
			isFloat = true;
			this.parser.advance();

			int i = 1;
			while (Character.isDigit(this.parser.peek())) {
				double f = (this.parser.readChar() - '0');
				f /= Math.pow(10.0, i);
				value += f;
				i++;
			}
		}

		// Optional exponent

		if (Character.toUpperCase(this.parser.peek()) == 'E') {
			this.parser.advance();

			if ((this.parser.peek() == '+') || (this.parser.peek() == '-')) {
				sign = this.parser.readChar();
			}

			while (Character.isDigit(this.parser.peek())) {
				exponent = (int) (10.0 * exponent)
						+ (this.parser.readChar() - '0');
			}

			if (sign == '-') {
				isFloat = true;
				exponent = -exponent;
			}

			value = value * Math.pow(10, exponent);
		}

		if (neg) {
			value = -value;
		}

		ProgramNode v = this.holder.getFunctions().factorProgramNode("#const",
				holder, new ProgramNode[] {});

		if (isFloat) {
			v.getData()[0] = new ExpressionValue(value);
		} else {
			v.getData()[0] = new ExpressionValue((int) value);
		}

		outputQueue(v);
	}

	private void handleAlpha(boolean neg) {
		final StringBuilder varName = new StringBuilder();
		while (Character.isLetterOrDigit(this.parser.peek())) {
			varName.append(this.parser.readChar());
		}

		this.parser.eatWhiteSpace();

		if (varName.toString().equals("true")) {
			if (neg) {
				throw new EACompileError("Invalid negative sign.");
			}
			ProgramNode v = this.holder.getFunctions().factorProgramNode("#const",
					holder, new ProgramNode[] {});
			v.getData()[0] = new ExpressionValue(true);
			outputQueue(v);
		} else if (varName.toString().equals("false")) {
			if (neg) {
				throw new EACompileError("Invalid negative sign.");
			}
			ProgramNode v = this.holder.getFunctions().factorProgramNode("#const",
					holder, new ProgramNode[] {});
			v.getData()[0] = new ExpressionValue(false);
			outputQueue(v);
		} else if (this.parser.peek() != '(') {
			ProgramNode v;
			// either a variable or a const, see which
			if (this.holder.getFunctions().isDefined(varName.toString(), 0)) {
				v = this.holder.getFunctions().factorProgramNode(
						varName.toString(), holder, new ProgramNode[] {});
			} else {
				this.holder.getVariables().setVariable(varName.toString(),
						new ExpressionValue(0));
				v = this.holder.getFunctions().factorProgramNode("#var", holder,
						new ProgramNode[] {});
				v.getData()[0] = new ExpressionValue((int) this.holder.getVariables()
						.getVariableIndex(varName.toString()));
			}

			if (neg) {
				v = this.holder.getFunctions().factorProgramNode("-", holder,
						new ProgramNode[] { v });
			}
			outputQueue(v);
		} else {
			ProgramExtensionTemplate temp = this.holder.getFunctions()
					.findFunction(varName.toString());
			if (temp == null) {
				throw new EACompileError("Undefined function: "
						+ varName.toString());
			}
			functionQueue(temp);
		}
	}

	private void handleSymbol() {
		char ch1 = this.parser.readChar();

		// handle unary
		if (this.unary) {
			if (ch1 == '+') {
				return;
			} else if (ch1 == '-') {
				this.functionStack.push(StandardExtensions.EXTENSION_NEG);
				return;
			}
		}

		// handle regular operator
		char ch2 = 0;
		if (!this.parser.eol()) {
			ch2 = this.parser.peek();
		}

		ProgramExtensionTemplate temp = this.holder.getFunctions()
				.findOperator(ch1, ch2);

		// did we find anything?
		if (temp != null) {
			// if we found a 2-char operator, then advance beyond the 2nd
			// char
			if (temp.getName().length() > 1) {
				this.parser.advance();
			}
			functionQueue(temp);
		} else {
			throw new EACompileError("Unknown symbol: " + ch1);
		}

	}

	private void handleString() {
		final StringBuilder str = new StringBuilder();

		char ch;

		if (this.parser.peek() == '\"') {
			this.parser.advance();
		}
		do {
			ch = this.parser.readChar();
			if (ch == 34) {
				// handle double quote
				if (this.parser.peek() == 34) {
					this.parser.advance();
					str.append(ch);
					ch = this.parser.readChar();
				}
			} else {
				str.append(ch);
			}
		} while ((ch != 34) && (ch > 0));

		if (ch != 34) {
			throw (new EACompileError("Unterminated string"));
		}

		ProgramNode v = this.holder.getFunctions().factorProgramNode("#const",
				holder, new ProgramNode[] {});
		v.getData()[0] = new ExpressionValue(str.toString());
		outputQueue(v);
	}

	private void handleRightParen() {
		// move past the paren
		this.parser.advance();

		// Until the token at the top of the stack is a left parenthesis, pop
		// operators off the stack onto the output queue.

		while (this.functionStack.peek() != this.LEFT_PAREN) {
			outputQueue(this.functionStack.pop());
		}

		// Pop the left parenthesis from the stack, but not onto the output
		// queue.
		this.functionStack.pop();

		// If the token at the top of the stack is a function token, pop it onto
		// the output queue.
		if (!this.functionStack.isEmpty()
				&& this.functionStack.peek().getNodeType() == NodeType.Function) {
			outputQueue(this.functionStack.pop());
		}

		// If the stack runs out without finding a left parenthesis, then there
		// are mismatched parentheses.
	}

	private void handleFunctionSeparator() {
		// advance past
		this.parser.advance();

		// Until the token at the top of the stack is a left parenthesis,
		// pop operators off the stack onto the output queue.
		while (this.functionStack.peek() != this.LEFT_PAREN) {
			outputQueue(this.functionStack.pop());
		}
		
		// If no left parentheses are encountered, either the separator was
		// misplaced or parentheses were mismatched.

	}

	public ProgramNode parse(final String expression) {
		this.parser = new SimpleParser(expression);
		this.unary = true;

		while (!parser.eol()) {
			parser.eatWhiteSpace();
			char ch = parser.peek();
			if (ch == '.' || Character.isDigit(ch)) {
				handleNumeric();
				this.unary = false;
			} else if (Character.isLetter(ch)) {
				handleAlpha(false);
				this.unary = false;
			} else if (ch == '(') {
				this.parser.advance();
				this.functionStack.push(LEFT_PAREN);
				this.unary = true;
			} else if (ch == ')') {
				handleRightParen();
				this.unary = false;
			} else if ("<>^*/+-=&|".indexOf(ch) != -1) {
				handleSymbol();
				this.unary = true;
			} else if (ch == '\"') {
				handleString();
				this.unary = false;
			} else if (ch == ',') {
				handleFunctionSeparator();
				this.unary = true;
			} else {
				throw new EACompileError("Unparsable character: " + ch);
			}
		}

		// pop off any functions still on the stack
		while (!this.functionStack.isEmpty()) {
			ProgramExtensionTemplate f = this.functionStack.pop();
			outputQueue(f);
		}

		// were there no operators?
		if (this.rootNode == null) {
			this.rootNode = this.outputStack.pop();
		}

		return this.rootNode;
	}
}
