package de.uni_hildesheim.sse.trans.convert;

import org.junit.Assert;
import org.junit.Test;

import de.uni_hildesheim.sse.model.confModel.AssignmentState;
import de.uni_hildesheim.sse.model.confModel.Configuration;
import de.uni_hildesheim.sse.model.confModel.ConfigurationException;
import de.uni_hildesheim.sse.model.cst.CSTSemanticException;
import de.uni_hildesheim.sse.model.cst.ConstraintSyntaxTree;
import de.uni_hildesheim.sse.model.cst.OCLFeatureCall;
import de.uni_hildesheim.sse.model.cst.Parenthesis;
import de.uni_hildesheim.sse.model.cst.Variable;
import de.uni_hildesheim.sse.model.cstEvaluation.EvaluationVisitor;
import de.uni_hildesheim.sse.model.varModel.Constraint;
import de.uni_hildesheim.sse.model.varModel.DecisionVariableDeclaration;
import de.uni_hildesheim.sse.model.varModel.Project;
import de.uni_hildesheim.sse.model.varModel.datatypes.BooleanType;
import de.uni_hildesheim.sse.model.varModel.datatypes.OclKeyWords;
import de.uni_hildesheim.sse.model.varModel.filter.ConstraintFinder;
import de.uni_hildesheim.sse.model.varModel.values.BooleanValue;

/**
 * Tests the {@link MaxTermConverter2}.
 * 
 * @author Adam Krafczyk
 */
public class MaxTermConverter2Test {

    /**
     * Tests the convert method.
     */
    @Test
    public void testConvert() {
        DecisionVariableDeclaration declA = new DecisionVariableDeclaration("a", BooleanType.TYPE, null);
        DecisionVariableDeclaration declB = new DecisionVariableDeclaration("b", BooleanType.TYPE, null);
        DecisionVariableDeclaration declC = new DecisionVariableDeclaration("c", BooleanType.TYPE, null);
        
        Variable varB = new Variable(declB);
        OCLFeatureCall notB = new OCLFeatureCall(varB, OclKeyWords.NOT);
        OCLFeatureCall termA = new OCLFeatureCall(new Variable(declA), OclKeyWords.OR, varB);
        OCLFeatureCall termB = new OCLFeatureCall(new Variable(declC), OclKeyWords.OR, notB);
        Parenthesis parenthesis = new Parenthesis(termB);
        OCLFeatureCall completeTerm = new OCLFeatureCall(termA, OclKeyWords.AND, parenthesis);
        
        // (a OR b) AND (c OR !b)
        /*
         * a b c  r
         * 0 0 0  0
         * 1 0 0  1
         * 0 1 0  0
         * 1 1 0  0
         * 0 0 1  0
         * 1 0 1  1
         * 0 1 1  1
         * 1 1 1  1
         */
        // (a OR b OR c) AND (a OR !b OR c) AND (!a OR !b OR c) AND (a OR b OR !c)
        
        Project testProject = new Project("TestProject");
        Constraint constraint = new Constraint(testProject);
        try {
            constraint.setConsSyntax(completeTerm);
        } catch (CSTSemanticException e) {
            Assert.fail(e.getMessage());
        }
        
        new MaxTermConverter2().convert(constraint);
        
        ConstraintFinder finder = new ConstraintFinder(testProject);
        Assert.assertTrue(finder.getConstraints().size() > 0);
        for (Constraint c : finder.getConstraints()) {
            testConstraintOnlyContainsORandNOT(c.getConsSyntax());
            //c.getConsSyntax().accept(new DebugConstraintTreeVisitor(System.out));
        }
    }
    
    /**
     * Tests whether the {@link EvaluationVisitor}, which is used inside of {@link MaxTermConverter2}, is sufficient
     * for {@link MaxTermConverter}.
     */
    @Test
    public void testEvaluationVisitor() {
        Project testProject = new Project("TestProject");
        DecisionVariableDeclaration declA = new DecisionVariableDeclaration("a", BooleanType.TYPE, testProject);
        DecisionVariableDeclaration declB = new DecisionVariableDeclaration("b", BooleanType.TYPE, testProject);
        DecisionVariableDeclaration declC = new DecisionVariableDeclaration("c", BooleanType.TYPE, testProject);
        testProject.add(declA);
        testProject.add(declB);
        testProject.add(declC);
        
        Variable varB = new Variable(declB);
        OCLFeatureCall notB = new OCLFeatureCall(varB, OclKeyWords.NOT);
        OCLFeatureCall termA = new OCLFeatureCall(new Variable(declA), OclKeyWords.OR, varB);
        OCLFeatureCall termB = new OCLFeatureCall(new Variable(declC), OclKeyWords.OR, notB);
        Parenthesis parenthesis = new Parenthesis(termB);
        OCLFeatureCall completeTerm = new OCLFeatureCall(termA, OclKeyWords.AND, parenthesis);
        
        // (a OR b) AND (c OR !b)
        /*
         * a b c  r
         * 0 0 0  0
         * 1 0 0  1
         * 0 1 0  0
         * 1 1 0  0
         * 0 0 1  0
         * 1 0 1  1
         * 0 1 1  1
         * 1 1 1  1
         */
        // Tests whether b = true and c = false are sufficient to evaluate the constraint to false.
        Constraint constraint = new Constraint(testProject);
        try {
            constraint.setConsSyntax(completeTerm);
            testProject.add(constraint);
        } catch (CSTSemanticException e) {
            Assert.fail(e.getMessage());
        }
        
        Configuration config = new Configuration(testProject);
        try {
            config.getDecision(declB).setValue(BooleanValue.TRUE, AssignmentState.ASSIGNED);
            config.getDecision(declC).setValue(BooleanValue.FALSE, AssignmentState.ASSIGNED);
        } catch (ConfigurationException e) {
            Assert.fail(e.getMessage());
        }
        EvaluationVisitor evaluator = new EvaluationVisitor(config, null, false, null);
        completeTerm.accept(evaluator);
        
        Assert.assertTrue(evaluator.constraintFailed());
    }
    
    /**
     * Tests whether a constraint only contains OR's and NOT's.
     * @param tree The constraint to be tested.
     */
    private void testConstraintOnlyContainsORandNOT(ConstraintSyntaxTree tree) {
        if (tree instanceof OCLFeatureCall) {
            OCLFeatureCall call = (OCLFeatureCall) tree;
            Assert.assertTrue(call.getOperation().equals(OclKeyWords.OR)
                    || call.getOperation().equals(OclKeyWords.NOT));
            testConstraintOnlyContainsORandNOT(call.getOperand());
        }
    }
    
    /**
     * Tests the convert method with a constraint with more than 15 variables.
     */
    @Test
    public void testConvertWithBigConstraint() {
        Variable[] vars = new Variable[18];
        for (int i = 0; i < vars.length; i++) {
            vars[i] = new Variable(new DecisionVariableDeclaration((char) ('a' + i) + "", BooleanType.TYPE, null));
        }
        
        ConstraintSyntaxTree term = vars[0];
        for (int i = 1; i < vars.length; i++) {
            if (Math.random() > 0.5) {
                term = new OCLFeatureCall(term, OclKeyWords.OR, vars[i]);
            } else {
                term = new OCLFeatureCall(term, OclKeyWords.OR, new OCLFeatureCall(vars[i], OclKeyWords.NOT));
            }
        }
        
        Project testProject = new Project("TestProject");
        Constraint constraint = new Constraint(testProject);
        try {
            constraint.setConsSyntax(term);
        } catch (CSTSemanticException e) {
            Assert.fail(e.getMessage());
        }
        
        new MaxTermConverter2().convert(constraint);
        
        ConstraintFinder finder = new ConstraintFinder(testProject);
        Assert.assertTrue(finder.getConstraints().size() > 0);
        for (Constraint c : finder.getConstraints()) {
            testConstraintOnlyContainsORandNOT(c.getConsSyntax());
        }
    }

}
