/********************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.esr.ui.common.action;

// Java Classes
import java.util.List;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;

// Libraries Classes
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMessage;

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

// ESR Classes
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.lookup.State;
import gov.va.med.esr.common.model.lookup.ZipCode;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.UnknownLookupCodeException;

/**
 * This struts action is used to provide address validation routines to actions that require it.
 * The stateValid and zipCodeMatchesCityAndState methods both use reflection given that Medicare Part B
 * uses different address fields and requires the same validation.  See the Javadoc for these methods
 * for details on what parameters to pass.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public abstract class AddressValidationAction extends PersonAbstractAction
{
    // Properties
    public static final String COUNTRY_INITIALLY_NULL_MESSAGE = "message.address.countryInitiallyNull";
    public static final String INVALID_STATE_MESSAGE = "errors.address.invalidState";
    public static final String STATE_CODE_SUBSTITUTION_MESSAGE = "message.address.stateCodeSubstitution";
    public static final String CITY_STATE_NO_MATCH_ZIP_CODE_MESSAGE = "errors.address.cityStateNoMatchZipCode";
    public static final String START_DATE_NULL="errors.address.nullstartDate";
    public static final String END_DATE_NULL="errors.address.nullendDate";
    
    // UI Fields
    public static final String CITY_FIELD = "city";
    public static final String CITY2_FIELD = "city2";
    public static final String STATE_FIELD = "state";
    public static final String STATE2_FIELD = "state2";
    public static final String ZIPCODE_FIELD = "zipCode";
    public static final String ZIPCODE2_FIELD = "zipCode2";
    public static final String START_DATE_FIELD="startDate";
    public static final String END_DATE_FIELD="endDate";

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

    /**
     * Checks if the country is null for the passed in address.  If so, an informational message
     * is added informing the user as such.
     *
     * @param address The address to check
     * @param request The HttpServletRequest
     */
    public void validateCountryNull(Address address, HttpServletRequest request)
    {
        if (address != null)
        {
            if (address.getCountry() == null)
            {
                addInformationMessage(request, COUNTRY_INITIALLY_NULL_MESSAGE);
            }
        }
    }

    /**
     * Verifies that the state is valid.
     *
     * @param addressForm The address form that has the state filled in.
     * @param formClass The name of the class that has getters and setters for the state.
     * @param stateFieldName The name of the field for getting and setting the state.
     * @param forward An action forward that we can set an additional "state code substituted" message key to.
     *        This should be a new instance of the "successful update" forward
     *        (e.g. forward = new ActionForward(successForward);).
     * @param request The HttpServletRequest
     * @return True if the state is valid or false if not.
     * @throws Exception if any problems were encountered.
     */
    public boolean stateValid(AddressForm addressForm, Class formClass, String stateFieldName, ActionForward forward,
        HttpServletRequest request) throws Exception
    {
        // Get a handle to the lookup service
        LookupService lookupService = getLookupService();

        // Get a handle to the getter and setter methods on the passed in form for the passed in state field name.
        Class clazz = Class.forName(formClass.getName());
        Method getterMethod = clazz.getDeclaredMethod(
            "get" + StringUtils.capitalize(stateFieldName), new Class[] {});
        Method setterMethod = clazz.getDeclaredMethod(
            "set" + StringUtils.capitalize(stateFieldName), new Class[] {String.class});

        // Get the currently entered state
        String stateName = (String)getterMethod.invoke(addressForm, new Object[] {});

        // Validate the state
        if (!StringUtils.isBlank(stateName))
        {
            State state = null;
            try
            {
                // First, lookup by code.  If found, don't do anything else since the code
                // will be persisted.
                state = lookupService.getStateByCode(stateName);
            }
            catch (UnknownLookupCodeException ex)
            {
                // Leave state null
            }

            if (state == null)
            {
                // The code wasn't found.  Try looking up the long name.
                state = lookupService.getStateByName(stateName);
                if (state == null)
                {
                    // The state is invalid so display an error message
                    addActionMessageForField(request, new ActionMessage(INVALID_STATE_MESSAGE), stateFieldName);
                    return false;
                }
                else
                {
                    // The state is valid, but the long form was entered.
                    // Substitute the code and add a message to inform the user.
                    setterMethod.invoke(addressForm, new Object[] {state.getPostalName()});

                    // ***** REVISIT *****
                    // We might want to make the addition of additional message keys handled in a special class.
                    // It should read the path, parse it, break it into its pieces, and add the message type and key
                    // appropriately.

                    // Add an extra message for the state substitution
                    if (forward != null)
                    {
                        forward.setPath(forward.getPath() + "," + STATE_CODE_SUBSTITUTION_MESSAGE);
                    }
                }
            }
        }

        // State is valid
        return true;
    }

    /**
     * Validates that the city and state entered matches the zip code.
     *
     * @param addressForm The address form that has the state filled in.
     * @param formClass The name of the class that has getters and setters for the city, state, and zip code.
     * @param cityFieldName The name of the field for getting and setting the city.
     * @param stateFieldName The name of the field for getting and setting the state.
     * @param zipCodeFieldName The name of the field for getting and setting the zip code.
     *
     * @param request The HttpServletRequest
     * @return True if the zip code matches the city/state or false if not.
     * @throws Exception If any problems were encountered
     */
    public boolean zipCodeMatchesCityAndState(AddressForm addressForm, Class formClass,
        String cityFieldName, String stateFieldName, String zipCodeFieldName, HttpServletRequest request)
        throws Exception
    {
        // Get a handle to the lookup service
        LookupService lookupService = getLookupService();

        // Get a handle to the getter and setter methods on the passed in form for the passed in state field name.
        Class clazz = Class.forName(formClass.getName());
        Method cityGetterMethod = clazz.getDeclaredMethod(
            "get" + StringUtils.capitalize(cityFieldName), new Class[] {});
        Method stateGetterMethod = clazz.getDeclaredMethod(
            "get" + StringUtils.capitalize(stateFieldName), new Class[] {});
        Method zipCodeGetterMethod = clazz.getDeclaredMethod(
            "get" + StringUtils.capitalize(zipCodeFieldName), new Class[] {});

        // Get the currently entered city, state, and zip code
        String cityName = (String)cityGetterMethod.invoke(addressForm, new Object[] {});
        String stateCode = (String)stateGetterMethod.invoke(addressForm, new Object[] {});
        String zipCode = (String)zipCodeGetterMethod.invoke(addressForm, new Object[] {});

        // Validate that the city/state combination matches the entered zip code
        // Note that this validation needs to occur after any possible state substitution has occurred
        // since the zip code lookup is based on state code and not the long state name.
        if ((!StringUtils.isBlank(cityName)) && (!StringUtils.isBlank(stateCode)) &&
            (!StringUtils.isBlank(zipCode)))
        {
            // Lookup the zip code information.  Only validate the city/state matching the zip code
            // if the zip code was found.  If the zip code wasn't found, then just allow
            // anything the user entered for city/state.
            ZipCode zipCodeObject = lookupService.getPartialZipCodeByCode(zipCode);
            if (zipCodeObject != null)
            {
                List zipCodes = lookupService.getPartialZipCodeByCityState(cityName, stateCode);
                boolean invalidZipCode = true;
                for (int i=0; ((i < zipCodes.size()) && (invalidZipCode)); i++)
                {
                    ZipCode zipCodeObj = (ZipCode)zipCodes.get(i);
                    if (zipCode.equals(zipCodeObj.getZipCode()))
                    {
                        invalidZipCode = false;
                    }
                }

                if (invalidZipCode)
                {
                    // The zip code doesn't match the city/state entered so display an error message
                    addActionMessageForField(request, new ActionMessage(CITY_STATE_NO_MATCH_ZIP_CODE_MESSAGE),
                        zipCodeFieldName);
                    return false;
                }
            }
        }

        // Valid zip code
        return true;
    }
}