/**
 * File:    StringtoTreeParser.java
 * Created: 19.09.2006
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Wolf Fischer, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.misc.guards;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jwt.meta.model.data.Data;
import org.eclipse.jwt.meta.model.processes.GuardSpecification;
import org.eclipse.jwt.meta.model.processes.OperationType;
import org.eclipse.jwt.meta.model.processes.ProcessesPackage;
import org.eclipse.jwt.we.misc.logging.Logger;


/**
 * This class takes a string and converts it into an boolean tree.
 * 
 * @version $Id: StringtoTreeParser.java,v 1.7 2009-11-26 12:41:01 chsaad Exp $
 * @author Wolf Fischer, Programming distributed Systems Lab, University of Augsburg,
 *         Germany, www.ds-lab.org
 */
@SuppressWarnings("nls")
public class StringtoTreeParser
{

	Logger logger = Logger.getLogger(StringtoTreeParser.class);

	/**
	 * Those are strings representing the "should-be" syntax
	 */
	private static final String AND = "&";

	private static final String OR = "|";

	private static final String L = "<";

	private static final String IS = "=";

	private static final String G = ">";

	private static final String OB = "(";

	private static final String CB = ")";

	private static final String NOT = "!";

	private static final String[] allowedSigns =
	{ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
			"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "�", "�", "�", "<", "=",
			">", "(", ")", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ".", "&",
			"|", "!" };

	/**
	 * This later holds the {@link GuardSpecification} root node
	 */
	private GuardSpecification rootnode;

	/**
	 * Holds a list of all data. This is used for automatically searching the data object,
	 * whose name matches a given name in the formula
	 */
	private List<Data> datalist;


	/**
	 * Takes a formula and a datalist and constructs out of the string a boolean tree
	 * 
	 * @param formula
	 * @param datalist
	 * @throws NotaFormulaException
	 */
	@SuppressWarnings("unchecked")
	public StringtoTreeParser(String formula, List<Data> datalist)
			throws NotaFormulaException
	{
		this.datalist = datalist;
		formula = formula.replaceAll(" ", "");
		isCorrectSyntax(formula);
		if (formula.length() > 0)
		{
			while (formula.substring(0, 1).equals(OB))
			{
				int index = searchClosingBracket(formula, 0);
				if (index == formula.length() - 1)
				{
					formula = formula.substring(1);
					formula = formula.substring(0, formula.length() - 1);
				}
				else if (index < 0)
				{
					throw new NotaFormulaException(0,
							NotaFormulaException.MISSING_CLOSING_BRACKET);
				}
				else
					break;
			}
		}
		/**
		 * Now split the term into its smaller parts by parsing through it and analysing
		 * it char by char. If the algorithm recognized a term, its subpart will be added
		 * to the objects list and later on put into GuardSpecification form.
		 */
		StringBuilder strb = new StringBuilder();
		List objects = new ArrayList();
		BooleanParserState ps = BooleanParserState.None;
		for (int i = 0; i < formula.length(); i++)
		{
			String sign = formula.substring(i, i + 1);
			if (sign.equals(OB))
			{
				int getclosingbracket = searchClosingBracket(formula, i);
				if (getclosingbracket < 0)
				{
					throw new NotaFormulaException(i,
							NotaFormulaException.MISSING_CLOSING_BRACKET);
				}
				String term = formula.substring(i + 1, getclosingbracket);
				if (!strb.toString().equals(""))
				{
					objects.add(strb.toString());
					strb.delete(0, strb.length());
					if ((ps != BooleanParserState.ReadLogicalAnd)
							&& (ps != BooleanParserState.ReadLogicalOr))
					{
						throw new NotaFormulaException(0,
								NotaFormulaException.UNKNOWN_TERM_COMBINATION);
					}
					objects.add(new BooleanConnector(ps));
				}
				objects.add(new StringtoTreeParser(term, datalist));
				i = getclosingbracket;
				ps = BooleanParserState.ReadOpeningBracket;
			}
			else if (sign.equals(NOT))
			{
				ps = BooleanParserState.ReadNot;
			}
			else if (sign.equals(IS))
			{
				if (ps == BooleanParserState.ReadIs)
				{
					ps = BooleanParserState.ReadEquals;
				}
				else if (ps == BooleanParserState.ReadLower)
				{
					ps = BooleanParserState.ReadLowerEquals;
				}
				else if (ps == BooleanParserState.ReadGreater)
				{
					ps = BooleanParserState.ReadGreaterEquals;
				}
				else if (ps == BooleanParserState.ReadNot)
				{
					ps = BooleanParserState.ReadUnequal;
				}
				else
					ps = BooleanParserState.ReadIs;
			}
			else if (sign.equals(L))
			{
				ps = BooleanParserState.ReadLower;
			}
			else if (sign.equals(G))
			{
				ps = BooleanParserState.ReadGreater;
			}
			else if (sign.equals(AND))
			{
				if (ps == BooleanParserState.ReadAnd)
				{
					ps = BooleanParserState.ReadLogicalAnd;
				}
				else
					ps = BooleanParserState.ReadAnd;
			}
			else if (sign.equals(OR))
			{
				if (ps == BooleanParserState.ReadOr)
				{
					ps = BooleanParserState.ReadLogicalOr;
				}
				else
					ps = BooleanParserState.ReadOr;
			}
			else
			{
				if (ps != BooleanParserState.ReadSign)
				{
					if (ps != BooleanParserState.None)
					{
						String buffer = strb.toString();
						if (!buffer.equals(""))
							objects.add(buffer);
						objects.add(new BooleanConnector(ps));
					}

					strb.delete(0, strb.length());
				}
				strb.append(sign);
				ps = BooleanParserState.ReadSign;
			}
		}
		if (ps == BooleanParserState.ReadSign)
			objects.add(strb.toString());

		/**
		 * Now create a tree out of the objects existing in the objects list
		 */
		rootnode = null;
		GuardSpecification currentnode = null;
		GuardSpecification ornode = null;
		GuardSpecification andnode = null;
		GuardSpecification lastnode = null;
		if (objects.size() >= 3)
		{
			for (int i = 0; i < objects.size() - 2; i += 2)
			{
				if (objects.get(i + 1) instanceof BooleanConnector)
				{
					BooleanConnector ops = (BooleanConnector) objects.get(i + 1);

					if (ops.getOperator() == BooleanParserState.ReadLogicalAnd
							|| (ops.getOperator() == BooleanParserState.ReadLogicalOr))
					{
						if (ops.getOperator() == BooleanParserState.ReadLogicalAnd)
						{
							if (andnode == null)
							{
								andnode = createnewGuardSpec(null, ops.getOperator(),
										null);
								if (ornode == null)
									rootnode = andnode;
								else if (rootnode == andnode || rootnode == ornode)
								{
									rootnode = createnewGuardSpec(null,
											BooleanParserState.ReadLogicalAnd, null);
									rootnode.getSubSpecification().add(andnode);
									rootnode.getSubSpecification().add(ornode);
								}
							}
							currentnode = andnode;
						}
						else
						{
							if (ornode == null)
							{
								ornode = createnewGuardSpec(null, ops.getOperator(), null);
								if (andnode == null)
									rootnode = ornode;
								else if (rootnode == andnode || rootnode == ornode)
								{
									rootnode = createnewGuardSpec(null,
											BooleanParserState.ReadLogicalAnd, null);
									rootnode.getSubSpecification().add(andnode);
									rootnode.getSubSpecification().add(ornode);
								}
							}
							currentnode = ornode;
						}
						if (lastnode != null)
						{
							currentnode.getSubSpecification().add(lastnode);
							lastnode = null;
						}
						if (objects.get(i) instanceof StringtoTreeParser)
						{
							StringtoTreeParser stp = (StringtoTreeParser) objects.get(i);
							currentnode.getSubSpecification().add(stp.getRootNode());
						}
						else if (objects.get(i + 2) instanceof StringtoTreeParser)
						{
							StringtoTreeParser stp = (StringtoTreeParser) objects
									.get(i + 2);
							currentnode.getSubSpecification().add(stp.getRootNode());
						}
					}
					else if (objects.get(i) instanceof String
							&& objects.get(i + 2) instanceof String)
					{
						lastnode = createnewGuardSpec((String) objects.get(i), ops
								.getOperator(), (String) objects.get(i + 2));
						if (currentnode != null)
						{
							currentnode.getSubSpecification().add(lastnode);
							lastnode = null;
						}
						if (rootnode == null)
							rootnode = lastnode;
					}
					else
					{
						throw new NotaFormulaException(0,
								NotaFormulaException.UNKNOWN_TERM_COMBINATION);
					}
				}
			}
		}
		else
		{
			throw new NotaFormulaException(0, NotaFormulaException.NOT_A_CORRECT_TERM);
		}
	}


	/**
	 * Creates a {@link GuardSpecification} out of an attribute, a
	 * {@link BooleanParserState} and a value.
	 * 
	 * @param attribute
	 * @param op
	 * @param value
	 * @return
	 * @throws NotaFormulaException
	 */
	private GuardSpecification createnewGuardSpec(String attribute,
			BooleanParserState op, String value) throws NotaFormulaException
	{
		GuardSpecification node = (GuardSpecification) EcoreUtil
				.create(ProcessesPackage.Literals.GUARD_SPECIFICATION);

		String[] attrparts = new String[]
		{ "" };
		if (attribute != null)
			attrparts = attribute.split("[.]");
		if (attrparts.length == 0)
		{
			node.setAttribute(attribute);
		}
		if (attrparts.length == 1)
		{
			node.setAttribute(attrparts[0]);
		}
		else if (attrparts.length == 2)
		{
			node.setData(getData(attrparts[0]));
			node.setAttribute(attrparts[1]);
		}
		else
		{
			throw new NotaFormulaException(0, NotaFormulaException.UNKNOWN_ATTRIBUTE);
		}
		setOperationinGuardSpec(node, op);
		node.setValue(value);
		return node;
	}


	/**
	 * Retrieves a data by name (case_in_sensitive!)
	 * 
	 * @param name
	 * @return
	 */
	private Data getData(String name)
	{
		for (int i = 0; i < datalist.size(); i++)
		{
			Data d = datalist.get(i);
			String dataname = d.getName().replaceAll(" ", "");
			if (dataname.toLowerCase().equals(name.toLowerCase()))
				return d;
		}
		return null;
	}


	/**
	 * Sets the operation in a {@link GuardSpecification}
	 * 
	 * @param gs
	 * @param bs
	 * @throws NotaFormulaException
	 */
	private static void setOperationinGuardSpec(GuardSpecification gs,
			BooleanParserState bs) throws NotaFormulaException
	{
		switch (bs)
		{
			case ReadLower:
			{
				gs.setSubSpecificationConnector(null);
				gs.setOperation(OperationType.LOWER_LITERAL);
			}
				break;
			case ReadLowerEquals:
			{
				gs.setSubSpecificationConnector(null);
				gs.setOperation(OperationType.LOWER_EQUALS_LITERAL);
			}
				break;
			case ReadGreater:
			{
				gs.setSubSpecificationConnector(null);
				gs.setOperation(OperationType.GREATER_LITERAL);
			}
				break;
			case ReadGreaterEquals:
			{
				gs.setSubSpecificationConnector(null);
				gs.setOperation(OperationType.GREATE_EQUALS_LITERAL);
			}
				break;
			case ReadEquals:
			{
				gs.setSubSpecificationConnector(null);
				gs.setOperation(OperationType.EQUALS_LITERAL);
			}
				break;
			case ReadUnequal:
			{
				gs.setSubSpecificationConnector(null);
				gs.setOperation(OperationType.UN_EQUALS_LITERAL);
			}
				break;
			case ReadLogicalAnd:
			{
				gs.setOperation(null);
				gs
						.setSubSpecificationConnector(org.eclipse.jwt.meta.model.processes.BooleanConnector.AND_LITERAL);
			}
				break;
			case ReadLogicalOr:
			{
				gs.setOperation(null);
				gs
						.setSubSpecificationConnector(org.eclipse.jwt.meta.model.processes.BooleanConnector.XOR_LITERAL);
			}
				break;
			default:
			{
				throw new NotaFormulaException(0, NotaFormulaException.UNKNOWN_OPERATOR);
			}
		}
	}


	/**
	 * Checks a term for its correct syntax.
	 * 
	 * @param term
	 *            The term to check
	 * @throws NotaFormulaException
	 *             Is thrown if there is an error found
	 */
	public static void isCorrectSyntax(String term) throws NotaFormulaException
	{

		/**
		 * Check for wrong signs in this term
		 */
		for (int i = 0; i < term.length(); i++)
		{
			boolean found = false;
			for (int i2 = 0; i2 < allowedSigns.length; i2++)
			{
				if (allowedSigns[i2].equals(term.substring(i, i + 1).toLowerCase()))
				{
					found = true;
					break;
				}
			}
			if (!found)
				throw new NotaFormulaException(i, NotaFormulaException.UNKNOWN_SIGN);
		}

		/**
		 * Check for wrong number of opening- and closing brackets
		 */
		int openingbrackets = 0;
		int closingbrackets = 0;
		for (int i = 0; i < term.length(); i++)
		{
			String substr = term.substring(i, i + 1);
			if (substr.equals(OB))
				openingbrackets++;
			else if (substr.equals(CB))
				closingbrackets++;
		}
		if (openingbrackets != closingbrackets)
			throw new NotaFormulaException(0,
					NotaFormulaException.UNEVEN_NUMBER_OF_BRACKET);

		// /**
		// * Check for correct positioning of brackets
		// */
		// for (int i = 0; i < term.length(); i++)
		// {
		// if (term.substring(i, i+1).equals(OB))
		// {
		// //if (searchClosingbracket(term, i) < 0) throw new
		// NotaFormulaException("There is no closing bracket for opening bracket
		// @ " + i);
		// }
		// }

	}


	/**
	 * Returns the rootnode
	 * 
	 * @return
	 */
	public GuardSpecification getRootNode()
	{
		return rootnode;
	}


	/**
	 * Searches the corresponding closing bracket to an opening bracket
	 * 
	 * @param formula
	 * @param startindex
	 * @return
	 */
	private static int searchClosingBracket(String formula, int startindex)
	{
		int countbrackets = 0;
		for (; startindex < formula.length(); startindex++)
		{
			if (formula.substring(startindex, startindex + 1).equals(OB))
			{
				countbrackets++;
			}
			if (formula.substring(startindex, startindex + 1).equals(CB))
			{
				countbrackets--;
			}
			if (countbrackets == 0)
				return startindex;
		}
		return -1;
	}

}
