/* Copyright or (C) or Copr. GET / ENST, Telecom-Paris, Ludovic Apvrille
 * 
 * ludovic.apvrille AT enst.fr
 * 
 * This software is a computer program whose purpose is to allow the
 * edition of TURTLE analysis, design and deployment diagrams, to
 * allow the generation of RT-LOTOS or Java code from this diagram,
 * and at last to allow the analysis of formal validation traces
 * obtained from external tools, e.g. RTL from LAAS-CNRS and CADP
 * from INRIA Rhone-Alpes.
 * 
 * This software is governed by the CeCILL  license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 */

package ui;

import java.io.StringReader;
import java.util.LinkedList;
import java.util.List;

import compiler.tmlparser.ParseException;
import compiler.tmlparser.SimpleNode;
import compiler.tmlparser.TMLExprParser;
import compiler.tmlparser.TokenMgrError;
import myutil.TraceManager;
import translator.ADActionStateWithGate;
import translator.ADActionStateWithParam;
import translator.ADChoice;
import translator.ADComponent;
import translator.ADDelay;
import translator.ADLatency;
import translator.ADParallel;
import translator.ADTLO;
import translator.ADTimeInterval;
import translator.ActivityDiagram;
import translator.CheckingError;
import translator.Gate;
import translator.Param;
import translator.TClass;
import translator.TURTLEModeling;


/**
 * Class TURTLEModelChecker
 * Creation: 09/12/2003
 * @version 1.0 09/12/2003
 * @author Ludovic APVRILLE
 */
public class TURTLEModelChecker {
    private TURTLEModeling tm;
    private LinkedList<CheckingError> warnings;
    
    //private String SEPARATOR = ": ";
    //private String END = "\n";
    
    //private String RULE_CD_001 = "Only one association between two TClasses";
    //private String RULE_CD_002 = "Every association between two TClasses must be attributed with one and only one composition operator";
    //private String RULE_CD_003 = "Synchronization composition operator must be accompanied with an OCL formula";
    //private String RULE_CD_004 = "TClasses must have a unique name";
    
    //private String RULE_AD_000 = "All Tclasses must have an activity diagram";
    private String ERROR_AD_000 = "has no activity diagram";
    //private String RULE_AD_001 = "An activity diagram must have one start state, and only one";
    private String ERROR_AD_001a = "activity diagram has no start state";
    private String ERROR_AD_001b = " should be followed by another component";
    private String ERROR_AD_001c = " activity diagram is badly built";
    //private String RULE_AD_002 = "An activity diagram should terminate";
    //private String RULE_AD_003 = "All activities must be linked either to a loop or to a stop state";
    //private String RULE_AD_004 = "Parallel operator should have a valid list of synchronization gates";
    private String ERROR_AD_004 = "the list of gates is badly formatted";
    //private String RULE_AD_006 = "Parallel operator should no more than two nexts if they define synchronization gates";
    private String ERROR_AD_006 = "No more than two next activities are allowed after parallel with synchronization gates";
    //private String RULE_AD_005 = "Preferably, no recursive process should start after a parallel operator";
    private String WARNING_AD_001 = "A recursive process starts after a parallel operator";
    //private String RULE_AD_007 = "Synchronization gates of parallel operators should not be involved in external synchronization";
    private String ERROR_AD_007 = " gate is involved in both internal and external synchronization";
    //private String RULE_AD_008 = "From an internal synchronization, reaching another synchronization on the same gate is not allowed";
    private String ERROR_AD_008 = " gate is involved in several internal synchronizations";
    
	
    private String ERROR_AD_009_0 = " syntax error";
	private String ERROR_AD_009_1 = " variable is not correctly used";
	private String ERROR_AD_009_2 = " unknown variable";
	private String ERROR_AD_009_3 = " unknown gate";
	//private String ERROR_AD_009_4 = " unknown param";
	private String ERROR_AD_009_5 = " null gate";
	private String ERROR_AD_009_6 = " null param";
	
	private CorrespondanceTGElement listE;
    
    
    public TURTLEModelChecker(TURTLEModeling _tm, CorrespondanceTGElement _listE) {
        tm = _tm;
		listE = _listE;
    }
	
	 public TURTLEModelChecker(TURTLEModeling _tm) {
        tm = _tm;
		listE = null;
    }
    
    public void setTURTLEModeling(TURTLEModeling _tm) {
        tm = _tm;
    }
    
    public LinkedList<CheckingError> getWarnings() {
        return warnings;
    }
    
    public LinkedList<CheckingError> syntaxAnalysisChecking() {
        //TraceManager.addDev("modelChecking");
        LinkedList<CheckingError> errors = new LinkedList<CheckingError>();
        warnings = new LinkedList<CheckingError>();
        syntaxAnalysisCheckingCD(errors, warnings);
        TClass t;
        for(int i=0; i<tm.classNb(); i++) {
            t = tm.getTClassAtIndex(i);
            syntaxAnalysisCheckingAD(t, errors, warnings);
        }
		
		tm.removeUselessVariables(warnings);
		tm.removeUselessGates(warnings);
        return errors;
    }
    
    public void syntaxAnalysisCheckingCD(LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
    	checkRuleCD001(errors, warnings);
    }
    
    public void syntaxAnalysisCheckingAD(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        //TraceManager.addDev("Checking activity diagram of " + t.getName());
        checkRuleAD000(t, errors, warnings);
        checkRuleAD001(t, errors, warnings);
        checkRuleAD004(t, errors, warnings);
        checkRuleAD005(t, errors, warnings);
        checkRuleAD006(t, errors, warnings);
        checkRuleAD007(t, errors, warnings);
        checkRuleAD008(t, errors, warnings);
		checkRuleAD009(t, errors, warnings);
    }
    
    
    private void checkRuleCD001(LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        return;
    }
    
    private void checkRuleAD000(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        CheckingError error;
        
        if (ad == null) {
            error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName()+ ERROR_AD_000);
            error.setTClass(t);
            errors.add(error);
        }
        
        return;
    }
    
    // assumes a non-null activity diagram
    private void  checkRuleAD001(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        
        if (ad == null) {
            return;
        }
        
        CheckingError error;
        
        if (ad.getStartState() == null) {
            error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName()+ ERROR_AD_001a);
            error.setTClass(t);
            errors.add(error);
            return;
        }
        
        // put to false all components
        ad.setSelectedAll(false);
        
        analyzeAccessibility(t, ad.getStartState(), errors, warnings);
        
        ad.setSelectedAll(false);
    }
    
    private void analyzeAccessibility(TClass t, ADComponent ad, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        if (ad == null) {
			
            CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName()+ ERROR_AD_001c);
            error.setTClass(t);
            errors.add(error);
            return;
        }
        
        if (ad.isSelected()) {
            return;
        }
        
        ad.setSelected(true);
        
        if (ad.realNbOfNext() < ad.getMinNext()) {
            CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, ad + ERROR_AD_001b);
            error.setTClass(t);
            errors.add(error);
            return;
        } else {
            //search for each next
            ADComponent ad1;
            for(int i=0; i<ad.realNbOfNext(); i++) {
                ad1 = ad.getNext(i);
                analyzeAccessibility(t, ad1, errors, warnings);
            }
        }
    }
    
    // Valid list of synchronization gates for parallel operators
    private void  checkRuleAD004(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        ADComponent ad1;
        ADParallel adp;
        
        if (ad == null) {
            return;
        }
        
        if (ad.getStartState() == null) {
            return;
        }
        
        for(int i=0; i<ad.size(); i++) {
            ad1 = ad.elementAt(i);
            if (ad1 instanceof ADParallel) {
                adp = (ADParallel) ad1;
                boolean b = adp.isAValidMotif(t);
                if (!b) {
                    CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName() + "/Parallel operator: " + ERROR_AD_004);
                    error.setTClass(t);
                    errors.add(error);
                }
            }
        }
    }
    
    // No recursive process after parallel operator
    private void  checkRuleAD005(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        if (ad == null) {
            CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName() + " has no activity diagram");
            error.setTClass(t);
            errors.add(error);
            return ;
        }
        ADComponent ad1;
        ADParallel adp;
        
        for(int i=0; i<ad.size(); i++) {
            ad1 = ad.elementAt(i);
            if (ad1 instanceof ADParallel) {
                adp = (ADParallel) ad1;
                if (ad.hasRecursivePath(adp)) {
                    CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName() + "/Parallel operator: " + WARNING_AD_001);
                    error.setTClass(t);
                    warnings.add(error);
                }
            }
        }
    }
    
    // no more than two nexts after parallel operators with synchronized gates
    private void  checkRuleAD006(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        ADComponent ad1;
        ADParallel adp;
        
        if (ad == null) {
            return;
        }
        
        if (ad.getStartState() == null) {
            return;
        }
        
        for(int i=0; i<ad.size(); i++) {
            ad1 = ad.elementAt(i);
            if (ad1 instanceof ADParallel) {
                adp = (ADParallel) ad1;
                if (adp.nbGate() > 0) {
                    if (adp.getNbNext() > 2) {
                        CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName() + "/Parallel operator: " + ERROR_AD_006);
                        error.setTClass(t);
                        errors.add(error);
                    }
                }
            }
        }
    }
    
    // Valid list of synchronization gates for parallel operators: gates should not be involved in external synchronization
    private void  checkRuleAD007(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        ADComponent ad1;
        ADParallel adp;
        int i, j;
        Gate g;

        if (ad == null) {
            return;
        }

        if (ad.getStartState() == null) {
            return;
        }

        for(i=0; i<ad.size(); i++) {
            ad1 = ad.elementAt(i);
            if (ad1 instanceof ADParallel) {
                adp = (ADParallel) ad1;
                boolean b = adp.isAValidMotif(t);
                if (b) {
                   for(j=0; j<adp.nbGate(); j++) {
                     //TraceManager.addDev("Getting gate #" + j + " of t " + t.getName());
                     g = adp.getGate(j);
                     if (tm.syncRelationWith(t, g) != null) {
                        CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName() + "/Parallel operator: " + g.getName() + ERROR_AD_007);
                        error.setTClass(t);
                        errors.add(error);
                     }
                   }
                }

            }
        }
    }
    
    private void  checkRuleAD008(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
        ActivityDiagram ad = t.getActivityDiagram();
        ADComponent ad1;
        ADParallel adp;
        int i, j;
        Gate g;

        if (ad == null) {
            return;
        }

        if (ad.getStartState() == null) {
            return;
        }

        for(i=0; i<ad.size(); i++) {
            ad1 = ad.elementAt(i);
            if (ad1 instanceof ADParallel) {
                adp = (ADParallel) ad1;
                boolean b = adp.isAValidMotif(t);
                if (b) {
                   for(j=0; j<adp.nbGate(); j++) {
                     //TraceManager.addDev("Getting gate #" + j + " of t " + t.getName());
                     g = adp.getGate(j);
                     if (tm.canReachSynchroOn(adp, g)) {
                        CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, t.getName() + "/Parallel operator: " + g.getName() + ERROR_AD_008);
                        error.setTClass(t);
                        errors.add(error);
                     }
                   }
                }

            }
        }
    }
	
	
	// Syntax error
	private void  checkRuleAD009(TClass t, LinkedList<CheckingError> errors, LinkedList<CheckingError> warnings) {
		ActivityDiagram ad = t.getActivityDiagram();
        ADComponent ad1;
		Param p;
		Gate g;
		String action;
		String s;
		int j;
		ADChoice choice;
		
		if (ad == null) {
			return;
		}
		
		for(int i=0; i<ad.size(); i++) {
            ad1 = ad.elementAt(i);
			
			if (ad1 instanceof ADActionStateWithGate) {
				g = ((ADActionStateWithGate)ad1).getGate();
				
				if (g == null) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_5 + " in an action state of tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
					return;
				}
				
				if (!t.getGateList().contains(g)) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, g.getName() + ": " + ERROR_AD_009_3 + " in tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
				}
				
				action = ((ADActionStateWithGate)ad1).getActionValue();
				if ((action!= null) && (action.length() > 0)) {
					parsing(t, ad1, "actiongate", action, errors);
				} else {
					//TraceManager.addDev("null action on gate=" + ((ADActionStateWithGate)ad1).getGate().getName() + action);
				}
				
			} else if (ad1 instanceof ADActionStateWithParam) {
				p = ((ADActionStateWithParam)ad1).getParam();
				
				if (p == null) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_6 + " in an action state of tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
					return;
				}
				
				if (!t.getParamList().contains(p)) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, p.getName() + ": " + ERROR_AD_009_3 + " in tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
				}
				
				action = ((ADActionStateWithParam)ad1).getActionValue();
				if (p.isNat()) {
					parsing(t, ad1, "assnat", p.getName() + " = " + action, errors);
				} else if (p.isBool()) {
					parsing(t, ad1, "assbool", p.getName() + " = " + action, errors);
				} else if (p.isQueueNat()) {
					parsing(t, ad1, "assqueuenat", p.getName() + " = " + action, errors);
				}
				
			} else if (ad1 instanceof ADChoice) {
				choice = (ADChoice)ad1;
				for(j=0; j<choice.getNbGuard(); j++) {
					if (choice.isGuarded(j)) {
						parsing(t, ad1, "guard", choice.getGuard(j), errors);
					}
				}
				
			} else if (ad1 instanceof ADDelay) {
				parsing(t, ad1, "actionnat", ((ADDelay)ad1).getValue(), errors);
				
			} else if (ad1 instanceof ADLatency) {
				parsing(t, ad1, "actionnat", ((ADLatency)ad1).getValue(), errors);
				
			} else if (ad1 instanceof ADTimeInterval) {
				parsing(t, ad1, "actionnat", ((ADTimeInterval)ad1).getMinValue(), errors);
				parsing(t, ad1, "actionnat", ((ADTimeInterval)ad1).getMaxValue(), errors);
				
			} else if (ad1 instanceof ADTLO) {
				g = ((ADTLO)ad1).getGate();
				
				if (g == null) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_5 + " in an action state of tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
					return;
				}
				
				if (!t.getGateList().contains(g)) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, g.getName() + ": " + ERROR_AD_009_3 + " in tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
				}
				
				action = ((ADTLO)ad1).getAction();
				s = TURTLEModeling.manageGateDataStructures(t, action);
				
				if (s == null) {
					CheckingError error = new CheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_0 + " in expression " + action + " of tclass " + t.getName());
					error.setTClass(t);
					errors.add(error);
				}
				
				parsing(t, ad1, "actionnat", ((ADTLO)ad1).getDelay(), errors);
				parsing(t, ad1, "actionnat", ((ADTLO)ad1).getLatency(), errors);
				
			}
			
		}
		
	}

	/**
	 * Parsing in two steps:
	 * 1. Parsing the expression with no variable checking
	 * 2. Parsing the expression with variables values to see whether variables are well-placed or not
	 * The second parsing is performed iff the first one succeeds
     * @param t         : {@link TClass}
     * @param elt       : {@link ADComponent}
     * @param parseCmd  : Parse Command
     * @param action    : Action
     * @param errors    : list of checking errors
	*/
	public void parsing(TClass t, ADComponent elt, String parseCmd, String action, LinkedList<CheckingError> errors) {
		TMLExprParser parser;
		SimpleNode root;
		int i;
		
		// First parsing
		parser = new TMLExprParser(new StringReader(parseCmd + " " + action));
		try {
			//TraceManager.addDev("\nParsing :" + parseCmd + " " + action);
			root = parser.CompilationUnit();
			//root.dump("pref=");
			//TraceManager.addDev("Parse ok");
		} catch (ParseException e) {
			TraceManager.addDev("\nParsing :" + parseCmd + " " + action);
			TraceManager.addDev("ParseException --------> Parse error in :" + parseCmd + " " + action);
			UICheckingError error = new UICheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_0 + " in expression " + action + " of tclass " + t.getName());
            error.setTClass(t);
			putCorrespondance(error, elt);
            errors.add(error);
			return;
		} catch (TokenMgrError tke) {
			TraceManager.addDev("TokenMgrError --------> Parse error in :" + parseCmd + " " + action);
			UICheckingError error = new UICheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_0 + " in expression " + action + " of tclass " + t.getName());
            error.setTClass(t);
			putCorrespondance(error, elt);
            errors.add(error);
			return;
		}  
		
		// Second parsing
		// We only replace variables values after the "=" sign
		int index = action.indexOf('=');
		String modif = action;
		
		if (parseCmd.startsWith("ass")) { 
			if (index != -1) {
				modif = action.substring(index+1, action.length());
			}
			
			parseCmd = "action" + parseCmd.substring(3, parseCmd.length()); 
		}
		
		for(i=0; i<t.paramNb(); i++) {
			modif = tm.putParamValueInString(modif, t.getParam(i));
		}
		
		if (parseCmd.compareTo("actiongate") ==0) {
			parseCmd = "actiongatevalue";
		}
		
		parser = new TMLExprParser(new StringReader(parseCmd + " " + modif));
		try {
			//TraceManager.addDev("\nParsing :" + parseCmd + " " + modif);
			root = parser.CompilationUnit();
			//root.dump("pref=");
			//TraceManager.addDev("Parse ok");
		} catch (ParseException e) {
			TraceManager.addDev("\nParsing :" + parseCmd + " " + modif);
			TraceManager.addDev("\n(Original parsing :" + parseCmd + " " + action);
			TraceManager.addDev("ParseException --------> Parse error in :" + parseCmd + " " + action);
			UICheckingError error = new UICheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_1 + " in expression " + action + " of tclass " + t.getName());
            error.setTClass(t);
            errors.add(error);
			putCorrespondance(error, elt);
			return;
		} catch (TokenMgrError tke ) {
			TraceManager.addDev("\nParsing :" + parseCmd + " " + modif);
			TraceManager.addDev("TokenMgrError --------> Parse error in :" + parseCmd + " " + action);
			UICheckingError error = new UICheckingError(CheckingError.BEHAVIOR_ERROR, ERROR_AD_009_1 + " in expression " + action + " of tclass " + t.getName());
            error.setTClass(t);
			putCorrespondance(error, elt);
            errors.add(error);
			return;
		}  
		
		// Tree analysis: if the tree contains a variable, then, this variable has not been declared
		List<String> vars = root.getVariables();
		for(String s: vars) {
			// is that string a variable?
			if ((s.compareTo("true") != 0) && (s.compareTo("false") != 0) && (s.compareTo("nil") != 0)) {
				TraceManager.addDev("\nParsing :" + parseCmd + " " + modif + " tclass" + t.getName());
				TraceManager.addDev("Variable not declared: " +s);
				UICheckingError error = new UICheckingError(CheckingError.BEHAVIOR_ERROR, s + ": " + ERROR_AD_009_2 + " in expression " + action + " of tclass " + t.getName());
				error.setTClass(t);
				putCorrespondance(error, elt);
				errors.add(error);
			}
		}
		
	}
	
	private void putCorrespondance(UICheckingError _error, ADComponent _elt) {
		if (listE == null) {
			return;
		}
		
		TGComponent tgc = listE.getTG(_elt);
		_error.setTGComponent(tgc);
		
	}
}