/*
 * 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.eca;

import java.util.Calendar;
import java.util.Enumeration;
import java.util.GregorianCalendar;

import org.apache.log4j.Logger;
import org.mandarax.kernel.InferenceException;
import org.mandarax.kernel.ResultSet;


import rbsla.wrapper.KnowledgeBaseWrapper;
import rbsla.environment.dialogs.ECARuleWindow;
import rbsla.utils.ConsoleWriter;

/**
 * Starts a Deamon which check for events and do actions, if necessary.
 * 
 * Also a Multithreading Variant is available.
 * 
 * @author <A HREF="mailto:paschke@in.tum.de">Adrian Paschke</A>
 * @version 0.1 <1 Sept 2005>
 * @since 0.1
 * 
 */
public class Daemon extends ActiveKnowledgeTask
{

    private boolean debug = false;

    private boolean doAbort = false;

    protected static Logger logger = Logger.getLogger(Daemon.class);
	
	private boolean timer = false; // hide timer
		
	private int interval = 1;  // default loop interval = 1 sec.
	
	private static ECARuleWindow win;
	
	/**
	 * Print timer information to console
	 * @param _timer boolean true = show / false = hide
	 */
	public void showTimer(boolean _timer) {
		timer = _timer;
	}

	/**
	 * set the loop interval for the eca daemon.
	 * @param _interval defines the query interval of the daemon in seconds	 
	 */
	public void setInterval(int _interval) {
		interval = _interval;
	}
	
	/**
	 * Returns the loop interval (in seconds)
	 */
	public int getInterval() {
		return interval;
	}
	
	/**
	 * Start the daemon loop
	 */
    public void run()
    {
		setUpDaemon();	
		
        Calendar currentTime;

        long timeDiff = 0;


        // refresh active knowledge		
        akb = getActiveRules();

		if (akb==null) {
			System.out.println("No active rules found in ContractLog script - Execution finished");			
			return;
		}
		
        akb.startSystemTime(SystemTime.onStartup); // onStartup

        if (!debug) {// real time
			currentTime = Calendar.getInstance();
			try { // synchronize start with milliseconds ~ 0
				Thread.sleep(1010-currentTime.get(Calendar.MILLISECOND));
			} catch (InterruptedException ex) {}
        } else { // debug mode			
            currentTime = new GregorianCalendar(2004, 1, 1, 0, 0, 0); 
        }

        while (!doAbort) // loop
        {
		  try {	
			  
            
			currentTime = Calendar.getInstance();
            // refresh active knowledge - update ECA rules
            akb = getActiveRules();
            
			// print current time
			if (timer) {
				Calendar timer = Calendar.getInstance();
				System.out.println("");
				System.out.println("----------------- time "
                    + timer.get(Calendar.HOUR_OF_DAY) + ":"
                    + timer.get(Calendar.MINUTE) + ":"
                    + timer.get(Calendar.SECOND) + " ("
                    + timer.get(Calendar.MILLISECOND) + ")"
                    + " ----------------------");

			}			
			
            // evaluate ECA rules in a sequence  
            for (Enumeration e = akb.getRuleEnumeration(); e.hasMoreElements();)
            {
                ActiveRule rule = (ActiveRule) e.nextElement(); // active rule
				
				// evaluate time part and add bound variables
				
				ResultSet rs = rule.getTime().evaluate(akb.getKnowledgeBaseWrapper());
				
				boolean evaluate = false;
				if (rs==null) evaluate = true;
				else try {			
					if (rs.first()) evaluate = true; // time part suceeded					
				} catch (Exception ex) { }
			
                if (evaluate)
                {
					// time succeeded --> evaluate the ECA part of the rule 
                    sheduleEventConditionAction(rule, rs);
                }
				
				
				
            }

            try
            {
                if (debug)
                    // needed to have time for updating the
                    // views, while debugging
                    Thread.sleep(800);
                else
                {
                    // check if we have a time problem
					Calendar actualTime = Calendar.getInstance();
                    timeDiff = actualTime.getTimeInMillis()
                            - currentTime.getTimeInMillis();
					
					 System.out.println("Dur: "+ timeDiff);
                    					
					if (timeDiff > 1000*interval)
						throw new Exception("The Validation of the ECA rules cost to much time.");						
                    else
                    {
                        // not bussy --> wait, give processor power to action
                        // execution
						Thread.sleep((1010-actualTime.get(Calendar.MILLISECOND))+((interval-1)*1000));
                    }
                }

            } catch (InterruptedException ex)
            {
                //	should not happen		
				System.err.println(ex);
            }
			            

          }  catch (Exception ex)
          {           
			//logger.info("", ex);
			
			// trigger delay handling via meta ECA rule (with SysTime event)
			akb.startSystemTime(SystemTime.onDelay);	
			currentTime = Calendar.getInstance();
          }  
		  
        }	
    }

    // default time zone and locale
    protected Calendar getCurrentTime()
    {
        return GregorianCalendar.getInstance();
    }

    // stop the daemon
    public void abort()
    {
        doAbort = true;
    }

    private void sheduleEventConditionAction(ActiveRule rule, ResultSet rs)
    {
        EventConditionActionExecutor eventConditionAction = new EventConditionActionExecutor(akb, rule,rs);
        eventConditionAction.run(); // start ECA thread
    }



    KnowledgeBaseWrapper silentWrapper = null;
	
    public void setUpDaemon()
    {
      if (!rbsla.RBSLAStart.getRBSLAflag()) {
        KnowledgeBaseWrapper wrapper = akb.getKnowledgeBaseWrapper();	// get wrapper			
        ActiveKnowledgeBase akb = new ActiveKnowledgeBase(wrapper); // active knowldegebase
		((ConsoleWriter) wrapper.getPrintWriter()).startReactive(true); // start reactive processing output mode
		
        if (silentWrapper == null) silentWrapper = wrapper.createChild();		

		ConsoleWriter cw = (ConsoleWriter) silentWrapper.getPrintWriter();		
		boolean oldcon = cw.consoleHidden();
		cw.showConsole(false); // hide console output
        
		try
        {	
		
		//query settings from script
		ResultSet rs = silentWrapper.consult("daemonInterval(X)");		
		if ((rs!=null) && (rs.first())) setInterval(((Integer) rs.getResult(Object.class, "X")).intValue());
		rs = silentWrapper.consult("timer(Flag)");		
		if ((rs!=null) && (rs.first())) showTimer(((Boolean)rs.getResult(Object.class, "Flag")).booleanValue());
		rs = silentWrapper.consult("console(Flag)");		
		if ((rs!=null) && (rs.first())) oldcon=(((Boolean)rs.getResult(Object.class, "Flag")).booleanValue());
						
		
		
		} catch (InferenceException ex)
        {
            System.err.println(ex);
			logger.warn("Exception " + ex.toString()
                    + " Raised during start up reading ECA runtime settings from Prova script", ex);            
        }
		
		cw.showConsole(oldcon); // show console output
      }
    }

    public ActiveKnowledgeBase getActiveRules()
    {

        KnowledgeBaseWrapper wrapper = akb.getKnowledgeBaseWrapper();	// get wrapper			
        ActiveKnowledgeBase akb = new ActiveKnowledgeBase(wrapper); // active knowldegebase
		

        if (silentWrapper == null) silentWrapper = wrapper.createChild();		

		ConsoleWriter cw = (ConsoleWriter) silentWrapper.getPrintWriter();		
		boolean oldcon = cw.consoleHidden();
		cw.showConsole(false); // hide console output
        
		//query eca rules
		ResultSet rs = silentWrapper.consult("eca(TIME, EVENT, CONDITION, ACTION, POSTCONDITION, ELSE)");		
		
		cw.showConsole(oldcon); // show console output
		
		try
        {			
			if (rs==null) return null;			
			boolean more = rs.first();
			if (!more) return null; // no active rule found
			int i=0;
			while (more) {				
				// add rule to kb
				akb.addRule(new ActiveRule(
						"ecaRule" + (i+++1),
						new ProvaTime(rs.getResult(Object.class, "TIME")),
						new ProvaEvent(rs.getResult(Object.class, "EVENT")),
						new ProvaCondition(rs.getResult(Object.class, "CONDITION")),
						new ProvaAction(rs.getResult(Object.class, "ACTION")),
						new ProvaCondition(rs.getResult(Object.class, "POSTCONDITION")),
						new ProvaAction(rs.getResult(Object.class, "ELSE"))));
				more = rs.next();
			}
        
        } catch (InferenceException ex)
        {
            System.err.println(ex);
			logger.warn("Exception " + ex.toString()
                    + " Raised during inference: eca(TIME, EVENT, CONDITION, ACTION, POSTCOND, ELSE))", ex);
            return null;
        }


        return akb; // return active knowledge base
    }
	
	   public static void setRuleWindow(ECARuleWindow window)
	    {
	    	win = window;
	    }
    
}