package timer ;

import java.util.Calendar ;
import jess.* ;

public class MyTimer implements Userfunction {
	public final String NAME = "jess-timer" ;
	public final String START = "start" ;
	public final String PAUSE = "pause" ;
	public final String RESUME = "resume" ;
	public final String STOP = "stop" ;
	private TimerThread tt = null ;
	
	// provide the Jess function name
	public String getName() { return NAME ; }
	
	/* The invocation entry point.
	*
	* @param vv Provided by Jess.
	* @param c Provided by Jess.
	* @return Value The function return value.
	*/
	public Value call(ValueVector vv, Context c)
	throws JessException {
		String action = vv.get(1).stringValue(c) ;
		Value retVal = Funcall.TRUE ;
		
		// perform the necessary operation based on the parameter
		if(action.equals(START)) {
			// start the timer
			// create a new timer thread
			tt = new TimerThread(c.getEngine()) ;	
			
			// start processing timer events
			try {
				tt.start() ;
			} catch(IllegalThreadStateException itse) {
				throw new JessException(getName(), "Couldn't start timer thread", itse) ;
			}
		} else if(action.equals(PAUSE)) {
			// temporarily pause the timer
			if(tt != null)
				tt.pause(true) ;
		} else if(action.equals(RESUME)) {
			// temporarily pause the timer
			if(tt != null)
				tt.pause(false) ;
		} else if(action.equals(STOP)) {
			// stop the thread
			if(tt != null) {
				tt.shutDown() ;
				try {
					tt.join(5000) ;
				} catch(InterruptedException ie) {
					throw new JessException(getName(), "Couldn't stop the timer thread", ie) ;
				}
				tt = null ;
			}
		} else {
			retVal = Funcall.FALSE ;
		}
		
		return retVal ;
	}
}




class TimerThread extends Thread {
	private Rete rete = null ;
	private Deftemplate template = null ;
	private long granularity = 1000l ;
	private boolean running = false ;
	private boolean shutdown = false ;
	private boolean paused = false ;
	private final String templateName = "temporal-event" ;
	
	TimerThread(Rete rete)
	throws JessException {
		super("JessTimerThread") ;
		this.rete = rete ;
		setDaemon(true) ;
		
		// define the timer fact template
		template =
		new Deftemplate(templateName, "Generated by the jess-timer", rete) ;
		template.addSlot("ctm", Funcall.NIL, "LONG") ;
		template.addSlot("second", Funcall.NIL, "INTEGER") ;
		template.addSlot("minute", Funcall.NIL, "INTEGER") ;
		template.addSlot("hour", Funcall.NIL, "INTEGER") ;
		template.addSlot("day-of-week", Funcall.NIL, "INTEGER") ;
		template.addSlot("day-of-month", Funcall.NIL, "INTEGER") ;
		template.addSlot("month", Funcall.NIL, "INTEGER") ;

		// make sure the Rete network knows about the timer fact type
		rete.addDeftemplate(template) ;
	}
	
	TimerThread(Rete rete, long granularity)
	throws JessException {
		this(rete) ;
		this.granularity = granularity ;
	}
	
	
	public void run() {
		// indicate that the timer is running
		running = true ;
		
		// wait for a while
		while(!shutdown) {
			try {
				// assert a timer record into working memory
				if(!paused)
					createEvent() ;

				sleep(granularity) ;
			} catch(InterruptedException ie) {}
		}
	}
	
	
	/* Schedule the service to shut down on the next cycle
	* @return void
	*/
	public void shutDown() {
		shutdown = true ;
	}
	
	
	/* Schedule the service to pause on the next cycle.  Pause means that the
	* timer keeps running, but doesn't assert any timer facts
	*
	* @return void
	*/
	public void pause(boolean b) {
		paused = b ;
	}
	
	
	
	/* Create a timer event fact and assert into working memory
	* @return void
	*/
	protected void createEvent() {
		Fact fact = null ;
		Calendar now = Calendar.getInstance() ;
		
		// assert the fact
		try {
			// create the fact
			fact = new Fact(template) ;
			
			// populate it
			fact.setSlotValue("ctm", new LongValue(now.getTimeInMillis())) ;
			fact.setSlotValue("second", new Value(now.get(Calendar.SECOND), RU.INTEGER)) ;
			fact.setSlotValue("minute", new Value(now.get(Calendar.MINUTE), RU.INTEGER)) ;
			fact.setSlotValue("hour", new Value(now.get(Calendar.HOUR_OF_DAY), RU.INTEGER)) ;
			fact.setSlotValue("day-of-week", new Value(now.get(Calendar.DAY_OF_WEEK), RU.INTEGER)) ;
			fact.setSlotValue("day-of-month", new Value(now.get(Calendar.DAY_OF_MONTH), RU.INTEGER)) ;
			fact.setSlotValue("month", new Value(now.get(Calendar.MONTH), RU.INTEGER)) ;
			
			// assert it
			rete.assertFact(fact) ;
		} catch(JessException je) {
			// to logging here
			je.printStackTrace() ;
		}
	}
}
