/********************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ********************************************************************/

package gov.va.med.fw.batchprocess;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.quartz.Trigger;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.scheduling.quartz.JobDetailAwareTrigger;

import gov.va.med.fw.scheduling.ScheduledProcessInvocationContext;
import gov.va.med.fw.scheduling.ScheduledProcessTriggerEvent;
import gov.va.med.fw.security.SecurityContextHelper;
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.jms.QueueDescriptor;
import gov.va.med.fw.service.jms.QueueMessageProducerService;
import gov.va.med.fw.service.trigger.TriggerEvent;
import gov.va.med.fw.service.trigger.TriggerRouter;
import gov.va.med.fw.service.trigger.TriggerIdentity.DestinationType;
import gov.va.med.fw.util.InvalidConfigurationException;

/**
 * Invoker for dynamic execution of batch processes.  This can be used statically as a bootstrap
 * from an external JVM (eg, main method) or directly injected as a Spring bean into invoking
 * component (eg, Struts action).
 * 
 * <p>Currently makes an assumption that a JobName is unique across groups.
 * 
 * Created Jan 25, 2006 4:38:50 PM
 * 
 * @author DNS   BOHMEG
 */
public class BatchProcessInvoker extends AbstractComponent {
	static String CONFIG_FILE = "config";

	static String CONFIG_SERVER = "server";

	static String CONFIG_CONTEXT = "context";

	static String CONFIG_FACTORY = "factory";

	static String CONFIG_QUEUE = "queue";
	
	public static final String EXECUTE_FROM_MANAGEMENT_PAGE_FLAG = "EXECUTE_FROM_MANAGEMENT_PAGE_FLAG";

	private DestinationType defaultDestinationType = DestinationType.BATCH_PROCESS;

	private TriggerRouter triggerRouter;

	/** These are disconnected in the sense that they cannot be discovered in the injected
	 * applicationContext.
	 */
	private List undiscoverableBatchProcessDetails;
	
	private static Map batchProcessDetails = new TreeMap();

	public Collection getBatchProcessDetails() {
		return batchProcessDetails.values();
	}
	
	private void initializeBatchProcessDetails() {
		// only discover Triggers in the Context that are statically hooked up to a Job
		Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), JobDetailAwareTrigger.class);
		Iterator itr = beans != null ? beans.values().iterator() : null;
		JobDetailAwareTrigger trigger = null;
		BatchProcessDetail detail = null;
		while(itr != null && itr.hasNext()) {
			trigger = (JobDetailAwareTrigger) itr.next();
			if(trigger.getJobDetail() != null) {				
				detail = new BatchProcessDetail();
				detail.setJobName(trigger.getJobDetail().getName());
				String executeFromManagementPage = (String)trigger.getJobDetail().getJobDataMap().get(EXECUTE_FROM_MANAGEMENT_PAGE_FLAG);
				
				if ( executeFromManagementPage != null ) {
					detail.setExecutableFromManagementPage(new Boolean(executeFromManagementPage).booleanValue());
				}
				detail.setJobDescription(trigger.getJobDetail().getDescription());
				detail.setGroupName(trigger.getJobDetail().getGroup());
				detail.setDestinationType(defaultDestinationType);
				if(trigger instanceof Trigger) {
					detail.setTriggerName(((Trigger) trigger).getName());
					detail.setTriggerGroup(((Trigger) trigger).getGroup());
				}
				batchProcessDetails.put(detail.getJobName(), detail);	
				
			}
		}
		
		itr = undiscoverableBatchProcessDetails != null ? undiscoverableBatchProcessDetails.iterator() : null;
		while (itr != null && itr.hasNext()) {
			detail = (BatchProcessDetail) itr.next();
			batchProcessDetails.put(detail.getJobName(), detail);
		}		
	}

	public void invokeBatchProcessWithEvent(String jobName) throws ServiceException {
		BatchProcessDetail detail = (BatchProcessDetail) batchProcessDetails.get(jobName);
		invokeBatchProcessWithEvent(detail);
	}
	
	public BatchProcessDetail getBatchProcessDetail(String jobName) {
		return (BatchProcessDetail) batchProcessDetails.get(jobName);
	}

	public void invokeBatchProcessWithEvent(BatchProcessDetail detail, Object[] invocationArgs) throws ServiceException {
		triggerRouter.processTriggerEvent(createTriggerEvent(detail, invocationArgs));
	}

	public void invokeBatchProcessWithEvent(BatchProcessDetail detail, ScheduledProcessInvocationContext invContext) throws ServiceException {
		triggerRouter.processTriggerEvent(createTriggerEvent(detail, invContext));
	}
	
	public void invokeBatchProcessWithEvent(BatchProcessDetail detail) throws ServiceException {
		invokeBatchProcessWithEvent(detail, (Object[]) null);
	}

	public void afterPropertiesSet() {
		Validate.notNull(triggerRouter, "A TriggerRouter is required");
		initializeBatchProcessDetails();
	}

	protected TriggerEvent createTriggerEvent(BatchProcessDetail detail, Object[] invocationArgs) {
		return createTriggerEvent(detail, null, invocationArgs,(detail.getDestinationType() != null ? detail.getDestinationType() : defaultDestinationType));
	}

	protected TriggerEvent createTriggerEvent(BatchProcessDetail detail, ScheduledProcessInvocationContext invContext) {
		return createTriggerEvent(detail, invContext, null,(detail.getDestinationType() != null ? detail.getDestinationType() : defaultDestinationType));
	}
	
	protected static TriggerEvent createTriggerEvent(BatchProcessDetail detail, ScheduledProcessInvocationContext invContext,
			Object[] invocationArgs, DestinationType dest) {
		if(invContext == null) {
			invContext = new ScheduledProcessInvocationContext();
		
			// Set common properties
			invContext.setJobDetailBeanName(detail.getJobName());
			invContext.setJobName(detail.getJobName());
			invContext.setJobGroup(detail.getGroupName());
			if(invocationArgs != null)
				invContext.setInvocationArguments(invocationArgs.length == 1 ? invocationArgs[0] : invocationArgs);
			String userName = SecurityContextHelper.getUserName();
			if(StringUtils.isNotBlank(userName))
				invContext.setExecutionContext(userName);
		}
		
		// Create an event encapsulating a context
		return new ScheduledProcessTriggerEvent(invContext, TriggerEvent.createTriggerIdentity(dest));
	}

	public static void invokeBatchProcess(String connectionConfigFileName,
			BatchProcessDetail detail, Object[] executionArgs) {
		TriggerEvent triggerEvent = createTriggerEvent(detail, null, executionArgs, detail.getDestinationType());

		QueueMessageProducerService messageProducer = new QueueMessageProducerService();
		messageProducer
				.setDescriptor(getJMSDescriptor(connectionConfigFileName));

		try {
			messageProducer.send(triggerEvent,
					triggerEvent.getRoutingProperties());
		} catch (Exception e) {
			RuntimeException e2 = new InvalidConfigurationException(
					"Unable to publish TriggerEvent for batch process: "
							+ detail.getJobName());
			e2.initCause(e);
			throw e2;
		}
	}

	protected static QueueDescriptor getJMSDescriptor(
			String connectionConfigFileName) {
		ResourceBundle bundle = ResourceBundle
				.getBundle(connectionConfigFileName);
		QueueDescriptor jmsDescriptor = new QueueDescriptor();
		jmsDescriptor.setContextName(bundle.getString(CONFIG_CONTEXT));
		jmsDescriptor.setFactoryName(bundle.getString(CONFIG_FACTORY));
		List primary = new ArrayList();
		primary.add(bundle.getString(CONFIG_SERVER));
		jmsDescriptor.setProviderURLs(primary);
		jmsDescriptor.setQueueName(bundle.getString(CONFIG_QUEUE));
		return jmsDescriptor;
	}

	private static void fail() {
		System.err.println("\nUsage: java -Djob.name=a -Djob.group=b BatchProcessInvoker [arg1 arg2 ...argN]");
		System.exit(-1);
	}
	
	public static void main(String args[]) throws Exception {
		String jobName = System.getProperty("job.name");
		String jobGroup = System.getProperty("job.group");
		if(StringUtils.isBlank(jobName) || StringUtils.isBlank(jobGroup))
			fail();
		
		BatchProcessDetail detail = new BatchProcessDetail();
		detail.setJobName(jobName);
		detail.setGroupName(jobGroup);				
		
		detail.setDestinationType(DestinationType.DEFAULT);
				
		Object[] invocationArgs = null;
		if(args != null && args.length != 0) {
			invocationArgs = args;
		}
		
		invokeBatchProcess(CONFIG_FILE, detail, invocationArgs);
	}

	/**
	 * @return Returns the triggerRouter.
	 */
	public TriggerRouter getTriggerRouter() {
		return triggerRouter;
	}

	/**
	 * @param triggerRouter
	 *            The triggerRouter to set.
	 */
	public void setTriggerRouter(TriggerRouter triggerRouter) {
		this.triggerRouter = triggerRouter;
	}

	/**
	 * @return Returns the undiscoverableBatchProcessDetails.
	 */
	public List getUndiscoverableBatchProcessDetails() {
		return undiscoverableBatchProcessDetails;
	}

	/**
	 * @param undiscoverableBatchProcessDetails The undiscoverableBatchProcessDetails to set.
	 */
	public void setUndiscoverableBatchProcessDetails(
			List undiscoverableBatchProcessDetails) {
		this.undiscoverableBatchProcessDetails = undiscoverableBatchProcessDetails;
	}

	/**
	 * @return Returns the defaultDestinationType.
	 */
	public DestinationType getDefaultDestinationType() {
		return defaultDestinationType;
	}

	/**
	 * @param defaultDestinationType The defaultDestinationType to set.
	 */
	public void setDefaultDestinationType(DestinationType defaultDestinationType) {
		this.defaultDestinationType = defaultDestinationType;
	}
}
