
package es.ejie.frmk.infraestructura.traza;

import java.util.ArrayList;
import java.util.Stack;
import java.util.StringTokenizer;

import es.ejie.frmk.infraestructura.logs.base.Q70LogInterno;


/**
 * <p>Realiza un parseo de la expresión booleana a la vez que crea una representación postfija
 * de la misma. El algoritmo es una versión simplificada ya que no existe precedencia
 * de operadores, ni operadores unarios. Únicamente contempla parentesis. Las operaciones
 * pueden ser:</p>
 * <p><B>op1 = op2</B> donde op1 siempre debe representar una variable y op2 el valor de la 
 * misma.</p>
 * <p><B>op1 OR op2</B>  y <B>op1 AND op2</B> donde op1 y op2 siempre han de ser comparaciones 
 * (no se admite el uso de FALSE O TRUE directamente)</p>
 * <p>Ejemplo: ((ID_USUARIO=HOLA AND COD_ENTIDAD=PEPE) OR ID_USUARIO=ADMIN) AND NIVEL=INFO</p>
 * <p>NIVEL es considerada una variable especial que únicamente puede tomar como valor DEBUG,
 * INFO, WARN, ERROR Y FATAL.
 * 
 * @author Carlos Rodriguez
 * @version 1.0  fecha: 17-dic-03
 */
public class Q70AnalizadorCondicion
{
	private final static int ID_TIPO_VARIABLE = 1;
	private final static int ID_TIPO_VALOR = 2;
	private final static int ID_TIPO_OPERADOR = 3;

	private final static String NIVEL = "NIVEL";
	private final static String DEBUG = "DEBUG";
	private final static String INFO = "INFO";
	private final static String WARN = "WARN";
	private final static String ERROR = "ERROR";
	private final static String FATAL = "FATAL";

	StringTokenizer st;
	String actualToken;
	String condition;
	String parsedExpression;
	ArrayList expresionPostfija;
	Stack pilaOperadores;
	
	ArrayList variableNames = new ArrayList();

	
	public void setVariableNames(ArrayList variableNames)
	{
		//asignar el nombre de las variables que se utilizan en el parseo
		this.variableNames = variableNames;

	}
	
	public void setVariableNames(String variableNames)
	{
		//asignar el nombre de las variables que se utilizan en el parseo
		//con un string separado por comas
		StringTokenizer st = new StringTokenizer(variableNames, ",");
		while (st.hasMoreTokens())
		{
			this.variableNames.add(st.nextToken());
		}
	}
	
	private String nextToken()
	{
		//lee los tokens evitando caracteres en blanco
		String token = null;
		do
		{
			if (st.hasMoreTokens())
				token = st.nextToken();
			else
				return null;

		}
		while (token.equals(" "));

		Q70LogInterno.log("next token: " + token);
		parsedExpression += token + " ";

		return token;
	}

	private void comparison() throws Q70ExcepcionAnalizadorCondicion
	{
		//parsea variable = valor
		String variable = null;
		boolean isNivel = false;
		String valor = null;

		variable = actualToken;
		
		try
		{
			//comprobar que la variable existe.
			if (!variableNames.contains(actualToken))
				throw new Q70ExcepcionAnalizadorCondicion(
					parsedExpression + ". variable desconocida: "+ actualToken);
			if (variable != null)
			{
				if (variable.equals(NIVEL))
					isNivel = true;

				actualToken = nextToken();
				boolean negacion = false;
				if (actualToken.equals("!"))
				{
					negacion = true;
					actualToken = nextToken();

				}
				if (actualToken.equals("="))
				{
					actualToken = nextToken();
					valor = actualToken;
					//si estamos hablando del nivel entonces tenemos que comprobar
					//que se trata de uno de los establecidos
					if (isNivel)
						if (!(valor.equals(DEBUG)
							|| valor.equals(INFO)
							|| valor.equals(WARN)
							|| valor.equals(ERROR)
							|| valor.equals(FATAL)))
							throw new Q70ExcepcionAnalizadorCondicion(
								parsedExpression + ". NIVEL INCORRECTO");
								
				}
				else
				{
					throw new Q70ExcepcionAnalizadorCondicion(
						parsedExpression + ". Se esperaba = o !=");
				}

				expresionPostfija.add(new Q70Token(ID_TIPO_VARIABLE, variable));
				expresionPostfija.add(new Q70Token(ID_TIPO_VALOR, valor));
				if(isNivel)
					expresionPostfija.add(new Q70Token(ID_TIPO_OPERADOR, ">"));
				else if (negacion)
					expresionPostfija.add(new Q70Token(ID_TIPO_OPERADOR, "!="));
				else
					expresionPostfija.add(new Q70Token(ID_TIPO_OPERADOR, "="));

				if (!pilaOperadores.isEmpty())
				{
					//Si el operador no es el parentesis abierto añadir el operador
					if (!pilaOperadores.peek().equals("("))
					{
						expresionPostfija.add(
							new Q70Token(
								ID_TIPO_OPERADOR,
								(String) pilaOperadores.pop()));
					}
				}
			}
		}
		catch (NullPointerException npe)
		{
			throw new Q70ExcepcionAnalizadorCondicion(parsedExpression + "Expresion incompleta");
		}
	}
	private void condition() throws Q70ExcepcionAnalizadorCondicion
	{

		//CONDICION
		//	|						  -----------------------------------------------	
		//	|						  |												|
		//  |-->  "(" ---> CONDICION ---> ")"-------->"AND" |"OR"------>CONDICION	|
		//	|									|									|
		//	|-->  COMPARACION----------------------------------------------------------->  		
		//							 				

		try
		{
			if (actualToken.equals("("))
			{
				pilaOperadores.push(actualToken);
				actualToken = nextToken();
				condition();
				if (actualToken.equals(")"))
				{
					pilaOperadores.pop(); //quitar el parentesis abierto 
					if (!pilaOperadores.isEmpty()
						&& !pilaOperadores.peek().equals("("))
					{
						expresionPostfija.add(
							new Q70Token(
								ID_TIPO_OPERADOR,
								(String) pilaOperadores.pop()));
					}
					actualToken = nextToken();
					if (actualToken == null)
						return;
					else if (
						actualToken.equals("AND") || actualToken.equals("OR"))
					{
						pilaOperadores.push(actualToken);
						actualToken = nextToken();
						condition();
					}
				}
				else
				{
					throw new Q70ExcepcionAnalizadorCondicion(
						parsedExpression + ". Error se esperaba )");
				}

			}
			//COMPARACION --> AND|OR --> CONDICION 
			else
			{
				//actualToken = nextToken();
				comparison();
				actualToken = nextToken();
				if (actualToken == null)
					return;

				if (actualToken.equals("OR") || actualToken.equals("AND"))
				{
					pilaOperadores.push(actualToken);
					actualToken= nextToken();
					condition();
					
				}
				
			}
		}
		catch (NullPointerException npe)
		{
			throw new Q70ExcepcionAnalizadorCondicion("Expresion incompleta");
		}
	}

	public void parseBoolExpr() throws Q70ExcepcionAnalizadorCondicion
	{
		parsedExpression = "";
		Q70LogInterno.log(condition);
		//Si no hay condicion entonces no hay que hacer nada
		if(condition==null || condition.trim().equals(""))
			return;
		
		//Hay que separar la expresion booleana en tokens, strings reconocibles.
		//Los separadores utilizados son: '(', ')', '=', '!'(negacion:delante de un '=')
		st = new StringTokenizer(condition, " ()=!", true);
		actualToken = nextToken();
		condition();
		if(actualToken!=null)
			throw new Q70ExcepcionAnalizadorCondicion("Elemento \"" + actualToken + "\" inesperado en \"" + parsedExpression +"\"");

	}

	public void setCondition(String condition)
	{
		expresionPostfija = new ArrayList();
		pilaOperadores = new Stack();
		this.condition = condition;
	}

	public ArrayList getParsedCondition()
	{
		return expresionPostfija;
	}
	public String getParsedConditionString()
	{
		StringBuffer s = new StringBuffer();
		for (int i = 0; i < expresionPostfija.size(); i++)
		{
			s.append(((Q70Token) expresionPostfija.get(i)).getValor());
			s.append(" # ");
		}
		return s.toString();
	}

/*
	private void printPostExp()
	{
		int size = expresionPostfija.size();
		Q70Token token = null;
		String postfix = null;
		for (int i = 0; i < size; i++)
		{
			token = (Q70Token) expresionPostfija.get(i);
			postfix += "[" + token.getValor() + "] ";

		}
		Q70LogInterno.log(postfix);
	}
*/	
}
