package gov.va.med.esr.common.rule.service.impl;

// Java classes
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.Validate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;

import gov.va.med.esr.UseCaseName;
import gov.va.med.esr.common.clock.Clock.Type;
import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.comms.CommsLogEntry;
import gov.va.med.esr.common.model.comms.HandBookMailQueue;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.party.Address;
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.persistent.comms.CommsLogEntryDAO;
import gov.va.med.esr.common.persistent.comms.HandBookMailQueueDAO;
import gov.va.med.esr.common.rule.data.CommsInputData;
import gov.va.med.esr.common.rule.parameter.CommunicationsInputParameter;
import gov.va.med.esr.common.rule.service.CommunicationRuleService;
import gov.va.med.esr.service.DemographicService;
import gov.va.med.esr.service.HandBookService;
import gov.va.med.esr.service.IVMFinancialInfo;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PersonLockedException;
import gov.va.med.esr.service.trigger.LetterTrigger;
import gov.va.med.esr.service.trigger.LetterTriggerEvent;
import gov.va.med.esr.service.trigger.LetterTriggerIdentity;
import gov.va.med.esr.service.trigger.LetterTriggerEvent.MailType;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.rule.RuleParameters;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.validation.ValidationMessages;

/**
 * Project: Common
 * 
 * @author DNS   RUIZC
 * @version 1.0
 */
public class CommunicationRuleServiceImpl extends AbstractRuleValidationServiceAwareImpl
      implements CommunicationRuleService {

   /**
    * 
    */
   private static final long serialVersionUID = 112127217356394622L;

   private String handleUndeliverableMailRuleParameters = null;

   private String manageCommunicationsLogRuleParameters = null;

   private String triggerAutomaticLettersRuleParameters = null;

   private String processMailResponsesRuleParameters = null;

   private CommsLogEntryDAO commsLogEntryDAO = null;

   private HandBookMailQueueDAO handBookMailQueueDAO = null;

   private String demographicServiceName = null;
   
   private String handBookServiceName = null;
   
   private BeanFactory beanFactory;

   private LookupService lookupService;
   
   public CommunicationRuleServiceImpl() {
      super();
   }

   /**
	 * @return Returns the lookupService.
	 */
	public LookupService getLookupService() {
		return lookupService;
	}

	/**
	 * @param lookupService The lookupService to set.
	 */
	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}
	
   /**
    * Process a list of comms log entries.
    */
   public void handleUndeliverableMail(List commsLogEntries) throws ServiceException {
      if( commsLogEntries != null && commsLogEntries.size() > 0 ) {
         for( Iterator iter = commsLogEntries.iterator(); iter.hasNext(); ) {
            CommsLogEntry incoming = (CommsLogEntry)iter.next();
            CommsLogEntry onFile = null;
            try {
               onFile = (CommsLogEntry)this.getCommsLogEntryDAO().getByKey(incoming.getEntityKey());
            }
            catch( DAOException e ) {
               throw new ServiceException("Failed to find comms log", e);
            }
            this.doProcessCommsRules( this.getRuleParameters( this.getHandleUndeliverableMailRuleParameters() ),
                                      incoming, 
                                      onFile );
         }
      }
   }

   /**
    * Process a list of comms log entries.
    */
   public void handleUndeliverableHandbook(HandBookMailQueue incoming) throws ServiceException {
      if( incoming != null) {
    	  HandBookMailQueue onFile = null;
            try {
               onFile = (HandBookMailQueue)this.getHandBookMailQueueDAO().getByKey(incoming.getEntityKey());
            }
            catch( DAOException e ) {
               throw new ServiceException("Failed to find HandBookMailQueue", e);
            }
            this.doProcessCommsRules( this.getRuleParameters( this.getHandleUndeliverableMailRuleParameters() ),
                                      incoming, 
                                      onFile );
      }
   }
 
   /**
    * @see gov.va.med.esr.common.rule.service.CommunicationRuleService#processLetterSendRequest(gov.va.med.esr.common.model.person.Person, gov.va.med.esr.common.model.lookup.ComLetterTemplateType, java.lang.String, gov.va.med.esr.service.trigger.LetterTriggerEvent.MailType, java.lang.Boolean, java.util.List, java.lang.String)
    */
   public ValidationMessages processLetterSendRequest(Person person, ComLetterTemplateType formNumber, String workflowCaseId, MailType mailType, Boolean override, List logs, String categoryType) throws ServiceException {
       Validate.notNull(person, "The person must not be null");
       Validate.notNull(formNumber, "The formNumber must not be null");
       Validate.notNull(mailType, "The mailType must not be null");
       return this.getRuleValidationService().validateLetterSendRequest(person,
             formNumber, workflowCaseId, mailType, override, logs, categoryType, null);
   }

    /**
    * @see gov.va.med.esr.common.rule.service.CommunicationRuleService#manageCommunicationsLog(gov.va.med.esr.common.model.comms.CommsLogEntry,
    *      gov.va.med.esr.common.model.comms.CommsLogEntry)
    */
   public void manageCommunicationsLog(CommsLogEntry incoming, CommsLogEntry onFile)
         throws ServiceException {
      this.doProcessCommsRules(this.getRuleParameters(this.getManageCommunicationsLogRuleParameters()), incoming, onFile, false);
   }

 
   /**
    * @see gov.va.med.esr.common.rule.service.CommunicationRuleService#triggerAutomaticLetters(gov.va.med.esr.common.model.person.Person, java.lang.Integer, boolean)
    */
   public void triggerAutomaticLetters(Person person, Integer incomeYear, boolean reviewOnly, Type clockType) throws ServiceException {
       Validate.notNull(person, "person must not be null");
       Person pristine = this.getPristinePerson(person);
       
       CommsInputData data = new CommsInputData(person, pristine);
       data.setReviewOnly(reviewOnly);
       data.setIncomeYearForCurrentTest(incomeYear);
       data.setFiredClockType(clockType);       
       
       this.invokeRuleFlow( this.getRuleParameters( this.getTriggerAutomaticLettersRuleParameters() ), data);
   }
    
   /**
    * @see gov.va.med.esr.common.rule.service.CommunicationRuleService#triggerAutomaticLetters(gov.va.med.esr.common.model.person.Person, gov.va.med.esr.common.clock.Clock.Type, java.lang.String)
    */
   public void triggerAutomaticLetters(Person person, Type clockType, String subject) throws ServiceException {
       Validate.notNull(person, "person must not be null");
       Person pristine = this.getPristinePerson(person);
       
       CommsInputData data = new CommsInputData(person, pristine);
       data.setReviewOnly(false);
       data.setFiredClockType(clockType);
       data.setSubject(subject);
       
       this.invokeRuleFlow( this.getRuleParameters( this.getTriggerAutomaticLettersRuleParameters() ), data);
   }
   
   /*
    * IVM specific letters
    */
   public void triggerAutomaticLetters(Person person, IVMFinancialInfo info) throws ServiceException {
	   this.doTriggerAutomaticLetters(person, null, false, null, null, info, UseCaseName.IVM_FINAL_LETTERS);
   }
   
   /*
    * Suggest that developers use this method to invoke trigger auto letter flow
    */
   private void doTriggerAutomaticLetters(Person person, Integer incomeYear, boolean reviewOnly, 
		   Type clockType, String subject, IVMFinancialInfo info, String useCaseName)  throws ServiceException {
       Validate.notNull(person, "person must not be null");
       Person pristine = this.getPristinePerson(person);
       
       CommsInputData data = new CommsInputData(person, pristine);
       data.setReviewOnly(reviewOnly);
       data.setIncomeYearForCurrentTest(incomeYear);
       data.setFiredClockType(clockType);       
       data.setSubject(subject);
       data.setIvmFinancialInfo(info);
       data.setUseCaseName(useCaseName);
       
       this.invokeRuleFlow( this.getRuleParameters( this.getTriggerAutomaticLettersRuleParameters() ), data);	   
   }

   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
       this.beanFactory = beanFactory;
   }

   /**
    * @see gov.va.med.esr.common.rule.service.CommunicationRuleService#handleMailingResponses(java.util.List)
    */
   public void handleMailingResponses(List responses) {
	      if( responses != null && responses.size() > 0 ) {
	         for( Iterator iter = responses.iterator(); iter.hasNext(); ) {
	            CommsLogEntry incoming = (CommsLogEntry)iter.next();
	            CommsLogEntry onFile = null;
	            try {
	               onFile = (CommsLogEntry)this.getCommsLogEntryDAO().getByKey(incoming.getEntityKey());
	               this.doProcessCommsRules( this.getRuleParameters( this.getProcessMailResponsesRuleParameters() ),
	                                         incoming, 
	                                         onFile );
	            }
	            catch( DAOException e ) {
	               logger.error(
	                     "Failed to find comms log: " + incoming.getCommsLogIdString(), e);
	            }
	            catch( PersonLockedException ex ) {
	            	// no need to log entire stack trace as this could be considered normal scenario...
	                logger.error("Failed to handleMailingResponses for CommsLogEntry id="
	                      + incoming.getCommsLogIdString() + " because received a PersonLockedException");
	             }            
	            catch( ServiceException ex ) {
	               logger.error("Failed to handleMailingResponses for CommsLogEntry id="
	                     + incoming.getCommsLogIdString(), ex);
	            }
	         }
	      }
	   }
   
   // CCR9192- transaction timeout in aacimportprocess
   public void handleMailingResponses(CommsLogEntry incoming) {
      if( incoming != null ){
            CommsLogEntry onFile = null;
            try {
               onFile = (CommsLogEntry)this.getCommsLogEntryDAO().getByKey(incoming.getEntityKey());
               this.doProcessCommsRules( this.getRuleParameters( this.getProcessMailResponsesRuleParameters() ),
                                         incoming, onFile );
            }
            catch( DAOException e ) {
               logger.error(
                     "Failed to find comms log: " + incoming.getCommsLogIdString(), e);
            }
            catch( PersonLockedException ex ) {
            	// no need to log entire stack trace as this could be considered normal scenario...
                logger.error("Failed to handleMailingResponses for CommsLogEntry id="
                      + incoming.getCommsLogIdString() + " because received a PersonLockedException");
             }            
            catch( ServiceException ex ) {
               logger.error("Failed to handleMailingResponses for CommsLogEntry id="
                     + incoming.getCommsLogIdString(), ex);
            }
         }
   }

   
   public void  handleHandbookMailingResponse(HandBookMailQueue incoming) throws ServiceException {
	     if( incoming != null ){
	    	 HandBookMailQueue onFile = null;
	            try {
	               onFile = (HandBookMailQueue)this.getHandBookMailQueueDAO().getByKey(incoming.getEntityKey());
	               this.doProcessCommsRules( this.getRuleParameters( this.getProcessMailResponsesRuleParameters() ),
	                                         incoming, onFile );
	            }
	            catch( DAOException e ) {
	               logger.error(
	                     "Failed to find handbookMailQueue: " + incoming.getId(), e);
	            }
	            catch( PersonLockedException ex ) {
	            	// no need to log entire stack trace as this could be considered normal scenario...
	                logger.error("Failed to handleHandbookMailingResponse for HandBookMailQueue id="
	                      + incoming.getId() + " because received a PersonLockedException");
	             }            
	            catch( ServiceException ex ) {
	               logger.error("Failed to handleHandbookMailingResponse for HandBookMailQueue id="
	                     + incoming.getId(), ex);
	            }
	         }
	   
   }
   

   /**
    * @see gov.va.med.esr.common.rule.service.impl.AbstractRuleValidationServiceAwareImpl#afterPropertiesSet()
    */
   public void afterPropertiesSet() throws Exception {
      super.afterPropertiesSet();
      Validate.notNull(this.handleUndeliverableMailRuleParameters,
            "handleUndeliverableMailRuleParameters property is required");
      Validate.notNull(this.manageCommunicationsLogRuleParameters,
            "manageCommunicationsLogRuleParameters property is required");
      Validate.notNull(this.triggerAutomaticLettersRuleParameters,
            "triggerAutomaticLettersRuleParameters property is required");
      Validate.notNull(this.processMailResponsesRuleParameters,
            "processMailResponsesRuleParameters property is required");
   }

   /**
    * @return Returns the handleUndeliverableMailRuleParameters.
    */
   public String getHandleUndeliverableMailRuleParameters() {
      return handleUndeliverableMailRuleParameters;
   }

   /**
    * @param handleUndeliverableMailRuleParameters
    *           The handleUndeliverableMailRuleParameters to set.
    */
   public void setHandleUndeliverableMailRuleParameters(
         String handleUndeliverableMailRuleParameters) {
      this.handleUndeliverableMailRuleParameters = handleUndeliverableMailRuleParameters;
   }

   /**
    * @return Returns the manageCommunicationsLogRuleParameters.
    */
   public String getManageCommunicationsLogRuleParameters() {
      return manageCommunicationsLogRuleParameters;
   }

   /**
    * @param manageCommunicationsLogRuleParameters
    *           The manageCommunicationsLogRuleParameters to set.
    */
   public void setManageCommunicationsLogRuleParameters(
         String manageCommunicationsLogRuleParameters) {
      this.manageCommunicationsLogRuleParameters = manageCommunicationsLogRuleParameters;
   }

   /**
    * @return Returns the triggerAutomaticLettersRuleParameters.
    */
   public String getTriggerAutomaticLettersRuleParameters() {
      return triggerAutomaticLettersRuleParameters;
   }

   /**
    * @param triggerAutomaticLettersRuleParameters
    *           The triggerAutomaticLettersRuleParameters to set.
    */
   public void setTriggerAutomaticLettersRuleParameters(
         String triggerAutomaticLettersRuleParameters) {
      this.triggerAutomaticLettersRuleParameters = triggerAutomaticLettersRuleParameters;
   }

   /**
    * @return Returns the processMailResponsesRuleParameters.
    */
   public String getProcessMailResponsesRuleParameters() {
      return processMailResponsesRuleParameters;
   }

   /**
    * @param processMailResponsesRuleParameters
    *           The processMailResponsesRuleParameters to set.
    */
   public void setProcessMailResponsesRuleParameters(
         String processMailResponsesRuleParameters) {
      this.processMailResponsesRuleParameters = processMailResponsesRuleParameters;
   }

   private void doProcessCommsRules(RuleParameters parameters, CommsLogEntry incoming,
         CommsLogEntry onFile, boolean overrideRequest) throws ServiceException {
      CommsInputData data = this.createCommsInputData(incoming, onFile);
      data.setOverrideRequest(overrideRequest);

      CommsLogEntry pristine = null;
      if( incoming.getEntityKey() != null ) {
         try {
            pristine = (CommsLogEntry)this.getCommsLogEntryDAO().getByKey(incoming.getEntityKey());
         }
         catch( DAOException e ) {
            throw new ServiceException("Failed to find comms log", e);
         }
      }
      else {
         pristine = (CommsLogEntry)incoming.clone();
      }

      data.setPristineCommsLogEntry(pristine);

      this.invokeRuleFlow(parameters, data);
   }

   private void doProcessCommsRules(RuleParameters parameters, CommsLogEntry incoming,
         CommsLogEntry onFile) throws ServiceException {
      CommsInputData data = this.createCommsInputData(incoming, onFile);

      this.invokeRuleFlow(parameters, data);

      try {
         this.getCommsLogEntryDAO().update(onFile);
      }
      catch( DAOException e ) {
         throw new ServiceException("Failed to update comms log", e);
      }

      if( data.isAddressChanged() ) {
         this.getPersonService().save((Person)data.getResultData());
      }
   }

   private CommsInputData createCommsInputData(CommsLogEntry incoming,
         CommsLogEntry onFile) throws ServiceException {
      Validate.notNull(incoming, "The incoming log entry must not be null");
      Validate.notNull(onFile, "The onFile log entry must not be null");
      Validate.notNull(incoming.getPersonId(), "The person id must not be null");

      // Get the data needed for rules
      Person person = this.getPersonService().getPerson(
            CommonEntityKeyFactory.createPersonIdEntityKey(incoming.getPersonId()
                  .toString()));
      // Get the specific mailing address in question

      Address updatedMailingAddress = ( (DemographicService)this.getComponent(this
            .getDemographicServiceName()) ).getUpdatedLetterAddress(incoming);
      Address mailingAddress = ( (DemographicService)this.getComponent(this
            .getDemographicServiceName()) ).getLetterAddress(person);

      Validate.notNull(person, "The person must not be null");

      // Create the input data and provide the entry
      return new CommsInputData(person, incoming, onFile, mailingAddress,
            updatedMailingAddress);
   }


   /*
    * Overloaded method to handle Undeliverable Handbook 
    */
   private void doProcessCommsRules(RuleParameters parameters, HandBookMailQueue incoming,
		   HandBookMailQueue onFile) throws ServiceException {
	      CommsInputData data = this.createCommsInputData(incoming, onFile);

	      this.invokeRuleFlow(parameters, data);

	      try {
	         this.getHandBookMailQueueDAO().update(onFile);
	      }
	      catch( DAOException e ) {
	         throw new ServiceException("Failed to update comms log", e);
	      }

	      if( data.isAddressChanged() ) {
	         this.getPersonService().save((Person)data.getResultData());
	      }
	   }

   /*
    * Overloaded method to handle Undeliverable Handbook 
    */
	   private CommsInputData createCommsInputData(HandBookMailQueue incoming,
			   HandBookMailQueue onFile) throws ServiceException {
	      Validate.notNull(incoming, "The incoming handbookMailQueue must not be null");
	      Validate.notNull(onFile, "The onFile handbookMailQueue must not be null");
	      Validate.notNull(incoming.getPersonId(), "The person id must not be null");

	      // Get the data needed for rules
	      Person person = this.getPersonService().getPerson(
	            CommonEntityKeyFactory.createPersonIdEntityKey(incoming.getPersonId()
	                  .toString()));
	      // Get the specific mailing address in question

	      // handbook mailing address is retrieved from the address history table
	      Address handbookAddress = ((HandBookService)this.getComponent(this.getHandBookServiceName())).getHandBookMailingAddress(incoming);
	      incoming.setAddress(handbookAddress);
	      
	      Address updatedMailingAddress = ( (DemographicService)this.getComponent(this
	            .getDemographicServiceName()) ).getUpdatedLetterAddress(incoming);
	      Address mailingAddress = ( (DemographicService)this.getComponent(this
	            .getDemographicServiceName()) ).getLetterAddress(person);

	      Validate.notNull(person, "The person must not be null");

	      // Create the input data and provide the entry
	      return new CommsInputData(person, incoming, onFile, mailingAddress,
	            updatedMailingAddress);
	   }

	   /**
    * @return Returns the commsLogEntryDAO.
    */
   public CommsLogEntryDAO getCommsLogEntryDAO() {
      return commsLogEntryDAO;
   }

   /**
    * @param commsLogEntryDAO
    *           The commsLogEntryDAO to set.
    */
   public void setCommsLogEntryDAO(CommsLogEntryDAO commsLogEntryDAO) {
      this.commsLogEntryDAO = commsLogEntryDAO;
   }

   
   public HandBookMailQueueDAO getHandBookMailQueueDAO() {
	return handBookMailQueueDAO;
   }

   public void setHandBookMailQueueDAO(HandBookMailQueueDAO handBookMailQueueDAO) {
		this.handBookMailQueueDAO = handBookMailQueueDAO;
   }

	/**
    * @return Returns the demographicServiceName.
    */
   public String getDemographicServiceName() {
      return demographicServiceName;
   }

   /**
    * @param demographicServiceName
    *           The demographicServiceName to set.
    */
   public void setDemographicServiceName(String demographicServiceName) {
      this.demographicServiceName = demographicServiceName;
   }
	/**
    * @return Returns the handBookServiceName.
    */
   public String getHandBookServiceName() {
      return handBookServiceName;
   }

   /**
    * @param demographicServiceName
    *           The handBookServiceName to set.
    */
   public void setHandBookServiceName(String handBookServiceName) {
      this.handBookServiceName = handBookServiceName;
   }

}
