// Created Apr 28, 2005 12:21:21 PM

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

package gov.va.med.esr.messaging.service.outbound;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.Validate;

import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.rule.service.impl.AssociationRuleServiceImpl;
import gov.va.med.esr.messaging.util.MessagingWorkloadCaseHelper;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.trigger.CommonTriggerIdentity;
import gov.va.med.esr.service.trigger.FilterSitesPersonTriggerEvent;
import gov.va.med.esr.service.trigger.IncomeYearTriggerEvent;
import gov.va.med.esr.service.trigger.PersonTrigger;
import gov.va.med.esr.service.trigger.PersonTriggerEvent;
import gov.va.med.esr.service.trigger.PersonTriggerIdentity;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.trigger.TriggerEvent;
import gov.va.med.fw.service.trigger.TriggerIdentity;
import gov.va.med.fw.util.InvalidConfigurationException;
import gov.va.med.fw.util.StopWatchLogger;

/**
 * Handles PersonTriggerEvents that causes messages to be triggered.
 * 
 * @author DNS   BOHMEG
 */
public class OutboundPersonMessageRouter extends OutboundMessageRouter {
	/*
	 * Note: outboundProcessServices is configuration injected as such: Map<PersonTriggerIdentity,
	 * OutboundProcessService>
	 */

	private PersonService personService;
	
	private MessagingWorkloadCaseHelper messagingWorkloadCaseHelper;

	/**
	 * @return Returns the personService.
	 */
	public PersonService getPersonService() {
		return personService;
	}

	/**
	 * @param personService
	 *            The personService to set.
	 */
	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}

	public void trigger(TriggerEvent triggerEvent) throws ServiceException {
		
		if (triggerEvent != null) {
			
			if (!(triggerEvent instanceof PersonTriggerEvent))
				throw new IllegalArgumentException(
						"TriggerEvent must be of type PersonTriggerEvent");
			
			PersonTriggerEvent userEvent = (PersonTriggerEvent) triggerEvent;
			Person person = null;
			
			StopWatchLogger watch = null;			
			try {
				if (logger.isDebugEnabled()) {
					
					String messageType = getOutboundMessage((CommonTriggerIdentity) triggerEvent.getTriggerIdentity());					
					watch = new StopWatchLogger((new StringBuffer(messageType)
							.append("-OutboundPersonMessageRouter triggerEvent"))
							.toString());
					if (watch != null) {
						watch.start();
					}
				}
				
				
				OutboundProcessService service = getOutboundProcessService((PersonTriggerIdentity) userEvent
						.getTriggerIdentity());
				if (service == null) {
					throw new InvalidConfigurationException(userEvent.getName()
							+ " not mapped to an OutboundProcessService");
				}
	
				// make sure can retrieve Person from database
				try {
					person = getPerson(userEvent);
				} catch(Exception e) {
					// unable to retrieve Person, this will create a generic Workload case
					messagingWorkloadCaseHelper.createWorkloadCaseForCanNotRetrievePerson(userEvent, e);
				}
				
				if(person != null) {
					invokeOutboundProcessService(service, userEvent, person);
				}

			}finally
			{
				if (logger.isDebugEnabled()) {
					try {
						StringBuffer info = new StringBuffer(
								"Total time to process trigger event for Person Id ").
								append(person == null ? "" : person.getEntityKey().getKeyValueAsString());

						if (watch != null) {
							watch.stopAndLog(info.toString());
						}
					} catch (Exception e) {
					}
				}
			}
		}
	}

	/** Overrides default implementation */
	protected OutboundProcessService getOutboundProcessService(
			CommonTriggerIdentity triggerIdentity) {
		return (OutboundProcessService) findMatch(triggerIdentity, getOutboundProcessServices());
	}
	
	private Object findMatch(CommonTriggerIdentity triggerIdentity, Map targetMap) {
		if (!(triggerIdentity instanceof PersonTriggerIdentity))
			throw new IllegalArgumentException(
					"CommonTriggerIdentity must be of type PersonTriggerIdentity");

		PersonTriggerIdentity personTriggerIdentity = (PersonTriggerIdentity) triggerIdentity;
		Object match = null;
		Iterator itr = targetMap.keySet().iterator();
		PersonTriggerIdentity keyFromConfiguration = null;
		while (itr.hasNext()) {
			keyFromConfiguration = (PersonTriggerIdentity) itr.next();
			if (keyFromConfiguration
					.matchTriggerCriteria(personTriggerIdentity)) {
				match = targetMap.get(keyFromConfiguration);
				break;
			}
		}
		return match;		
	}

	/** Overrides default implementation */
	protected String getOutboundMessage(CommonTriggerIdentity triggerIdentity) {
		return (String) findMatch(triggerIdentity, getOutboundMessages());		
	}


	private Person getPerson(PersonTriggerEvent triggerEvent)
			throws ServiceException {
		try {
			PersonEntityKey key = triggerEvent.getPersonId();
			// since Messages always are sent with the latest information, make conscious decision to do
			// this
			key.disableVersionCheck();

			//If the trigger event contains identity traits, retrieve the Person by passing the identity traits
			//which will save a call to PSIM
			Person person = null;
			if (isZ05(triggerEvent) && triggerEvent.getPayload() != null) {
				//retrieve updated person as part of processZ07 method
				person = (Person)triggerEvent.getPayload();
			} else if(triggerEvent.getIdentityTraits() != null) {
				//CCR 11758
				if (isQRYZ07(triggerEvent) && triggerEvent.getIdentityTraits().getVpid() != null)
					//get composite call to find person for QRYZ07 message
					person = personService.getPersonWithCompositeCall(triggerEvent.getIdentityTraits().getVpid());
				else {
					//use the regular find call for the rest of messages 
					person = personService.find(triggerEvent.getIdentityTraits());
				}
			} else {
				person = personService.getPerson(key);
			}			
			
			if(person == null) {
				// since this router is only used when dealing with a Person, this is an exception scenario
				throw new IllegalStateException("Unable to retrieve a Person for PersonTriggerEvent: " + triggerEvent);
			}
			return person;
		} catch (ServiceException e) {
			throw new ServiceException(
					"Error while finding a person from PersonTriggerEvent", e);
		}
	}

	//CCR 11758 identify message type of QRYZ07
	private boolean isQRYZ07(PersonTriggerEvent triggerEvent)
	{
		if (triggerEvent instanceof IncomeYearTriggerEvent)
		{
			IncomeYearTriggerEvent evt = (IncomeYearTriggerEvent) triggerEvent;
			TriggerIdentity tid = evt.getTriggerIdentity();
			
			if (tid != null)
			{
				return (PersonTrigger.DestinationType.MESSAGING.equals(tid.getDestinationType()) &&
						PersonTrigger.TargetType.VISTA.equals(tid.getTargetType()) &&
						PersonTrigger.DispatchType.QUERY.equals(tid.getDispatchType())); // &&
						//PersonTrigger.DataType.FULL_DATA.equals(tid.getDispatchType()));
			}
		}
		return false;
	}
	
	private boolean isZ05(PersonTriggerEvent triggerEvent)
	{
		if (triggerEvent instanceof FilterSitesPersonTriggerEvent)
		{
			FilterSitesPersonTriggerEvent evt = (FilterSitesPersonTriggerEvent) triggerEvent;
			TriggerIdentity tid = evt.getTriggerIdentity();
			
			if (tid != null)
			{
				return (PersonTrigger.DestinationType.MESSAGING.equals(tid.getDestinationType()) &&
						PersonTrigger.TargetType.VISTA.equals(tid.getTargetType()) &&
						PersonTrigger.DispatchType.NOTIFY.equals(tid.getDispatchType()) &&
						AssociationRuleServiceImpl.TRIGGER_Z05.equals(triggerEvent.getName()));
			}
		}
		return false;
	}	
    
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		if (personService == null)
			throw new InvalidConfigurationException(
					"personService must be set on " + getClass().getName());
		Validate.notNull(messagingWorkloadCaseHelper, "messagingWorkloadCaseHelper is required");
	}

	/**
	 * @return Returns the messagingWorkloadCaseHelper.
	 */
	public MessagingWorkloadCaseHelper getMessagingWorkloadCaseHelper() {
		return messagingWorkloadCaseHelper;
	}

	/**
	 * @param messagingWorkloadCaseHelper The messagingWorkloadCaseHelper to set.
	 */
	public void setMessagingWorkloadCaseHelper(
			MessagingWorkloadCaseHelper messagingWorkloadCaseHelper) {
		this.messagingWorkloadCaseHelper = messagingWorkloadCaseHelper;
	}

}
