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

package org.pietschy.command;

import org.pietschy.command.log.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.lang.ref.WeakReference;
import java.util.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

/**
 * This class is responsible for the management of the faces for a given {@link CommandManager}.
 * It is used by {@link Face faces} to locate their parents.  Construction of {@link Face} instances
 * is delegated to an instance of {@link AbstractFaceBuilder}.
 *
 * @see CommandManager#getFaceManager
 * @see #setFaceBuilder
 */
public class
FaceManager
{
   static final String _ID_ = "$Id: FaceManager.java,v 1.19 2007/01/03 22:41:23 pietschy Exp $";

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

   private HashMap faceRegistry = new HashMap();
   private WeakHashMap anonymousFaceRegistry = new WeakHashMap();

   private AbstractFaceBuilder faceBuilder;

   private boolean menuTooltipsEnabled = false;

   private CommandManager commandManager;

//   private PropertyChangeListener faceChangeListener = new FaceChangeListener();

   protected FaceManager(CommandManager manager)
   {
      this.commandManager = manager;
   }

   /**
    * Sets the {@link AbstractFaceBuilder} that the manager is to use for constructing faces.
    *
    * @param faceBuilder the new builder to use.
    */
   public void
   setFaceBuilder(AbstractFaceBuilder faceBuilder)
   {
      this.faceBuilder = faceBuilder;
      faceBuilder.setCommandManager(commandManager);
   }

   /**
    * Gets the {@link AbstractFaceBuilder} currently in use by the manager.
    *
    * @return the {@link AbstractFaceBuilder} currently in use by the manager.
    */
   public AbstractFaceBuilder
   getFaceBuilder()
   {
      return faceBuilder;
   }

   public Face
   createFace(Command command, String name)
   {
      FaceId faceId = new FaceId(command.getId(), name);
      Face face = faceBuilder.createFace(faceId, this);
//      face.addPropertyChangeListener(faceChangeListener);
      put(face);

      return face;
   }


   /**
    * Checks if tooltip are globally enabled on menus.  This setting will only be used by
    * {@link Face faces} that haven't been explicitly configured to enable or disable tooltips on
    * menus.
    *
    * @return <tt>true</tt> if tooltips are enabled on menus, <tt>false</tt> otherwise.
    */
   public boolean
   isMenuTooltipsEnabled()
   {
      return menuTooltipsEnabled;
   }

   /**
    * Configures the current menu tooltip state and notifies all {@link Face faces} that the
    * state has changed.  This setting will be used by all {@link Face faces} that haven't been
    * explicitly configured to enable or disable tooltips on menus.
    *
    * @param menuTooltipsEnabled <tt>true</tt> to enable tooltips on menus, <tt>false</tt> to
    *                            disable them.
    */
   public void
   setMenuTooltipsEnabled(boolean menuTooltipsEnabled)
   {
      this.menuTooltipsEnabled = menuTooltipsEnabled;
      for (Iterator iter = faceRegistry.values().iterator(); iter.hasNext();)
      {
         ((Face) iter.next()).notifyMenuTooltipDefaultChanged(menuTooltipsEnabled);
      }
   }

   /**
    * This is invoked for every element that contains face information in a newly loaded
    * configuration file.
    *
    * @param parent the element that contains one or more face elements.
    */
   protected void
   extractFaces(Element parent)
   {
      log.enter("extractFaces");

      String parentId;
      // this is to handle the extension id within the faces element.
      if (Names.EXTENSION_ELEMENT.equals(parent.getTagName()))
         parentId = Util.getAttribute(parent, Names.COMMAND_ID_ATTRIBUTE);
      else
         parentId = Util.getElementId(parent);

      log.param("parentId", String.valueOf(parentId));

      boolean defaultFaceUsed = false;

      NodeList faceElements = parent.getElementsByTagName(Names.FACE_ELEMENT);

      for (int i = 0; i < faceElements.getLength(); i++)
      {
         Element faceRoot = (Element) faceElements.item(i);
         String faceName = Util.getAttribute(faceRoot, Names.NAME_ATTRIBUTE);

         if (faceName == null || faceName.trim().length() < 1)
         {
            if (!defaultFaceUsed)
               faceName = Face.DEFAULT;
            else
               log.warn("Face ignored in: " + parentId);
         }

//         Locale locale = null;
//         String localeName = Util.getAttribute(faceRoot, Names.LOCALE_ATTRIBUTE);
//         if (localeName != null)
//         {
//            String[] localeBits = localeName.split("_");
//            switch (localeBits.length)
//            {
//               case 1:
//                  locale = new Locale(localeBits[0]);
//                  break;
//               case 2:
//                  locale = new Locale(localeBits[0], localeBits[1]);
//                  break;
//               case 3:
//                  locale = new Locale(localeBits[0], localeBits[1], localeBits[2]);
//                  break;
//               default:
//                  locale =
//                  break;
//
//            }
//         }
         if (faceName == Face.DEFAULT)
            defaultFaceUsed = true;

         FaceId faceId = new FaceId(parentId, faceName);

         Face face = get(faceId);
         if (face == null)
         {
            face = faceBuilder.createFace(faceId, this);
            faceBuilder.configure(face, faceRoot);
            put(face);
            log.debug("Registered face: " + faceId);
         }
         else
         {
            log.debug("Duplicate face found for: " + faceId);
         }
      }
      log.exit("extractFaces()");
   }

   /**
    * Retreives the faces that are registered for the specified parent id.
    */
   public Face[]
   getFacesFor(Command parent)
   {
      log.enter("getFacesFor");
      log.param("parent", String.valueOf(parent));

      String parentId = parent.getId();
      ArrayList faces = new ArrayList(10);

      if (parent.isAnonymous())
      {
         for (Iterator iter = anonymousFaceRegistry.entrySet().iterator(); iter.hasNext();)
         {
            Map.Entry entry = (Map.Entry) iter.next();
            FaceId faceId = (FaceId) entry.getKey();

            if (faceId.getParentId().equals(parentId))
               faces.add(((WeakReference) entry.getValue()).get());
         }
      }
      else
      {
         for (Iterator iter = faceRegistry.entrySet().iterator(); iter.hasNext();)
         {
            Map.Entry entry = (Map.Entry) iter.next();
            FaceId faceId = (FaceId) entry.getKey();

            if (faceId.getParentId().equals(parentId))
               faces.add(entry.getValue());
         }
      }
      log.returned(String.valueOf(faces));
      log.exit("getFacesFor()");

      return (Face[]) faces.toArray(new Face[faces.size()]);
   }

   public Face
   get(FaceId faceId)
   {
      if (faceId.isAnonymous())
      {
         WeakReference ref = (WeakReference) anonymousFaceRegistry.get(faceId);
         return ref != null ? (Face) ref.get() : null;
      }
      else
      {
         return (Face) faceRegistry.get(faceId);
      }
   }

   public void
   put(Face face)
   {
      FaceId faceId = face.getId();

      if (faceId.isAnonymous())
      {
         if (anonymousFaceRegistry.containsKey(faceId))
            throw new RuntimeException("Face '" + faceId + "' already contained in the registry");

         anonymousFaceRegistry.put(faceId, new WeakReference(face));

      }
      else
      {
         if (faceRegistry.containsKey(faceId))
            throw new RuntimeException("Face '" + faceId + "' already contained in the registry");

         faceRegistry.put(faceId, face);
      }
   }

//   private class FaceChangeListener implements PropertyChangeListener
//   {
//      public void propertyChange(PropertyChangeEvent evt)
//      {
//
//      }
//   }
}
