// Package 
package gov.va.med.esr.ui.person.action;

// Java Classes
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

// Library Classes
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;

// Framework Classes
import gov.va.med.fw.conversion.CopyServiceException;
import gov.va.med.fw.model.AbstractEntity;
import gov.va.med.fw.model.AbstractKeyedEntity;
import gov.va.med.fw.model.lookup.AbstractLookup;
import gov.va.med.fw.util.Reflector;
import gov.va.med.fw.util.StringUtils;

// ESR Classes
import gov.va.med.esr.common.model.ee.CDCondition;
import gov.va.med.esr.common.model.ee.CDDiagnosis;
import gov.va.med.esr.common.model.ee.CDProcedure;
import gov.va.med.esr.common.model.ee.CDDescriptor;
import gov.va.med.esr.common.model.ee.FeeBasis;
import gov.va.med.esr.common.model.ee.MonetaryBenefit;
import gov.va.med.esr.common.model.ee.MonetaryBenefitAward;
import gov.va.med.esr.common.model.ee.POWEpisode;
import gov.va.med.esr.common.model.ee.ReceivedEligibility;
import gov.va.med.esr.common.model.ee.ServiceConnectionAward;
import gov.va.med.esr.common.model.financials.BeneficiaryTravel;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.IncomeTest;
import gov.va.med.esr.common.model.financials.PatientVisitSummary;
import gov.va.med.esr.common.model.insurance.InsurancePolicy;
import gov.va.med.esr.common.model.insurance.PrivateInsurance;
import gov.va.med.esr.common.model.insurance.Medicare;
import gov.va.med.esr.common.model.lookup.MonetaryBenefitType;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.person.Employment;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PersonMergeInfo;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.ui.common.action.PersonAbstractAction;
import gov.va.med.esr.ui.person.beans.PersonMergeRowInfo;

/**
 * This struts action is used to support the various Person Merge pages.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public abstract class PersonMergeAction extends PersonAbstractAction implements MergeConstants
{
    protected Map personMergeRowInfoMetaMap = null;
    protected Map personMergeRowGroupInfoMetaMap = null;

    /**
     * Default constructor.
     */
    public PersonMergeAction()
    {
        super();
    }

    /**
     * Returns false.  Veteran merge doesn't require the person to be selected.
     * @return false.
     */
    protected boolean requiresSelectedPerson()
    {
        return false;
    }

    public Person mergeRecords(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Get the person merge info map from session
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);

        // Get the user selection map that we need to save
        Map userSelectionMap = (Map)personMergeInfoMap.get(PERSON_USER_SELECTIONS_KEY);

        // Get the person mege info from the map
        PersonMergeInfo personMergeInfo =
            (PersonMergeInfo)personMergeInfoMap.get(PERSON_MERGE_INFO_KEY);


        //Check whether we have these two objects in session
        PersonEntityKey primaryPersonKey = personMergeInfo.getPrimaryPersonEntityKey();
        PersonEntityKey deprecatedPersonKey = personMergeInfo.getDeprecatedPersonEntityKey();

        // Re-retrieve the primary and deprecated person to perform the actual merge
        Person primaryPerson = getPersonService().getPerson(primaryPersonKey);
        Person deprecatedPerson = getPersonService().getPerson(deprecatedPersonKey);

        mergePersonRecords(primaryPerson, deprecatedPerson, personMergeInfoMap, userSelectionMap);

        return primaryPerson;
    }

    private void mergePersonRecords(Person primaryPerson, Person deprecatedPerson,
            Map personMergeInfoMap, Map userSelectionMap) throws Exception{

        //Get the list of tab names to process from meta data
        Set groupNames = getPersonMergeRowGroupInfoMetaMap().keySet();

        PropertyUtilsBean propUtils = new PropertyUtilsBean();

        //process each tab for conversion
        for (Iterator group = groupNames.iterator(); group.hasNext();) {
            String metaGroupKey = (String)group.next();

            if (logger.isDebugEnabled()) {
                logger.debug("MERGE - Processing Section: " + metaGroupKey);
            }

            //Get the section list for each tab (group)
            List groupSectionList = (List) personMergeInfoMap.get(metaGroupKey);

            //Process the sections in the group
            for (Iterator sections = groupSectionList.iterator(); sections.hasNext(); ) {
                PersonMergeRowInfo sectionInfo = (PersonMergeRowInfo)sections.next();
                if (logger.isDebugEnabled()) {
                    logger.debug("MERGE - Processing Section: " + sectionInfo.getFieldName());
                }
              //This condition is added to fix CCR 10759 --check commons.xml
                //if the deprecated person has address in the private insurance, it throws error in getCopy.
                // So  remove the address from the deprecated person's private insurance
                if(metaGroupKey.equalsIgnoreCase("demographics") && 
                		sectionInfo.getFieldName().equalsIgnoreCase("demographics_privateInsurances") ) {
                	//continue;
                	 removeAddressFromPrivateInsurance(deprecatedPerson);
                }
                // CCR 11755 use similar fix as used by 10759 to handle Medicare insurance with address 
                if(metaGroupKey.equalsIgnoreCase("demographics") && 
                		sectionInfo.getFieldName().equalsIgnoreCase("demographics_medicareInsurances") ) {
                	//continue;
                	 removeAddressFromMedicareInsurance(deprecatedPerson);
                }                
                //Check whether this is selectable and selected and has an associated property to merge
                String fieldProperty =
                    sectionInfo.getMergeProperty() == null ?
                        sectionInfo.getFieldProperty() : sectionInfo.getMergeProperty();

                if (logger.isDebugEnabled()) {
                    logger.debug("MERGE fieldProperty:" + fieldProperty +
                        " mergeMethod:" + sectionInfo.getMergeMethodName());
                }

                //If the selection is selectable and selected copy the property as is
                if (sectionInfo.isSelectable()) {
                    //primary is selected no action
                    if (userSelectionMap.containsKey(sectionInfo.getPrimaryFieldKey())) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("MERGE - Primary is selected - skipping");
                        }
                        continue;   //process next item in the section
                    }
                    //deprecated is selected copy deprecated to primary
                    else if (userSelectionMap.containsKey(sectionInfo.getDeprecatedFieldKey())){
                        if (logger.isDebugEnabled()) {
                            logger.debug("MERGE - Deprecated is selected - copying");
                        }
                        copyProperty(propUtils, userSelectionMap,
                                primaryPerson, deprecatedPerson, sectionInfo);
                        //process next item in the section
                        continue;
                    }
                }

                //section is not selectable or not selected
                //process children if specified
                if (sectionInfo.isParentRow()) {
                    
                   //if primary and deprecated have null properties - process next section
                   if (StringUtils.isNotEmpty(fieldProperty)) {
                      //if both the properties are null skip processing
                      if (isNull(propUtils,primaryPerson, deprecatedPerson, fieldProperty))
                          continue;

                      AbstractEntity primaryValue = 
                          (AbstractEntity) getNestedProperty(propUtils, primaryPerson, fieldProperty);
                      AbstractEntity deprecatedValue = 
                          (AbstractEntity) getNestedProperty(propUtils, deprecatedPerson, fieldProperty);

                      //if deprecated doesn't have a value skip the processing
                      if (deprecatedValue == null) 
                          continue;
                      
                      //pass the section property for merging
                      processParentSection(propUtils, userSelectionMap,
                              primaryValue, deprecatedValue, sectionInfo);
                   }
                   //no parent property process children
                   else {
                          //process individual elements of a group
                       //mergeGroupSelection
                       processParentSection(propUtils, userSelectionMap,
                           primaryPerson, deprecatedPerson, sectionInfo);
                   }
                }
            }
        }
    }
    //
    private void removeAddressFromPrivateInsurance(Person deprecatedPerson){
    	if( deprecatedPerson != null && deprecatedPerson.getPrivateInsurances() != null){
    	
    		Set privateInsurancesSet = deprecatedPerson.getPrivateInsurances();
    		Set toAdd = new HashSet();
    		Set toRemove = new HashSet(privateInsurancesSet);
        
    		for (Iterator i = toRemove.iterator(); i.hasNext();)
    		{
    			PrivateInsurance privateInsurance = (PrivateInsurance) i.next();
        		if(privateInsurance.getAddress() != null){
        			privateInsurance.setAddress(null);
        		}
        		toAdd.add((InsurancePolicy)privateInsurance);
        		deprecatedPerson.removeInsurance((InsurancePolicy)privateInsurance);
    		}

    		for (Iterator i = toAdd.iterator(); i.hasNext();)
    		{
    			deprecatedPerson.addInsurance((InsurancePolicy) i.next());
    		}
    	}
    }
    private void removeAddressFromMedicareInsurance(Person deprecatedPerson){
    	if( deprecatedPerson != null && deprecatedPerson.getMedicareInsurances() != null){
    		Set medicareInsurancesSet = deprecatedPerson.getMedicareInsurances();
    		Set toAdd = new HashSet();
    		Set toRemove = new HashSet(medicareInsurancesSet);
        
    		for (Iterator i = toRemove.iterator(); i.hasNext();) {
    			Medicare medicareInsurance = (Medicare) i.next();
        		if(medicareInsurance.getAddress() != null){
        			medicareInsurance.setAddress(null);
        		}
        		toAdd.add((InsurancePolicy)medicareInsurance);
        		deprecatedPerson.removeInsurance((InsurancePolicy)medicareInsurance);
    		}

    		for (Iterator i = toAdd.iterator(); i.hasNext();) {
    			deprecatedPerson.addInsurance((InsurancePolicy) i.next());
    		}
    	}
    }    
    private void processParentSection(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo sectionInfo) throws Exception{
        
        List sectionItems = sectionInfo.getChildren();
        if (sectionItems == null || sectionItems.size() == 0)
            return;
        
        for (Iterator i=sectionItems.iterator(); i.hasNext();) {
            PersonMergeRowInfo sectionItem = (PersonMergeRowInfo) i.next();
            
            //Section can have sets/ list of individual properties (nested and simple)
            //if the property or set is not selectable process next item
            if (!sectionItem.isSelectable()) {
                continue;
            }
            
            //determine the property type
            //Check whether this is selectable and selected and has an associated property to merge
            String fieldProperty =
                sectionItem.getMergeProperty() == null ?
                        sectionItem.getFieldProperty() : sectionItem.getMergeProperty();
                    
            //process sets                         
            if (sectionItem.isParentRow()) {
                if (isNull(propUtils, primary, deprecated, fieldProperty))
                {
                    //both primary and deprecated are null process next item
                }
                else if (isSet(propUtils, primary, deprecated, fieldProperty)){
                    mergeSets(propUtils, userSelectionMap, primary, deprecated, sectionItem);
                }
                else {
                    throw new Exception("Individual children are not supported for section " + 
                        sectionItem.getFieldName());
                }
            }     
            //merge simple/nested property
            else {
                //copy the property if the deprecated is selected
                copyProperty(propUtils, userSelectionMap,primary, deprecated,sectionItem);
           }           
        }
    }
    
    /**
     * Copy the property from deprecated to Primary
     */
    private void copyProperty(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo sectionItem) throws Exception{

            // Copy using a custom function
            if (sectionItem.getMergeMethodName() != null) {
                mergeCustomPropertyFunction(propUtils, userSelectionMap,
                        primary, deprecated, sectionItem);
            }
            //TODO not implemented (copy the specified) 
            else if (sectionItem.getMergeProperty() != null) {
                mergeCustomProperty(propUtils, userSelectionMap,
                        primary, deprecated, sectionItem);
            }
            else if (StringUtils.isNotEmpty(sectionItem.getFieldProperty()))
            {
                //default - copy the individual property specifie
                if (isSimpleProperty(sectionItem.getFieldProperty())) {
                    mergeSimpleProperty(propUtils, userSelectionMap,
                            primary, deprecated, sectionItem);
                }
                else {
                    //complex nested properties are not yet supported
                    //check whether it is a lookup
                    mergeNestedProperty(propUtils, userSelectionMap,
                            primary, deprecated, sectionItem);
                }
            }
            else {
                String errMsg = "MERGE section Item " +
                    sectionItem.getFieldName() + " is selectable but property is empty";
                log.error(errMsg);
                throw new RuntimeException(errMsg);
            }
    }
    /**
     * Merge Sets
     * @param propUtils
     * @param userSelectionMap
     * @param primary
     * @param deprecated
     * @param propertyInfo
     * @throws Exception
     */
    private void mergeSets(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo propertyInfo) throws Exception {

            //If the set is not selectable - return
            if (!propertyInfo.isSelectable()) {
                return;
            }

            if (primary == null && deprecated == null) return;

            //Verify whether we have a custom method specified for the set for merging
            //This is specically coded to handle facilities merge (PatientVisitSummary/FeeBasis)
            if (propertyInfo.getMergeMethodName() != null) {
                Object[] params = new Object[] {primary,deprecated,userSelectionMap,propertyInfo};
                executeMethod(this,propertyInfo.getMergeMethodName(),params);
                return;
            }

            //If nested property is specified retrieve the primary and deprectated objects
            //and simple property name
            String fieldProperty =
                propertyInfo.getMergeProperty() == null ?
                    propertyInfo.getFieldProperty() : propertyInfo.getMergeProperty();

            String simpleFieldProperty = fieldProperty;
            String parentFiledProperty = null;

            //for simple properties the methods are executed on the object passed in
            Object primaryObj = primary;
            Object deprecatedObj = deprecated;

            if (!isSimpleProperty(fieldProperty)) {
                simpleFieldProperty = fieldProperty.substring(fieldProperty.lastIndexOf('.')+1);
                parentFiledProperty = fieldProperty.substring(0,fieldProperty.lastIndexOf('.'));

                primaryObj = getNestedProperty(propUtils, primary, parentFiledProperty);
                deprecatedObj = getNestedProperty(propUtils, deprecated,parentFiledProperty);

                if (primaryObj == null) {
                    throw new Exception("Parent property for a set is null on source object Class" +
                            primary.getClass().getName() + " property " + parentFiledProperty);

                }
            }

            //Both primary and deprecated are null - nothing to process
            if (primaryObj == null && deprecatedObj == null) {
                return;
            }

            //Process Primary object - remove all unselected ones
            Set primarySet = (Set) getSimpleProperty(propUtils, primaryObj, simpleFieldProperty);
            Set depSet = (Set) getSimpleProperty(propUtils, deprecatedObj,simpleFieldProperty);

            //Determine the object types to find the property access methods
            Class parentType = primaryObj == null ? deprecatedObj.getClass() : primaryObj.getClass();
            Class propertyType = getType(primarySet);
            if (propertyType == null)
                propertyType = getType(depSet);

            //If the property type could not be determined as both the sets are empty
            if (propertyType == null) {
                return;
            }

            String addMethod = getAddMethod(parentType, propertyType, simpleFieldProperty);
            String removeMethod= getRemoveMethod(parentType, propertyType, simpleFieldProperty);


            if (primarySet != null && primarySet.size() > 0) {
                if (propertyInfo.isSelectable() &&
                    userSelectionMap.containsKey(propertyInfo.getPrimaryFieldKey())) {
                    //all children are retained and no need to process children
                }
                else {
                    //remove all non-selected children
                    primarySet = new HashSet(primarySet);
                    for (Iterator i=primarySet.iterator(); i.hasNext();) {
                        Object objRemove = i.next();
                        String key = getKey((AbstractEntity)objRemove);
                        if (!userSelectionMap.containsKey(propertyInfo.getFieldName()+"_"+key)) {
                            executeMethod(primaryObj,removeMethod,objRemove);
                        }
                    }
                }
            }

            //Process deprecated set

            if (depSet != null && depSet.size() > 0) {
                //Process deprecated object - add all selections
                if (propertyInfo.isSelectable() &&
                        userSelectionMap.containsKey(propertyInfo.getDeprecatedFieldKey())) {
                    //Loop through the set and create a new copies of entities
                    Set newSet = getCopy(depSet);
                    //Add all the entities to the primary object with specified add Method
                    for (Iterator i=newSet.iterator(); i.hasNext();) {
                        Object objAdd = i.next();
                        executeMethod(primaryObj,addMethod,objAdd);
                    }
                }
                else {
                    //add all selected children
                    for (Iterator i=depSet.iterator(); i.hasNext();) {
                        Object objDep = i.next();
                        Object objAdd = getCopy(objDep);
                        String key = getKey((AbstractEntity)objDep);
                        if (userSelectionMap.containsKey(propertyInfo.getFieldName()+"_"+key)) {
                            executeMethod(primaryObj,addMethod,objAdd);
                        }
                    }
                }
            }
    }

    /**
     * TODO mergeCustomProperty not implemented 
     * @param propUtils
     * @param userSelectionMap
     * @param primary
     * @param deprecated
     * @param propertyInfo
     * @throws Exception
     */
    private void mergeCustomProperty(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo propertyInfo) throws Exception {

        //If the item is not selectable return - it shouldn't come as we are already checking
        if (!propertyInfo.isSelectable()) {
            return;
        }

        //Selection field is different from the actual merge property
        //selection of a single property from the object requires copying the entire object
        String customPpropertyName = propertyInfo.getMergeProperty();

        //If the info from deprecated record is selected copy the data
        if (userSelectionMap.containsKey(propertyInfo.getDeprecatedFieldKey())){
            //replace the value from deprecated entity
            //copy simple or nested property value
        }
    }

    private void mergeCustomPropertyFunction(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo propertyInfo) throws Exception {

        //If the item is not selectable return - it shouldn't come here as we are already checking
        if (!propertyInfo.isSelectable()) {
            return;
        }

        //custom merge methods
        String customMergeMethodName = propertyInfo.getMergeMethodName();

        //If the info from deprecated record is selected copy the data
        if (userSelectionMap.containsKey(propertyInfo.getDeprecatedFieldKey())){
            //replace the value from deprecated entity
            executeMethod(this,customMergeMethodName,deprecated,primary);
        }
    }


    /**
     * Copy simple property
     */
    private void mergeSimpleProperty(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo propertyInfo) throws Exception {

        //If the item is not selectable return - it shouldn't come as we are already checking
        if (!propertyInfo.isSelectable()) {
            return;
        }

        String fieldProperty = propertyInfo.getFieldProperty();

        //Check whther this is selectable property
        if (isSimpleProperty(fieldProperty)) {

            //Single simple property - get the source and target values
            String srcKey = propertyInfo.getPrimaryFieldKey();
            String depKey = propertyInfo.getDeprecatedFieldKey();
            //Check the user selections and set the property value
            if (userSelectionMap.containsKey(srcKey)) {
                //nothing to do retain the old value

            }else if (userSelectionMap.containsKey(depKey)){
                //replace the value from deprecated entity
                Object depPropValue = getNestedProperty(propUtils, deprecated, fieldProperty);

                //get a copy of the value
                Object depPropCopy = getCopy(depPropValue);

                //Set the value on Person
                propUtils.setSimpleProperty(primary,fieldProperty,depPropCopy);
            }
            //None is selected case throw an error ??
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Property is not slected skipping - " + fieldProperty);
                }
            }
        }
    }

    private void mergeNestedProperty(PropertyUtilsBean propUtils, Map userSelectionMap,
            AbstractEntity primary, AbstractEntity deprecated,
            PersonMergeRowInfo propertyInfo) throws Exception {

        //If the item is not selectable return - it shouldn't come as we are already checking
        if (!propertyInfo.isSelectable()) {
            return;
        }

        String depKey = propertyInfo.getDeprecatedFieldKey();
        String fieldProperty = propertyInfo.getFieldProperty();

        if (userSelectionMap.containsKey(depKey)){
            Object propValue = propUtils.getNestedProperty(deprecated,fieldProperty);
            if (propValue != null) {
                if (String.class.isAssignableFrom(propValue.getClass())) {
                    //check whether its parent is lookup
                    int index = fieldProperty.lastIndexOf('.');
                    String parentPropName = fieldProperty.substring(0,index);
                    Object parentPropValue = propUtils.getNestedProperty(deprecated,parentPropName);

                    if (AbstractLookup.class.isAssignableFrom(parentPropValue.getClass())) {
                        //set the lookup object
                        propUtils.setNestedProperty(primary,parentPropName,parentPropValue);
                    }
                    else
                    {
                        //copy as a simple nested property using create and set
                        //propUtils.setNestedProperty(primary,fieldProperty,propValue);
                    	setNestedPropertyCreate(propUtils,primary,fieldProperty,propValue);
                    }
                    return;
                }
                //copy as a simple nested property using create and set
                //propUtils.setNestedProperty(primary,fieldProperty,propValue);
                setNestedPropertyCreate(propUtils,primary,fieldProperty,propValue);
            }
        }
    }
    
    private void setNestedPropertyCreate(PropertyUtilsBean propUtils, AbstractEntity primary, String fieldProperty, Object propValue) throws Exception {
    	boolean isSet = false;
    	try {
    		//copy as a simple nested property
    		propUtils.setNestedProperty(primary,fieldProperty,propValue);
    	} catch(IllegalArgumentException iae) {
    		String msg = iae.getMessage();
    		logger.warn("Ignoring IllegalArgumentException for " + fieldProperty + ", error is "+msg);
    		
    		if (primary != null && propValue != null) {
        		//if fieldProperty is for employment then create and set
	    		if (SET_NESTED_PROP_ERR_FOR_EMP.equals(msg)) {
	    			((Person)primary).setEmployment(new Employment());
	        		//copy as a simple nested property
	        		propUtils.setNestedProperty(primary,fieldProperty,propValue);
	        		isSet = true;
	    		}
    		}
    		if (!isSet) {
    			throw iae; 
    		}
     	}
        return;
    }
    
    /**
     * Check whther the property is of type Set
     * @param propUtils
     * @param primary
     * @param deprecated
     * @param propertyName
     * @return
     * @throws Exception
     */
    public boolean isSet(PropertyUtilsBean propUtils,
        AbstractEntity primary, AbstractEntity deprecated, String propertyName) throws Exception {

        Object primaryValue = getNestedProperty(propUtils, primary, propertyName);
        Object deprecatedValue = getNestedProperty(propUtils, deprecated, propertyName);

        Object typeCheck = primaryValue == null ? deprecatedValue : primaryValue;
        return (Set.class.isAssignableFrom(typeCheck.getClass()));
    }

    /**
     * Check whther the properties are null
     * @param propUtils
     * @param primary
     * @param deprecated
     * @param propertyName
     * @return
     * @throws Exception
     */
    public boolean isNull(PropertyUtilsBean propUtils,
        AbstractEntity primary, AbstractEntity deprecated, String propertyName) throws Exception {

        Object primaryValue = getNestedProperty(propUtils, primary, propertyName);
        Object deprecatedValue = getNestedProperty(propUtils, deprecated, propertyName);

        if (primaryValue == null && deprecatedValue == null) {
            return true;
        }

        return false;
    }

    public static Object getSimpleProperty(PropertyUtilsBean propUtils, Object target,
            String propertyName) throws Exception {

        if (target == null) {
            return null;
        }
        if (propertyName == null) {
            throw new Exception("Null Property specified for Class: " +
                target.getClass().getName());
        }
        try {
            return propUtils.getSimpleProperty(target,propertyName);
        }catch(NullPointerException e){
            return null;
        } catch (Exception e) {
            throw new Exception("Reflection Exception on Class: " +
                target.getClass().getName() + " property: " + propertyName,e);
        }
    }

    public static Object getNestedProperty(PropertyUtilsBean propUtils, Object target,
        String propertyName) throws Exception {

        if (target == null) {
            return null;
        }
        if (propertyName == null) {
            throw new Exception("Null Property specified for Class: " +
                target.getClass().getName());
        }
        try {
            return propUtils.getNestedProperty(target,propertyName);
        }catch(NullPointerException e){
            return null;
        } catch (Exception e) {
            throw new Exception("Reflection Exception on Class: " +
                target.getClass().getName() + " property: " + propertyName,e);
        }
    }


    public static boolean isSimpleProperty(String propertyName) {
        return (propertyName.indexOf('.') <= 0);
    }

    /*
     * Custom merge method to copy claim folder number and location
     */
    protected void mergeClaimFolderNumber(Person src, Person target)
    {
        target.setClaimFolderNumber(src.getClaimFolderNumber());
        target.setClaimFolderLocation(src.getClaimFolderLocation());
    }

    /**
     * Copy ssn source of change
     * @param src
     * @param target
     * @throws Exception
     */
    protected void mergeSSNsourceOfChange(Person src, Person target) throws Exception
    {
        SSN ssnSource = src.getOfficialSsn();
        if (ssnSource == null) {
            return; //source is empty
        }
        SSN ssnTarget = target.getOfficialSsn();
        if (ssnTarget == null) {
            return; //target is empty ;
        }
        ssnTarget.setSourceOfChange(ssnSource.getSourceOfChange());
   }

    protected void mergeSSAVerification(Person src, Person target) throws Exception
    {
        SSN ssnSource = src.getOfficialSsn();
        if (ssnSource == null) {
            return; //source is empty
        }
        SSN ssnTarget = target.getOfficialSsn();
        if (ssnTarget == null) {
            //target is empty
            return;
        }
        //Copy the data related to ssa verification status,date and message
        ssnTarget.setSsaVerificationDate(ssnSource.getSsaVerificationDate());
        ssnTarget.setSsaVerificationStatus(ssnSource.getSsaVerificationStatus());
        ssnTarget.setSsaMessage(ssnSource.getSsaMessage());   
   }    
    
    protected void mergeUserEnrollee(Person src, Person target) throws Exception
    {
        target.setUserEnrolleeSite(src.getUserEnrolleeSite());
        target.setUserEnrolleeValidThrough(src.getUserEnrolleeValidThrough());
    }
    
    protected void mergeAppointmentRequest(Person src, Person target) throws Exception
    {
        target.setAppointmentRequestDate(src.getAppointmentRequestDate());
        target.setAppointmentRequestResponse(src.getAppointmentRequestResponse());
    }    
    /**
     * Merge Facility Information
     * @param primary
     * @param deprecated
     * @param userSelectionMap
     * @param propertyInfo
     * @throws CopyServiceException
     */
    protected void mergeFacilities(Person primary, Person deprecated,
            HashMap userSelectionMap, PersonMergeRowInfo propertyInfo) throws CopyServiceException
    {
        //process primary selections of sets
        Set primarySet = primary.getFacilities();
        Set depSet = deprecated.getFacilities();

        Set removeSet = new HashSet();
        Set addSet = new HashSet();

        //Process Primary selections
        if (primarySet != null && primarySet.size() > 0) {
            if (propertyInfo.isSelectable() &&
                userSelectionMap.containsKey(propertyInfo.getPrimaryFieldKey())) {
                //all children are retained and no need to process children
            }
            else {
                //remove all non-selected children
                for (Iterator i=primarySet.iterator(); i.hasNext();) {
                    Object objRemove = i.next();
                    String key = getKey((AbstractEntity)objRemove) + getKey(primary);
                    if (!userSelectionMap.containsKey(propertyInfo.getFieldName()+"_"+key)) {
                        //remove the facility info
                        removeSet.add(objRemove);
                    }
                }
            }
        }
        //Process deprecated selections
        if (depSet != null && depSet.size() > 0) {
            if (propertyInfo.isSelectable() &&
                userSelectionMap.containsKey(propertyInfo.getDeprecatedFieldKey())) {
                //all children are selected add them all
                addSet.addAll(depSet);
            }
            else {
                //remove all non-selected children
                for (Iterator i=primarySet.iterator(); i.hasNext();) {
                    Object objAdd = i.next();
                    String key = getKey((AbstractEntity)objAdd) + getKey(deprecated);
                    if (userSelectionMap.containsKey(propertyInfo.getFieldName()+"_"+key)) {
                        //remove the facility info
                        addSet.add(objAdd);
                    }
                }
            }
        }

        //remove the unselected facilities info
        for (Iterator i=removeSet.iterator(); i.hasNext();) {
            VAFacility facility = (VAFacility)i.next();
            primary.removePatientVisitSummary(facility);
            primary.removeFeeBasis(facility);
       }

        //add the selected faclities info from deprecated person
        for (Iterator i=addSet.iterator(); i.hasNext();) {
            VAFacility facility = (VAFacility) i.next();
            Set summaries = deprecated.getPatientVisitSummaries(facility);
            //copy and add to the primary person object
            for (Iterator j=summaries.iterator(); j.hasNext();) {
                AbstractEntity entity = (AbstractEntity)j.next();
                AbstractEntity entityCopy = getPersonMergeCopyService().getCopy(entity);
                primary.addPatientVisitSummary((PatientVisitSummary)entityCopy);
            }

            //copy and add to the primary person object
            Set feeBasisInfo = deprecated.getFeeBasis(facility);
            for (Iterator k=feeBasisInfo.iterator(); k.hasNext();) {
                AbstractEntity entity = (AbstractEntity)k.next();
                FeeBasis feeBasisCopy = (FeeBasis) getPersonMergeCopyService().getCopy(entity);
                primary.addFeeBasis(feeBasisCopy);
            }
        }
    }

    protected void mergeFinancials(Person src, Person target) throws CopyServiceException
    {
        //get the income year from the source and target
        IncomeTest srcTest = getPersonHelperService().getCurrentIncomeTest(src);

        if (srcTest == null || srcTest.getIncomeYear() == null) {
            return;
        }

        Integer srcIncomeYear = srcTest.getIncomeYear();
       //if target contains financial data for the same selected income year
        //replace that with the selected data

        //Merge financials veteran/spouse/dependents
        FinancialStatement srcStmt = src.getFinancialStatement(srcIncomeYear);
        if (srcStmt != null) {
            FinancialStatement srcStmtCopy =
                (FinancialStatement) getPersonMergeCopyService().getCopy(srcStmt);
            target.setFinancialStatement(srcStmtCopy.getIncomeYear(),srcStmtCopy);
        }

        //Merge Income Test test, statuses, hardship - replaces the target value if exists
        IncomeTest srcTestCopy = (IncomeTest) getPersonMergeCopyService().getCopy(srcTest);
        target.setIncomeTest(srcTestCopy.getIncomeYear(),srcTestCopy);

        //Merge Beneficiary Travels - target is replaced with src objects
        Set beneficiaryTravels = src.getBeneficiaryTravels(srcIncomeYear);
        if (beneficiaryTravels != null){
	        for (Iterator i = beneficiaryTravels.iterator(); i.hasNext();) {
	            BeneficiaryTravel beneficiaryTravel = (BeneficiaryTravel)i.next();
	            	if (beneficiaryTravel != null)  {
	            		BeneficiaryTravel copyObj =
	            				(BeneficiaryTravel) getPersonMergeCopyService().getCopy(beneficiaryTravel);
	            		target.setBeneficiaryTravel(copyObj.getYear(),copyObj.getFacilityVisited(),copyObj);
	            	}
	        }
        }
    }

    /**
     * Copy MonetaryBenefitAward and Unemeployable indicator on service connection award
     * @param src
     * @param target
     * @throws CopyServiceException
     */
    protected void mergeMonetaryBenefitAward(Person src, Person target) throws CopyServiceException
    {
        MonetaryBenefitAward srcAward = src.getMonetaryBenefitAward();
        if (srcAward != null) {
            MonetaryBenefitAward srcCopy = (MonetaryBenefitAward) getCopy(srcAward);
            target.setMonetaryBenefitAward(srcCopy);
        }
        else{
            target.setMonetaryBenefitAward(null);
        }
        //copy unemployable indicator
        mergeUnemployable(src, target);
    }
    
    protected void mergeUnemployable(Person src, Person target) throws CopyServiceException
    {
        //copy unemployable indicator 
        Boolean indicator = null;
        ServiceConnectionAward svcConnAward = src.getServiceConnectionAward();
        if (svcConnAward != null) {
            indicator = svcConnAward.getUnemployable();
        }        
        svcConnAward = target.getServiceConnectionAward();
        if (svcConnAward == null){
            svcConnAward = new ServiceConnectionAward();
            target.setServiceConnectionAward(svcConnAward);
        }
        svcConnAward.setUnemployable(indicator);
    }
    
    protected void mergeAidAndAttendance(Person src, Person target) throws CopyServiceException
    {
        mergeMonetaryBenefit(src, target, MonetaryBenefitType.CODE_AID_AND_ATTENDANCE);
    }

    protected void mergeHouseBound(Person src, Person target) throws CopyServiceException
    {
        mergeMonetaryBenefit(src, target, MonetaryBenefitType.CODE_HOUSEBOUND);
    }
    
    protected void mergeVAPension(Person src, Person target) throws CopyServiceException
    {
        mergeMonetaryBenefit(src, target, MonetaryBenefitType.CODE_VA_PENSION);
    }

    protected void mergeDisabilityCompensation(Person src, Person target) throws CopyServiceException
    {
        mergeMonetaryBenefit(src, target, MonetaryBenefitType.CODE_DISABILITY_COMPENSATION);
    }    
    
    protected void mergeMonetaryBenefit(Person src, Person target, MonetaryBenefitType.Code mbtType) throws CopyServiceException
    {
        MonetaryBenefitAward mbAward = src.getMonetaryBenefitAward();
        MonetaryBenefit mb = null;
        if (mbAward != null) {
            mb = mbAward.getMonetaryBenefitByType(mbtType);
        }
        mbAward = target.getMonetaryBenefitAward();
        if (mbAward == null){
            mbAward = new MonetaryBenefitAward ();
            target.setMonetaryBenefitAward(mbAward);
        }
        MonetaryBenefit mbt = null;
        mbt = mbAward.getMonetaryBenefitByType(mbtType);
        if (mb == null){
            mbAward.removeMonetaryBenefitByType(mbtType);
        }
        else if (mbt == null){
            mbAward.addMonetaryBenefit((MonetaryBenefit) getCopy(mb));
        }
        else {
            getCopyService().copy(mb,mbt);
        }        
    }    
    protected void mergeCheckAmount(Person src, Person target) throws CopyServiceException
    {
        MonetaryBenefitAward mbAward = src.getMonetaryBenefitAward();
        BigDecimal checkAmount = null;
        if (mbAward != null) {
            checkAmount = mbAward.getCheckAmount();
            mbAward = target.getMonetaryBenefitAward();
            if (mbAward == null){
                mbAward = new MonetaryBenefitAward();
                target.setMonetaryBenefitAward(mbAward);
            }
            mbAward.setCheckAmount(checkAmount);
        }
    }
    
    protected Map getCopy(Map source) throws Exception{
        Map newMap = new HashMap();
        if (source != null && source.size() > 0) {
            Set keys = source.keySet();
            for (Iterator i=keys.iterator(); i.hasNext();) {
                Object key = i.next();
                newMap.put(key,getCopy(source.get(key)));
            }
        }
        return newMap;
    }

    protected Set getCopy(Set source) throws Exception{
        Set newSet = new HashSet();
        if (source != null && source.size() > 0) {
            for (Iterator i=source.iterator(); i.hasNext();) {
                newSet.add(getCopy(i.next()));
            }
        }
        return newSet;
    }

    protected Object getCopy(Object source) throws CopyServiceException{

        if (source == null) return null;

        if (source instanceof AbstractLookup) {
            return source;
        }
        //If the object is of type AbstractKeyedEntity create a copy and return
        else if (source instanceof AbstractEntity) { //not a lookup class
            //use the copy service to create a copy of this entity
            Object target = getPersonMergeCopyService().getCopy((AbstractEntity) source);
            return target;
        }
        else {
            return source;
        }
    }

    //TODO implement generic methods based on type and property
    public static String getAddMethod(Class parentType, Class propertyType, String property)
    {
        String methodName = null;
        if (propertyType != null) {
            if (InsurancePolicy.class.isAssignableFrom(propertyType)) {
                return "addInsurance";
            }
            if (CDDiagnosis.class.isAssignableFrom(propertyType)) {
                return "addDiagnosis";
            }
            if (CDProcedure.class.isAssignableFrom(propertyType)) {
                return "addProcedure";
            }
            if (CDCondition.class.isAssignableFrom(propertyType)) {
                return "addCondition";
            }
            if (CDDescriptor.class.isAssignableFrom(propertyType)) {
                return "addCDDescriptor";
            }            
            if (POWEpisode.class.isAssignableFrom(propertyType)) {
                return "addEpisode";
            }
            if (ReceivedEligibility.class.isAssignableFrom(propertyType)) {
                return "addReceivedSecondaryEligibility";
            }
            String name = propertyType.getName();
            return "add" + name.substring(name.lastIndexOf('.')+1);
        }
        return methodName;
    }

    public static String getRemoveMethod(Class parentType, Class propertyType, String property)
    {
        String methodName = null;
        if (propertyType != null) {
            if (InsurancePolicy.class.isAssignableFrom(propertyType)) {
                return "removeInsurance";
            }
            if (CDDiagnosis.class.isAssignableFrom(propertyType)) {
                return "removeDiagnosis";
            }
            if (CDProcedure.class.isAssignableFrom(propertyType)) {
                return "removeProcedure";
            }
            if (CDCondition.class.isAssignableFrom(propertyType)) {
                return "removeCondition";
            }
            if (CDDescriptor.class.isAssignableFrom(propertyType)) {
                return "removeCDDescriptor";
            }            
            if (POWEpisode.class.isAssignableFrom(propertyType)) {
                return "removeEpisode";
            }
            if (ReceivedEligibility.class.isAssignableFrom(propertyType)) {
                return "removeReceivedSecondaryEligibility";
            }            
            String name = propertyType.getName();
            return "remove" + name.substring(name.lastIndexOf('.')+1);
        }
        return methodName;
    }
    
    //convenient execute method with single parameter
    protected Object executeMethod(Object targetObj, String methodName, Object arg1) throws Exception
    {
        Object[] params = new Object[] { arg1 };
        return executeMethod(targetObj,methodName,params);
    }
    //convenient execute method with single parameter
    protected Object executeMethod(Object targetObj, String methodName, Object arg1, Object arg2) throws Exception
    {
        Object[] params = new Object[] {arg1, arg2};
        return executeMethod(targetObj,methodName,params);
    }
    
    //Generic execute method with multiple parameters    
    protected Object executeMethod(Object targetObj, String methodName, Object[] params) throws Exception
    {
        if (targetObj != null && methodName != null && params != null)
        {
            Class currentClass = targetObj.getClass();

            // If the user passed in a class instead of an object, use that
            if (Class.class.isAssignableFrom(targetObj.getClass()))
            {
                currentClass = (Class)targetObj;
            }

            while (currentClass != null)
            {
                try
                {
                    // Attempt to find the method on the current class
                    Method method = Reflector.findMethod(currentClass, methodName, Reflector.typesOf(params));

                    // If found, invoke the method
                    return method.invoke(targetObj, params);
                }catch (NoSuchMethodException nsmf) {
                    currentClass = currentClass.getSuperclass();
                }
            }

            // If the first parameter (i.e. the data parameter) was null, just return null.
            // Otherwise, every helper method would have to have a first parameter that took
            // an Object.
            if (params[0] == null)
            {
                return null;
            }

            // If we get here, no method could be found so throw an exception
            String className = targetObj.getClass().getName();
            if (Class.class.isAssignableFrom(targetObj.getClass()))
            {
                className = ((Class)targetObj).getName();
            }
            StringBuffer paramBuffer = new StringBuffer();
            for (int i=0; i < params.length; i++)
            {
                if (paramBuffer.length() > 0)
                {
                    paramBuffer.append(", ");
                }
                Object param = params[i];
                paramBuffer.append("param[" + i + "] class: " + param == null ? "null" : param.getClass().getName());
            }

            throw new NoSuchMethodException("Unable to find a matching '" + methodName + "' method in the '" +
                className + "' class hierarchy.  Params: " + paramBuffer.toString());
        }
        else
        {
            return null;
        }        
    }

    /**
     * Get field property to be copied
     * @param rowInfo
     * @return the field property
     */
    public static String getFieldProperty(PersonMergeRowInfo rowInfo)
    {
        return rowInfo.getMergeProperty() == null ? 
            rowInfo.getFieldProperty() : rowInfo.getMergeProperty();        
    }
    
    public static Class getType(Collection collection) {
    
        Class clazz = null;
        if (collection != null && collection.size() > 0) {
            return collection.iterator().next().getClass();
        }
        return clazz;
    }
    
    public static String getKey(AbstractEntity entity) {
        if (entity == null) 
            return null;
        else if (AbstractKeyedEntity.class.isAssignableFrom(entity.getClass())) {
            return ((AbstractKeyedEntity)entity).getEntityKey().getKeyValueAsString();
        }        
        else if (AbstractLookup.class.isAssignableFrom(entity.getClass())) {
            return String.valueOf(((AbstractLookup)entity).getIdentifier());
        }
        else {
            return null;
        }
    }
    
    /**
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet()
    {
    }

    protected static void setPersonMergeInfoMapInSession(HttpServletRequest request, Map personMergeInfoMap)
    {
        HttpSession session = request.getSession();
        session.setAttribute(PERSON_MERGE_INFO_MAP_SESSION_KEY, personMergeInfoMap);
    }

    protected static Map getPersonMergeInfoMapFromSession(HttpServletRequest request)
    {
        HttpSession session = request.getSession();
        return (Map)session.getAttribute(PERSON_MERGE_INFO_MAP_SESSION_KEY);
    }

    protected static void setPersonMergeInfoInSession(HttpServletRequest request, PersonMergeInfo personMergeInfo)
    {
        HttpSession session = request.getSession();
        session.setAttribute(PERSON_MERGE_INFO_SESSION_KEY, personMergeInfo);
    }

    protected static PersonMergeInfo getPersonMergeInfoFromSession(HttpServletRequest request)
    {
        HttpSession session = request.getSession();
        return (PersonMergeInfo)session.getAttribute(PERSON_MERGE_INFO_SESSION_KEY);
    }

    protected static void setPrimaryMergePersonInSession(HttpServletRequest request, Person primaryMergePerson)
    {
        HttpSession session = request.getSession();
        session.setAttribute(PRIMARY_MERGE_PERSON_SESSION_KEY, primaryMergePerson);
    }

    protected static Person getPrimaryMergePersonFromSession(HttpServletRequest request)
    {
        HttpSession session = request.getSession();
        return (Person)session.getAttribute(PRIMARY_MERGE_PERSON_SESSION_KEY);
    }

    protected static void setDeprecatedMergePersonInSession(HttpServletRequest request, Person DeprecatedMergePerson)
    {
        HttpSession session = request.getSession();
        session.setAttribute(DEPRECATED_MERGE_PERSON_SESSION_KEY, DeprecatedMergePerson);
    }

    protected Person getDeprecatedMergePersonFromSession(HttpServletRequest request)
    {
        HttpSession session = request.getSession();
        return (Person)session.getAttribute(DEPRECATED_MERGE_PERSON_SESSION_KEY);
    }
    public Map getPersonMergeRowInfoMetaMap()
    {
        return personMergeRowInfoMetaMap;
    }

    public void setPersonMergeRowInfoMetaMap(Map personMergeRowInfoMetaMap)
    {
        this.personMergeRowInfoMetaMap = personMergeRowInfoMetaMap;
    }

    public Map getPersonMergeRowGroupInfoMetaMap()
    {
        return personMergeRowGroupInfoMetaMap;
    }

    public void setPersonMergeRowGroupInfoMetaMap(Map personMergeRowGroupInfoMetaMap)
    {
        this.personMergeRowGroupInfoMetaMap = personMergeRowGroupInfoMetaMap;
    }    
}
