package eu.dnetlib.msro.cron;

import java.util.Date;

import javax.annotation.Resource;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronExpression;
import org.springframework.beans.factory.annotation.Required;

import com.googlecode.sarasvati.GraphProcess;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.miscutils.datetime.DateUtils;
import eu.dnetlib.msro.workflows.sarasvati.loader.WorkflowExecutor;
import eu.dnetlib.msro.workflows.sarasvati.registry.GraphProcessRegistry;

public class ScheduledWorkflowLauncher {

	private static final Log log = LogFactory.getLog(ScheduledWorkflowLauncher.class);

	private static final DateUtils dateUtils = new DateUtils();
	
	private WorkflowExecutor workflowExecutor;
	
	private GraphProcessRegistry graphProcessRegistry;
	
	private int windowSize = 7200000; // 2 hours 
	
	@Resource(name="lookupLocator")
	private ServiceLocator<ISLookUpService> lookupLocator;

	public void verifySheduledWorkflows() {
		log.info("Verifying scheduled workflows - START");
		
		final String query = "for $x in collection('/db/DRIVER/MetaWorkflowDSResources/MetaWorkflowDSResourceType') " +
				"where $x//CONFIGURATION/@status='EXECUTABLE' " +
				"and $x//SCHEDULING/@enabled='true' " +
				"return concat($x//RESOURCE_IDENTIFIER/@value, ' @@@ ', $x//SCHEDULING/CRON, ' @@@ ', $x//SCHEDULING/MININTERVAL)";


		try {
			for (String s : lookupLocator.getService().quickSearchProfile(query)) {
				final String[] arr = s.split("@@@");
				final String id = arr[0].trim();
				final String cron = arr[1].trim();
				final int minInterval = NumberUtils.toInt(arr[2].trim(), 0) * 60000; // MINUTES to MILLIS
				
				if (CronExpression.isValidExpression(cron)) {
					final Date now = new Date();
					final Date last = calculateLastExecutionDate(id);
					final int elapsed = Math.abs(Math.round(now.getTime() - last.getTime()));
					
					if (log.isDebugEnabled()) {
						log.debug("**************************************************************");
						log.debug("META WORKFLOW ID   : " + id);
						log.debug("NOW                : " + now);
						log.debug("LAST EXECUTION DATE: " + last);
						log.debug("MIN INTERVAL       : " + minInterval);
						log.debug("WINDOW SIZE        : " + windowSize);
						log.debug("TIME ELAPSED       : " + elapsed);
					}

					if (elapsed > minInterval && isFired(cron, last, now) && !isAlreadyRunning(id)) {
						log.debug("MUST BE EXECUTED   : true");
						try {
							workflowExecutor.startMetaWorkflow(id);
						} catch (Exception e) {
							log.error("Error launching scheduled wf: " + id, e);
						}
					} else {
						log.debug("MUST BE EXECUTED   : false");
					}
					log.debug("**************************************************************");
				}
			}
		} catch (ISLookUpException e) {
			log.error("Error executing query " + query);
		}
		
		log.info("Verifying scheduled workflows - END");
	}

	private boolean isFired(final String cronExpression, final Date startDate, final Date now) {
		try {
			final CronExpression cron = new CronExpression(cronExpression);
			
			final Date prev = new Date(now.getTime() - windowSize);
			final Date date = (prev.getTime() < startDate.getTime()) ? startDate : prev;
			final Date next = cron.getNextValidTimeAfter(date);
			
			if (log.isDebugEnabled()) {
				log.debug("NEXT EXECUTION DATE: " + next);
				log.debug("FIRED              : " + (next.getTime() < now.getTime()));
			}
			return (next.getTime() < now.getTime());
		} catch (Exception e) {
			log.error("Error calculating next cron event: " + cronExpression, e);
			return false;
		} 
	}

	private boolean isAlreadyRunning(final String metaWfId) {
		final String query = "for $x in collection('/db/DRIVER/MetaWorkflowDSResources/MetaWorkflowDSResourceType') " +
				"where $x//RESOURCE_IDENTIFIER/@value = '" + metaWfId + "' " +
				"return $x//WORKFLOW/@id/string()";
		
		try {
			for (String profileId : lookupLocator.getService().quickSearchProfile(query)) {
				if (profileId.length() > 0) {
					for (GraphProcess p : graphProcessRegistry.findProcessesByResource(profileId)) {
						switch (p.getState()) {
						case Created:
							return true;
						case Executing:
							return true;
						default:
							break;
						}
					}
				}
			}
		} catch (ISLookUpException e) {
			log.error("Error executing query " + query);
		}
		return false;
	}


	private Date calculateLastExecutionDate(final String id) {
		final String query = "for $x in collection('/db/DRIVER/MetaWorkflowDSResources/MetaWorkflowDSResourceType') " +
				"for $y in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') " +
				"where $x//RESOURCE_IDENTIFIER/@value = '" + id + "' " +
				"and $x//WORKFLOW/@id = $y//RESOURCE_IDENTIFIER/@value " +
				"and $y//LAST_EXECUTION_STATUS = 'SUCCESS' " +
				"return $y//LAST_EXECUTION_DATE/text() ";
		
		long time = 0;
		try {
			for (String s : lookupLocator.getService().quickSearchProfile(query)) {
				if (s.length() > 0) {
					final Date d = dateUtils.parse(s);
					if (time < d.getTime()) {
						time = d.getTime();
					}
				}
			}
		} catch (ISLookUpException e) {
			log.error("Error executing query " + query);
		} catch (Exception e) {
			log.error("Error calculating date", e);
		}
		return new Date(time);
	}

	public WorkflowExecutor getWorkflowExecutor() {
		return workflowExecutor;
	}

	@Required
	public void setWorkflowExecutor(WorkflowExecutor workflowExecutor) {
		this.workflowExecutor = workflowExecutor;
	}

	public GraphProcessRegistry getGraphProcessRegistry() {
		return graphProcessRegistry;
	}
	
	@Required
	public void setGraphProcessRegistry(GraphProcessRegistry graphProcessRegistry) {
		this.graphProcessRegistry = graphProcessRegistry;
	}

	public int getWindowSize() {
		return windowSize;
	}
	
	@Required
	public void setWindowSize(int windowSize) {
		this.windowSize = windowSize;
	}

}
