/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.ui.taglib;

// Java classes
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.jsp.JspException;

// Library classes
import org.apache.struts.taglib.html.OptionsTag;
import org.apache.struts.taglib.html.SelectTag;
import org.apache.struts.taglib.html.Constants;
import org.apache.struts.taglib.TagUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.web.context.WebApplicationContext;

// Framework classes
import gov.va.med.fw.ui.DelegatingActionUtils;
import gov.va.med.fw.model.lookup.AbstractNamedActiveLookup;
import gov.va.med.fw.model.lookup.Lookup;
import gov.va.med.esr.ui.common.service.LookupCacheService;

/**
 * Tag handler for the ESR Code options tag.
 *
 * @author Muddaiah Ranga
 * @author Andrew Pach
 * @version 3.0
 */
public class CustomOptionsTag extends OptionsTag
{
    // A map to hold the values and their respective labels
    protected Map valueToLabelMap = new HashMap();

    /**
     * An instance of serialVersionUID
     */
    private static final long serialVersionUID = 7621136698690373048L;

    /**
     * Return an iterator for the option labels or values, based on our configured properties. If the collection in not
     * found, then calles the lookupSerrvice to get the new collection. Note: If the collection is found in any of the
     * scopes, it will return an iterator for that collection otherwise gets the collection from the LookupService.
     *
     * @param col Name of the bean attribute (if any)
     * @param prop Name of the bean property (if any)
     *
     * @throws JspException if an error occurs
     */
    protected Iterator getIterator(String col, String prop) throws JspException
    {
        Iterator iter = null;
        try
        {
            // Try to get the default iterator
            iter = super.getIterator(col, prop);
        }
        catch (JspException jspEx)
        {
            // If we weren't able to get the default iterator, get a custom iterator that will lookup
            // all values from the lookup cache service.
            try
            {
                WebApplicationContext ac =
                    DelegatingActionUtils.findRequiredWebApplicationContext(pageContext.getServletContext());
                LookupCacheService lookupCacheService =
                    (LookupCacheService)ac.getBean(LookupCacheService.LOOKUP_CACHE_SERVICE);
                Collection list = lookupCacheService.getFromCache(collection);
                if (list != null)
                {
                    iter = list.iterator();
                }
            }
            catch (Exception ex)
            {
                throw new JspException("Error while getting lookup data.", ex);
            }
        }
        return iter;
    }

    /**
     * Process the end of this tag.
     *
     * @throws JspException if a JSP exception has occurred
     */
    public int doEndTag() throws JspException
    {
        // Call the parent's standard processing
        int returnValue = super.doEndTag();

        // Acquire the select tag we are associated with
        SelectTag selectTag = (SelectTag)pageContext.getAttribute(Constants.SELECT_KEY);
        if (selectTag == null)
        {
            throw new JspException(messages.getMessage("optionsTag.select"));
        }

        // Create a string buffer to hold the inactive option
        StringBuffer optionBuffer = new StringBuffer();

        // Get the values from the select tag
        String selectValues[] = getSelectValues(selectTag);

        // Iterate through each select value and see if it has already been added
        if (selectValues != null)
        {
            for (int i = 0; i < selectValues.length; i++)
            {
                // Get a select value and see if it is in the map
                String selectValue = selectValues[i];
                if (valueToLabelMap.get(selectValue) == null)
                {
                    try
                    {
                        // We found a select value that is not in the option list.  We need to lookup its description
                        // and add it as an option since it is inactive (i.e. not returned in the findAll method).
                        WebApplicationContext ac =
                            DelegatingActionUtils.findRequiredWebApplicationContext(pageContext.getServletContext());
                        LookupCacheService lookupCacheService =
                            (LookupCacheService)ac.getBean(LookupCacheService.LOOKUP_CACHE_SERVICE);
                        Lookup lookup = lookupCacheService.getByCodeFromCache(collection, selectValue);
                        if (lookup != null)
                        {
                            // Append "[Inactive] " text to description only for inactive codes.
                            AbstractNamedActiveLookup activeLookup = lookup instanceof AbstractNamedActiveLookup? 
                                                                        (AbstractNamedActiveLookup) lookup : null;
                            String flag=activeLookup != null && !activeLookup.isActive() ? "[Inactive] " : "";
                            String description = lookup.getDescription() == null ? "" : lookup.getDescription();
                            addOption(optionBuffer, selectValue,  flag + description, selectTag.isMatched(selectValue));
                        }
                    }
                    catch(ClassNotFoundException classCastEx) {
                        //This can happen only when the "collection" is not a Lookup class. Don't add this selected value.
                    }
                    catch (Exception ex)
                    {
                        throw new JspException("Error while getting lookup data.", ex);
                    }
                }
            }
        }

        // Write this option to the output
        TagUtils.getInstance().write(pageContext, optionBuffer.toString());

        // Return the parent's return value
        return returnValue;
    }

    /**
     * Gets a list of values that are defined in the select tag.  These values are computed in the SelectTag in a
     * "match" array, but this array is not made available to us so we need to recalculate the values here ourselves.
     *
     * @throws JspException if the values could not be determined.
     */
    protected String[] getSelectValues(SelectTag selectTag) throws JspException
    {
        // Create an array to hold the return values
        String[] returnValues;

        // Get the select tag properties we need to get the values
        String selectValue = selectTag.getValue();
        String selectName = selectTag.getName();
        String selectProperty = selectTag.getProperty();

        if (selectValue != null)
        {
            // A value was specified directly so set it.
            returnValues = new String[1];
            returnValues[0] = selectValue;
        }
        else
        {
            // We need to lookup the value so start by getting the bean of the specified name
            Object bean = TagUtils.getInstance().lookup(pageContext, selectName, null);
            if (bean == null)
            {
                JspException e = new JspException(messages.getMessage("getter.bean", name));
                TagUtils.getInstance().saveException(pageContext, e);
                throw e;
            }

            try
            {
                // Get the values from the bean for the selected property
                returnValues = BeanUtils.getArrayProperty(bean, selectProperty);
                if (returnValues == null)
                {
                    returnValues = new String[0];
                }
            }
            catch (IllegalAccessException e)
            {
                TagUtils.getInstance().saveException(pageContext, e);
                throw new JspException(messages.getMessage("getter.access", property, name));

            }
            catch (InvocationTargetException e)
            {
                Throwable t = e.getTargetException();
                TagUtils.getInstance().saveException(pageContext, t);
                throw new JspException(messages.getMessage("getter.result", property, t.toString()));

            }
            catch (NoSuchMethodException e)
            {
                TagUtils.getInstance().saveException(pageContext, e);
                throw new JspException(messages.getMessage("getter.method", property, name));
            }
        }

        // Return the values
        return returnValues;
    }

    /**
     * Extends the default addOption functionality by storing the value and label in a local map.
     *
     * @param sb StringBuffer accumulating our results
     * @param value Value to be returned to the server for this option
     * @param label Value to be shown to the user for this option
     * @param matched Should this value be marked as selected?
     */
    protected void addOption(StringBuffer sb, String value, String label, boolean matched)
    {
        super.addOption(sb, value, label, matched);
        valueToLabelMap.put(value, label);
    }

    /**
     * Release any acquired resources.
     */
    public void release()
    {
        super.release();
        valueToLabelMap = new HashMap();
    }
}