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

// Java Classes
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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



// Library Classes
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.MessageResources;
import org.apache.struts.Globals;



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

import gov.va.med.esr.common.model.person.PersonLockedReason;
// ESR Classes
import gov.va.med.esr.common.model.person.PersonMergeInfo;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.ui.person.beans.PersonMergeRowInfo;

/**
 * This struts action is used to support the Person Merge Detail page (all tabs).
 *
 * @author Andrew Pach
 * @version 3.0
 */
public class PersonMergeDetailAction extends PersonMergeAction implements MergeConstants
{
    // Validation constants
    public static final int ERROR_FOUND = 0;
    public static final int CHILD_SELECTION_NEEDED = 1;
    public static final int CHILD_SELECTION_NOT_NEEDED = 2;

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

    /**
     * Discards all the user's selections.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward discardAllSelections(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Get the person merge info map from session
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);

        // Get the person mege info from the map
        PersonMergeInfo personMergeInfo =
            (PersonMergeInfo)personMergeInfoMap.get(PERSON_MERGE_INFO_KEY);
        if (personMergeInfo == null)
        {
            throw new Exception("Unable to obtain PersonMergeInfo from Session");
        }

        // Create a clone for doing an update
        PersonMergeInfo updatePersonMergeInfo = (PersonMergeInfo)personMergeInfo.clone();

        // Remove user selections
        updatePersonMergeInfo.setMergeSelections(null);
        updatePersonMergeInfo.setMergeStartDate(null);
        updatePersonMergeInfo.setMergeEndDate(null);
        updatePersonMergeInfo.setRecordLocked(Boolean.FALSE);

        // Update the PersonMergeInfo
        updatePersonMergeInfo =
            getPersonMergeService().updatePersonMergeInfo(updatePersonMergeInfo);

        // Remove the lock information
        setLockedMergeInfoEntityKey(request, null);

        if (logger.isDebugEnabled())
        {
            logger.debug("Unlocking person merge (from discardSelections) record with ID " + personMergeInfo.getEntityKey().getKeyValueAsString() +
                " on session " + request.getSession().getId() + " and lock key removed from session.");
        }

        // Remove the person merge info from session.
        setPersonMergeInfoMapInSession(request, null);

        // We're done with the merge so forward to the worklist
        return mapping.findForward(FORWARD_PERSON_MERGE_WORKLIST);
    }

    /**
     * Saves the users selections to be merged later.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward saveAndMergeLater(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Get the person merge info map from session
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);

        // Save the user selections
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Get the person mege info from the map
        PersonMergeInfo personMergeInfo =
            (PersonMergeInfo)personMergeInfoMap.get(PERSON_MERGE_INFO_KEY);
        if (personMergeInfo == null)
        {
            throw new Exception("Unable to obtain PersonMergeInfo from Session");
        }

        // Create a clone for doing an update
        PersonMergeInfo updatePersonMergeInfo = (PersonMergeInfo)personMergeInfo.clone();

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

        // Store user selections
        updatePersonMergeInfo
            .setMergeSelections(MergeActionHelper.getUserSelectionsAsString(userSelectionMap));
       
        // CCR # REEG_00009624 (Merge Tool Issue): lock on merge record should be maintained on save.
        //                                       : commented out record lock false setting and added true setting.
        // updatePersonMergeInfo.setRecordLocked(Boolean.FALSE);       
        updatePersonMergeInfo.setRecordLocked(Boolean.TRUE); 
        
        // Update the PersonMergeInfo
        updatePersonMergeInfo =
            getPersonMergeService().saveInProgress(updatePersonMergeInfo);

        // Remove the lock information
        setLockedMergeInfoEntityKey(request, null);

        if (logger.isDebugEnabled())
        {
            logger.debug("Unlocking person merge (from saveAndMergeLater) record with ID " + personMergeInfo.getEntityKey().getKeyValueAsString() +
                " on session " + request.getSession().getId() + " and lock key removed from session.");
        }

        // Remove the person merge info from session.
        setPersonMergeInfoMapInSession(request, null);

        // We're done with the merge so forward to the worklist
        return mapping.findForward(FORWARD_PERSON_MERGE_WORKLIST);
    }

    /**
     * Performs the actual merge.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward mergeRecordsNow(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Save the user selections
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Validate the user selections to ensure all required selection are selected
        String tabForward = validateUserSelections(request);
        if (StringUtils.isNotEmpty(tabForward))
        {
            // Convert the tab name into the appropriate forward
            // (e.g. financials --> displayFinancials)
            String forward = "display" + tabForward.substring(0, 1).toUpperCase() +
                tabForward.substring(1, tabForward.length());

            // A missing field was found so forward to the tab that has the errors
            return mapping.findForward(forward);
        }

        // Populate default selections
        populateDefaultSelections(request);

        // Merge the two records into one
        Person mergedPerson = mergeRecords(mapping, form, request, response);

        // Update the Merged Person
        // This method will also update the PersonMergeInfo object.
        getPersonMergeService().updatePerson(mergedPerson);

        // Remove the lock information
        setLockedMergeInfoEntityKey(request, null);

        if (logger.isDebugEnabled())
        {
            logger.debug("Unlocking person merge (from mergeRecordsNow) record for person ID " +
                mergedPerson.getEntityKey().getKeyValueAsString() +
                " on session " + request.getSession().getId() + " and lock key removed from session.");
        }

        // Remove the person merge info from session.
        setPersonMergeInfoMapInSession(request, null);

        // Update the veteran header to show that a merge isn't pending if the selected veteran just got merged.
        updateHeader(request);

        // We're done with the merge so forward to the worklist
        return mapping.findForward(FORWARD_PERSON_MERGE_WORKLIST);
    }

    /**
     * Displays the initial page.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward display(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Forward to the eligibility tab
        return mapping.findForward(FORWARD_DISPLAY_ELIGIBILITY);
    }

    /**
     * Cancels this page (and all selections) and goes back to the worklist page.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward cancel(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Get the person merge info map from session
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);

        // Get the person mege info from the map
        PersonMergeInfo personMergeInfo =
            (PersonMergeInfo)personMergeInfoMap.get(PERSON_MERGE_INFO_KEY);
        if (personMergeInfo == null)
        {
            throw new Exception("Unable to obtain PersonMergeInfo from Session");
        }

        // Create a clone for doing an update
        PersonMergeInfo updatePersonMergeInfo = (PersonMergeInfo)personMergeInfo.clone();

        // Store user selections
        updatePersonMergeInfo.setRecordLocked(Boolean.FALSE);

        // Update the PersonMergeInfo
        updatePersonMergeInfo =
            getPersonMergeService().updatePersonMergeInfo(updatePersonMergeInfo);

        // Remove the lock information
        setLockedMergeInfoEntityKey(request, null);

        if (logger.isDebugEnabled())
        {
            logger.debug("Unlocking person merge (from cancel) record with ID " + personMergeInfo.getEntityKey().getKeyValueAsString() +
                " on session " + request.getSession().getId() + " and lock key removed from session.");
        }

        // Remove the person merge info from session.
        setPersonMergeInfoMapInSession(request, null);

        // We're done with the merge so forward to the worklist
        return mapping.findForward(FORWARD_PERSON_MERGE_WORKLIST);
    }

    /**
     * Selects the Eligibility tab.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward selectEligibility(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Save the user selections
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Forward to the eligibility tab
        return mapping.findForward(FORWARD_DISPLAY_ELIGIBILITY);
    }

    /**
     * Selects the Demographics tab.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward selectDemographics(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Save the user selections
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Forward to the demographics tab
        return mapping.findForward(FORWARD_DISPLAY_DEMOGRAPHICS);
    }

    /**
     * Selects the Military Service tab.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward selectMilitaryService(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Save the user selections
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Forward to the military service tab
        return mapping.findForward(FORWARD_DISPLAY_MILITARY_SERVICE);
    }

    /**
     * Selects the Financials tab.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward selectFinancials(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Save the user selections
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Forward to the financials tab
        return mapping.findForward(FORWARD_DISPLAY_FINANCIALS);
    }

    /**
     * Selects the Facility tab.
     *
     * @param mapping Struts action mapping for this action
     * @param form Struts form bean for this action
     * @param request The Http Request
     * @param response The Http Response
     *
     * @return A struts action forward for where we will go next.
     * @throws Exception If there are any errors during processing.
     */
    public ActionForward selectFacility(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response) throws Exception
    {
        // Save the user selections
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);
        MergeActionHelper.saveUserSelections(request, personMergeInfoMap);

        // Forward to the facility tab
        return mapping.findForward(FORWARD_DISPLAY_FACILITY);
    }

    /**
     * Validates that all user selections have been made across all tabs.  If the user
     * didn't make a selection, a customized error message is added.  Each tab is checked
     * one at a time and the first tab that encountered an error is returned for its errors
     * to be highlighted.  If no errors are encountered, null is returned and no errors are added.
     *
     * @param request The HttpServletRequest
     * @return The tab name that encountered errors or null if no errors were encountered.
     * @throws Exception if any problems were encountered during the validation processing.
     */
    protected String validateUserSelections(HttpServletRequest request) throws Exception
    {
        // Get the message resources in case we need to generate error messages
        MessageResources messageResources =
            ((MessageResources)request.getAttribute(Globals.MESSAGES_KEY));

        // Get the person merge info map from session
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);

        // Get the user selection map
        Map userSelectionMap = (Map)personMergeInfoMap.get(PERSON_USER_SELECTIONS_KEY);

        // Process each tab's worth of data one at a time
        for (int i = 0; i < TABS.length; i++)
        {
            // Get one tab
            String tabName = (String)TABS[i];

            // Keep track whether an error was found or not
            boolean errorFound = false;

            // Get the list of sections for this tab
            List sectionList = (List)personMergeInfoMap.get(tabName);
            for (Iterator sectionIter = sectionList.iterator(); sectionIter.hasNext();)
            {
                // Get a section
                PersonMergeRowInfo sectionInfo = (PersonMergeRowInfo)sectionIter.next();

                // Validate this section
                int returnStatus = validateGroup(sectionInfo, messageResources, userSelectionMap, request);

                // If an error was found, set a flag
                if (returnStatus == ERROR_FOUND)
                {
                    errorFound = true;
                }
            }

            // If an error was found, return this tab name as the forward
            if (errorFound)
            {
                return tabName;
            }
        }

        // No errors were found so return null
        return null;
    }

    /**
     * Recursively validates a group and its children.  If a child is a group, this method
     * is called with that child.
     *
     * @param groupInfo The group information
     * @param messageResources The message resources
     * @param userSelectionMap The user selection map
     * @param request The HttpServletRequest
     * @return A status of ERROR_FOUND if an error was encountered, CHILD_SELECTION_NEEDED
     * if a user selection was needed for a child, but no error was found, or
     * CHILD_SELECTION_NOT_NEEDED if the children did not require user input.  If no children
     * require input, then the section could be considered required.
     */
    protected int validateGroup(PersonMergeRowInfo groupInfo, MessageResources messageResources,
        Map userSelectionMap, HttpServletRequest request)
    {
        // Keep track whether an error was found or not
        boolean errorFound = false;

        // See if a selection is needed for this group.  Remember that it might be
        // an optional override selection.
        if (groupInfo.isSelectionNeeded())
        {
            // If only input type is present or the user made a selection, consider this
            // group selection available and fulfilled.
            if ((!groupInfo.areBothInputTypesPresent()) ||
                (userSelectionMap.containsKey(groupInfo.getPrimaryFieldKey())) ||
                (userSelectionMap.containsKey(groupInfo.getDeprecatedFieldKey())))
            {
                // A selection is made at the group level so return no error
                return CHILD_SELECTION_NEEDED;
            }
        }

        // No selection was made at the group level so check the children
        boolean childSelectionNeeded = false;
        List children = groupInfo.getChildren();
        for (int j = 0; j < children.size(); j++)
        {
            // Get a child
            PersonMergeRowInfo childInfo = (PersonMergeRowInfo)children.get(j);

            // If our child is a parent as well, call this method with the child to recurse.
            if (childInfo.isParentRow())
            {
                // Get the status of the child and set the status flags appropriately.
                int status = validateGroup(childInfo, messageResources, userSelectionMap, request);
                if ((status == ERROR_FOUND) && (!errorFound))
                {
                    errorFound = true;
                }
                if ((status == CHILD_SELECTION_NEEDED) && (!childSelectionNeeded))
                {
                    childSelectionNeeded = true;
                }
            }
            else
            {
                // See if a selection is needed for this child.
                if (childInfo.isSelectionNeeded())
                {
                    // Note that we found at least one child selection that is needed
                    childSelectionNeeded = true;

                    // See if the child selection was made
                    if ((childInfo.areBothInputTypesPresent()) &&
                        ((!userSelectionMap.containsKey(childInfo.getPrimaryFieldKey())) &&
                         (!userSelectionMap.containsKey(childInfo.getDeprecatedFieldKey()))))
                    {
                        // Note that an error was found in this group
                        errorFound = true;

                        // Get the field from the resource bundle
                        String fieldLabel =
                            messageResources.getMessage(childInfo.getFieldMessageKey());
                        String fieldName = childInfo.getFieldName();

                        // A selection is needed, but wasn't made so add an error message
                        addActionMessageForField(request, ERROR_SELECTION_REQUIRED_KEY,
                            fieldLabel, fieldName);
                    }
                }
            }
        }

        // If no errors were found yet and no children required a selection
        // and if the group requires a selection, then produce an error at the
        // group level.
        if (!errorFound && !childSelectionNeeded && groupInfo.isSelectionNeeded() &&
            groupInfo.areBothInputTypesPresent())
        {
            // Note that an error was found
            errorFound = true;

            // Get the field from the resource bundle
            String fieldLabel =
                messageResources.getMessage(groupInfo.getFieldMessageKey());
            String fieldName = groupInfo.getFieldName();

            // Add the iterator count if needed
            if ((groupInfo.isParentRow()) && (groupInfo.getCount() >= 0))
            {
                fieldLabel = messageResources
                    .getMessage(groupInfo.getGroupFieldMessageKey()) + " " +
                    (groupInfo.getCount() + 1);
                fieldName = fieldName + UNDERSCORE + groupInfo.getCount();
            }

            // A selection is needed at the group level, but wasn't made so add an error message
            addActionMessageForField(request, ERROR_SELECTION_REQUIRED_KEY,
                fieldLabel, fieldName);
        }

        // Return if an error was found
        if (errorFound)
        {
            return ERROR_FOUND;
        }

        // Return if a child selected was needed and fulfilled
        if (childSelectionNeeded)
        {
            return CHILD_SELECTION_NEEDED;
        }

        // Return that a child selection was not needed
        return CHILD_SELECTION_NOT_NEEDED;
    }

    /**
     * Populates the default selections that were removed from the UI since no user selection
     * was required.  For example, if both the primary and deprecated entries are the same,
     * we remove the check boxes from the UI and will make the selection now.
     *
     * @param request The HttpServletRequest
     * @throws Exception if any problems were encountered
     */
    protected void populateDefaultSelections(HttpServletRequest request) throws Exception
    {
        // Get the person merge info map from session
        Map personMergeInfoMap = getPersonMergeInfoMapFromSession(request);

        // Get the user selection map
        Map userSelectionMap = (Map)personMergeInfoMap.get(PERSON_USER_SELECTIONS_KEY);

        // Process each tab's worth of data one at a time
        for (int i = 0; i < TABS.length; i++)
        {
            // Get one tab
            String tabName = (String)TABS[i];

            // Get the list of sections for this tab
            List sectionList = (List)personMergeInfoMap.get(tabName);
            for (Iterator sectionIter = sectionList.iterator(); sectionIter.hasNext();)
            {
                // Get a section
                PersonMergeRowInfo sectionInfo = (PersonMergeRowInfo)sectionIter.next();

                // Populate default group and children selections
                populateDefaultGroupSelections(sectionInfo, userSelectionMap);
            }
        }
    }

    /**
     * Populates the default selections for the group and the children.  This method will
     * recurse on the children when they are parents as well.
     *
     * @param groupInfo The group information
     * @param userSelectionMap The user selection map
     */
    protected void populateDefaultGroupSelections(PersonMergeRowInfo groupInfo, Map userSelectionMap)
    {
        // See if a selection is defaulted for this section.
        if (groupInfo.isSelectionDefaulted())
        {
            // Default the selection
            userSelectionMap.put(groupInfo.getPrimaryFieldKey(), CHECKED);
        }

        // Check the children as well
        List children = groupInfo.getChildren();
        for (int j = 0; j < children.size(); j++)
        {
            // Get a child
            PersonMergeRowInfo childInfo = (PersonMergeRowInfo)children.get(j);

            // If the child is a parent, recurse by calling this method again with the child
            if (childInfo.isParentRow())
            {
                populateDefaultGroupSelections(childInfo, userSelectionMap);
            }
            else
            {
                // See if a selection is needed for this child.
                if (childInfo.isSelectionDefaulted())
                {
                    // Default the selection
                    userSelectionMap.put(childInfo.getPrimaryFieldKey(), CHECKED);
                }
            }
        }
    }

    /**
     * Initialize and get the Struts lookup dispatch method map hashmap.
     *
     * @return the method map
     * @see org.apache.struts.actions.LookupDispatchAction#getKeyMethodMap()
     */
    protected Map getKeyMethodMap()
    {
        Map map = new HashMap();
        map.put("button.display", "display");
        map.put("button.person.discardAllSelections", "discardAllSelections");
        map.put("button.person.saveAndMergeLater", "saveAndMergeLater");
        map.put("button.person.mergeRecordsNow", "mergeRecordsNow");
        map.put("button.person.eligibility", "selectEligibility");
        map.put("button.person.demographics", "selectDemographics");
        map.put("button.person.militaryService", "selectMilitaryService");
        map.put("button.person.financials", "selectFinancials");
        map.put("button.person.facility", "selectFacility");
        map.put("button.cancel", "cancel");
        return map;
    }
}
