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

// Library classes
import org.apache.commons.lang.Validate;

// Framework classes
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.validation.ValidationMessage;
import gov.va.med.fw.validation.ValidationMessages;
import gov.va.med.fw.validation.ValidationServiceException;

// ESR classes
import gov.va.med.esr.common.model.ee.IncomingMessageInfo;
import gov.va.med.esr.common.model.ee.VerificationInfo;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.person.Employment;
import gov.va.med.esr.common.model.financials.IncomeTest;
import gov.va.med.esr.common.rule.data.EventInputData;
import gov.va.med.esr.common.rule.service.EventRuleService;
import gov.va.med.esr.service.PersonHelperService;
import gov.va.med.esr.UseCaseName;
import gov.va.med.fw.util.StringUtils;

/**
 * A class that processes/manages events from messages or UI
 * 
 * Project: Common
 * @author DNS   RUIZC
 * @version 1.0
 */
public class EventRuleServiceImpl extends
        AbstractRuleValidationServiceAwareImpl implements EventRuleService {

    private static final long serialVersionUID = -6645463158040971084L;
    private static final String QRYZ11 = "QRYZ11";    
    private static final String QRYZ10 = "QRYZ10";    
    public static final String PERSON_NOT_FOUND = "PERSON_NOT_FOUND";    
    public static final String NO_DATA_ON_FILE = "NO_DATA_ON_FILE";
    private PersonHelperService personHelperService = null;
    
    /**
     * An instance of a event rule parameters
     */
    private String triggerRuleParameters = null;    

    private String personEventRuleParameters = null;   
    
    private String handBookEventRuleParameters = null;   

    public EventRuleServiceImpl() {
        super();
    }    
    
    public void processMessageEvents(Person onFile)
            throws ServiceException {
        this.doTriggerMessage(onFile, false, false);
    }

    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#processMessageEvents(gov.va.med.esr.common.model.person.Person, gov.va.med.esr.common.model.ee.VerificationInfo)
     */
    public void processMessageEvents(Person person, VAFacility sendingFacility, VerificationInfo verificationInfo,  boolean isZ10Sent) throws ServiceException {
        this.doTriggerMessage(person, sendingFacility, verificationInfo,  isZ10Sent); //added sendingFacility is isMessageFromVoa can work correctly
    }
    
    
    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#processMessageEvents(gov.va.med.esr.common.model.person.Person, gov.va.med.esr.common.model.ee.VerificationInfo)
     */
    public void processMessageEvents(Person person, VerificationInfo verificationInfo,  boolean isZ10Sent,  boolean fromAddNewPerson) throws ServiceException {
        this.doTriggerMessage(person, verificationInfo,  isZ10Sent, fromAddNewPerson);
    }
    
    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#processMessageEvents(gov.va.med.esr.common.model.person.Person, gov.va.med.esr.common.model.ee.VerificationInfo)
     */
    public void processMessageEvents(Person person, VerificationInfo verificationInfo) throws ServiceException {
        this.doTriggerMessage(person, null, verificationInfo,  false);
    }

    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#processMessageEvents(gov.va.med.esr.common.model.person.Person, java.lang.Integer, gov.va.med.esr.common.model.ee.IncomingMessageInfo, gov.va.med.esr.common.model.ee.VerificationInfo)
     */
    public void processMessageEvents(Person onFile, Integer incomeYear, IncomingMessageInfo incomingMessageInfo, VerificationInfo verificationInfo) throws ServiceException {
        this.doTriggerMessage(onFile, incomeYear, incomingMessageInfo, verificationInfo);
    }

    public void processMessageEventsForMsds(Person onFile) throws ServiceException {
      Validate.notNull(onFile, "An on file person must not be null ");
        
        Person pristine = this.getPristinePerson(onFile);
        EventInputData data = new EventInputData(onFile, pristine);
        data.setDataFromGUI(true); // don't need message behavior        
        data.setUseCaseName(UseCaseName.MSDSVBAQRY); // add some special behavior for Msds
        this.invokeRuleFlow(this.getRuleParameters(getTriggerRuleParameters()), data);
	}
    
    public void postProcessVOASubmissions(Person onFile) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");
          
          Person pristine = this.getPristinePerson(onFile);
          EventInputData data = new EventInputData(onFile, pristine);
          data.setUseCaseName(UseCaseName.POST_PROCESS_VOA); // add some special behavior for VOA
          this.invokeRuleFlow(this.getRuleParameters(getTriggerRuleParameters()), data);
  	}

	/**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#manageMessageEvents(gov.va.med.esr.common.model.person.Person)
     */
    public void manageMessageEvents(Person onFile) throws ServiceException {
        this.doTriggerMessage(onFile, true, false);
    }
    
    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#managePersonEvents(gov.va.med.esr.common.model.person.Person)
     */
    public void managePersonEvents(Person onFile) throws ServiceException {
        this.doProcessPersonEvents(onFile, true, null);
    }
    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#manageHandBookEvents(gov.va.med.esr.common.model.person.Person)
     */
    public void manageHandBookEvents(Person onFile) throws ServiceException {
        this.doProcessHandBookEvents(onFile, true, null);
    }

    /**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#processPersonEvents(gov.va.med.esr.common.model.person.Person)
     */
    public void processPersonEvents(Person onFile) throws ServiceException {
        this.doProcessPersonEvents(onFile, false, null);
    }
    


    public void processPersonEventsForAppointmentConversion(Person onFile) throws ServiceException {
    	this.doProcessPersonEvents(onFile, false, UseCaseName.APPT); 
	}
    
    //trigger broker
    public void processMessageEvents(Person onFile, boolean isMSDSTrigger)
    throws ServiceException {
    	this.doTriggerMessage(onFile,isMSDSTrigger);
    }

	/**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#handleCommonPostEvents(gov.va.med.esr.common.model.person.Person, boolean)
     */
    public void handleCommonPostEvents(Person onFile, boolean isDataFromUI) throws ServiceException {
            this.doTriggerMessage(onFile, isDataFromUI, false);
            this.doProcessPersonEvents(onFile, isDataFromUI, null);
            this.doProcessHandBookEvents(onFile, isDataFromUI, null);            
    }
    
    public void processMessageEventsForIVM(Person onFile) throws ServiceException {
    	// Stop Z10 from being sent here. It is done in process IVM financials
        this.doTriggerMessage(onFile, true, true);
	}

	/**
     * @see gov.va.med.esr.common.rule.service.EventRuleService#handleCommonPostEvents(gov.va.med.esr.common.model.person.Person, boolean, boolean)
     */
    public void handleCommonPostEvents(Person onFile, boolean isDataFromUI, boolean isZ10Sent) throws ServiceException {
        this.doTriggerMessage(onFile, isDataFromUI, isZ10Sent);
        this.doProcessPersonEvents(onFile, isDataFromUI, null);  
        this.doProcessHandBookEvents(onFile, isDataFromUI, null);
    }

    public void addPersonChangeEventsForAdvice(Person person) throws ServiceException {
    	Validate.notNull(person, "the person must not be null ");

    	Person pristine = this.getPristinePerson(person);
    	/**
    	 * CCR 12062 
    	 * 1. Claim Folder Location
    	 * 2. Claim Folder Number
    	 */
    	String updatedNumber = person.getClaimFolderNumber();
    	VAFacility fac = person.getClaimFolderLocation();
    	String oldNumber = pristine.getClaimFolderNumber();
    	VAFacility oldFac = pristine.getClaimFolderLocation();
    	if ((updatedNumber != null && oldNumber == null) ||
    	    (updatedNumber == null && oldNumber != null) ||
    	    (updatedNumber != null && oldNumber != null &&
    	    !updatedNumber.equals(oldNumber))) {
    		person.setChangeEvent(UseCaseName.CLAIMNUM, "NUMBER");
    	}
    		
    	if ((fac != null && oldFac == null) ||
    			(fac == null && oldFac != null) ||
    			(fac != null && oldFac != null &&
    					!fac.equals(oldFac))) {
    		person.setChangeEvent(UseCaseName.CLAIMLOC, "LOCATION");
    	}
    	Employment updatedEmployment = person.getEmployment();
    	Employment oldEmployment = pristine.getEmployment();
    	
    	if ((updatedEmployment != null && oldEmployment == null) ||
        	    (updatedEmployment == null && oldEmployment != null))
    		person.setChangeEvent(UseCaseName.EMPLOYMENT, "EMPLOYMENT");
    	else if (updatedEmployment != null && oldEmployment != null) {
    		if (hasEmploymentChange(updatedEmployment, oldEmployment))
    			person.setChangeEvent(UseCaseName.EMPLOYMENT, "EMPLOYMENT");
    	}
    }
    private boolean hasEmploymentChange(Employment updatedEmployment, Employment oldEmployment) throws ServiceException {
		// UI puts "" into String fields which messes up the normal
		// compare of domain values. 
		// EMPTY flags
		boolean updNameFlag = StringUtils.isEmpty(updatedEmployment.getEmployerName());
		boolean updPhoneFlag = StringUtils.isEmpty(updatedEmployment.getEmployerPhone());
		boolean updOccupationFlag = StringUtils.isEmpty(updatedEmployment.getOccupation()); 
		boolean oldNameFlag = StringUtils.isEmpty(oldEmployment.getEmployerName());
		boolean oldPhoneFlag = StringUtils.isEmpty(oldEmployment.getEmployerPhone());
		boolean oldOccupationFlag = StringUtils.isEmpty(oldEmployment.getOccupation());
		
		if ((!updNameFlag && oldNameFlag) ||
				(updNameFlag && !oldNameFlag) || 
				(!updNameFlag && !oldNameFlag && 
						!updatedEmployment.getEmployerName().equals(oldEmployment.getEmployerName())))
			return true;

		if ((!updPhoneFlag && oldPhoneFlag) ||
				(updPhoneFlag && !oldPhoneFlag) ||
				(!updPhoneFlag && !oldPhoneFlag && 
						!updatedEmployment.getEmployerPhone().equals(oldEmployment.getEmployerPhone())))
			return true;

		if ((!updOccupationFlag && oldOccupationFlag) ||
				(updOccupationFlag && !oldOccupationFlag) ||
				(!updOccupationFlag && !oldOccupationFlag &&
						!updatedEmployment.getOccupation().equals(oldEmployment.getOccupation())))
			return true;    		

		if ((updatedEmployment.getEmploymentStatus() != null && oldEmployment.getEmploymentStatus() == null) ||
				(updatedEmployment.getEmploymentStatus() == null && oldEmployment.getEmploymentStatus() != null) ||
				(updatedEmployment.getEmploymentStatus() != null && oldEmployment.getEmploymentStatus() != null &&
    			!updatedEmployment.getEmploymentStatus().equals(oldEmployment.getEmploymentStatus())))
			return true;
		
		if ((updatedEmployment.getRetirementDate() != null && oldEmployment.getRetirementDate() == null) ||
				(updatedEmployment.getRetirementDate() == null && oldEmployment.getRetirementDate() != null) ||
				(updatedEmployment.getRetirementDate() != null && oldEmployment.getRetirementDate() != null &&
				!updatedEmployment.getRetirementDate().equals(oldEmployment.getRetirementDate())))
			return true;		
    	Address updated = updatedEmployment.getEmployerAddress();
    	Address old = oldEmployment.getEmployerAddress();
    	if ((updated != null && old == null) ||
    			(updated == null && old != null) ||
    			(updated != null && old != null && 
    					!updated.matchesDomainValues(old)))
    		return true;
    	
    	return false;
    }

	public void triggerCreateHecRecord(Person person) throws ServiceException {
      Validate.notNull(person, "the person must not be null ");
        
        Person pristine = this.getPristinePerson(person);
        
        EventInputData data = new EventInputData(person, pristine);
        data.setUseCaseName(UseCaseName.HEC_RECORD_CREATE);

        this.invokeRuleFlow(this.getRuleParameters(getTriggerRuleParameters()), data);
	}

	/**
     * @see gov.va.med.esr.common.rule.service.impl.AbstractRuleValidationServiceAwareImpl#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        Validate.notNull( this.triggerRuleParameters, "Trigger rule parameters must not be null");
        Validate.notNull( this.personEventRuleParameters, "Person event rule parameters must not be null");        
    }
    
    /**
     * @return Returns the triggerRuleParameters.
     */
    public String getTriggerRuleParameters() {
        return triggerRuleParameters;
    }
    
    /**
     * @return Returns the personEventRuleParameters.
     */
    public String getPersonEventRuleParameters() {
        return personEventRuleParameters;
    }
   
    /**
     * @param personEventRuleParameters The personEventRuleParameters to set.
     */
    public void setPersonEventRuleParameters(
          String personEventRuleParameters) {
        this.personEventRuleParameters = personEventRuleParameters;
    }
    /**
     * @return Returns the HandBookEventRuleParameters.
     */
    public String getHandBookEventRuleParameters() {
        return handBookEventRuleParameters;
    }
    /**
     * @param handBookEventRuleParameters The handBookEventRuleParameters to set.
     */
    public void setHandBookEventRuleParameters(
          String handBookEventRuleParameters) {
        this.handBookEventRuleParameters = handBookEventRuleParameters;
    }

    /**
     * @param triggerRuleParameters The triggerRuleParameters to set.
     */
    public void setTriggerRuleParameters(String triggerRuleParameters) {
        this.triggerRuleParameters = triggerRuleParameters;
    }
    
    //trigger broker
    private void doTriggerMessage(Person onFile, boolean isMSDSTrigger) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");
        
        Person pristine = this.getPristinePerson(onFile);
        
        EventInputData data = new EventInputData(onFile, pristine);
        data.setTriggerMSDSBroker(isMSDSTrigger);

        this.invokeRuleFlow(this.getRuleParameters(getTriggerRuleParameters()), data);
    }
    
    private void doTriggerMessage(Person onFile, boolean isDataFromGUI, boolean isZ10Sent) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");
        
        Person pristine = this.getPristinePerson(onFile);
        
        EventInputData data = new EventInputData(onFile, pristine);
        data.setDataFromGUI(isDataFromGUI);
        data.setZ10Sent(isZ10Sent);

        this.invokeRuleFlow(this.getRuleParameters(getTriggerRuleParameters()), data);
    }
    
    
    private void doTriggerMessage(Person onFile, Integer incomeYear,
            IncomingMessageInfo incomingMessageInfo,
            VerificationInfo verificationInfo) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");
        Validate.notNull(incomingMessageInfo, "The incoming message info must not be null ");        
        Validate.notNull(verificationInfo, "The incoming verification info must not be null ");        

        Person pristine = this.getPristinePerson(onFile);
        
        if (QRYZ11.equals(verificationInfo.getMessageType()) ||
                QRYZ10.equals(verificationInfo.getMessageType())) {
            if (onFile.getEnrollmentDetermination() == null) {
                ValidationMessages validationMessages = new ValidationMessages();
                validationMessages.add(new ValidationMessage(PERSON_NOT_FOUND));
                throw new ValidationServiceException(PERSON_NOT_FOUND, validationMessages);
            }            
        }

        if (QRYZ10.equals(verificationInfo.getMessageType())) {
            IncomeTest test = incomeYear != null ? onFile.getIncomeTest(incomeYear) : null;
            //Added for CR_7069 If the incomeYear is a FutureDatedTest then get the current income year
            if (incomeYear == null || test == null || (test.getFutureTest()!= null && test.getFutureTest().booleanValue())) {
                IncomeTest currentTest = this.getPersonHelperService().getCurrentIncomeTest(onFile);
                if (currentTest != null) {
                    incomeYear = currentTest.getIncomeYear();
                }
                else {
                    ValidationMessages validationMessages = new ValidationMessages();
                    validationMessages.add(new ValidationMessage(NO_DATA_ON_FILE));
                    throw new ValidationServiceException(NO_DATA_ON_FILE, validationMessages);
                }
            }
        }            
        
        EventInputData data = new EventInputData(onFile, pristine, incomeYear, incomingMessageInfo, verificationInfo);
        this.invokeRuleFlow(this.getRuleParameters( getTriggerRuleParameters() ), data);
    }
    
    private void doTriggerMessage(Person onFile, VAFacility sendingFacility, //added sendingFacility is isMessageFromVoa can work correctly
            VerificationInfo verificationInfo,  boolean isZ10Sent) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");

        Person pristine = this.getPristinePerson(onFile);

        EventInputData data = new EventInputData(onFile, sendingFacility, //added sendingFacility is isMessageFromVoa can work correctly
        		onFile, pristine, verificationInfo);

        data.setZ10Sent(isZ10Sent);
        
        this.invokeRuleFlow(this.getRuleParameters( getTriggerRuleParameters() ), data);
    }     
    
    private void doTriggerMessage(Person onFile,
            VerificationInfo verificationInfo,  boolean isZ10Sent, boolean fromAddNewPerson) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");

        Person pristine = this.getPristinePerson(onFile);

        EventInputData data = new EventInputData(onFile, null, onFile, pristine,
                verificationInfo);

        data.setZ10Sent(isZ10Sent);
        data.setFromAddNewPerson(fromAddNewPerson);
        
        this.invokeRuleFlow(this.getRuleParameters( getTriggerRuleParameters() ), data);
    }
    private void doProcessPersonEvents(Person onFile, boolean isDataFromGUI, String useCaseName) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");
        
        Person pristine = this.getPristinePerson(onFile);
        
        EventInputData data = new EventInputData(onFile, pristine);
        data.setDataFromGUI(isDataFromGUI);
        data.setUseCaseName(useCaseName);

        this.invokeRuleFlow(this.getRuleParameters( getPersonEventRuleParameters() ), data);
    }

    private void doProcessHandBookEvents(Person onFile, boolean isDataFromGUI, String useCaseName) throws ServiceException {
        Validate.notNull(onFile, "An on file person must not be null ");
        
        Person pristine = this.getPristinePerson(onFile);
        
        EventInputData data = new EventInputData(onFile, pristine);
        data.setDataFromGUI(isDataFromGUI);
        data.setUseCaseName(useCaseName);

        this.invokeRuleFlow(this.getRuleParameters( getHandBookEventRuleParameters() ), data);
    }
    /**
     * @return Returns the personHelperService.
     */
    public PersonHelperService getPersonHelperService() {
        return personHelperService;
    }

    /**
     * @param personHelperService The personHelperService to set.
     */
    public void setPersonHelperService(PersonHelperService personHelperService) {
        this.personHelperService = personHelperService;
    }  
    
    public void processPersonEventsForPensionBulletin(Person onFile) throws ServiceException {
    	this.doProcessPersonEvents(onFile, false, UseCaseName.PENSIONBUL); 
	}
    
    
}