package eu.dnetlib.enabling.tools;

import java.text.ParseException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Required;

/**
 * Common implementation for schedulable beans.
 * 
 * @author claudio
 * 
 */
public abstract class AbstractSchedulable implements Schedulable, Job, BeanNameAware {

	private static final String THIS = "this";

	private final static String GROUP = "schedulableJobs";

	private boolean enabled;

	private CronExpression cronExpression;

	private String beanName;

	@Resource(name = "dnetJobScheduler")
	private Scheduler jobScheduler;

	@PostConstruct
	protected void init() {
		try {

			JobDataMap jobDataMap = new JobDataMap();
			jobDataMap.put(THIS, this);

			JobDetail jd = new JobDetail();
			jd.setName(getBeanName());
			jd.setGroup(GROUP);
			jd.setJobDataMap(jobDataMap);
			jd.setJobClass(this.getClass());

			jobScheduler.scheduleJob(jd, createTrigger());

		} catch (SchedulerException e) {
			throw new RuntimeException(e);
		}
	}

	private Trigger createTrigger() {
		try {
			CronTrigger trigger = new CronTrigger(getBeanName(), GROUP, getCronExpression());
			trigger.setMisfireInstruction(Trigger.INSTRUCTION_NOOP);
			trigger.setJobGroup(GROUP);
			trigger.setJobName(getBeanName());
			return trigger;
		} catch (ParseException e) {
			throw new IllegalArgumentException("invalid cron expression: " + cronExpression, e);
		}
	}

	protected abstract void doExecute();

	@Override
	public void execute() {
		// bean represents the quartz instance of this object
		if (isEnabled()) {
			doExecute();
		}
	}

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		AbstractSchedulable bean = (AbstractSchedulable) context.getJobDetail().getJobDataMap().get(THIS);

		// bean represents the quartz instance of this object
		if (bean.isEnabled()) {
			bean.doExecute();
		}
	}

	@Override
	public boolean isEnabled() {
		return enabled;
	}

	@Override
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	@Override
	public String getCronExpression() {
		return cronExpression.getCronExpression();
	}

	@Required
	public void setCronExpression(String cronExpression) {
		try {
			this.cronExpression = new CronExpression(cronExpression);
		} catch (ParseException e) {
			throw new IllegalArgumentException("invalid cron expression: " + cronExpression, e);
		}
	}

	@Override
	public void updateCronExpression(String cronExpression) {

		if (!cronExpression.equals(getCronExpression())) {
			setCronExpression(cronExpression);
			try {
				jobScheduler.rescheduleJob(getBeanName(), GROUP, createTrigger());
			} catch (SchedulerException e) {
				throw new RuntimeException("unable to reschedule trigger", e);
			}
		}
	}

	@Override
	public String getNextFireTime() {
		try {
			if (isPaused()) {
				return "";
			}
			if (!isEnabled()) {
				return "";
			}
			Trigger t = jobScheduler.getTrigger(getBeanName(), GROUP);
			return t != null ? t.getNextFireTime().toString() : "";
		} catch (SchedulerException e) {
			throw new RuntimeException("unable to get trigger", e);
		}
	}

	@Override
	public boolean isPaused() {
		try {
			int state = jobScheduler.getTriggerState(getBeanName(), GROUP);
			switch (state) {
			case Trigger.STATE_PAUSED:
			case Trigger.STATE_NONE:
			case Trigger.STATE_ERROR:
				return true;
			default:
				return false;
			}
		} catch (SchedulerException e) {
			throw new RuntimeException("unable to get trigger", e);
		}
	}

	@Override
	public void pause() {
		try {
			jobScheduler.pauseTrigger(getBeanName(), GROUP);
		} catch (SchedulerException e) {
			throw new RuntimeException("unable to pause trigger", e);
		}
	}

	@Override
	public void resume() {
		try {
			jobScheduler.resumeTrigger(getBeanName(), GROUP);
		} catch (SchedulerException e) {
			throw new RuntimeException("unable to resume trigger", e);
		}
	}

	public String getBeanName() {
		return beanName;
	}

	@Override
	@Required
	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}

}
