/**
 * GUI Commands
 * Copyright 2004 Andrew Pietsch
 *
 * Licensed 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.
 *
 * $Id: ToggleCommand.java,v 1.16 2006/02/25 22:06:08 pietschy Exp $
 */

package org.pietschy.command;

import javax.swing.*;
import java.util.Iterator;

/**
 * The toggle command class implements a command that has a selected state.  Each
 * execution of the command will toggle the selected state of the command.  Subclasses
 * should override the {@link #handleSelection} method.
 */
public abstract class
ToggleCommand
extends ActionCommand
{

   private boolean selected = false;
   private ToggleGroupController toggleController;


   /**
    * Creates a new anonymous ToggleCommand.  Anonymous commands can't must be fully programatically
    * generated and can't be exported to command containers.
    */
   public ToggleCommand()
   {
   }


   /**
    * Creates a new toggle command with the specified Id that is bound to
    * {@link CommandManager#defaultInstance}.
    * @param commandId the id of the command.
    */
   public ToggleCommand(String commandId)
   {
      super(commandId);
   }

   /**
    * Creates a new anonymous toggle command bound to the specified {@link CommandManager#defaultInstance()}.
    */
   public ToggleCommand(CommandManager commandManager)
   {
      super(commandManager);
   }

   /**
    * Creates a new toggle command with the specified Id that is bound to
    * the specified {@link CommandManager}.
    * @param commandManager the {@link CommandManager} to which the command belongs.
    * @param commandId the id of the command.
    */
   public ToggleCommand(CommandManager commandManager, String commandId)
   {
      super(commandManager, commandId);
   }


   /**
    * Overrides the default {@link Command#configureButtonStates} to include the configuration of
    * the buttons {@link JToggleButton#setSelected selected} state.
    *
    * @param button the button to initialize.
    */
   protected void
   configureButtonStates(AbstractButton button)
   {
      super.configureButtonStates(button);
      button.setSelected(selected);
   }


   /**
    * Attempts to set the selected state of the command.  This method delegats the selection
    * request to {@link #handleSelection} and configures all attachements appropriately
    * based on the return value.
    * <p>
    * It isn't guarenteed that the final state of the command will be the value specified. The
    * actual final state will be determined by {@link #handleSelection}.
    *
    * @param selected <tt>true</tt> if the command is being selected, <tt>false</tt> if it is
    * being deselected.
    */
   public final void
   setSelected(boolean selected)
   {
      internalLog.enter("setSelected");
      internalLog.param("selected", String.valueOf(selected));

      // Toggle commands that are members of an exclusive group always delegate
      // selection to the group.
      if (toggleController != null)
      {
         // notify the controller of the request..
         toggleController.handleSelectionRequest(this, selected);
      }
      else
      {
         // since we aren't a member of an exclusive group, we just apply the current
         // selection state directly.
         try
         {
            attemptSelection(selected);
         }
         catch (ToggleVetoException e)
         {
            internalLog.info("Selection request Vetoed", e);
         }
      }

      internalLog.exit("setSelected()");
   }

   /**
    * Attempts to set the selected state of the command.  This method delegats the selection
    * request to {@link #handleSelection} and on return configures all attachements appropriately.
    *
    * @param selected the desired selection state of the command.
    * @throws ToggleVetoException if a ToggleVetoException is thrown by {@link #handleSelection(boolean)}.
    */
   protected final void
   attemptSelection(boolean selected)
   throws ToggleVetoException
   {
      internalLog.enter("attemptSelection");
      internalLog.param("selected", String.valueOf(selected));

      if (this.selected != selected)
      {
         boolean oldValue = this.selected;

         // let the subclasses handle it..
         try
         {
            handleSelection(selected);
            applySelection(selected);
         }
         catch (ToggleVetoException veto)
         {
            applySelection(oldValue);
            throw veto;
         }
      }

      internalLog.exit("attemptSelection()");
   }

   /**
    * Applies the selected state to the toggle and updates all its buttons.  This method also fires a property
    * change event if the new selection has changed from the previous state.
    * <p>
    * This method should only be called by subclasses if the wish to by-pass all of the normal behaviour and explicitly
    * set the state of this toggle (such as when reverting state after an undo request).  This method completely
    * ignores any exclusive group membership and will not update the state of any other toggles which may share
    * membership with the command.
    * @param selected the desired selection state of the command.
    */
   protected void
   applySelection(boolean selected)
   {
      internalLog.enter("applySelection()");
      internalLog.param("selected", Boolean.valueOf(selected));

      boolean oldValue = this.selected;
      this.selected = selected;

      // make sure all our buttons have the correct state regardless of what happened.
      Iterator iter = buttonIterator();
      while (iter.hasNext())
      {
         AbstractButton button = (AbstractButton) iter.next();
         button.setSelected(selected);
      }

      // only fire if the value has actually changed.
      if (oldValue != selected)
         pcs.firePropertyChange("selected", oldValue, selected);

      internalLog.exit("applySelection()");
   }


   public boolean
   isSelected()
   {
      return selected;
   }

   /**
    * This method is called whenever the Command is executed.  If this command is a member of
    * and exclusive group, then the selection request is delegated to the group by calling
    * {@link ToggleGroupController#handleSelectionRequest}, otherwise {@link #setSelected} is called with
    * the value of !{@link #isSelected}.
    */
   protected void handleExecute()
   {
      setSelected(!isSelected());
   }

   /**
    * Entry for subclasses to handle the selection process.  When a request to change
    * the selection is made, this method will be called.
    * <p>
    * To deny the selection request, subclassed must throw a {@link ToggleVetoException}.
    * <p>
    * Please note that
    * the current state of {@link #isSelected} will not be updated until after this method
    * has been called and so should not be used in this method.
    *
    * @param selected the requested selection state.
    */
   protected abstract void
   handleSelection(boolean selected)
   throws ToggleVetoException;


   /**
    * Creates a new {@link JCheckBoxMenuItem} that is bound to this command.
    *
    * @param factory
    * @param faceId
    * @return a new {@link JCheckBoxMenuItem} for this command.
    */
   public JMenuItem
   createMenuItem(MenuFactory factory, String faceId)
   {
      JMenuItem menuItem;

      if (toggleController != null)
         menuItem = factory.createRadioButtonMenuItem();
      else
         menuItem = factory.createCheckBoxMenuItem();

      attach(menuItem, faceId);
      return menuItem;
   }

   /**
    * Creates a toggle button for this command using the specified {@link ButtonFactory} and {@link org.pietschy.command.Face}.
    *
    * @return a new {@link JToggleButton} for this command.
    */
   public AbstractButton createButton(ButtonFactory factory, String faceId)
   {
      AbstractButton button = factory.createToggleButton();
      attach(button, faceId);
      return button;
   }

   /**
    * Creates a checkbox for this command using the default button factory and the button face.
    *
    * @return a new {@link JCheckBox} for this command.
    */
   public AbstractButton
   createCheckBox()
   {
      return createCheckBox(getButtonFactory(), Face.BUTTON);
   }

   /**
    * Creates a checkbox for this command using the default button factory and the specified face.
    *
    * @return a new {@link JCheckBox} for this command.
    */
   public AbstractButton
   createCheckBox(String faceName)
   {
      return createCheckBox(getButtonFactory(), faceName);
   }

   /**
    * Creates a checkbox for this command using the specifed button factory and the button face.
    *
    * @return a new {@link JCheckBox} for this command.
    */
   public AbstractButton
   createCheckBox(ButtonFactory factory)
   {
      return createCheckBox(factory, Face.BUTTON);
   }

   /**
    * Creates a checkbox for this command using the specified {@link ButtonFactory} and {@link org.pietschy.command.Face}.
    *
    * @return a new {@link JCheckBox} for this command.
    */
   public AbstractButton createCheckBox(ButtonFactory factory, String faceId)
   {
      AbstractButton button = factory.createCheckBox();
      attach(button, faceId);
      return button;
   }

   /**
    * Warning this method does nothing as toggle commands are implmented using
    * {@link JToggleButton}s and default buttons must be instances of {@link JButton}.
    */
   public void requestDefautIn(RootPaneContainer container)
   {

   }

   public void
   addNotify(CommandGroup parent)
   {
      if (parent instanceof ToggleCommandGroup)
      {
         if (toggleController != null)
            throw new IllegalStateException("Toggles can't be contained in more than one toggle group");

         toggleController = ((ToggleCommandGroup) parent).getController();
         toggleController.add(this);
      }
   }

   public void
   removeNotify(CommandGroup parent)
   {
      if (parent instanceof ToggleCommandGroup)
      {
         if (toggleController != null)
         {
            toggleController.remove(this);
            toggleController = null;
         }
      }
   }

}
