/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.messaging.parser;

// Java classes
import java.util.Map;

// Library Classes
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.beans.factory.InitializingBean;

// Framework classes
import gov.va.med.fw.hl7.HL7MessageUtils;
import gov.va.med.fw.hl7.InvalidMessageException;
import gov.va.med.fw.hl7.Message;
import gov.va.med.fw.hl7.HL7Message;
import gov.va.med.fw.util.builder.BuilderException;
import gov.va.med.fw.service.ServiceException;

//ESR classes
import gov.va.med.esr.common.model.lookup.Disability;
import gov.va.med.esr.common.model.ee.VerificationInfo;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.workload.WorkflowCaseInfo;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.MessagingService;
import gov.va.med.esr.service.UnknownLookupCodeException;
import gov.va.med.esr.messaging.constants.HL7Constants;
import gov.va.med.esr.messaging.util.Z11NonCatastrophicException;

/**
 * Intercepts exception thrown in a Z11Parser to handle exceptions accordingly. This
 * class is mainly used to determine when to invoke processVBA in case of a work flow
 * case creation.
 * 
 * @author VL
 */
public class Z11ExceptionInterceptor implements MethodInterceptor, InitializingBean {
	
	/**
	 * An instance of messagingService
	 */
	private MessagingService messagingService = null;

   /**
    * An instance of lookupService 
    */
   private LookupService lookupService;
   
   /**
    * An instance of messageCodes 
    */
   private Map messageCodes = null;
   
   /**
    * A default constructor
    */
   public Z11ExceptionInterceptor() {
      super();
   }

   /**
    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
    */
   public void afterPropertiesSet() {
      Validate.notNull(messagingService, "A message service must be configued");
      Validate.notNull(messageCodes, "A message code map must be configued");
   }
   
   /**
    * Returns an instance of lookupService
    * @return LookupService lookupService.
    */
   public LookupService getLookupService() {
      return lookupService;
   }

   /**
    * Sets the lookupService of type LookupService
    * @param lookupService The lookupService to set.
    */
   public void setLookupService(LookupService lookupService) {
      this.lookupService = lookupService;
   }

   /**
    * @return Returns the messagingService.
    */
   public MessagingService getMessagingService() {
      return messagingService;
   }

   /**
    * @param messagingService
    *           The messagingService to set.
    */
   public void setMessagingService(MessagingService messagingService) {
      this.messagingService = messagingService;
   }

	/**
    * Returns an instance of messageCodes
    * @return Map messageCodes.
    */
   public Map getMessageCodes() {
      return messageCodes;
   }

   /**
    * Sets the messageCodes of type Map
    * @param messageCodes The messageCodes to set.
    */
   public void setMessageCodes(Map messageCodes) {
      this.messageCodes = messageCodes;
   }

   /**
	 * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
	 */
	public Object invoke(MethodInvocation invocation) throws Throwable {

		// There are 5 cases in processVBA.
		//
		// 1. Missing rated disability code.  This case occurs when MVR responds with
		// with an AA in either ORFZ11 or ORUZ11 message and a message doesn't have 
		// a rated disability in a ZRD segment.  In this case, no exception is thrown.
		// 2. Missing entitlement code.  This case occurs when MVR responds with
		// with an AA in either ORFZ11 or ORUZ11 message and a message doesn't have 
		// an entitlement code in a ZEL segment.  In this case, no exception is thrown.
		// 3. Invalid entitlement code. Entitlement code is a not a lookup code so a 
		// message-to-entity builder (Z11Parser) doesn't thrown an exception. An invalid
		// code is detected in processVBA rule.  This case occurs when MVR responds with
		// with an AA in either ORFZ11 or ORUZ11 message and a message contains an invalid 
		// entitlement code in a ZEL segment.
		// 4. Invalid  rated disability code. Disability code is a lookup code so a 
		// message-to-entity builder (Z11Parser) throws an exception when detecting an
		// invalid rated disability code.  This case occurs when MVR responds with
		// with an AA in either ORFZ11 or ORUZ11 message and a message contains an  
		// invalid rated disability code in a ZRD segment.
		// 5. AE No data on file.  This case occurs when MVR doesn't have data for a
		// person that was inquired by ESR in a QYZ11 message.  In this case, MVR 
		// responds with an AE in ORFZ11 message and MSA segment contains a "No data
		// on file" acknoledgement message.
		//
		// -VL
		
		Object[] args = (Object[]) ((Object[]) invocation.getArguments())[0];
		Object result = null;

		if( args != null && args.length >= 2 ) {
			
			Person person = args[0] instanceof Person ? (Person) args[0] : null;
			Message message = args[1] instanceof Message ? (Message) args[1] : null;
			if( message != null && person != null ) {
				
				HL7Message hl7 = new HL7Message( message );
				String type = hl7.getAcknowledgmentCode();
				
				// Case 5: Handles an ORFZ11 or ORUZ11 as an AE No data on file 
				if( HL7Constants.AE.equals( type ) ) {
					
					// Let the rules check for a "No data on file" message
					proceedWithException(person, message, null);
					throw new Z11NonCatastrophicException( "Received an " + type + ": " + hl7.getAcknowledgmentTextMessage() );
				}
				else {
					// Case 1,2,3,4: Handle AA with either missing or invalid 
					// entitlement and rated disability code
					try {
						// Case 1,2,3 should go through without an exception
						result = invocation.proceed();
					}
					catch( BuilderException e ) {
						// Case 4: Handle invalid rated disability code
						// Extract excaption cause of type UnknownLookupCodeException
						Throwable[] causes = ExceptionUtils.getThrowables(e);
						int index = ExceptionUtils.indexOfThrowable(e,UnknownLookupCodeException.class);
						
						if( causes != null && index != -1 && causes.length >= (index + 1) ) {
							// Found an UnknownLookupCodeException
							UnknownLookupCodeException unknownCode = (UnknownLookupCodeException) causes[index];
							if (ClassUtils.isAssignable(unknownCode.getType(), Disability.class)) {
								proceedWithException(person, message, unknownCode);
								throw new Z11NonCatastrophicException( "Received an " + type + ": " + unknownCode.getMessage() );
							}
						}
						throw e;
					}
					catch( Exception e ) {
						throw new BuilderException(e.getMessage(), e);
					}
				}
			}
		}
		return result;
	}

	/**
	 * Even though there was an Exception, business requirements dictate that
	 * Rules processing occurs anyway.
	 * 
	 * @param person
	 * @param message
	 * @param context
	 * @throws BuilderException
	 */
	private void proceedWithException( Person person, Message message, Exception exception ) throws ServiceException {
		
      try {
         String messageType = (String)getMessageCodes().get( message.getMessageType() );
         VerificationInfo info = new VerificationInfo( HL7MessageUtils.getEntitlementCode( message ), 
                                                       messageType,
                                                       HL7MessageUtils.getErrorText( message ) );

         info.setCombinedSCPercentage( HL7MessageUtils.getCombinedPercentage( message ) );
         info.setUnsolicitedType( HL7MessageUtils.getUnsolicitedType( message ) );
         
         // create workflow case object for potential usage by Rules
         WorkflowCaseInfo caseInfo = new WorkflowCaseInfo();
         caseInfo.setTransmissionSite(getLookupService().getVaFacilityByStationNumber(message.getSendingFacility()));
         caseInfo.setMessageID(message.getMessageID());
    
         info.setWorkflowCaseInfo( caseInfo );
         info.addException( exception );
         messagingService.processNonQueryZ11(person, info);
      }
      catch(InvalidMessageException e) {
         throw new ServiceException("Unable to create VerificationInfo object", e);
      }
	}

}