/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.common.rule.service.impl;

// Java classes
import java.util.Set;

// ESR class
import gov.va.med.esr.common.rule.service.RuleValidationService;

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

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

// Framework classes
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.validation.Validatable;
import gov.va.med.fw.validation.ValidationMessages;
import gov.va.med.fw.validation.ValidationServiceException;

// EDB classes
import gov.va.med.esr.common.model.ee.MedicaidFactor;
import gov.va.med.esr.common.model.insurance.InsurancePolicy;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.rule.service.InsuranceRuleService;
import gov.va.med.esr.common.rule.data.InsuranceInputData;
import gov.va.med.esr.common.rule.data.PersonInputData;

/** Implements business rules applied to use cases for processing insurance information<br>
 * of a veteran.  Insurance information is either entered in the Add / Edit Insurance screen <br>
 * or is obtained from a HL7 Z07 message. <br><br>
 *
 * <b>Project: Common</b>
 *
 * @author DNS   LEV
 * @version 1.0
 */
public class InsuranceRuleServiceImpl extends AbstractRuleValidationServiceAwareImpl implements InsuranceRuleService {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 1089804822535342268L;

	private static final String POST_VOA_PROCESS = "POST_VOA_PROCESS";

	/**
	 * An instance of insuranceRuleParameters
	 */
	private String insuranceRuleParameters = null;

	/**
	 * An instance of processMedicaidFactorsRuleParameters
	 */
	private String processMedicaidFactorsRuleParameters = null;

	/**
	 * A default constructor
	 */
	public InsuranceRuleServiceImpl() {
		super();
	}

    /**
     * @see gov.va.med.esr.common.rule.service.InsuranceRuleService#manageMedicaidFactor(gov.va.med.esr.common.model.ee.MedicaidFactor, gov.va.med.esr.common.model.person.Person)
     */
    public void manageMedicaidFactor(MedicaidFactor incoming,Person onFile) throws ServiceException {
        Validate.notNull(onFile, "An onFile person must not be null ");

        if (incoming != null) {
            MedicaidFactor medicaidFactor = onFile.getMedicaidFactor();
            if (medicaidFactor == null) {
                medicaidFactor = new MedicaidFactor();
                onFile.setMedicaidFactor(medicaidFactor);
            }
            this.getMergeRuleService().mergeMedicaidFactor(incoming, medicaidFactor);
        } else {
            onFile.setMedicaidFactor(null);
        }
    }

	/**
	 * Processes a collection of insurance policies.  There are 3 types of insurance policies
	 * <br>in the ESR application: Medicaid, Medicare, and Private Insurance
	 *
	 * <br><br>Following are the business requirements implemented in this method:<br>
	 *
	 * <br><b> 1730 [UC34.3.7.1] Processing Requirements for Person Insurance Data </b><br>
	 * The following requirements are applied to incoming Person Insurance Data. <br>
	 *
	 * <br><b>4166[UC34.3.7.1.1]</b>
	 * <br>When Insurance Company Information meets the constraints to be accepted,
	 * <br>it will be uploaded and stored by site so that multiple site Insurance Company Information can be viewed.
	 *
	 * <br><br><b>4167[UC34.3.7.1.1.1] For a new person: </b>
	 * <br> Accept the Insurance Company Information.
	 *
	 * <br><br><b>4168[UC34.3.7.1.1.2] For an existing person: </b>
	 * <br>if the EDB does not have Insurance Company Information
	 * <br>then accept the HL7 Insurance Company Information.
	 *
	 * <br><br><b>4169[UC34.3.7.1.1.3] For an existing person: </b>
	 * <br>if EDB has Insurance Company Information
	 * <br>and the HL7 is from the same site and the Insurance Company Information is different
	 * <br>then the system will update the sites existing data in place.
	 *
	 * @param policies A collection of insurance policies
	 * @param onFile A person currently stored in the system
	 * @throws ServiceException In case of error in processing insurance data
	 * @see Use case model survey report 2005/07/12
	 * @see gov.va.med.esr.common.rule.service.InsuranceRuleService#manageInsurancePolicies(java.util.Set,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public ValidationMessages manageInsurancePolicies(Set policies, Person onFile, VAFacility sendingFacility) throws ServiceException {
	    return this.doProcessInsurancePolicies(policies,onFile,sendingFacility,UI);
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.InsuranceRuleService#processInsurancePolicies(java.util.Set, gov.va.med.esr.common.model.person.Person)
	 */
	public void processInsurancePolicies(Set policies, Person onFile,VAFacility sendingFacility) throws ServiceException {
	    this.doProcessInsurancePolicies( policies, onFile,sendingFacility, null);
	}

	public void postProcessVOASubmissions(Set policies, Person onFile,VAFacility sendingFacility) throws ServiceException {
	    this.doProcessInsurancePolicies( policies, onFile,sendingFacility,POST_VOA_PROCESS );
	}
	/**
	 * @see gov.va.med.esr.common.rule.service.InsuranceRuleService#processMedicaidFactor(gov.va.med.esr.common.model.ee.MedicaidFactor,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public void processMedicaidFactor(MedicaidFactor incoming,Person onFile) throws ServiceException {
        Validate.notNull(onFile, "An onFile person must not be null ");
        if (incoming != null) {
        	Person pristine = this.getPristinePerson(onFile);
        	invokeRuleFlow( this.getRuleParameters( this.getProcessMedicaidFactorsRuleParameters() ),
        			new PersonInputData(incoming.getPerson(),onFile,pristine,false) );
        }
	}

	private ValidationMessages doProcessInsurancePolicy(InsurancePolicy incoming,Person onFile,String caller, String validationMode, VAFacility sendingFacility) throws ServiceException {

		Validate.notNull( onFile, "A veteran on file must not be null" );
		Validate.notNull( incoming, "An insurance policy must not be null" );

		Person pristine = this.getPristinePerson(onFile);

		// Validate an incoming address first
		RuleValidationService ruleService = this.getRuleValidationService();
		ValidationMessages messages = ruleService.validateInsurancePolicy(incoming,onFile,pristine,UI.equals(caller));

		if( messages != null && !messages.isEmpty() ) {
		    throw new ValidationServiceException( messages );
		}

		//Invoke process insurance rule flow. If there are any validation messages,
		//return mesages to the caller. Note: post VOA process need to trigger Z04, so the isUpdatedFromUI flag need to be true.
		messages = invokeRuleFlow( this.getRuleParameters( getInsuranceRuleParameters() ),
                      new InsuranceInputData(incoming,onFile,pristine,UI.equals(caller) || this.POST_VOA_PROCESS.equals(caller), sendingFacility), validationMode);

		return messages;
	}

	private ValidationMessages doProcessInsurancePolicy(InsurancePolicy incoming,Person onFile,String caller, VAFacility sendingFacility) throws ServiceException {
	    return doProcessInsurancePolicy(incoming, onFile, caller, Validatable.ELIMINATION, sendingFacility);
	}

	private ValidationMessages doProcessInsurancePolicies(Set incoming, Person onFile, VAFacility sendingFacility, String caller) throws ServiceException {

	    Validate.notNull( onFile, "A veteran on file must not be null" );

	    ValidationMessages messages = null;

        Set onFileInsurances = new HashSet(onFile.getInsurances());
        Set reportSites = new HashSet();
        if(UI.equals(caller)) {
            // When called from UI use the HEC facility
            reportSites.add(sendingFacility);

            //Get the map of EntityKey/Indurance
            Map incomingMap = this.getInsuranceMap(incoming);

            // Remove all the insurances that are removed by the UI user
            for(Iterator iter=onFileInsurances.iterator(); iter.hasNext();) {
                InsurancePolicy onFileInsPolicy = (InsurancePolicy)iter.next();
                if(reportSites.contains(onFileInsPolicy.getReportSite()) &&
                   incomingMap.get(onFileInsPolicy.getEntityKey()) == null) {
                    onFile.removeInsurance(onFileInsPolicy);
                }
            }

        } else {
            reportSites = this.getReportSites(incoming);
            // If there are no reporting sites use the sending site as the reporting site. This is needed to delete the isnurances
            // when there are no incoming insurances in the message
            if(reportSites.isEmpty()) reportSites.add(sendingFacility);

            // Remove all the insurances matching the reports site from the onFile person
            for(Iterator iter=onFileInsurances.iterator(); iter.hasNext();) {
                InsurancePolicy onFileInsPolicy = (InsurancePolicy)iter.next();
                if(reportSites.contains(onFileInsPolicy.getReportSite()) &&
                   (incoming == null || this.findMatchingElement(onFileInsPolicy,incoming) == null)) {
                    onFile.removeInsurance(onFileInsPolicy);
                }
            }
        }

        // Process all the incoming insurances
        if( incoming != null && !incoming.isEmpty() ) {
            //Get the map of EntityKey/Indurance for on file insurances
            Map onFileMap = this.getInsuranceMap(onFile.getInsurances());
            for(Iterator iter=incoming.iterator(); iter.hasNext();) {
                InsurancePolicy policy = (InsurancePolicy)iter.next();
                if(UI.equals(caller)) {
                    String reportSite = (policy.getReportSite() != null ) ? policy.getReportSite().getStationNumber() : null;
                    // Call insurance rule flow only for HEC policies
                    if(StringUtils.equals(reportSite,VAFacility.CODE_HEC.getName()) ||VAFacility.CODE_MHV.getCode().equals(sendingFacility.getCode())) {
                        InsurancePolicy onFilePolicy = (policy != null && policy.getEntityKey() != null) ? (InsurancePolicy)onFileMap.get(policy.getEntityKey()) : null;
                        //Call rule flow only when incoming policy is new or updated
                        if(onFilePolicy == null || !onFilePolicy.equals(policy)) {
                            messages = doProcessInsurancePolicy(policy,onFile,caller, Validatable.EXHAUSTIVE, sendingFacility);
                        }
                    }
                } else {
                    // Call insurance rule flow for each incoming policies
                    messages = doProcessInsurancePolicy(policy,onFile,caller, sendingFacility);
                }
			}
		}

        return messages;
	}

    private Set getReportSites(Set set) {
        Set reportSites = new HashSet();
        if(set != null) {
            for(Iterator iter=set.iterator(); iter.hasNext();) {
                InsurancePolicy policy = (InsurancePolicy)iter.next();
                reportSites.add(policy.getReportSite());
            }
        }
        return reportSites;
    }

    /**
     * Returns an insurance rule flow's input parameters
     * @return insuranceRuleParameters A parameter class to an insurance rule flow
     */
    public String getInsuranceRuleParameters() {
        return this.insuranceRuleParameters;
    }

    /**
     * Sets an insurance rule flow's input parameters
     * @param insuranceRuleParameters A collection of parameters to set
     */
    public void setInsuranceRuleParameters( String insuranceRuleParameters ) {
        this.insuranceRuleParameters = insuranceRuleParameters;
    }

    /**
     * @return Returns the processMedicaidFactorsRuleParameters.
     */
    public String getProcessMedicaidFactorsRuleParameters() {
        return processMedicaidFactorsRuleParameters;
    }
    /**
     * @param processMedicaidFactorsRuleParameters The processMedicaidFactorsRuleParameters to set.
     */
    public void setProcessMedicaidFactorsRuleParameters(String processMedicaidFactorsRuleParameters) {
        this.processMedicaidFactorsRuleParameters = processMedicaidFactorsRuleParameters;
    }

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

    private Map getInsuranceMap(Set insurance) {
        Map map = new HashMap();
        if(insurance == null || insurance.isEmpty()) return map;
        for(Iterator iter=insurance.iterator(); iter.hasNext();) {
            InsurancePolicy insPolicy = (InsurancePolicy)iter.next();
            if(insPolicy.getEntityKey() != null) {
                map.put(insPolicy.getEntityKey(),insPolicy);
            }
        }
        return map;
    }
}