/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2012 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.lang;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableInstant;

import divconq.log.Logger;
import divconq.util.BasicSettingsObfuscator;
import divconq.util.ISettingsObfuscator;
import divconq.util.StringUtil;
import divconq.util.TimeUtil;
import divconq.xml.XElement;

/**
 * Clock has three uses:
 * 
 * 1) Control the date time settings - DivConq's DateTime are based on Joda's library.
 *    With Joda it is possible to set the effective timezone and the current date time
 *    itself.  Through configuration the Clock can be made to enforce a fixed time,
 *    a sped up time (clock moving faster than 1 second per system second), or a regular
 *    time.  Also the time zone can be set.
 * 2) There are some methods for scheduling work on another thread. However,  
 *    typically one should use {@link divconq.scheduler.Scheduler} for task scheduling.
 * 3) DivConq supports obfuscated (encrypted) settings in the config files.  This class
 *    provides access to the feature for encrypting and decrypting config settings.
 *    
 * @author Andy
 *
 */
public class Clock {

    // periodic internal intervals, not for use with scheduling "real" tasks 
	// does not preserve user context, uses system clock not divconq clock
	protected ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

	// how many seconds to travel per system second - only enabled if using CoreDate
	protected long speed = 0;
	protected XElement config = null;
	
	protected ISettingsObfuscator obfus = null; 
	
    /**
     * Called from Hub.start this method configures the clock features.
     * 
     * @param config xml holding the configuration
     */
	public void init(XElement config) {
    	// TODO return operation result
		
		this.config = config;
		
		if (config != null) {
			String timeZone = config.getAttribute("TimeZone");
			
			if (StringUtil.isNotEmpty(timeZone))
				DateTimeZone.setDefault(DateTimeZone.forID(timeZone));
			
			String coreDate = config.getAttribute("CoreDate");
			
			if (StringUtil.isNotEmpty(coreDate)) {
				this.setAppClock(TimeUtil.parseDateTime(coreDate));
			}
			
			this.speed = StringUtil.parseInt(config.getAttribute("Speed"), 0);
			
			String obclass = config.getAttribute("TimerClass");
			
			if (StringUtil.isNotEmpty(obclass)) {
				try {
					Class<?> obc = this.getClass().getClassLoader().loadClass(obclass);
					this.obfus = (ISettingsObfuscator) obc.newInstance();
				}
				catch (Exception x) {
					Logger.error("Unable to load custom Settings Obfuscator class: " + obclass, "Code", "207");
				}
			}
		}

		if (this.obfus == null)
			this.obfus = new BasicSettingsObfuscator();
		
		this.obfus.init(config);
	}

	/**
	 * If the framework time is configured to use an accelerated clock, this method gets 
	 * that accelerated clock going.
	 */
	public void start() {
		if (this.speed != 0) 
			this.schedulePeriodicInternal(new Runnable() {
				@Override
				public void run() {
					DateTimeUtils.setCurrentMillisFixed(DateTimeUtils.currentTimeMillis() + (Clock.this.speed * 1000));
				}
			}, 1);
	} 

	/**
	 * Terminate tasks being run on the Clock's scheduler.  Called by
	 * Hub.stop during the final steps of shutdown.
	 */
	public void stop() {
    	// TODO return operation result
		
		// TODO
		this.scheduler.shutdown();
		
		try {
			this.scheduler.awaitTermination(5, TimeUnit.SECONDS);
		} 
		catch (InterruptedException e) {
			// TODO 
		}
	} 
	
	/**
	 * set framework's time to use the system time
	 */
	public void resetAppClock() {
		DateTimeUtils.setCurrentMillisSystem();
	}
	
	/**
	 * set framework's time to use a fixed time
	 * 
	 * @param time fixed time to use
	 */
	public void setAppClock(ReadableInstant time) {
		DateTimeUtils.setCurrentMillisFixed(time.getMillis());
	}
	
	/**
	 * Typically use {@link divconq.scheduler.Scheduler} for task scheduling.
	 * However, when a task should run on the system clock rather than the 
	 * app clock, these methods may be used.  Use sparingly as these are run on
	 * a single threaded pool.  These methods also do not preserve the TaskContext.
	 * 
	 * @param command the code for the task to run
	 * @param delaySecs how many seconds until the task is run
	 * @return the token for canceling the task 
	 */	
	public ScheduledFuture<?> scheduleOnceInternal(Runnable command, long delaySecs) {
		return this.scheduler.schedule(command, delaySecs, TimeUnit.SECONDS);
	}
	
	/**
	 * Typically use {@link divconq.scheduler.Scheduler} for task scheduling.
	 * However, when a task should run on the system clock rather than the 
	 * app clock, these methods may be used.  Use sparingly as these are run on
	 * a single threaded pool.  These methods also do not preserve the TaskContext.
	 * 
	 * @param command the code for the task to run
	 * @param delay how many "units" until the task is run
	 * @param unit time unit used with delay
	 * @return the token for canceling the task 
	 */	
	public ScheduledFuture<?> scheduleOnceInternal(Runnable command, long delay, TimeUnit unit) {
		return this.scheduler.schedule(command, delay, unit);
	}
	
	/**
	 * Typically use {@link divconq.scheduler.Scheduler} for task scheduling.
	 * However, when a task should run on the system clock rather than the 
	 * app clock, these methods may be used.  Use sparingly as these are run on
	 * a single threaded pool.  These methods also do not preserve the TaskContext.
	 * 
	 * @param command the code for the task to run
	 * @param periodSecs how many seconds until the task is run first time, and then how long between runs
	 * @return the token for canceling the task 
	 */	
	public ScheduledFuture<?> schedulePeriodicInternal(Runnable command, long periodSecs) {
		return this.scheduler.scheduleAtFixedRate(command, periodSecs, periodSecs, TimeUnit.SECONDS);
	}
	
	/**
	 * Typically use {@link divconq.scheduler.Scheduler} for task scheduling.
	 * However, when a task should run on the system clock rather than the 
	 * app clock, these methods may be used.  Use sparingly as these are run on
	 * a single threaded pool.  These methods also do not preserve the TaskContext.
	 * 
	 * @param command the code for the task to run
	 * @param initialSecs how many "units" until the task is first run
	 * @param period how many "units" between task runs, after first run
	 * @param unit time unit used with delay and period
	 * @return the token for canceling the task 
	 */	
	public ScheduledFuture<?> schedulePeriodicInternal(Runnable command, long initialDelay, long period, TimeUnit unit) {
		return this.scheduler.scheduleAtFixedRate(command, initialDelay, period, unit);
	}
	
	/**
	 * Config file settings containing sensitive info may be obscured (encypted)
	 * to make it hard for a hacker to get anything useful from just a copy of the
	 * config file.  How the settings are obscured is based on the Clock's settings.
	 * 
	 * For more details on how this works @see divconq.util.ISettingsObfuscator
	 *  
	 * @return the obfuscator used with this settings file 
	 */
	public ISettingsObfuscator getObfuscator() {
		return this.obfus;
	}
}
