/*
 * 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;

import java.util.Map;

import com.arsdigita.bebop.event.ChangeListener;
import com.arsdigita.util.Assert;
import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.util.Assert;
import com.arsdigita.util.Lockable;

/**
 * A simple implementation of a {@link ComponentSelectionModel}. Uses
 * a map to bind keys to components.
 * <P>This class also encapsulates a {@link SingleSelectionModel}, which
 * is useful if the {@link SingleSelectionModel} comes from a {@link List} or
 * similar class.
 *
 * @author David Lutterkort 
 * @author Stanislav Freidin 
 * @version $Id: //core-platform/dev/src/com/arsdigita/bebop/MapComponentSelectionModel.java#7 $
 */

public class MapComponentSelectionModel
    implements ComponentSelectionModel, Lockable {

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

    private SingleSelectionModel m_selModel;
    private Map m_components;
    private boolean m_locked;

    /**
     * Constructs a new MapSingleSelectionModel, using selModel as the inner
     * selection model and encapsulating the components map.
     *
     * @param selModel the single selection model to use to determine
     *                 the currently selected key/component
     * @param components the map of components that can be selected. The map
     *                   is stored by reference. Therefore, changes to the map will
     *                   affect the MapComponentSelectionModel instance.
     */
    public MapComponentSelectionModel(
                                      SingleSelectionModel selModel, Map components
                                      ) {
        m_components = components;
        m_selModel = selModel;
        m_locked = false;
    }

    /**
     * Constructs a new MapSingleSelectionModel, using a DefaultSingleSelectionModel
     * selection model and encapsulating the components map.
     *
     * @param components the map of components that can be selected. The map
     *                   is stored by reference. Therefore, changes to the map will
     *                   affect the MapComponentSelectionModel instance.
     */
    public MapComponentSelectionModel(Map components) {
        this(new DefaultSingleSelectionModel(), components);
    }

    /**
     * Retrieves the internal SingleSelectionModel.
     * @return the internal SingleSelectionModel.
     */
    public final SingleSelectionModel getSingleSelectionModel() {
        return m_selModel;
    }

    /**
     * Retrieves the internal Map of components. Deprecate ???
     * @return the internal map of components.
     */
    public final Map getComponentsMap() {
        return m_components;
    }

    /**
     * Returns the component that should be used to output the currently
     * selected element.
     *
     * @param state the state of the current request
     * @return the component used to output the selected element.
     */
    public Component getComponent(PageState state) {
        if(!isSelected(state)) {
            return null;
        }
        return (Component)m_components.get((m_selModel.getSelectedKey(state)));
    }


    /**
     * Adds another key-component mapping to the model. Passthrough to the
     * underlying Map.
     * @param key the key for the mapping
     * @param c the component for the mapping
     */
    public void add(Object key, Component c) {
        Assert.assertNotLocked(this);
        m_components.put(key, c);
    }

    // Passthrough to SingleSelectionModel

    /**
     * Returns <code>true</code> if there is a selected element.
     *
     * @param state the state of the current request
     * @return <code>true</code> if there is a selected component
     * <code>false</code> otherwise.
     */
    public boolean isSelected(PageState state) {
        return m_selModel.isSelected(state);
    }

    /**
     * Returns the key that identifies the selected element.
     *
     * @param state a <code>PageState</code> value
     * @return a <code>String</code> value.
     */
    public Object getSelectedKey(PageState state) {
        return m_selModel.getSelectedKey(state);
    }

    /**
     * Sets the selected key. If <code>key</code> is not in the collection of
     * objects underlying this model, an
     * <code>IllegalArgumentException</code> is thrown.
     *
     * @param state the state of the current request
     * @throws IllegalArgumentException if the supplied <code>key</code> cannot
     * be selected in the context of the current request.
     */
    public void setSelectedKey(PageState state, Object key) {
        m_selModel.setSelectedKey(state, key);
    }

    /**
     * Clears the selection.
     *
     * @param state the state of the current request
     * @post ! isSelected(state)
     */
    public void clearSelection(PageState state) {
        m_selModel.clearSelection(state);
    }

    /**
     * Adds a change listener to the model. The listener's
     * <code>stateChanged</code> is called whenever the selected key changes.
     *
     * @param l a listener to notify when the selected key changes
     */
    public void addChangeListener(ChangeListener l) {
        Assert.assertNotLocked(this);
        m_selModel.addChangeListener(l);
    }

    /**
     * Removes a change listener from the model.
     *
     * @param l the listener to remove
     */
    public void removeChangeListener(ChangeListener l) {
        Assert.assertNotLocked(this);
        m_selModel.removeChangeListener(l);
    }

    /**
     * Returns the state parameter that will be used to keep track
     * of the currently selected key. Typically, the implementing
     * class will simply call:<br>
     * <code><pre>return new StringParameter("foo");</pre></code>
     * This method may return null if a state parameter is not
     * appropriate in the context of the implementing class.
     *
     * @return the state parameter to use to keep
     *         track of the currently selected component.
     */
    public ParameterModel getStateParameter() {
        return m_selModel.getStateParameter();
    }

    // implement Lockable
    public final void lock() {
        m_locked = true;
    }

    public final boolean isLocked() {
        return m_locked;
    }
}
