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

package gov.va.med.esr.common.rule.parameter;

// Java classes
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.exception.ExceptionUtils;

import gov.va.med.fw.rule.RuleException;
import gov.va.med.fw.service.ServiceException;

import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.model.ee.IncompetenceRuling;
import gov.va.med.esr.common.model.ee.RatedDisability;
import gov.va.med.esr.common.model.ee.ServiceConnectionAward;
import gov.va.med.esr.common.model.lookup.Disability;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.rule.ServiceConnectionInput;
import gov.va.med.esr.service.UnknownLookupCodeException;

/**
 * This class is a facade that provides access to service connection data.
 * Parameter objects are loaded into ILOG working memory for use by rules.
 * 
 * @author Carlos Ruiz
 * @version 1.0
 */
public class ServiceConnectionInputParameter extends BaseParameter implements
        ServiceConnectionInput {

    private static final long serialVersionUID = -8383695576970070293L;

    private Integer serviceConnectedPercentage = null;

    private Date combinedSCEffectiveDate = null;

    private Boolean unemploymentIdicator = null;

    private Boolean areAACRatedDisabilitiesLessThan = null;

    private Integer combinedSCPercentageWithNoRDs = null;

    private Integer scPercentageWithNoRDs = null;

    private Boolean permanentAndTotalIndicator = null;

    private Boolean ratedIncompetentIndicator = null;

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

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getServiceConnectionAward()
     */
    public ServiceConnectionAward getServiceConnectionAward()
            throws RuleException {
        return this.getServiceConnectionAward(this.getIncomingPerson());
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnection#getServiceConnectedPercentage()
     */
    public Integer getServiceConnectedPercentage() throws RuleException {
        if (serviceConnectedPercentage == null) {
            this.serviceConnectedPercentage = this
                    .getServiceConnectedPercentage(this.getIncomingPerson());
        }
        return this.serviceConnectedPercentage;
        
    }  
   public boolean isServiceConnectedPercentageChanged() throws RuleException {
       
    	Integer incoming  = this.getServiceConnectedPercentage();
    	Integer  prior= getServiceConnectedPercentage(this.getPristinePerson());
    	 if (prior == null && incoming == null){
    		 return false;
    	 }
    	 else if (prior != null && incoming != null && prior.intValue() ==incoming.intValue()) {
                   return false;
    	 }
    	 else{
    		 return true;
    	 }
    }
    
  /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#isUnemployable()
     */
    public Boolean isUnemployable() throws RuleException {
        if (this.unemploymentIdicator == null) {
            this.unemploymentIdicator = this.getUnemploymentIndicator(this
                    .getIncomingPerson());
        }
        return this.unemploymentIdicator;
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#hasRatedDisabilityCodeBeenAdded()
     */
    public boolean hasRatedDisabilityCodeBeenAdded() throws RuleException {
        Set incomingRDs = this.getRatedDisabilities(this.getIncomingPerson());
        Set currentRDs = this.getRatedDisabilities(this.getPristinePerson());
        return checkRatedDisabilityMembership(incomingRDs, currentRDs);
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#hasRatedDisabilityCodeBeenRemoved()
     */
    public boolean hasRatedDisabilityCodeBeenRemoved() throws RuleException {
        Set incomingRDs = this.getRatedDisabilities(this.getIncomingPerson());
        Set currentRDs = this.getRatedDisabilities(this.getPristinePerson());
        return checkRatedDisabilityMembership(currentRDs, incomingRDs);
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#areAACRatedDisabilitiesLessThan()
     */
    public boolean areAACRatedDisabilitiesLessThan() throws RuleException {
        if (areAACRatedDisabilitiesLessThan == null) {
            Person onSitePerson = this.getIncomingPerson();
            Person onFilePerson = this.getPristinePerson();

            // Update rated disabilities
            Set siteRatedDisabilities = this.getHelperService()
                    .getRatedDisabilities(onSitePerson);
            Set onFileRatedDisabilities = this.getHelperService()
                    .getRatedDisabilities(onFilePerson);

            boolean lessThan = (siteRatedDisabilities != null ? siteRatedDisabilities
                    .size()
                    : 0) < (onFileRatedDisabilities != null ? onFileRatedDisabilities
                    .size()
                    : 0);
            this.areAACRatedDisabilitiesLessThan = Boolean.valueOf(lessThan);
        }
        return this.areAACRatedDisabilitiesLessThan.booleanValue();
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#getCombinedSCPercentageWithNoRDs()
     */
    public Integer getCombinedSCPercentageWithNoRDs() {
        if (this.combinedSCPercentageWithNoRDs == null) {
            this.combinedSCPercentageWithNoRDs = this
                    .getServiceConnnectedPercentageWithNoRDs();
        }
        return this.combinedSCPercentageWithNoRDs;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getServiceConnectedIndicator()
     */
    public Boolean getServiceConnectedIndicator() throws RuleException {
        return this.getServiceConnectedIndicator(this.getIncomingPerson());
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#getPermanentAndTotalIndicator()
     */
    public Boolean getPermanentAndTotalIndicator() {

        if (permanentAndTotalIndicator == null) {
            Boolean value = this.getPermanentAndTotalIndicator(this
                    .getIncomingPerson());
            // Both NULL and FALSE are treated as FALSE
            permanentAndTotalIndicator = Boolean.TRUE.equals(value) ? Boolean.TRUE
                    : Boolean.FALSE;
        }
        return this.permanentAndTotalIndicator;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getPermanentAndTotalIndicator(gov.va.med.esr.common.model.person.Person)
     */
    public Boolean getPermanentAndTotalIndicator(Person person) {
        ServiceConnectionAward award = person != null ? person
                .getServiceConnectionAward() : null;
        Boolean ind = award != null ? award.isPermanentAndTotal() : null;
        return Boolean.TRUE.equals(ind) ? Boolean.TRUE : Boolean.FALSE;
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#getRatedIncompetentIndicator()
     */
    public Boolean getRatedIncompetentIndicator() throws RuleException {
        if (this.ratedIncompetentIndicator == null) {
            Boolean value = this.getRatedIncompetentIndicator(this
                    .getIncomingPerson());
            // Both NULL and FALSE are treated as FALSE
            this.ratedIncompetentIndicator = Boolean.TRUE.equals(value) ? Boolean.TRUE
                    : Boolean.FALSE;
        }
        return this.ratedIncompetentIndicator;
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#hasAtleastOneInvalidDisabilityCode()
     */
    public boolean hasAtleastOneInvalidDisabilityCode() {
        boolean invalid = false;
        if (this.getAacInfo() != null
                && this.getAacInfo().getExceptions() != null
                && !this.getAacInfo().getExceptions().isEmpty()) {
            for (Iterator i = this.getAacInfo().getExceptions().iterator(); i
                    .hasNext();) {

                // for each exception, go up its chain to find
                // UnknownLookupCodeException
                Exception e = (Exception) i.next();

                // Get a list of throwables
                Throwable[] causes = ExceptionUtils.getThrowables(e);
                int index = ExceptionUtils.indexOfThrowable(e,
                        UnknownLookupCodeException.class);

                // Find a throwable of type UnknownLookupCodeException
                if (causes != null && index != -1
                        && causes.length >= (index + 1)) {
                    UnknownLookupCodeException unknownCode = (UnknownLookupCodeException) causes[index];
                    if (ClassUtils.isAssignable(unknownCode.getType(),
                            Disability.class)) {
                        invalid = true;
                        break;
                    }
                }
            }
        }
        return invalid;
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#setRateIncompetentIndicator(java.lang.Boolean)
     */
    public void setRateIncompetentIndicator(Boolean parameter) {
        IncompetenceRuling ruling = this.getResultPerson()
                .getIncompetenceRuling();
        if (ruling == null) {
            ruling = new IncompetenceRuling();
            this.getResultPerson().setIncompetenceRuling(ruling);
        }
        ruling.setIncompetent(parameter);
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#setServiceConnectedPercent(java.lang.Integer)
     */
    public void setServiceConnectedPercentage(Integer parm) {
        ServiceConnectionAward sca = getResultPerson()
                .getServiceConnectionAward();
        if (sca != null) {
            sca.setServiceConnectedPercentage(parm);
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.parameter.ServiceConnectionInput#setUnemploymentIndicator(java.lang.Boolean)
     */
    public void setUnemploymentIndicator(Boolean parameter) {
        ServiceConnectionAward sca = this.getResultPerson()
                .getServiceConnectionAward();
        if (sca != null) {
            sca.setUnemployable(parameter);
        }
    }

    public Boolean getUnemploymentIndicator(Person person) throws RuleException {

        Boolean unemployment = Boolean.FALSE;
        ServiceConnectionAward award = this.getServiceConnectionAward(person);
        if (award != null && award.isUnemployable() != null) {
            unemployment = award.isUnemployable();
        }
        return unemployment;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#setPermanentAndTotal(java.lang.Boolean)
     */
    public void setPermanentAndTotal(Boolean parameter) {
        ServiceConnectionAward serviceConnectionAward = this.getResultPerson()
                .getServiceConnectionAward();
        if (serviceConnectionAward != null) {
            serviceConnectionAward.setPermanentAndTotal(parameter);
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#addServiceConnectionAward()
     */
    public void addServiceConnectionAward() {
        ServiceConnectionAward award = this.getServiceConnectionAward(this
                .getResultPerson());
        if (award == null) {
            this.getResultPerson().setServiceConnectionAward(
                    createServiceConnectionAward());
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#removeServiceConnectionAward()
     */
    public void removeServiceConnectionAward() {
        ServiceConnectionAward award = this.getServiceConnectionAward(this
                .getResultPerson());
        if (award != null) {
            this.getResultPerson().setServiceConnectionAward(null);
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#setPermanentAndTotalEffectiveDate(java.util.Date)
     */
    public void setPermanentAndTotalEffectiveDate(Date effectiveDate) {
        ServiceConnectionAward award = this.getServiceConnectionAward(this
                .getResultPerson());
        if (award != null) {
            award.setPermanentAndTotalEffectiveDate(effectiveDate);
        }
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getPermanentAndTotalEffectiveDate()
     */
    public Date getPermanentAndTotalEffectiveDate() {
        return this.getPermanentAndTotalEffectiveDate(this.getIncomingPerson());
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getPermanentAndTotalEffectiveDate(gov.va.med.esr.common.model.person.Person)
     */
    public Date getPermanentAndTotalEffectiveDate(Person person) {
        ServiceConnectionAward award = this.getServiceConnectionAward(person);
        return award != null ? award.getPermanentAndTotalEffectiveDate() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getServiceConnectedIndicator()
     */
    public Boolean getServiceConnectedIndicator(Person person)
            throws RuleException {
        ServiceConnectionAward award = this.getServiceConnectionAward(person);
        return award != null ? award.getServiceConnectedIndicator() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getCombinedSCPercentageEffectiveDate()
     */
    public Date getCombinedSCPercentageEffectiveDate() throws RuleException {
        if (combinedSCEffectiveDate == null) {
            combinedSCEffectiveDate = getCombinedSCPercentageEffectiveDate(getIncomingPerson());
        }
        return this.combinedSCEffectiveDate;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#getCombinedSCPercentageEffectiveDate(
     *      Person )
     */
    public Date getCombinedSCPercentageEffectiveDate(Person person)
            throws RuleException {
        ServiceConnectionAward sca = this.getServiceConnectionAward(person);
        ImpreciseDate date = sca != null ? sca
                .getCombinedServiceConnectedPercentageEffectiveDate() : null;
        return date != null ? date.getDate() : null;
    }

    /**
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#setCombinedSCPercentageEffectiveDate(java.util.Date)
     */
    public void setCombinedSCPercentageEffectiveDate(Date date)
            throws RuleException {

        ServiceConnectionAward sca = getServiceConnectionAward(getResultPerson());
        if (sca == null) {
            sca = createServiceConnectionAward();
            Person result = getResultPerson();
            result.setServiceConnectionAward(sca);
        }
        ImpreciseDate effDate = date != null ? new ImpreciseDate(date) : null;
        sca.setCombinedServiceConnectedPercentageEffectiveDate(effDate);
    }

    public Set getRatedDisabilities(Person person) throws RuleException {
        ServiceConnectionAward sca = this.getServiceConnectionAward(person);
        return sca != null ? sca.getRatedDisabilities() : null;
    }

    public Set getRatedDisabilities() throws RuleException {
    	/*
		 * I restored this back to original. Unfortunately, certain VBA ILOG
		 * rules compare RDs to null to figure if there are zero RDs. Well, ESR
		 * will always return a non-null set provided the service connection
		 * exists, making this unreliable for such a check. But, rather than
		 * returning a null set here, I changed ILOG virtual method to use the
		 * getSize method, which will handle both scenarios of empty/null set
		 * by returning 0.
		 * 
		 */
        return this.getRatedDisabilities(this.getIncomingPerson());
    }

    private ServiceConnectionAward createServiceConnectionAward() {
        return createServiceConnectionAward(null);
    }

    private ServiceConnectionAward createServiceConnectionAward(
            Boolean serviceConnectedIndicator) {
        ServiceConnectionAward sca = new ServiceConnectionAward();
        if (serviceConnectedIndicator != null)
            sca.setServiceConnectedIndicator(serviceConnectedIndicator);
        else
            sca.initDefaults();
        return sca;
    }

    public boolean hasRatedDisabilityMissingPercentageOrCode()
            throws RuleException {
        Set rds = getRatedDisabilities();
        Iterator itr = rds != null ? rds.iterator() : null;
        RatedDisability rd = null;
        while (itr != null && itr.hasNext()) {
            rd = (RatedDisability) itr.next();
            if (rd.getPercentage() == null || rd.getDisability() == null)
                return true;
        }
        return false;
    }

    /**
     * This method is only being used by rule flows that peform actions specific to HL7
     * messaging. Be aware that it may not provide behavior suitable for UI usage.
     * 
     * @see gov.va.med.esr.common.rule.ServiceConnectionInput#setRatedDisabilities(java.util.Set)
     */
    public void setRatedDisabilities(Set disabilities) throws RuleException {
        ServiceConnectionAward sca = getServiceConnectionAward(getResultPerson());
        if (sca == null) {
            sca = createServiceConnectionAward();
            Person result = getResultPerson();
            result.setServiceConnectionAward(sca);
        }
        try {
        	// CCR 10969 - protect the current rd's that have non-null dates        	
        	// CCR 11415 - fix the merge for rated disability. Had to reimplement CCR 10021.
        	Set updated = this.getMergeRuleService().mergeRatedDisabilitiesFromHL7(disabilities, sca.getRatedDisabilities());
            sca.removeAllRatedDisability();
            sca.addAllRatedDisability(updated);
        } catch (ServiceException e) {
            throw new RuleException(
                    "Failed to merge a set of rated disabilities", e);
        }
    }
    
    public Boolean getRatedIncompetentIndicator(Person person)
            throws RuleException {
        return (person != null && person.getIncompetenceRuling() != null) ? person
                .getIncompetenceRuling().isIncompetent()
                : null;
    }

    public Integer getServiceConnectedPercentage(Person person)
            throws RuleException {
        ServiceConnectionAward award = this.getServiceConnectionAward(person);
        Integer result = (award != null) ? award
                .getServiceConnectedPercentage() : null;
        logger.debug(result);
        return result;
    }

    public ServiceConnectionAward getServiceConnectionAward(Person person) {
        return person != null ? person.getServiceConnectionAward() : null;
    }

    private boolean checkRatedDisabilityMembership(Set a, Set b) {
        if (a == null)
            return false;
        if (b == null)
            return !a.isEmpty();
        if (a.size() > b.size())
            return true;
        Iterator iter = a.iterator();
        while (iter.hasNext()) {
            RatedDisability ra = (RatedDisability) iter.next();
            Iterator iterB = b.iterator();
            boolean found = false;
            while (iterB.hasNext()) {
                RatedDisability rb = (RatedDisability) iterB.next();
                if (compareRatedDisability(ra, rb)) {
                    found = true;
                    break;
                }
            }
            if (!found)
                return true;
        }
        return false;
    }

    private boolean compareRatedDisability(RatedDisability a, RatedDisability b) {
        return ((a == null) && (b == null))
                || ((a != null && b != null) && new EqualsBuilder().append(
                        a.getDisability(), b.getDisability()).isEquals());
    }

    private Integer getServiceConnnectedPercentageWithNoRDs() {
        if (scPercentageWithNoRDs == null && this.getAacInfo() != null) {
            this.scPercentageWithNoRDs = this.getAacInfo()
                    .getCombinedSCPercentage();
        }
        return this.scPercentageWithNoRDs;
    }
 
    
    public boolean doesRatedDisabilitiesHaveReceivedCode(String code)
        throws RuleException {
        Set rds = getRatedDisabilities();
        Iterator itr = rds != null ? rds.iterator() : null;
        RatedDisability rd = null;
        while (itr != null && itr.hasNext()) {
            rd = (RatedDisability) itr.next();
            if(rd.getDisability().getCode().equalsIgnoreCase(code))
               return true;
        }
        return false;
    }


}