/*
 * Copyright (C) 1999-2006 <a href="mailto:paschke@in.tum.de">Adrian Paschke</a>
 * 
 * The ECA Interpreter is part of the Rule Based Service Level Agreement (RBSLA)
 * framework, available at 
 * <a href=" http://ibis.in.tum.de/staff/paschke/rbsla/index.htm">RBSLA</a>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package rbsla.wrapper;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.mandarax.kernel.ConstantTerm;
import org.mandarax.kernel.Fact;
import org.mandarax.kernel.InferenceEngine;
import org.mandarax.kernel.InferenceException;
import org.mandarax.kernel.Predicate;
import org.mandarax.kernel.Query;
import org.mandarax.kernel.ResultSet;
import org.mandarax.kernel.SimplePredicate;
import org.mandarax.kernel.VariableTerm;
import org.mandarax.lib.date.DateArithmetic;
import org.mandarax.lib.math.IntArithmetic;
import org.mandarax.reference.AbstractResolutionInferenceEngine;
import org.mandarax.reference.ResolutionInferenceEngine2;
import org.mandarax.util.LogicFactorySupport;

/**
 * Wrapper for the ECA System using Mandarax rules.
 * These are some basic
 * function to call mandarax and have some basic test cases.
 * 
 * @author <A HREF="mailto:paschke@in.tum.de">Adrian Paschke</A>
 * @version 0.1 <1 Sept 2005>
 * @since 0.1
 */
 
abstract class MandaraxWrapper
{
    static Vector tests = new Vector();

    private static Logger logger = Logger.getLogger(MandaraxWrapper.class);

    static protected void collectTests()
    {
        tests.add(new Recursion1());
        tests.add(new StringTest1());
        tests.add(new FalseIntTest1());
        tests.add(new TimeTest1());
        tests.add(new TimeTestWithInt1());
    }

    static public void runTests()
    {
        collectTests();
        int wrong = 0;
        for (int i = 0; i < tests.size(); i++)
        {
            MandaraxWrapper test = (MandaraxWrapper) tests.get(i);
            logger.info("------------------ " + test.getClass().getName()
                    + "----------------");
            Query query = null;
            org.mandarax.reference.KnowledgeBase kb = new org.mandarax.reference.KnowledgeBase();
            wrong++;
            try
            {
                query = test.set(kb);
            } catch (Exception ex)
            {
                logger.info("Error while setting KB and Query.", ex);
            }
            try
            {

                if (calculateAndPrint(kb, query, test.correct()))
                    wrong--;
            } catch (Exception ex)
            {
                logger.info("Exception during execution of test:", ex);
            }
        }
        logger.info("============= " + wrong + " wrong tests ===============");

        //ProvaTest.testEasy();
        //ProvaTest.testSLA();
        //System.exit(1);
    }

    protected LogicFactorySupport lfs = new LogicFactorySupport();

    static private boolean calculateAndPrint(
            org.mandarax.reference.KnowledgeBase kb, Query query,
            Object correctValue) throws InferenceException
    {
        InferenceEngine ie = new ResolutionInferenceEngine2();
        if (ie instanceof AbstractResolutionInferenceEngine)
            ((AbstractResolutionInferenceEngine) ie).setMaxSteps(100);
        ResultSet rs = ie.query(query, kb, InferenceEngine.ONE,
                InferenceEngine.BUBBLE_EXCEPTIONS);

        for (Iterator iter = kb.getClauseSets().iterator(); iter.hasNext();)
        {
            logger.info(iter.next().toString());
        }
        logger.info("Query:");
        Fact[] facts = query.getFacts();
        for (int i = 0; i < facts.length; i++)
            logger.info(" (" + (i + 1) + ") " + facts[i] + " ");

        logger.info("Result: ");
        Object resultValue = null;
        while (rs.next())
        {
            /*
             * List queryVariables = rs.getQueryVariables(); for (Iterator i =
             * queryVariables.listIterator();i.hasNext();) { VariableTerm qw =
             * (VariableTerm) i.next(); System.out.println("Variable:" +
             * qw.getName()); }
             */
            //System.out.println("Root" + rs.getProof().getRoot().toString());
            Map results = rs.getResults();
            for (Iterator i = results.keySet().iterator(); i.hasNext();)
            {
                VariableTerm qw = (VariableTerm) i.next();
                Object v = results.get(qw);
                if (resultValue == null)
                    resultValue = ((ConstantTerm) v).getObject();
                logger.info(qw.getName() + "=" + v.toString() + "; ");
            }
        }

        rs.close();
        if ((correctValue == null && resultValue == null)
                || (correctValue != null && correctValue.equals(resultValue)))
            return true;
        else
        {
            logger.info("Wrong !!! Expected:" + correctValue);
            return false;

        }

    }

    static public boolean evaluateBooleanQuery(
            org.mandarax.reference.KnowledgeBase kb, Query query)
    {
        InferenceEngine ie = new ResolutionInferenceEngine2();
        if (ie instanceof AbstractResolutionInferenceEngine)
            ((AbstractResolutionInferenceEngine) ie).setMaxSteps(100);
        boolean isTrue = false;
        try
        {
            //	calculateAndPrint(kb, query, new Boolean(true));
            ResultSet rs = ie.query(query, kb, InferenceEngine.ONE,
                    InferenceEngine.BUBBLE_EXCEPTIONS);
            if (rs.next())
                isTrue = true;
            rs.close();

        } catch (Exception ex)
        {
            logger.info(null, ex);
        }
        return isTrue;
    }

    abstract Query set(org.mandarax.reference.KnowledgeBase kb);

    abstract Object correct();
}

class StringTest1 extends MandaraxWrapper
{
    Query set(org.mandarax.reference.KnowledgeBase kb)
    {
        // setzt einen Wert und bildet eine doppelte kausale Kette
        // Frage dann auf das Ende der Kette
        Predicate pred1 = new SimplePredicate("pred1",
                new Class[] { String.class });
        Predicate pred2 = new SimplePredicate("pred2",
                new Class[] { String.class });
        Predicate pred3 = new SimplePredicate("pred3",
                new Class[] { String.class });
        // setzen des ersten Wertes
        kb.add(lfs.fact(pred1, new String("value1")));

        // setzen der doppelten causalen Schlussfolgerung
        kb
                .add(lfs.rule(lfs.prereq(pred1, "value1"), lfs.fact(pred2,
                        "value2")));
        kb
                .add(lfs.rule(lfs.prereq(pred2, "value2"), lfs.fact(pred3,
                        "value3")));

        // Abfrage
        Query query = lfs.query(lfs
                .fact(pred3, lfs.variable("x", String.class)), "Test");
        return query;
    }

    Object correct()
    {
        return "value3";
    }
}

class FalseIntTest1 extends MandaraxWrapper
{

    Query set(org.mandarax.reference.KnowledgeBase kb)
    {
        // setzt eine Zahl und mus dadurch andere finden
        Predicate actualDateTimePred = new SimplePredicate("setValue",
                new Class[] { Integer.class });
        Fact actualDateTimeFact = lfs.fact(actualDateTimePred, new Integer(5));
        kb.add(actualDateTimeFact);

        Predicate solution = new SimplePredicate("solution",
                new Class[] { Integer.class });
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!
        // Reihenfolge Entscheidet
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!

        org.mandarax.kernel.Rule r = lfs.rule(lfs.prereq(IntArithmetic.EQUAL,
                lfs.variable("x", Integer.class), new Integer(5)), lfs.prereq(
                actualDateTimePred, lfs.variable("x", Integer.class)), lfs
                .fact(solution, new Integer(3)));
        kb.add(r);
        Query query = lfs.query(lfs.fact(solution, lfs.variable("x",
                Integer.class)), "isFiveSeconds");
        return query;
    }

    Object correct()
    {
        return null;
    }
}

class TimeTest1 extends MandaraxWrapper
{

    Query set(org.mandarax.reference.KnowledgeBase kb)
    {
        // aktuelle Zeit definieren
        Predicate actualDateTimePred = new SimplePredicate("actualDateTime",
                new Class[] { Calendar.class });
        Fact actualDateTimeFact = lfs.fact(actualDateTimePred,
                new GregorianCalendar(1990, 1, 1, 12, 0, 5));
        kb.add(actualDateTimeFact);

        Predicate solution = new SimplePredicate("solution",
                new Class[] { Boolean.class });

        // fünf Sekunden Abfrage
        org.mandarax.kernel.Rule r = lfs.rule(lfs.prereq(actualDateTimePred,
                lfs.variable("x", Calendar.class)), lfs.prereq(
                IntArithmetic.EQUAL, lfs.cplx(DateArithmetic.SECONDS, lfs
                        .variable("x", Calendar.class)), new Integer(5)), lfs
                .fact(solution, new Boolean(true)));
        kb.add(r);
        Query query = lfs.query(lfs.fact(solution, lfs.variable("x",
                Boolean.class)), "isFiveSeconds");
        return query;
    }

    Object correct()
    {
        return new Boolean(true);
    }
}

class TimeTestWithInt1 extends MandaraxWrapper
{

    Query set(org.mandarax.reference.KnowledgeBase kb)
    {
        // aktuelle Zeit definieren
        Predicate actualDateTimePred = new SimplePredicate("actualDateTime",
                new Class[] { Integer.class });
        kb.add(lfs.fact(actualDateTimePred, new Integer(30)));

        Predicate solution = new SimplePredicate("solution",
                new Class[] { String.class });

        // fünf Sekunden Abfrage
        /*
         * kb.add( lfs.rule( lfs.prereq(actualDateTimePred, lfs.variable("x",
         * Integer.class)), lfs.prereq(IntArithmetic.EQUAL,
         * lfs.cplx(IntArithmetic.MOD, lfs.variable("x", Integer.class), new
         * Integer(5)), new Integer(0)), lfs.fact(solution, "doSomething")));
         */Query query = lfs.query(lfs.fact(solution, lfs.variable("x",
                String.class)), "isTime");
        return query;
    }

    Object correct()
    {
        return "doSomething";
    }

}

class Recursion1 extends MandaraxWrapper
{

    int test = 3;

    Query set(org.mandarax.reference.KnowledgeBase kb)
    {
        // init kb
        // the order does matter, but problems can be avoided by setting the
        // appropriate kb comparator,
        // or by adding another prereq to the rule
        Predicate p = new SimplePredicate("factorial", new Class[] {
                Integer.class, Integer.class });
        // fact factorial(1,1)
        Fact f = lfs.fact(p, new Integer(1), new Integer(1));
        kb.add(f);
        // rule if factorial(n-1,k) then factorial(n,n*k)
        org.mandarax.kernel.Rule r = lfs.rule(lfs.prereq(p, lfs.cplx(
                IntArithmetic.MINUS, lfs.variable("n", Integer.class),
                new Integer(1)), lfs.variable("k", Integer.class)), lfs.fact(p,
                lfs.variable("n", Integer.class), lfs.cplx(IntArithmetic.TIMES,
                        lfs.variable("n", Integer.class), lfs.variable("k",
                                Integer.class))));
        kb.add(r);

        // query ? factorial(i,i)
        Query q = lfs.query(lfs.fact(p, new Integer(test), lfs.variable("x",
                Integer.class)), "?factorial(" + test + ")");

        return q;
    }

    private int factorial(int i)
    {
        if (i == 0)
            return 1;
        else
            return i * factorial(i - 1);
    }

    Object correct()
    {
        return new Integer(factorial(test));
    }

}

