/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Header:$
 */
package org.apache.beehive.netui.tags.html;

import org.apache.beehive.netui.util.internal.InternalStringBuilder;

import org.apache.beehive.netui.pageflow.ProcessPopulate;
import org.apache.beehive.netui.script.common.DataAccessProviderStack;
import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor;
import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor;
import org.apache.beehive.netui.tags.naming.PrefixNameInterceptor;
import org.apache.beehive.netui.tags.rendering.StringBuilderRenderAppender;
import org.apache.beehive.netui.tags.rendering.TagRenderingBase;
import org.apache.beehive.netui.tags.rendering.WriteRenderAppender;
import org.apache.beehive.netui.util.Bundle;
import org.apache.beehive.netui.util.logging.Logger;
import org.apache.beehive.netui.util.tags.GroupOption;

import javax.servlet.ServletRequest;
import javax.servlet.jsp.JspException;
import java.util.*;

/**
 * Groups a collection of RadioButtonOptions, and handles databinding of their values.
 *
 * If RadioButtonGroup uses any Format tags, it must have those tags come before above any nested
 * RadioButtonOption tags.
 * @jsptagref.tagdescription Renders a collection of radiobutton options
 * as &lt;input type="radio"> and handles the data binding of their values.
 *
 * <p>The &lt;netui:radioButtonGroup> tag can generate a set of
 * radiobutton options in two ways:
 *
 * <blockquote>
 * <ol>
 * <li>they can be dynamically generated by pointing the
 * &lt;netui:radioButtonGroup> tag at a {@link java.util.HashMap java.util.HashMap}
 * or String[] object</li>
 * <li>they can be statically generated by providing a set of children
 * {@link RadioButtonOption}
 * tags</li>
 * </ol>
 * </blockquote>
 *
 * <p><b>Dynamically Generated Radiobutton Options</b>
 *
 * <p>You can dynamically generate a set of radionbutton options by
 * pointing the &lt;netui:radioButtonGroup> tag at a HashMap
 * (or any object that implements the {@link java.util.Map java.util.Map} interface).
 *
 * <p>For example, if you define a HashMap object and get method in the Controller file...
 *
 * <pre>    public HashMap hashMap = new HashMap();
 *
 *    protected HashMap getHashMap()
 *    {
 *        return hashMap;
 *    }
 *
 *    protected void onCreate()
 *    {
 *        hashMap.put("value1", "Display Text 1");
 *        hashMap.put("value2", "Display Text 2");
 *        hashMap.put("value3", "Display Text 3");
 *    }</pre>
 *
 * ...point the &lt;netui:radioButtonGroup>
 * at the Map object using the <code>optionsDataSource</code> attribute.
 *
 * <pre>    &lt;netui:radioButtonGroup
 *            dataSource="actionForm.selection"
 *            optionsDataSource="${pageFlow.hashMap}"></pre>
 *
 * <p>In the generated radiobutton options, the display text and the
 * submitted value can be made to differ.  The HashMap keys will
 * form the submitted values, while the HashMap entries will form
 * the display texts.
 *
 * <pre>    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.selection}" value="value1">Display Text 1&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.selection}" value="value2">Display Text 2&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.selection}" value="value3">Display Text 3&lt;/input></pre>
 *
 * <p>Note that you can point the &lt;netui:radioButtonGroup> tag at a
 * String[] object.  A set of radiobutton options will be generated,
 * but there will be no difference between the
 * display texts and the submitted values.
 *
 * <p><b>Statically Generated Radiobutton Options</b></p>
 *
 * <p>To statically generate radiobutton options, place a set of &lt;netui:radioButtonOption> tags inside
 * the &lt;netui:radioButtonGroup> tag.
 *
 * <pre>    &lt;netui:radioButtonGroup dataSource="actionForm.selection">
 *        &lt;netui:radioButtonOption value="value1">Display Text 1&lt;/netui:radioButtonOption>&lt;br>
 *        &lt;netui:radioButtonOption value="value2">Display Text 2&lt;/netui:radioButtonOption>&lt;br>
 *        &lt;netui:radioButtonOption value="value3">Display Text 3&lt;/netui:radioButtonOption>&lt;br>
 *    &lt;/netui:radioButtonGroup></pre>
 *
 * <p><b>Submitting Data</b></p>
 *
 * <p>A &lt;netui:radioButtonGroup> is submitted as a String value.  Use the <code>dataSource</code> attribute
 * to submit to a String object.
 *
 * <pre>    &lt;netui:radioButtonGroup dataSource="actionForm.selection"></pre>
 *
 * <p>In this case, the &lt;netui:radioButtonGroup> submits to a String field of a Form Bean.
 *
 * <pre>    public static class ProcessDataForm extends FormData
 *    {
 *        private String selection;
 *
 *        public void setSelection(String selection)
 *        {
 *            this.selection = selection;
 *        }
 *
 *        public String getSelection()
 *        {
 *            return this.selection;
 *        }
 *    }</pre>
 * @example In this sample, the &lt;netui:radioButtonGroup>
 * submits data to the Form Bean field <code>preferredColors</code>.
 *
 * <pre>    &lt;netui:radioButtonGroup
 *            dataSource="actionForm.preferredColors"
 *            optionsDataSource="${pageFlow.colors}" /></pre>
 *
 * The <code>optionsDataSource</code> attribute points to a get method for a String[] on the Controller file:
 *
 * <pre>    String[] colors = new String[] {"Red", "Blue", "Green", "Yellow", "White", "Black"};
 *
 *    public String[] getColors()
 *    {
 *        return colors;
 *    }</pre>
 *
 * This automatically renders the appropriate set of radionbutton options:
 *
 * <pre>    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Red">Red&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Blue">Blue&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Green">Green&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Yellow">Yellow&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="White">White&lt;/input>
 *    &lt;input type="radio" name="wlw-radio_button_group_key:{actionForm.preferredColors}" value="Black">Black&lt;/input></pre>
 * @netui:tag name="radioButtonGroup" description="Defines a group of netui:radioButtonOption elements."
 */
public class RadioButtonGroup
        extends HtmlGroupBaseTag
{
    // @todo: selection may not work with options in repeater.
    private static final Logger logger = Logger.getInstance(RadioButtonGroup.class);

    public static final String RADIOBUTTONGROUP_KEY = "radio_button_group_key";

    private String _match;                      // The actual values we will match against, calculated in doStartTag().
    private String _defaultRadio;               //
    private Object _dynamicAttrs;               // The optionsDataSource object
    private InternalStringBuilder _saveBody;            // The body text
    private WriteRenderAppender _writer;


    private static final List _internalNamingChain;

    static
    {
        List l = new ArrayList(3);
        l.add(new FormDataNameInterceptor());
        l.add(new IndexedNameInterceptor());
        l.add(new PrefixNameInterceptor(RADIOBUTTONGROUP_KEY));
        _internalNamingChain = Collections.unmodifiableList(l);
    }

    static
    {
        org.apache.beehive.netui.pageflow.ProcessPopulate.registerPrefixHandler(RADIOBUTTONGROUP_KEY, new RadioButtonGroupPrefixHandler());
    }

    /**
     * The handler for naming and indexing the RadioButtonGroup.
     */
    public static class RadioButtonGroupPrefixHandler
            implements org.apache.beehive.netui.pageflow.RequestParameterHandler
    {
        public void process(javax.servlet.http.HttpServletRequest request, String key,
                            String expr, ProcessPopulate.ExpressionUpdateNode node)
        {
            if (logger.isDebugEnabled()) {
                logger.debug("*********************************************\n" +
                        "process with key \"" + key + "\" and expression \"" + node.expression + "\"" +
                        "*********************************************\n");
            }
        }
    }

    public RadioButtonGroup()
    {
        super();
    }

    /**
     * Return the name of the Tag.
     */
    public String getTagName()
    {
        return "RadioButtonGroup";
    }

    /**
     * Return an <code>ArrayList</code> which represents a chain of <code>INameInterceptor</code>
     * objects.  This method by default returns <code>null</code> and should be overridden
     * by objects that support naming.
     * @return an <code>ArrayList</code> that will contain <code>INameInterceptor</code> objects.
     */
    protected List getNamingChain()
    {
        return _internalNamingChain;
    }

    /**
     * Override the default value to return a string or the empty string if the default value results in a
     * <code>null</code> value.
     * @return the value returned from <code>super.evaluteDefaultValue</code> or the empty string.
     */
    private String evaluateDefaultValue()
    {
        Object val = _defaultValue;

        if (val != null)
            return val.toString();
        return "";
    }

    /**
     * Does the specified value match one of those we are looking for?
     * @param value Value to be compared
     */
    public boolean isMatched(String value, Boolean defaultValue)
    {
        // @todo: there isn't a defaultValue for radio button, what should we do here?
        if (value == null)
            return false;
        if (_match != null)
            return value.equals(_match);
        if (_defaultRadio != null)
            return value.equals(_defaultRadio);

        return false;
    }

    /**
     * Determine the match for the RadioButtonGroup
     * @throws JspException if a JSP exception has occurred
     */
    public int doStartTag()
            throws JspException
    {
        // evaluate the datasource and disabled state.
        Object val = evaluateDataSource();
        if (val != null)
            _match = val.toString();

        // Store this tag itself as a page attribute
        pageContext.setAttribute(RADIOBUTTONGROUP_KEY, this);
        _defaultRadio = evaluateDefaultValue();

        // see if there are errors in the evaluation
        if (hasErrors())
            return SKIP_BODY;

        ServletRequest req = pageContext.getRequest();
        if (_cr == null)
            _cr = TagRenderingBase.Factory.getConstantRendering(req);

        _writer = new WriteRenderAppender(pageContext);
        if (isVertical()) {
            _cr.TABLE(_writer);
        }

        // if this is a repeater then we shouid prime the pump...
        _dynamicAttrs = evaluateOptionsDataSource();
        assert (_dynamicAttrs != null);
        assert (_dynamicAttrs instanceof Map ||
                _dynamicAttrs instanceof Iterator);

        if (_repeater) {
            if (_dynamicAttrs instanceof Map) {
                _dynamicAttrs = ((Map) _dynamicAttrs).entrySet().iterator();

            }
            if (!(_dynamicAttrs instanceof Iterator)) {
                String s = Bundle.getString("Tags_OptionsDSIteratorError");
                registerTagError(s, null);
                return SKIP_BODY;
            }
            while (((Iterator) _dynamicAttrs).hasNext()) {
                _repCurItem = ((Iterator) _dynamicAttrs).next();
                if (_repCurItem != null)
                    break;
            }
            if (isVertical())
                _cr.TR_TD(_writer);

            DataAccessProviderStack.addDataAccessProvider(this, pageContext);
        }
        //write(results.toString());
        // This is basically this is if enough for 5 options
        _saveBody = new InternalStringBuilder(640);
        return EVAL_BODY_INCLUDE;
    }

    /**
     * Save any body content of this tag, which will generally be the
     * option(s) representing the values displayed to the user.
     * @throws JspException if a JSP exception has occurred
     */
    public int doAfterBody() throws JspException
    {
        StringBuilderRenderAppender writer = new StringBuilderRenderAppender(_saveBody);
        if (bodyContent != null) {
            String value = bodyContent.getString();
            bodyContent.clearBody();
            if (value == null)
                value = "";
            _saveBody.append(value);
        }

        if (_repeater) {
            ServletRequest req = pageContext.getRequest();
            if (_cr == null)
                _cr = TagRenderingBase.Factory.getConstantRendering(req);
            if (isVertical())
                _cr.end_TD_TR(writer);

            while (((Iterator) _dynamicAttrs).hasNext()) {
                _repCurItem = ((Iterator) _dynamicAttrs).next();
                if (_repCurItem != null) {
                    _repIdx++;
                    if (isVertical())
                        _cr.TR_TD(writer);
                    return EVAL_BODY_AGAIN;
                }
            }
        }

        return SKIP_BODY;
    }

    /**
     * Render the set of RadioButtonOptions.
     * @throws JspException if a JSP exception has occurred
     */
    public int doEndTag() throws JspException
    {
        if (hasErrors())
            return reportAndExit(EVAL_PAGE);

        String idScript = null;
        String altText = null;
        char accessKey = 0x00;

        // Remove the page scope attributes we created
        pageContext.removeAttribute(RADIOBUTTONGROUP_KEY);
        ServletRequest req = pageContext.getRequest();
        if (_cr == null)
            _cr = TagRenderingBase.Factory.getConstantRendering(req);

        //InternalStringBuilder results = new InternalStringBuilder(128);
        if (_saveBody != null)
            write(_saveBody.toString());

        // if this is a repeater we output the content during the body processing
        if (_repeater) {
            // Render a tag representing the end of our current form
            if (isVertical())
                _cr.end_TABLE(_writer);

            if (idScript != null)
                write(idScript);

            //write(results.toString());
            localRelease();
            return EVAL_PAGE;
        }

        // Render a tag representing the end of our current form
        if (_dynamicAttrs instanceof Map) {
            Map dynamicRadiosMap = (Map) _dynamicAttrs;
            Iterator keyIterator = dynamicRadiosMap.keySet().iterator();
            int idx = 0;
            while (keyIterator.hasNext()) {
                Object optionValue = keyIterator.next();
                String optionDisplay = null;
                if (dynamicRadiosMap.get(optionValue) != null) {
                    optionDisplay = dynamicRadiosMap.get(optionValue).toString();
                }
                else {
                    optionDisplay = "";
                }

                if (optionValue != null) {
                    addOption(_writer, INPUT_RADIO, optionValue.toString(), optionDisplay, idx++, altText, accessKey, _disabled);
                }
                
                if (hasErrors()) {
                    reportErrors();
                    localRelease();
                    return EVAL_PAGE;
                }
                write("\n");

            }
        }
        else {
            assert(_dynamicAttrs instanceof Iterator);

            Iterator it = (Iterator) _dynamicAttrs;
            int idx = 0;
            while (it.hasNext()) {
                Object o = it.next();
                if (o == null)
                    continue;

                if (o instanceof GroupOption) {
                    GroupOption go = (GroupOption) o;
                    addOption(_writer, INPUT_RADIO, go.getValue(), go.getName(), idx++, go.getAlt(), go.getAccessKey(), _disabled);
                }
                else {
                    String radioValue = o.toString();
                    addOption(_writer, INPUT_RADIO, radioValue, radioValue, idx++, altText, accessKey, _disabled);
                }
                if (hasErrors()) {
                    reportErrors();
                    localRelease();
                    return EVAL_PAGE;
                }
                write("\n");
            }
        }

        if (isVertical()) {
            _cr.end_TABLE(_writer);
        }

        if (idScript != null)
            write(idScript);

        //write(results.toString());
        localRelease();
        return EVAL_PAGE;
    }

    /**
     * Release any acquired resources.
     */
    protected void localRelease()
    {
        // remove the context allowing binding to container.item during binding
        if (_repeater)
            DataAccessProviderStack.removeDataAccessProvider(pageContext);

        super.localRelease();

        _match = null;
        _defaultRadio = null;
    }
}
