package eu.dnetlib.data.collective.manager.scheduling;

import java.text.ParseException;
import java.util.Date;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.springframework.scheduling.quartz.QuartzJobBean;

import eu.dnetlib.common.interfaces.nh.IBlackboardMessage.Action;
import eu.dnetlib.data.collective.manager.IInstanceListener;
import eu.dnetlib.data.collective.manager.IWorkerServiceManager;
import eu.dnetlib.data.collective.manager.profile.AbstractInstance;
import eu.dnetlib.enabling.tools.blackboard.BlackboardClientHandler;
import eu.dnetlib.enabling.tools.blackboard.BlackboardJobRegistry;


/**
 * @author jochen
 * @param <T> type of instance datastructure
 *
 */
public abstract class InstanceScheduling<T extends AbstractInstance> implements IInstanceListener<T>{
	
	public static enum TRIGGER_TYPE{SIMPLE, CRON};
	private static final Log log = LogFactory.getLog(InstanceScheduling.class);
	
	protected static final String jobDetailGroupName = "DEFAULT_JOBDETAILGROUP";
	protected static final String jobDetailSimpleTriggerGroupName = "SIMPLE_JOBDETAILGROUP";
	protected static final String triggerGroupName = "DEFAULT_TRIGGERGROUP";
	protected static final String simpleTriggerGroupName = "DEFAULT_SIMPLETRIGGERGROUP";
	
	protected static final String jobDataMapKey_bbMsgAction = "bbMsgAction";
	protected static final String jobDataMapKey_bbClientHandler = "bbClientHandler";
	protected static final String jobDataMapKey_bbJobRegistry = "bbJobRegistry";
	protected static final String jobDataMapKey_instance = "instance";
	protected static final String jobDataMapKey_workerServiceManager = "workerServiceManager";
	protected static final String jobDataMapKey_triggerType = "triggerType";
	
	private Scheduler scheduler;
	private Class<? extends QuartzJobBean> jobBeanClazz;
	@Resource(name="blackboardClientHandler")
	private BlackboardClientHandler bbClientHandler;  // parameter for the JobBeanClass
	@Resource(name="workerServiceBlackboardNotificationHandler")
	private BlackboardJobRegistry bbJobRegistry;      // parameter for the JobBeanClass
	@Resource(name="workerServiceManager")
	private IWorkerServiceManager workerServiceManager;         // parameter for the JobBeanClass
	private boolean isScheduling = true;
	
	public void init(){
		try {
			if (this.isScheduling){
				log.debug("resume scheduling for trigger-group " + triggerGroupName);
				scheduler.resumeTriggerGroup(triggerGroupName);
			}else{
				log.debug("pause scheduling for trigger-group " + triggerGroupName);
				scheduler.pauseTriggerGroup(triggerGroupName);
			}
		} catch (SchedulerException e) {
			throw new IllegalStateException();
		}
	}
	
	public boolean fireOnce(T aInstance, Action aAction){
		try{
			log.debug("fire once");
			if (aInstance.isOngoing()) return false;
			scheduler.scheduleJob(getJobDetail(aInstance, jobDetailSimpleTriggerGroupName, aAction, TRIGGER_TYPE.SIMPLE), getSimpleTrigger(aInstance, "simpleTrigger-"  + aInstance.getResourceId()));
		}catch(Exception e){
			log.error("cannot fire once for scheduling", e);
			aInstance.setOngoing(false);
			return false;
		}
		return true;
	}
	
	public boolean cancelOnce(T aInstance, Action aAction){
		try{
			log.debug("cancel once");
			scheduler.scheduleJob(getJobDetail(aInstance, jobDetailSimpleTriggerGroupName, aAction, TRIGGER_TYPE.SIMPLE), getSimpleTrigger(aInstance, "simpleCancelTrigger-"  + aInstance.getResourceId()));
		}catch(SchedulerException e){
			log.error("cannot cancel once for scheduling", e);
			return false;
		}
		return true;		
	}
	
	public boolean addInstance(T aInstance){
		try {
			if (isCronTriggerEnabled(aInstance)){
				scheduler.scheduleJob(getJobDetail(aInstance, jobDetailGroupName, aInstance.getDefaultBlackboardMessageAction(), TRIGGER_TYPE.CRON), getCronTrigger(aInstance));				
				log.debug("number of jobs now: " + scheduler.getJobNames(jobDetailGroupName).length);

			}else{
				return false;
			}
		} catch (SchedulerException e) {
			log.error("cannot add instance for scheduling", e);
			return false;
		}
		return true;
	}
	
	public boolean removeInstance(T aInstance){
		String triggerName = "trigger-" + aInstance.getResourceId();
		try {
			scheduler.unscheduleJob(triggerName, triggerGroupName);
		} catch (SchedulerException e) {
			log.error("cannot remove instance from scheduling", e);
			return false;
		}
		return true;
	}
	
	public boolean updateInstance(T aInstance){
		boolean removed = removeInstance(aInstance);
		boolean added = addInstance(aInstance);
		return (removed || added);
	}
	
	private JobDetail getJobDetail(T aInstance, String aJobDetailGroupName, Action aAction, TRIGGER_TYPE aTriggerType){
		String jobDetailName = "jobdetail-" + aInstance.getResourceId();
		JobDetail jd = new JobDetail(jobDetailName, aJobDetailGroupName, jobBeanClazz);
		JobDataMap dataMap = new JobDataMap();
		dataMap.put( jobDataMapKey_bbClientHandler, this.bbClientHandler);
		dataMap.put( jobDataMapKey_bbJobRegistry, this.bbJobRegistry);
		dataMap.put( jobDataMapKey_workerServiceManager, this.workerServiceManager);
		dataMap.put( jobDataMapKey_instance, aInstance);
		dataMap.put( jobDataMapKey_bbMsgAction, aAction);
		dataMap.put( jobDataMapKey_triggerType, aTriggerType);
		jd.setJobDataMap(dataMap);
		return jd;
	}
	
	/**
	 * configure a trigger to start a job once with 10 sec delay
	 * @param aInstance
	 * @return a simple trigger
	 */
	private SimpleTrigger getSimpleTrigger(T aInstance, String triggerName){
		long startTime = System.currentTimeMillis() + 10000L;
		SimpleTrigger t = null;
		t = new SimpleTrigger(triggerName, simpleTriggerGroupName, new Date(startTime));
		return t;
	}
	
	private boolean isCronTriggerEnabled(T aInstance){
		return aInstance.getScheduling().isCronSchedulingConfigured();
	}
	
	/**
	 * create and return a cronTrigger with next-fire-time updated in case of mis-fire
	 * @param aInstance - the abstract instance, configured for cron-scheduling with a defined cron-expression
	 * @return cronTrigger
	 */
	private CronTrigger getCronTrigger(T aInstance){
		String triggerName = "trigger-" + aInstance.getResourceId();
		
		String cronExpr = aInstance.getScheduling().getCronScheduling().getWhen();
		CronTrigger t = null;
		try {
			t = new CronTrigger(triggerName, triggerGroupName, cronExpr);
			t.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
		} catch (ParseException e) {
			log.debug("Error while parsing the cronExpr: " + cronExpr);
		}
		return t;
	}

	public Class<? extends QuartzJobBean> getJobBeanClazz() {
		return jobBeanClazz;
	}

	public void setJobBeanClazz(Class<? extends QuartzJobBean> jobBeanClazz) {
		this.jobBeanClazz = jobBeanClazz;
	}

	public void setBbClientHandler(BlackboardClientHandler bbClientHandler) {
		this.bbClientHandler = bbClientHandler;
	}

	public BlackboardClientHandler getBbClientHandler() {
		return bbClientHandler;
	}

	public void setBbJobRegistry(BlackboardJobRegistry bbJobRegistry) {
		this.bbJobRegistry = bbJobRegistry;
	}

	public BlackboardJobRegistry getBbJobRegistry() {
		return bbJobRegistry;
	}

	public  void setWorkerServiceManager(IWorkerServiceManager workerServiceManager) {
		this.workerServiceManager = workerServiceManager;
	}

	public IWorkerServiceManager getWorkerServiceManager() {
		return workerServiceManager;
	}

	/**
	 * @return the schedulerFactory
	 */
	public Scheduler getScheduler() {
		return scheduler;
	}

	/**
	 * @param schedulerFactory the schedulerFactory to set
	 */
	public void setScheduler(Scheduler aScheduler) {
		this.scheduler = aScheduler;
	}
	
	@Override
	public void enableScheduling(boolean aIsScheduling) {
		this.isScheduling = aIsScheduling;
		init();
	}

	@Override
	public void setEnableSchedulingOnInit(boolean aIsScheduling) {
		this.isScheduling = aIsScheduling;
	}
	
	@Override
	public boolean isSchedulingEnabled() {
		return this.isScheduling;
	}

}
