/*
 * Rule.java
 */

package com.uvigo.tMaestro.SCOCreator.tool.adaptation;

import com.uvigo.tMaestro.SCOCreator.tool.MainFrame;

/**
 * Esta clase contiene toda la informacion necesaria relativa
 * a una regla de adaptacion generada mediante el asistente de reglas.
 * Se utiliza para guardar tanto la regla (como una cadena de caracteres
 * y como una estructura que la representa como suma de productos logicos), asi
 * como toda la informacion necesaria para la edicion de la regla.
 * Todas la reglas generadas se almacenan en un ArrayList en el MainFrame.
 * @author raul
 * @version 0.1
 */
public class Rule implements java.io.Serializable {
    
    /**
     * Clase interna:
     * Esta clase representa un producto de expresiones logicas
     * @author raul
     * @version 0.1
     */
    public class Product implements java.io.Serializable{
        
        /**
         * Las expresiones que forman parte del producto se almacenan como un ArrayList
         */
        private java.util.ArrayList<Expression> oComponents;
        
        /**
         * Crea un nuevo producto con oE
         */
        public Product(Expression oE) {
            oComponents = new java.util.ArrayList<Expression>();
            oComponents.add(oE);
        }
        
        /**
         * Crea un nuevo producto vacio
         */
        public Product() {
            oComponents = new java.util.ArrayList<Expression>();
        }
        
        
        /**
         * Anade la nueva expresion oE al producto
         */
        private void addProduct(Expression oE) {
            if (!oComponents.contains(oE)) oComponents.add(oE);
        }
        
        
        /**
         * Anade el producto oNewProduct a este producto
         */
        private void addProduct(Product oP){
            for (int i=0;i<oP.oComponents.size();i++)
                if (!this.oComponents.contains(oP.oComponents.get(i))) this.oComponents.add(oP.oComponents.get(i));
            //this.oComponents.addAll(oP.oComponents);
        }
        
        /**
         * Devuelve una copia del producto.
         */
        private Product copyProduct() {
            Product oNewProduct = new Product();
            oNewProduct.oComponents.addAll(oComponents);
            return oNewProduct;
        }
        
        /**
         * Este metodo indica si oObject es igual a este producto
         */
        public boolean equals(Object oObject) {
            if (oObject instanceof Product) {
                Product oProduct = (Product) oObject;
                if (oProduct.oComponents.size()!=this.oComponents.size()) return false;
                for (int i=0;i<this.oComponents.size();i++) if (!oProduct.oComponents.contains(this.oComponents.get(i))) return false;
                return true;
            } else return super.equals(oObject);
        }
        
        /**
         * Devuelve el numero de expresiones de este Producto
         */
        public int sizeExpressions() {
            return oComponents.size();
        }
        
        /**
         * Devuelve la expresion de indice iIndex en este producto
         */
        public Expression getExpression(int iIndex) {
            return oComponents.get(iIndex);
        }
        
        /**
         * Devuelve la SOP resultado de negar este producto
         * @return La SOP resultante de negar el producto
         */
        private SOP negate() {
            SOP oSOP = new SOP(true);
            for (int i=0;i<oComponents.size();i++) {
                Expression oE = oComponents.get(i).copyExpression();
                oE.setNot();
                oSOP.orWithExpression(oE);
            }
            return oSOP;
        }
        
        
    } //Fin de la clase interna Product
    
    
    /**
     * Clase interna:
     * Esta clase representa una suma de productos de expresiones logicas
     * @author raul
     * @version 0.1
     */
    public class SOP implements java.io.Serializable {
        
        /**
         * Los sumandos se almacenan como un array de Product
         */
        private java.util.ArrayList<Product> oSum;
        
        
        /**
         * Crea una nueva instancia de este SOP a partir de oRule
         * @param bEmpty Si es true si crea una SOP vacia
         */
        public SOP(boolean bEmppty) {
            oSum = new java.util.ArrayList<Product>();
            if (bEmppty) return;
            int iLastAction;
            //Recorremos el Array de acciones
            for (int i=iIndex;i<oActions.size();i++) {
                if (i==0) iLastAction=INITIAL;
                else iLastAction = oActions.get(i-1);
                switch (oActions.get(i)) {
                    case EXPRESSION:
                        Expression oExpression = oExpressions.get(iIndexExpression);
                        iIndexExpression++;
                        if (iLastAction==NOT) {
                            oExpression.setNot();
                            if (oActions.get(i-2)==AND) {
                                if (oActions.get(i-3)==PARC) andWithExpression(oExpression);
                                else addExpresionToLastSum(oExpression);
                            } else  oSum.add(new Product(oExpression));
                        } else if (iLastAction==AND) {
                            if (oActions.get(i-2)==PARC) andWithExpression(oExpression);
                            else addExpresionToLastSum(oExpression);
                        } else  oSum.add(new Product(oExpression));
                        break;
                    case PARO:
                        iIndex=i+1;
                        if (iLastAction==NOT) {
                            if (oActions.get(i-2)==AND) {
                                if (oActions.get(i-3)==PARC) andWithSOP(new SOP(false).negate());
                                else andWithLastSum(new SOP(false).negate());
                            } else orWithSOP(new SOP(false).negate());
                        } else if (iLastAction==AND) {
                            if (oActions.get(i-2)==PARC) andWithSOP(new SOP(false));
                            else andWithLastSum(new SOP(false));
                        } else orWithSOP(new SOP(false));
                        i=iIndex;
                        break;
                    case PARC:
                        iIndex=i;
                        return;
                }
            }
            simplify();
            iIndex=oActions.size();
        }
        
        /**
         * A�ade oE al ultimo sumando de esta SOP
         */
        private void addExpresionToLastSum(Expression oE) {
            if (oSum.size()==0) oSum.add(new Product(oE));
            else oSum.get(oSum.size()-1).addProduct(oE);
        }
        
        /**
         * Este metodo realiza un AND entre sumas de productos
         * @param oSOP La otra suma de productos que forma parte del AND
         */
        private void andWithSOP(SOP oSOP) {
            int iSize=oSum.size();
            for (int i=0;i<iSize;i++) {
                for (int y=1;y<oSOP.oSum.size();y++) {
                    Product oP = oSum.get(i).copyProduct();
                    oP.addProduct(oSOP.oSum.get(y));
                    oSum.add(oP);
                }
                oSum.get(i).addProduct(oSOP.oSum.get(0));
            }
        }
        
        /**
         * Realiza una AND entre oSop y el ultimo sumando de este SOP
         * @param oSOP El SOP con el que se va a multiplicar el ultimo sumando
         */
        private void andWithLastSum(SOP oSOP) {
            int iLast = oSum.size()-1;
            for (int y=1;y<oSOP.oSum.size();y++) {
                Product oP = oSum.get(iLast).copyProduct();
                oP.addProduct(oSOP.oSum.get(y));
                oSum.add(oP);
            }
            oSum.get(iLast).addProduct(oSOP.oSum.get(0));
        }
        
        /**
         * Este metodo realiza el producto entre este SOP y la expression oE
         */
        private void andWithExpression(Expression oE) {
            for (int i=0;i<this.oSum.size();i++) this.oSum.get(i).addProduct(oE);
        };
        
        /**
         * Este mrtodo realiza un OR entre sumas de productos
         * @param oSOP La otra suma de productos que forma parte del OR
         */
        private void orWithSOP(SOP oSOP) {
            this.oSum.addAll(oSOP.oSum);
        }
        
        
        /**
         * Este metodo realiza un OR entre la SOP y oExpression
         * @param oExpression La expression con la que hacer el OR
         */
        private void orWithExpression(Expression oExpression) {
            this.oSum.add(new Product(oExpression));
        }
        
        /**
         * El metodo elimina los sumandos redundantes
         */
        private void simplify(){
            //Primero eliminamos los sumandos repetidos
            for (int i=0;i<oSum.size();i++) {
                Product oProduct = oSum.get(i);
                int iFrecuency=java.util.Collections.frequency(oSum, oProduct);
                if (iFrecuency>1) {
                    do {
                        oSum.remove(oProduct);
                        iFrecuency--;
                    } while (iFrecuency>1);
                }
            }
            //Ahora simplificamos los sumandos de forma que ab+abc=ab
            boolean bSimply=true;
            for (int i=0;i<oSum.size();i++) {
                int iNumProd = oSum.get(i).sizeExpressions();
                for (int y=i+1;y<oSum.size();y++) {
                    if (oSum.get(y).sizeExpressions()>iNumProd) {
                        bSimply=true;
                        for (int z=0;z<iNumProd;z++) {
                            if (!oSum.get(y).oComponents.contains(oSum.get(i).getExpression(z))) {
                                bSimply=false;
                                break;
                            }
                        }
                        if (bSimply) oSum.remove(y);
                    } else if (oSum.get(y).sizeExpressions()<iNumProd) {
                        bSimply=true;
                        for (int z=0;z<oSum.get(y).sizeExpressions();z++) {
                            if (!oSum.get(i).oComponents.contains(oSum.get(y).getExpression(z))) {
                                bSimply=false;
                                break;
                            }
                        }
                        if (bSimply) oSum.remove(i);
                    }
                }
            }
        }
        
        /**
         * Devuelve la SOP resultado de negar esta SOP
         */
        private SOP negate() {
            if (oSum.size()==0) return this;
            SOP oNewSOP = oSum.get(0).negate();
            for (int i=1;i<oSum.size();i++) {
                oNewSOP.andWithSOP(oSum.get(i).negate());
            }
            return oNewSOP;
        }
        
        
    } //Fin de la clase interna SOP
    
    /**
     * Estado de edici�n inicial en una regla
     */
    final public static int INITIAL=0;
    
    /**
     * Lo ultimo que se ha anadido ha sido un AND
     */
    final public static int AND=1;
    
    /**
     * Lo ultimo que se ha anadido ha sido un OR
     */
    final public static int OR=2;
    
    /**
     * Lo ultimo que se ha anadido ha sido un NOT
     */
    final public static int NOT=3;
    
    /**
     * Lo ultimo que se ha anadido ha sido un (
     */
    final public static int PARO=4;
    
    /**
     * Lo ultimo que se ha anadido ha sido un )
     */
    final public static int PARC=5;
    
    /**
     * Lo ultimo que se ha anadido ha sido una expresion
     */
    final public static int EXPRESSION=6;
    
    /**
     * Esta cadena representa la expresion logica
     * de esta regla. Se supone que esta expresionn logica,
     * generada mediante el asistente de reglas sera correcta
     * si la variable booleana bValid es true
     */
    private String sTextRule;
    
    /**
     * Indica si la regla ya ha sido validada
     */
    private boolean bValid;
    
    /**
     * Ha habido una transicion de validada a no validada
     */
    private boolean bTransition;
    
    /**
     * El nombre de esta regla
     */
    private String sName;
    
    
    /**
     * El indice de la opci�n que constituye la accion para esta regla
     * Vale -1 si no se le ha asignado una opcion
     */
    private int iOption;
    
    /**
     * Esta pila contiene la secuencia de acciones seguidas por el usuario
     * para crear la regla. Las acciones estan definidas al principio de la clase
     */
    private java.util.Stack<Integer> oActions;
    
    /**
     * Numero de caracteres en sTextRule de cada una de las acciones
     */
    private java.util.Stack<Integer> oCharActions;
    
    /**
     * Secuencia de expresiones que componen la regla
     */
    private java.util.Stack<Expression> oExpressions;
    
    /**
     * Lleva la cuenta del numero de parentesis abiertos (sin cerrar)
     * por el usuario en la edicion de la regla
     */
    private int iPar;
    
    /**
     * Expresion de esta regla mediante una suma de productos.
     */
    private SOP oSOP;
    
    /**
     * Esta variable se utiliza para recorrer las acciones
     * cuando se llama a la funcion computeSOP();
     */
    private int iIndex;
    
    
    /**
     * Esta variable se utiliza para recorrer las expresiones
     * cuando se llama a la funcion computeSOP();
     */
    private int iIndexExpression;
    
    
    /**
     * Contructor de la clase
     */
    public Rule(String sName) {
        oActions = new java.util.Stack<Integer>();
        oActions.push(INITIAL);
        iPar=0;
        bValid = false;
        bTransition=false;
        iOption = -1;
        sTextRule = new String("");
        this.sName = new String(sName);
        oCharActions = new java.util.Stack<Integer>();
        oCharActions.push(0);
        oExpressions = new java.util.Stack<Expression>();
    }
    
    /**
     * Este metodo computa la SOP, es decir
     * convierte la regla tal como la expreso el usuario
     * en una suma de productos
     */
    private void computeSOP() {
        iIndex=0;
        iIndexExpression=0;
        oSOP = new SOP(false);
    }
    
    /**
     * Indica si la regla ha sido validada o no
     */
    public boolean isValid() {
        return bValid;
    }
    
    
    /**
     * Elimina la ultima accion realizada por el usuario
     * cuando esta regla estaba cargada en el asistente.
     * Tambien se elimina del texto sTextRule
     */
    public void removeLastAction() {
        if (oActions.size()==0) return;
        if (bValid) {
            bValid=false; //La regla ya no puede estar validada
            bTransition=true;
        }
        
        int iAction = oActions.pop();
        int iChar = oCharActions.pop();
        sTextRule = sTextRule.substring(0,sTextRule.length()-iChar);
        if (iAction==PARC) iPar++;
        else if (iAction==PARO) iPar--;
        else if (iAction==EXPRESSION) oExpressions.pop();
    }
    
    /**
     * Devuelve la ultima accion realizada por el usuario
     * cuando esta regla estaba cargada en el asistente
     */
    public int getLastAction() {
        if (oActions.size()==0) return -1;
        return oActions.get(oActions.size()-1);
    }
    
    /**
     * Devuelve la accion de indice iIndex
     * int iIndex El indice de la accion
     */
    public int getAction(int iIndex) {
        if (oActions.size()==0) return -1;
        return oActions.get(iIndex);
    }
    
    /**
     * Devuelve el numero de acciones
     */
    public int sizeActions() {
        return oActions.size();
    }
    
    /**
     * Anade la ultima accio que el usuario ha realizado sobre
     * la regla
     * @param iAction El identificador de la accion.
     * @param sRule El texto de la accion
     */
    public void addAction(int iAction,String sRule) {
        if (bValid) { //Si se anade una accion a una regla validada, deja de estar validada
            bValid=false;
            bTransition=true;
        }
        oActions.push(iAction);
        sTextRule = sTextRule.concat(sRule);
        oCharActions.push(sRule.length());
    }
    
    /**
     * Anade una expresion a la regla.
     */
    public void addExpression(Expression oExpression) {
        oExpressions.push(oExpression);
    }
    
    /**
     * Cambia el texto de la regla por el indicado
     */
    public void setText(String sNew) {
        sTextRule = new String(sNew);
    }
    
    /**
     * Devuelve el texto correspondiente a la regla
     */
    public String getText() {
        return new String(sTextRule);
    }
    
    /**
     * Devuelve el numero de parentesis abietos (sin cerrar) en la edicion de la regla
     */
    public int getPar() {
        return iPar;
    }
    
    /**
     * Incrementa el numero de parentesis sin cerrar en una unidad
     */
    public void incPar() {
        iPar++;
    }
    
    /**
     * Decrementa el numero de parentesis sin cerrar en una unidad
     */
    public void decPar() {
        iPar--;
    }
    
    /**
     * Cambia el nombre de la regla por el indicado
     */
    public void setName(String sNewName) {
        sName = new String(sNewName); //Usamos el constructor para una correcta encapsulacion de la variable
    }
    
    
    /**
     * Devuelve el nombre correspondiente a la regla
     */
    public String getName() {
        return new String(sName);
    }
    
    /**
     * La funcion comprueba si la regla es valida.
     * Tal como esta realizado el editor todas las reglas
     * que llegan a esta funcion simpre seran correctas expresiones
     * logicas, los unicos problemas pueden ser parentesis sin cerrar
     * Asi, lo primero que hace esta funcion es cerrar los par�ntesis que falten por cerrar,
     * y finalmente computa la Suma de productos
     * de esta regla. Por ello, el nombre validar puede no resultar adecuado, si bien el usuario
     * no tiene porque percibirlo.
     */
    public void validate(javax.swing.JTextArea oTextRule) {
        //Cerramos los parentesis faltantes
        while (iPar!=0) {
            decPar();
            oTextRule.append(")");
            addAction(PARC,")");
            MainFrame.oMainFrame.getAssistant().stateChange(PARC);
        }
        //Computamos la suma de productos
        computeSOP();
        bValid=true;
        for (int i=0;i<oSOP.oSum.size();i++) {
            for (int y=0;y<oSOP.oSum.get(i).oComponents.size();y++) {
                System.out.println(oSOP.oSum.get(i).oComponents.get(y).getBoolean()+" "+oSOP.oSum.get(i).oComponents.get(y).getExpression()+".");
            }
            System.out.println(" + ");
        }
        System.out.println("********************************************************");
        System.out.println(isSumOfSimpleExpression());
    }
    
    /**
     * Esta funcion indica si esta regla se puede expresar como una suma simple
     * de expresiones
     */
    public boolean isSumOfSimpleExpression() {
        if (oSOP==null) return false;
        boolean bSimple=true;
        for (int i=0;i<oSOP.oSum.size();i++) {
            if (oSOP.oSum.get(i).oComponents.size()>1) {
                bSimple=false;
                break;
            }
        }
        return bSimple;
    }
    
    /**
     * Numero de sumas de productos que componen la SOP de esta regla
     */
    public int sizeSOP() {
        return oSOP.oSum.size();
    }
    
    /**
     * Devuelve el producto de indice iIndex en la SOP de esta regla
     */
    public Product getProduct(int iIndex) {
        return oSOP.oSum.get(iIndex);
    }
    
    /**
     * Devuelve el indice de la opcion para esta regla
     */
    public int getOption() {
        return this.iOption;
    }
    
    /**
     * Establece un nuevo valor para la opcion
     */
    public void setOption(int iOption) {
        this.iOption=iOption;
    }
    
    /**
     * Indica al asistente si se ha producido un cambio en el estado
     * de validacion de la regla de validada a no validada.
     * Se utiliza para actualizar la JList de reglas
     * del asistente.
     */
    public boolean hasTransition() {
        if (bTransition) {
            bTransition=false;
            return true;
        } else return false;
    }
    
}
