/**
 * 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: Face.java,v 1.24 2007/01/03 22:41:23 pietschy Exp $
 */

package org.pietschy.command;

import org.pietschy.command.log.Logger;

import javax.swing.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeEvent;
import java.util.HashMap;
import java.util.Set;


/**
 * The face class holds all the visiual information about a {@link Command}.  Faces are identified
 * by a {@link FaceId} that references the parent command and the name of the face.  Face names
 * may be any non empty string but are usually the defaults defined by {@link #DEFAULT},
 * {@link #BUTTON}, {@link #TOOLBAR}, {@link #MENU}, {@link #POPUP} and {@link #HTML}.  The commands
 * will determine the appropriate face based on the context in which a button or menu is created.
 */
public class
Face
{

   private static final Logger log = CommandManager.getLogger(Face.class);

   public static final String DEFAULT = "default";
   public static final String BUTTON = "button";
   public static final String TOOLBAR = "toolbar";
   public static final String MENU = "menu";
   public static final String POPUP = "popup";
   public static final String HTML = "html";

   private HashMap properties = new HashMap();

   protected FaceId extendsId;
   private FaceId id;
   private String text;
   private boolean textInherited = true;
   private FaceIconHelper icon;
   private FaceIconHelper selectedIcon;
   private FaceIconHelper rolloverIcon;
   private FaceIconHelper rolloverSelectedIcon;
   private FaceIconHelper pressedIcon;
   private FaceIconHelper disabledIcon;
   private Integer horizontalTextPosition;
   private Integer verticalTextPosition;
   private Integer iconTextGap;
   private boolean iconTextGapInherited = true;
   private Integer mnemonic;
   private Integer mnemonicIndex;
   private KeyStroke accelerator;
   private boolean acceleratorInherited = true;
   private StringBuilder description;
   private StringBuilder longDescription;
   private Boolean menuTooltipEnabled = null;


   protected PropertyChangeSupport pcs;
   protected FaceManager faceManager;
   private static final char MNEMONIC_MARKER = '_';


   /**
    * Constructs a new empty face with the specified id, that uses the specified
    * {@link CommandManager} to locating the face it extends.
    *
    * @param id      the id of this face.
    * @param manager the {@link FaceManager} to use to locate the parent face.
    */
   protected Face(FaceId id, FaceManager manager)
   {
      pcs = new PropertyChangeSupport(this);
      this.id = id;
      this.faceManager = manager;
      icon = new FaceIconHelper("icon", pcs);
      selectedIcon = new FaceIconHelper("selected", pcs);
      rolloverIcon = new FaceIconHelper("rollover", pcs);
      rolloverSelectedIcon = new FaceIconHelper("rolloverSelected", pcs);
      pressedIcon = new FaceIconHelper("pressed", pcs);
      disabledIcon = new FaceIconHelper("disabled", pcs);
      description = new StringBuilder(this);
      longDescription = new StringBuilder(this);
   }


   public FaceId
   getId()
   {
      return id;
   }

   public String
   getName()
   {
      return id.getName();
   }


   /**
    * Tests if this face's name is the same as the specified name.
    *
    * @param name the name to compare with this face's name.
    * @return <tt>true</tt> if the names are the same, <tt>false</tt> otherwise.
    */
   public boolean
   isNameEqualTo(String name)
   {
      return getName().equals(name);
   }


   public void
   putClientProperty(String name, String value)
   {
      properties.put(name, value);
   }

   public String
   getClientProperty(String name)
   {
      return (String) properties.get(name);
   }

   public String
   getClientProperty(String name, String defaultValue)
   {
      String value = (String) properties.get(name);
      return value != null ? value : defaultValue;
   }

   public String[]
   getClientPropertyNames()
   {
      Set keys = properties.keySet();
      return (String[]) keys.toArray(new String[keys.size()]);
   }


   /**
    * Returns the Test as displayed by attachments of this command.  This is the value that actually
    * appears on the button or menu item.
    *
    * @return the text this face displays.
    */
   public String
   getText()
   {
      if (text == null && textInherited && getParent() != null)
         return getParent().getText();

      return text;
   }


   /**
    * Gets the horizontal text position of this face.
    * <p>
    * This attribute will be ignored if the {@link #setVerticalTextPosition verticalPosition} has
    * been configured as the vertical position on button is only useful if the horizontal position is
    * set to {@link SwingConstants#CENTER}.
    *
    * @return the horizontal text position of the face.
    * @see #getVerticalTextPosition
    */
   public Integer getHorizontalTextPosition()
   {
      if (horizontalTextPosition == null && getParent() != null)
         return getParent().getHorizontalTextPosition();

      return horizontalTextPosition;
   }

   /**
    * Gets the vertical text position of this face.
    * <p>
    * Configuring this value will result in the {@link #getHorizontalTextPosition horizontalTextPosition}
    * being ignored.
    *
    * @return the vertical text position of the face.
    * @see #getVerticalTextPosition
    */
   public Integer getVerticalTextPosition()
   {
      if (verticalTextPosition == null && getParent() != null)
         return getParent().getVerticalTextPosition();

      return verticalTextPosition;
   }

   /**
    * Gets the vertical text position of this face.  If it hasn't been configured, then
    * <tt>null</tt> is returned.
    *
    * @return the icon text gap of the face or <tt>null</tt> if it hasn't been configured.
    */
   public Integer getIconTextGap()
   {
      if (iconTextGap == null && iconTextGapInherited && getParent() != null)
         return getParent().getIconTextGap();

      return iconTextGap;
   }

   public boolean isIconTextGapInherited()
   {
      return iconTextGapInherited;
   }

   public Icon getIcon()
   {
      if (icon.isNull() && icon.isInherited() && getParent() != null)
         return getParent().getIcon();

      return icon.getIcon();
   }

   public boolean isIconInherited()
   {
      return icon.isInherited();
   }

   public Icon getSelectedIcon()
   {
      if (selectedIcon.isNull() && selectedIcon.isInherited() && getParent() != null)
         return getParent().getSelectedIcon();

      return selectedIcon.getIcon();
   }

   public boolean isSelectedIconInherited()
   {
      return selectedIcon.isInherited();
   }

   public Icon getRolloverIcon()
   {
      if (rolloverIcon.isNull() && rolloverIcon.isInherited() && getParent() != null)
         return getParent().getRolloverIcon();

      return rolloverIcon.getIcon();
   }

   public boolean isRolloverIconInherited()
   {
      return rolloverIcon.isInherited();
   }

   public Icon getRolloverSelectedIcon()
   {
      if (rolloverSelectedIcon.isNull() && rolloverSelectedIcon.isInherited() && getParent() != null)
         return getParent().getRolloverSelectedIcon();

      return rolloverSelectedIcon.getIcon();
   }

   public boolean isRolloverSelectedIconInherited()
   {
      return rolloverSelectedIcon.isInherited();
   }

   public Icon getPressedIcon()
   {
      if (pressedIcon.isNull() && pressedIcon.isInherited() && getParent() != null)
         return getParent().getPressedIcon();

      return pressedIcon.getIcon();
   }

   public boolean isPressedIconInherited()
   {
      return pressedIcon.isInherited();
   }

   public Icon getDisabledIcon()
   {
      if (disabledIcon.isNull() && disabledIcon.isInherited() && getParent() != null)
         return getParent().getDisabledIcon();

      return disabledIcon.getIcon();
   }

   public boolean isDisabledIconInherited()
   {
      return disabledIcon.isInherited();
   }

   public Integer getMnemonic()
   {
      if (mnemonic == null && getParent() != null)
         return getParent().getMnemonic();

      return mnemonic;
   }

   public Integer getMnemonicIndex()
   {
      if (mnemonicIndex == null && getParent() != null)
         return getParent().getMnemonicIndex();

      return mnemonicIndex;
   }


   public KeyStroke getAccelerator()
   {
      if (accelerator == null && acceleratorInherited && getParent() != null)
         return getParent().getAccelerator();

      return accelerator;
   }

   public boolean isAcceleratorInherited()
   {
      return acceleratorInherited;
   }


   public String getDescription()
   {
      if (description.getTemplate() == null && getParent() != null)
         return getParent().getDescription();

      return description.getString();
   }


   public String getLongDescription()
   {
      if (longDescription.getTemplate() == null && getParent() != null)
         return getParent().getLongDescription();

      return longDescription.getString();
   }


   public boolean isMenuTooltipEnabled()
   {
      if (menuTooltipEnabled == null && getParent() != null)
         return getParent().isMenuTooltipEnabled();

      return menuTooltipEnabled != null ? menuTooltipEnabled.booleanValue() : faceManager.isMenuTooltipsEnabled();
   }

   public void
   setAccelerator(KeyStroke accelerator)
   {
      if (this.accelerator != accelerator)
      {
         KeyStroke old = this.accelerator;
         this.accelerator = accelerator;
         pcs.firePropertyChange("accelerator", old, accelerator);
      }
   }

   public void setAcceleratorInherited(boolean acceleratorInherited)
   {
      if (this.acceleratorInherited != acceleratorInherited)
      {
         boolean old = this.acceleratorInherited;
         this.acceleratorInherited = acceleratorInherited;
         pcs.firePropertyChange("acceleratorInherited", old, acceleratorInherited);
      }
   }

   public void
   setIcon(Icon icon)
   {
      this.icon.setIcon(icon);
   }

   public void setIconInherited(boolean iconInherited)
   {
      icon.setInherited(iconInherited);
   }

   public void
   setSelectedIcon(Icon selectedIcon)
   {
      this.selectedIcon.setIcon(selectedIcon);
   }

   public void setSelectedIconInherited(boolean selectedIconInherited)
   {
      selectedIcon.setInherited(selectedIconInherited);
   }

   public void
   setRolloverIcon(Icon icon)
   {
      rolloverIcon.setIcon(icon);
   }

   public void setRolloverIconInherited(boolean inherited)
   {
      rolloverIcon.setInherited(inherited);
   }

   public void
   setRolloverSelectedIcon(Icon icon)
   {
      rolloverSelectedIcon.setIcon(icon);
   }

   public void setRolloverSelectedIconInherited(boolean inherited)
   {
      rolloverSelectedIcon.setInherited(inherited);
   }

   public void
   setPressedIcon(Icon icon)
   {
      pressedIcon.setIcon(icon);
   }

   public void setPressedIconInherited(boolean inherited)
   {
      pressedIcon.setInherited(inherited);
   }

   public void
   setDisabledIcon(Icon icon)
   {
      disabledIcon.setIcon(icon);
   }

   public void setDisabledIconInherited(boolean inherited)
   {
      disabledIcon.setInherited(inherited);
   }

   public void setLongDescription(String string)
   {
      if (!longDescription.templateEquals(string))
      {
         String old = longDescription.getTemplate();
         longDescription.setTemplate(string);
         pcs.firePropertyChange("longDescription", old, string);
      }
   }

   public void setMnemonic(Integer mnemonic)
   {
      if (this.mnemonic == null || !this.mnemonic.equals(mnemonic))
      {
         Integer old = this.mnemonic;
         this.mnemonic = mnemonic;
         pcs.firePropertyChange("mnemonic", old, this.mnemonic);
      }
   }

   /**
    * Sets the mnemonic to use based on it character index in the string.
    *
    * @param mnemonicIndex
    */
   public void setMnemonicIndex(Integer mnemonicIndex)
   {
      if (this.mnemonicIndex == null || !this.mnemonicIndex.equals(mnemonicIndex))
      {
         Integer old = this.mnemonicIndex;
         this.mnemonicIndex = mnemonicIndex;
         pcs.firePropertyChange("mnemonicIndex", old, this.mnemonicIndex);
      }
   }

   public void setDescription(String string)
   {
      if (!description.templateEquals(string))
      {
         String old = description.getTemplate();
         description.setTemplate(string);
         pcs.firePropertyChange("description", old, string);
      }
   }

   /**
    * Sets the text for the face.  This methods interprets the '_' character as a mnemonic marker.  If present
    * this method will automatically update the mnemonic settings of the face as well.
    *
    * @param string the new string for the face.
    */
   public void setText(String string)
   {
      if (!safeEquals(text, string))
      {
         String old = text;
         text = string;

         int markerIndex = -1;
         if (text != null)
         {
            markerIndex = text.indexOf(MNEMONIC_MARKER);
            // now remove the marker from the text..
            if (markerIndex > -1)
               text = text.substring(0, markerIndex) + text.substring(markerIndex + 1);
         }

         pcs.firePropertyChange("text", old, string);

         if (markerIndex > -1)
            setMnemonicIndex(new Integer(markerIndex));
      }
   }

   public void
   setTextInherited(boolean textInherited)
   {
      this.textInherited = textInherited;
   }

   private boolean
   safeEquals(String s1, String s2)
   {
      if (s1 == null)
      {
         return (s2 == null);
      }
      else
      {
         return s1.equals(s2);
      }
   }

   /**
    * Sets the horizontal position for the text on this face.  If it <tt>null</tt>, then the
    * default for the look and feel will be used.
    *
    * @param position the horizontal position of the text.
    * @see #getHorizontalTextPosition
    */
   public void setHorizontalTextPosition(Integer position)
   {
      if (horizontalTextPosition == null || !horizontalTextPosition.equals(position))
      {
         Integer old = horizontalTextPosition;
         horizontalTextPosition = position;
         pcs.firePropertyChange("horizontalTextPosition", old, horizontalTextPosition);
      }
   }

   /**
    * Sets the vertical position for the text on this face.  If it <tt>null</tt>, then the
    * default for the look and feel will be used.
    *
    * @param position the vertical position of the text.
    * @see #getVerticalTextPosition
    */
   public void setVerticalTextPosition(Integer position)
   {
      if (verticalTextPosition == null || !verticalTextPosition.equals(position))
      {
         Integer old = verticalTextPosition;
         verticalTextPosition = position;
         pcs.firePropertyChange("verticalTextPosition", old, verticalTextPosition);
      }
   }

   public void setIconTextGap(Integer gap)
   {
      if (iconTextGap == null || !iconTextGap.equals(gap))
      {
         Integer old = iconTextGap;
         iconTextGap = gap;
         pcs.firePropertyChange("iconTextGap", old, iconTextGap);
      }
   }

   public void setIconTextGapInherited(boolean inherited)
   {
      if (this.iconTextGapInherited != inherited)
      {
         boolean old = this.iconTextGapInherited;
         this.iconTextGapInherited = inherited;
         pcs.firePropertyChange("iconTextGapInherited", old, inherited);
      }
   }


   public void setMenuTooltipEnabled(boolean menuTooltipEnabled)
   {
      if (this.menuTooltipEnabled == null || this.menuTooltipEnabled.booleanValue() != menuTooltipEnabled)
      {
         this.menuTooltipEnabled = new Boolean(menuTooltipEnabled);
         pcs.firePropertyChange("menuTooltipEnabled", !menuTooltipEnabled, menuTooltipEnabled);
      }
   }

   public FaceId
   getExtendsId()
   {
      return extendsId;
   }

   public void setExtendsId(FaceId extendsId)
   {                                              
      if (this.extendsId != extendsId)
      {
         if (!canExtend(extendsId))
            throw new IllegalArgumentException("extended face is anonymous and has different parent. extended parent=" + extendsId.getParentId() + ", my parent=" + getId().getParentId());

         FaceId old = this.extendsId;
         this.extendsId = extendsId;
         pcs.firePropertyChange("extendsId", old, extendsId);
      }
   }

   public boolean
   canExtend(FaceId faceId)
   {
      // Faces can only extend anonymous faces that share the
      // same command parent.
      if (IdHelper.isAnonymous(faceId.getParentId()))
      {
         // return true if they have the same parent..
         return getId().getParentId().equals(faceId.getParentId());
      }

      // if the command isn't anonymous, extension is fine.
      return true;
   }

   private Face
   getParent()
   {
      return extendsId != null ? faceManager.get(extendsId) : null;
   }


   protected void
   notifyMenuTooltipDefaultChanged(boolean newValue)
   {
      if (menuTooltipEnabled == null)
      {
         pcs.firePropertyChange("menuTooltipEnabled", !newValue, newValue);
      }
   }


   //////////////////////////////////////////////////////
   // Property change registration.
   //

   public void
   addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
   {
      pcs.addPropertyChangeListener(propertyName, listener);
   }

   public void
   removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
   {
      pcs.removePropertyChangeListener(propertyName, listener);
   }

   public void
   addPropertyChangeListener(PropertyChangeListener listener)
   {
      pcs.addPropertyChangeListener(listener);
   }

   public void
   removePropertyChangeListener(PropertyChangeListener listener)
   {
      pcs.removePropertyChangeListener(listener);
   }

   public String toString()
   {
      return getClass().getName() + "[id='" + id + "', text='" + text + "' ]";
   }

   
//   protected void parentPropertyChanged(PropertyChangeEvent evt)
//   {
//
//   }


}
