/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.bebop.form;

import com.arsdigita.bebop.RequestLocal;
import com.arsdigita.bebop.PageState;
import com.arsdigita.util.Assert;
import com.arsdigita.bebop.util.BebopConstants;
import com.arsdigita.bebop.parameters.ParameterModel;

import java.util.Iterator;
import java.util.ArrayList;

/**
 *     A class
 *    representing any widget that contains a list options.
 *
 *    @author Karl Goldstein 
 *    @author Uday Mathur    
 *    @author Rory Solomon   
 *    @author Michael Pih    
 *    @version $Id: //core-platform/dev/src/com/arsdigita/bebop/form/OptionGroup.java#8 $ */
public abstract class OptionGroup extends Widget
    implements BebopConstants {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/bebop/form/OptionGroup.java#8 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    /**
     * The XML element to be used by individual options belonging to this
     * group. This variable has to be initialized by every subclass of
     * OptionGroup.
     * LEGACY: An abstract method would be the better design, but changing it
     * would break the API.  */
    protected String m_xmlElement;

    // this only needs to be an ArrayList for multiple selection option groups
    private ArrayList m_selected;

    private ArrayList m_options;

    // request-local copy of selected elements, options
    private RequestLocal m_requestOptions = new RequestLocal() {
            public Object initialValue(PageState ps) {
                return new ArrayList();
            }
        };

    public final boolean isCompound() {
        return true;
    }

    // this is only used for single selection option groups
    private final static String TOO_MANY_OPTIONS_SELECTED =
        "Only one option may be selected by default on this option group.";

    /** The ParameterModel for mutliple OptionGroups is always an array
     *  parameter */
    protected OptionGroup(ParameterModel model) {
        super(model);
        m_options = new ArrayList();
        m_selected = new ArrayList();
    }

    /**
     *      Returns an Iterator of all the default Options in this group.
     */
    public Iterator getOptions() {
        return m_options.iterator();
    }

    /**
     *      Returns an Iterator of all the default Options in this group,
     * plus any request-specific options.
     */
    public Iterator getOptions(PageState ps) {
        ArrayList allOptions = new ArrayList();
        allOptions.addAll(m_options);
        ArrayList requestOptions = (ArrayList)m_requestOptions.get(ps);
        for (Iterator i = requestOptions.iterator(); i.hasNext(); ) {
            Object obj = i.next();
            if (!allOptions.contains(obj)) {
                allOptions.add(obj);
            }
        }
        return allOptions.iterator();
    }

    public void clearOptions() {
        Assert.assertNotLocked(this);
        m_options = new ArrayList();
    }

    /**
     * Adds a new option.
     * @param opt The {@link Option} to be added.  Note: the argument
     * is modified and associated with this OptionGroup, regardless of
     * what its group was.
     */
    public void addOption(Option opt) {
        addOption(opt, null);
    }

    public void removeOption(Option opt) {
        removeOption(opt, null);
    }

    /**
     * Adds a new option for the scope of the current request, or
     * to the page as a whole if there is no current request.
     *
     * @param opt The {@link Option} to be added.  Note: the argument
     * is modified and associated with this OptionGroup, regardless of
     * what its group was.
     * @param ps the current page state.  if ps is null, adds option to the
     * default option list.
     */
    public void addOption(Option opt, PageState ps) {
        ArrayList list = m_options;
        if (ps == null) {
            Assert.assertNotLocked(this);
        } else {
            list = (ArrayList)m_requestOptions.get(ps);
        }
        opt.setGroup( this );
        list.add(opt);
    }


    public void removeOption(Option opt, PageState ps) {
        ArrayList list = m_options;
        if (ps == null) {
            Assert.assertNotLocked(this);
        } else {
            list = (ArrayList)m_requestOptions.get(ps);
        }
        list.remove(opt);
    }

    public void removeOption(String key) {
        removeOption(key, null);
    }
    /**
     * Removes the first option whose key is equal
     * to the key that is passed in.
     */
    public void removeOption(String key, PageState ps) {
        // This is not an entirely efficient technique. A more
        // efficient solution is to switch to using a HashMap.
        ArrayList list = m_options;
        if (ps == null) {
            Assert.assertNotLocked(this);
        } else {
            list = (ArrayList)m_requestOptions.get(ps);
        }

        Iterator i = list.iterator();
        Option o = null;
        while ( i.hasNext() ) {
            o = (Option) i.next();
            if ( o.getValue().equals(key) ) {
                list.remove(o);
                break;
            }
        }

    }


    /** Make an option selected by default.  Updates the parameter
     *  model for the option group accordingly.
     *  @param value the value of the option to be added to the
     *  by-default-selected set.  */
    public void setOptionSelected(String value) {
        Assert.assertNotLocked(this);
        if (!isMultiple()) {
            // only one option may be selected
            // to this selected list better be empty
            Assert.assertTrue(m_selected.size() == 0, TOO_MANY_OPTIONS_SELECTED);
            m_selected.add(value);
            getParameterModel().setDefaultValue( value );
        } else {
            m_selected.add(value);
            getParameterModel().setDefaultValue( m_selected.toArray() );
        }
    }

    /** make an option selected by default
     *  @param option the option to be added to the by-default-selected set.
     */
    public void setOptionSelected(Option option) {
        setOptionSelected(option.getValue());
    }

    public Object clone() throws CloneNotSupportedException {
        OptionGroup cloned = (OptionGroup)super.clone();
        cloned.m_options = (ArrayList) m_options.clone();
        cloned.m_selected =
            (ArrayList) m_selected.clone();
        return cloned;
    }

    /**
     * Is this a multiple (and not single) selection option group?
     * Note that this should really be declared abstract, but we can't
     * because it used to be in the direct subclass Select and making
     * it abstract could break other subclasses that don't declare
     * isMultiple.  So we have a trivial implementation instead.
     *
     * @return true if this OptionGroup can have more than one
     * selected option; false otherwise.
     */
    public boolean isMultiple() {
        return true;
    }
}
