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

// Java Classes
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Date;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;

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

// Framework Classes
import gov.va.med.fw.util.StringUtils;

// ESR Classes
import gov.va.med.esr.common.infra.ImpreciseDate;
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.financials.PatientVisitSummary;
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.lookup.Indicator;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.ui.person.beans.PersonMergeRowInfoMetaData;
import gov.va.med.esr.ui.common.util.JspUtils;

/**
 * This is a helper class for all the merge actions.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public abstract class MergeActionHelper implements MergeConstants
{
    /**
     * Returns the user selection map as a String that can be used to store in a database as a
     * single field.  The String is built be appending all user selection keys and values together
     * separated by a DELIMITER.  Each key and value is stored as key=value.  If no value is
     * found, then only the key is stored.  For example, key1=value1;key2;key3=value3.
     *
     * @param userSelectionMap The user selection map
     *
     * @return The user selections as a String.
     */
    public static String getUserSelectionsAsString(Map userSelectionMap)
    {
        // Get the list of user selection keys
        Set keySet = userSelectionMap.keySet();

        // Create a list of the user selections
        List selectionList = new ArrayList();

        // Iterate through each map entry and create a list of selections
        for (Iterator iterator = keySet.iterator(); iterator.hasNext();)
        {
            String key = (String)iterator.next();
            String value = (String)userSelectionMap.get(key);

            StringBuffer singleSelectionBuffer = new StringBuffer(key);
            if (StringUtils.isNotEmpty(value))
            {
                singleSelectionBuffer.append(EQUALS);
                singleSelectionBuffer.append(value);
            }
            selectionList.add(singleSelectionBuffer.toString());
        }

        // Sort the selections to ensure they are stored in a predictable order
        Collections.sort(selectionList);

        // Initialize a StringBuffer to return the user selections in
        StringBuffer userSelectionBuffer = new StringBuffer();

        // Append all selections together while separating each one with a delimiter
        for (Iterator iterator = selectionList.iterator(); iterator.hasNext();)
        {
            if (userSelectionBuffer.length() > 0)
            {
                userSelectionBuffer.append(DELIMITER);
            }
            userSelectionBuffer.append((String)iterator.next());
        }

        // Return the user selections as a String
        return userSelectionBuffer.toString();
    }

    /**
     * Saves the user selections on the request in the user selection map.  Any previous selections
     * for the submitted tab are first removed and then all submitted selections are then saved.
     *
     * @param request The HttpServletRequest
     * @param personMergeInfoMap The person info map
     */
    public static void saveUserSelections(HttpServletRequest request, Map personMergeInfoMap)
    {
        // Get the user selection map that we need to update
        Map userSelectionMap = (Map)personMergeInfoMap.get(PERSON_USER_SELECTIONS_KEY);

        // Determine which tab we came from
        String tabName = request.getParameter(TAB_NAME_REQUEST_KEY);
        Validate.notEmpty(tabName,
            TAB_NAME_REQUEST_KEY + " request parameter must be part of the request.");

        // Remove all user selections that were previously saved for ths submitted tab.
        // All keys are prefixed with the tabName.
        Set keySet = userSelectionMap.keySet();
        for (Iterator iterator = keySet.iterator(); iterator.hasNext();)
        {
            String key = (String)iterator.next();
            if (key.startsWith(tabName))
            {
                iterator.remove();
            }
        }

        // Iterate through the submitted request parameters.  For each one that begins with the
        // tab name, we want to save the user selections.
        Map requestParameterMap = request.getParameterMap();
        keySet = requestParameterMap.keySet();
        for (Iterator iterator = keySet.iterator(); iterator.hasNext();)
        {
            String key = (String)iterator.next();
            if (key.startsWith(tabName))
            {
                // Each submission can contain 1 or 2 submissions depending on whether
                // the selection is a radio button/expand collapse entry or a check box respectively.
                String[] paramValues = (String[])requestParameterMap.get(key);

                // Iterate through each parameter
                for (int i = 0; i < paramValues.length; i++)
                {
                    String paramValue = paramValues[i];

                    // Store expand/collapse selection as is
                    if (key.endsWith(EXP_COL_ID))
                    {
                        // Store the expand collapse selection as is in the map
                        userSelectionMap.put(key, paramValue);
                    }
                    else
                    {
                        // Get the user selection key based on the paramter key and value
                        String userSelectionKey = getUserSelectionKey(key, paramValue);

                        // Store the user selection in the map
                        userSelectionMap.put(userSelectionKey, CHECKED);
                    }
                }
            }
        }
    }

    /**
     * Returns the user selection key based on the request key and value.  The user selection key is
     * a single String key that uniquely defines the user selection.
     * <p/>
     * For single value keys, the user selection key is the request key followed by an UNDERSCORE
     * followed by the request value. For example, "demographics_personal_sensitivityFlag=P" would
     * return demographics_personal_sensitivityFlag_P as the key.
     * <p/>
     * For collection value keys, the user selection key is determined the same way as the single
     * value key except any request key appended counter is removed. For example,
     * "demographics_addresses_1=12345" would return demographics_addresses_12345 as the key.
     *
     * @param requestKey The request key
     * @param requestValue The request value
     *
     * @return The user selection key.
     */
    public static String getUserSelectionKey(String requestKey, String requestValue)
    {
        // Ensure we have valid parameters
        Validate.notEmpty(requestKey, "Request key can not be empty.");
        Validate.notEmpty(requestValue, "Request value can not be empty.");

        // Process the singe value key
        if (isParameterSingleValue(requestValue))
        {
            return requestKey + UNDERSCORE + requestValue;
        }

        // Process the collection value key
        if (isParameterCollectionValue(requestValue))
        {
            // Split the key into pieces based on the UNDERSCORE character.
            String[] collectionKeyParts = requestKey.split(UNDERSCORE);

            // Add the first part to the collection key to start
            StringBuffer collectionKeyBuffer = new StringBuffer(collectionKeyParts[0]);

            // Add all other parts except the last part
            for (int i = 1; i < collectionKeyParts.length - 1; i++)
            {
                collectionKeyBuffer.append(UNDERSCORE);
                collectionKeyBuffer.append(collectionKeyParts[i]);
            }

            // Add the value
            collectionKeyBuffer.append(UNDERSCORE);
            collectionKeyBuffer.append(requestValue);

            // Return the collection key
            return collectionKeyBuffer.toString();
        }

        // We shouldn't really get here since we are checking for an empty key and value at
        // the beginning and all other values should be considered single or collection values.
        // So if we do get here, throw an exception
        throw new IllegalArgumentException("Request does not contain either a single value or " +
            "collection value with requestKey: " + requestKey + " and requestValue: " +
            requestValue + ".");
    }

    /**
     * Returns whether the request parameter value is a single value (as opposed to a collection
     * value). Single values are defined as values that are no collection values.
     *
     * @param parameterValue The parameter value to check
     *
     * @return True if parameter is a single value or false if not.
     */
    public static boolean isParameterSingleValue(String parameterValue)
    {
        // Empty strings are not considered single values
        if (StringUtils.isEmpty(parameterValue))
        {
            return false;
        }


        // Return the opposite of whether the parameter is a collection value
        return !isParameterCollectionValue(parameterValue);
    }

    /**
     * Returns whether the request parameter value is a collection value (as opposed to a single
     * value). Collection values are defined as when the parameterValue is a valid Integer.
     *
     * @param parameterValue The parameter value to check
     *
     * @return True if parameter is a collection value or false if not.
     */
    public static boolean isParameterCollectionValue(String parameterValue)
    {
        // Empty strings are not considered collection values
        if (StringUtils.isEmpty(parameterValue))
        {
            return false;
        }

        // If the value contains only digits, it is considered a collection
        if (parameterValue.matches(DIGIT_REGEX))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    // ******************************* Methods to MonetaryBenefits ************************* //
    
    public static boolean getAidAndAttendance(Person person, PersonMergeRowInfoMetaData metaData) {
        MonetaryBenefitAward mBenefitAward = person.getMonetaryBenefitAward();
        MonetaryBenefit mBenefit = (mBenefitAward != null) ? mBenefitAward.getMonetaryBenefitByType(MonetaryBenefitType.CODE_AID_AND_ATTENDANCE) : null;
        return (mBenefit != null);
    }
    
    public static boolean getHouseBound(Person person, PersonMergeRowInfoMetaData metaData) {
        MonetaryBenefitAward mBenefitAward = person.getMonetaryBenefitAward();
        MonetaryBenefit mBenefit = (mBenefitAward != null) ? mBenefitAward.getMonetaryBenefitByType(MonetaryBenefitType.CODE_HOUSEBOUND) : null;
        return (mBenefit != null);
    }
    
    public static boolean getVAPension(Person person, PersonMergeRowInfoMetaData metaData) {
        MonetaryBenefitAward mBenefitAward = person.getMonetaryBenefitAward();
        MonetaryBenefit mBenefit = (mBenefitAward != null) ? mBenefitAward.getMonetaryBenefitByType(MonetaryBenefitType.CODE_VA_PENSION) : null;
        return (mBenefit != null);
    }
    
    public static boolean getUnemployable(Person person, PersonMergeRowInfoMetaData metaData) {
        Boolean unemployable = (person.getServiceConnectionAward() != null) ? person.getServiceConnectionAward().getUnemployable() : null;
        return (unemployable != null) ? unemployable.booleanValue() : false;
    }
    
    public static boolean getDisabilityCompensation(Person person, PersonMergeRowInfoMetaData metaData) {
        MonetaryBenefitAward mBenefitAward = person.getMonetaryBenefitAward();
        MonetaryBenefit mBenefit = (mBenefitAward != null) ? mBenefitAward.getMonetaryBenefitByType(MonetaryBenefitType.CODE_DISABILITY_COMPENSATION) : null;
        return (mBenefit != null);
    }

    public static List getPatientVisitSummaries(Person person, PersonMergeRowInfoMetaData metaData) {
        List facilities = new ArrayList();
        
        Map facilityPatientVisitSummary = new LinkedHashMap();
        for(Iterator iter=person.getPatientVisitSummaries().values().iterator(); iter.hasNext();) {
            PatientVisitSummary pvs = (PatientVisitSummary)iter.next();
            VAFacility fac = (pvs != null) ? pvs.getFacilityVisited() : null;
            if(fac != null) {
                List list = (List)facilityPatientVisitSummary.get(fac);
                if(list == null) {
                    list = new ArrayList();
                    facilityPatientVisitSummary.put(fac,list);
                }
                list.add(pvs);
            }
        }

        for(Iterator iter=person.getFacilities().iterator(); iter.hasNext();) {
            VAFacility site = (VAFacility)iter.next();
            List pvsList = (List)facilityPatientVisitSummary.get(site);
 
            FacilityFeeBasis facFeeBasis = new FacilityFeeBasis(site, person.getEntityKey());
            facFeeBasis.setPatientVisitSummary(pvsList);
            facFeeBasis.setFeeBasis(new ArrayList(person.getFeeBasis(site)));
            facilities.add(facFeeBasis);
        }
        return facilities;
    }
    
    /**
     * Displays a date with the timestamp included.
     *
     * @param date The date to display
     * @param metaData The person merge row info metadata
     * @return The date formatted with the timestamp.
     */
    public static String formatTimestamp(Date date, PersonMergeRowInfoMetaData metaData)
    {
        return JspUtils.displayDate(date, null, true);
    }
    
    public static String formatTimestamp(ImpreciseDate date, PersonMergeRowInfoMetaData metaData)
    {
        return JspUtils.displayDate(date, null, true);
    }
    
    public static String formatTimestamp(String date, PersonMergeRowInfoMetaData metaData)
    {
        return date;
    }
    
    public static String formatCurrency(BigDecimal value, PersonMergeRowInfoMetaData metaData) {
        return "$" + value==null ? "" : NumberFormat.getCurrencyInstance().format(value.doubleValue());
    }
    
    public static String formatCurrency(Integer value, PersonMergeRowInfoMetaData metaData) {
        return "$" + value==null ? "" : NumberFormat.getCurrencyInstance().format(value.doubleValue());
    }
    public static boolean formatYesNo(Object obj, PersonMergeRowInfoMetaData metaData) {
        if (obj != null)
        {
            if (Boolean.class.isAssignableFrom(obj.getClass()))
            {
                return ((Boolean)obj).booleanValue();
            }
            else
            {
                if (Indicator.class.isAssignableFrom(obj.getClass()))
                {
                    return ((Indicator)obj).toBoolean().booleanValue();
                }
                else
                {
                    return (obj != null);
                }
            }
        }
        return false;
    }

    public static boolean isNotNull(Object obj, PersonMergeRowInfoMetaData metaData) {
        return (obj != null);
    }
}