/**
 * Copyright (c) 2002-2012 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.edit.ui.provider;


import java.util.ArrayList;
import java.util.Collection;

import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableColorProvider;
import org.eclipse.jface.viewers.ITableFontProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;

import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.provider.IChangeNotifier;
import org.eclipse.emf.edit.provider.IItemColorProvider;
import org.eclipse.emf.edit.provider.IItemFontProvider;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.emf.edit.provider.ITableItemColorProvider;
import org.eclipse.emf.edit.provider.ITableItemFontProvider;
import org.eclipse.emf.edit.provider.ITableItemLabelProvider;
import org.eclipse.emf.edit.provider.IViewerNotification;


/**
 * This label provider wraps an AdapterFactory
 * and it delegates its JFace provider interfaces to corresponding adapter-implemented item provider interfaces.
 * All method calls to the various label provider interfaces
 * are delegated to interfaces implemented by the adapters generated by the AdapterFactory.
 * {@link ILabelProvider} is delegated to {@link IItemLabelProvider};
 * {@link IFontProvider} is delegated to {@link IItemFontProvider};
 * {@link IColorProvider} is delegated to {@link IItemColorProvider};
 * {@link ITableLabelProvider} is delegated to {@link ITableItemLabelProvider};
 * and {@link ITableFontProvider} is delegated to {@link ITableItemFontProvider}.
 * and {@link ITableColorProvider} is delegated to {@link ITableItemColorProvider}.
 * <p>
 * The label provider has no mechanism for notifying the viewer of changes.
 * As long as the AdapterFactory is also used in an AdapterFactoryContentProvider, this won't be a problem,
 * because notifications will be forward as a result of that.
 */
public class AdapterFactoryLabelProvider implements ILabelProvider, ITableLabelProvider, INotifyChangedListener
{
  private static final long serialVersionUID = 1L;

  /**
   * An extended version of the adapter factory label provider that also provides for fonts.
   */
  public static class FontProvider extends AdapterFactoryLabelProvider implements IFontProvider, ITableFontProvider
  {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * Construct an instance that wraps the given factory and specifies the given default font.
     * @param adapterFactory an adapter factory that yield adapters that implement the various item label provider interfaces.
     * @param defaultFont the font that will be used when no font is specified.
     */
    public FontProvider(AdapterFactory adapterFactory, Font defaultFont)
    {
      super(adapterFactory);
      setDefaultFont(defaultFont);
    }

    /**
     * Construct an instance that wraps the given factory and uses the font of the viewer's control.
     * @param adapterFactory an adapter factory that yield adapters that implement the various item label provider interfaces.
     * @param viewer the viewer for which the control's font should be used.
     */
    public FontProvider(AdapterFactory adapterFactory, Viewer viewer)
    {
      this(adapterFactory, viewer.getControl().getFont());
    }
  }

  /**
   * An extended version of the adapter factory label provider that also provides for colors.
   */
  public static class ColorProvider extends AdapterFactoryLabelProvider implements IColorProvider, ITableColorProvider
  {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * Construct an instance that wraps the given factory and specifies the given default colors.
     * @param adapterFactory an adapter factory that yield adapters that implement the various item label provider interfaces.
     * @param defaultForeground the foreground color that will be used when no foreground color is specified.
     * @param defaultBackground the background color that will be used when no background color is specified.
     */
    public ColorProvider(AdapterFactory adapterFactory, Color defaultForeground, Color defaultBackground)
    {
      super(adapterFactory);
      setDefaultForeground(defaultForeground);
      setDefaultBackground(defaultBackground);
    }

    /**
     * Construct an instance that wraps the given factory and uses the colors of the viewer's control.
     * @param adapterFactory  an adapter factory that yield adapters that implement the various item label provider interfaces.
     * @param viewer the viewer for which the control's color should be used.
     */
    public ColorProvider(AdapterFactory adapterFactory, Viewer viewer)
    {
      this(adapterFactory, viewer.getControl().getForeground(), viewer.getControl().getBackground());
    }
  }

  /**
   * An extended version of the adapter factory label provider that also provides for fonts and colors.
   */
  public static class FontAndColorProvider extends AdapterFactoryLabelProvider implements IColorProvider, IFontProvider, ITableColorProvider, ITableFontProvider
  {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * Construct an instance that wraps the given factory and specifies the given default font and colors.
     * @param adapterFactory an adapter factory that yield adapters that implement the various item label provider interfaces.
     * @param defaultFont the font that will be used when no font is specified.
     * @param defaultForeground the foreground color that will be used when no foreground color is specified.
     * @param defaultBackground the background color that will be used when no background color is specified.
     */
    public FontAndColorProvider(AdapterFactory adapterFactory, Font defaultFont, Color defaultForeground, Color defaultBackground)
    {
      super(adapterFactory);
      setDefaultFont(defaultFont);
      setDefaultForeground(defaultForeground);
      setDefaultBackground(defaultBackground);
    }

    /**
     * Construct an instance that wraps the given factory and uses the font and colors of the viewer's control.
     * @param adapterFactory an adapter factory that yield adapters that implement the various item label provider interfaces.
     * @param viewer the viewer for which the control's font and color should be used.
     */
    public FontAndColorProvider(AdapterFactory adapterFactory, Viewer viewer)
    {
      this(adapterFactory, viewer.getControl().getFont(), viewer.getControl().getForeground(), viewer.getControl().getBackground());
    }
  }

  /**
   * This keep track of the one factory we are using.
   * Use a {@link org.eclipse.emf.edit.provider.ComposedAdapterFactory} 
   * if adapters from more the one factory are involved in the model.
   */
  protected AdapterFactory adapterFactory;

  /**
   * The font that will be used when no font is specified.
   */
  protected Font defaultFont;
  
  /**
   * The foreground color that will be used when no foreground color is specified.
   */
  protected Color defaultForeground;
  
  /**
   * The background color that will be used when no background color is specified.
   */
  protected Color defaultBackground; 

  /**
   * This keeps track of the label provider listeners.
   */
  protected Collection<ILabelProviderListener> labelProviderListeners;

  /**
   * Whether label update notifications are fired.
   */
  protected boolean isFireLabelUpdateNotifications;

  private static final Class<?> IItemLabelProviderClass = IItemLabelProvider.class;
  private static final Class<?> ITableItemLabelProviderClass = ITableItemLabelProvider.class;
  private static final Class<?> IItemFontProviderClass = IItemFontProvider.class;
  private static final Class<?> IItemColorProviderClass = IItemColorProvider.class;
  private static final Class<?> ITableItemFontProviderClass = ITableItemFontProvider.class;
  private static final Class<?> ITableItemColorProviderClass = ITableItemColorProvider.class;

  /**
   *  Construct an instance that wraps the given factory.
   * If the adapter factory is an {@link IChangeNotifier},
   * a listener is added to it,
   * so it's important to call {@link #dispose()}.
   *  @param adapterFactory an adapter factory that yield adapters that implement the various item label provider interfaces.
   */
  public AdapterFactoryLabelProvider(AdapterFactory adapterFactory)
  {
    this.adapterFactory = adapterFactory;
    if (adapterFactory instanceof IChangeNotifier)
    {
      ((IChangeNotifier)adapterFactory).addListener(this);
    }

    labelProviderListeners = new ArrayList<ILabelProviderListener>();
  }

  /**
   * Return the wrapped AdapterFactory.
   */
  public AdapterFactory getAdapterFactory()
  {
    return adapterFactory;
  }

  /**
   * Set the wrapped AdapterFactory.
   * If the adapter factory is an {@link IChangeNotifier},
   * a listener is added to it,
   * so it's important to call {@link #dispose()}.
   */
  public void setAdapterFactory(AdapterFactory adapterFactory)
  {
    if (this.adapterFactory instanceof IChangeNotifier)
    {
      ((IChangeNotifier)this.adapterFactory).removeListener(this);
    }

    if (adapterFactory instanceof IChangeNotifier)
    {
      ((IChangeNotifier)adapterFactory).addListener(this);
    }

    this.adapterFactory = adapterFactory;
  }

  /**
   * Return the default font.
   */
  public Font getDefaultFont()
  {
    return defaultFont;
  }

  /**
   * Set the default font.
   */
  public void setDefaultFont(Font font)
  {
    defaultFont = font;
  }

  /**
   * Return the default foreground color.
   */
  public Color getDefaultForeground()
  {
    return defaultForeground;
  }

  /**
   * Set the default foreground color.
   */
  public void setDefaultForeground(Color color)
  {
    defaultForeground = color;
  }

  /**
   * Return the default background color.
   */
  public Color getDefaultBackground()
  {
    return defaultBackground;
  }

  /**
   * Set the default background color.
   */
  public void setDefaultBackground(Color color)
  {
    defaultBackground = color;
  }

  /**
   * Since we won't ever generate these notifications, we can just ignore this.
   */
  public void addListener(ILabelProviderListener listener) 
  {
    labelProviderListeners.add(listener);
  }

  /**
   * Since we won't ever add listeners, we can just ignore this.
   */
  public void removeListener(ILabelProviderListener listener)
  {
    labelProviderListeners.remove(listener);
  }

  /**
   * This discards the content provider and removes this as a listener to the {@link #adapterFactory}.
   */
  public void dispose()
  {
    if (this.adapterFactory instanceof IChangeNotifier)
    {
      ((IChangeNotifier)adapterFactory).removeListener(this);
    }
  }

  /**
   * This always returns true right now.
   */
  public boolean isLabelProperty(Object object, String id)
  {
    return true;
  }

  /**
   * This implements {@link org.eclipse.jface.viewers.ILabelProvider}.getImage by forwarding it to an object that implements 
   * {@link org.eclipse.emf.edit.provider.IItemLabelProvider#getImage IItemLabelProvider.getImage}
   */
  public Image getImage(Object object) 
  {
    // Get the adapter from the factory.
    //
    IItemLabelProvider itemLabelProvider = (IItemLabelProvider)adapterFactory.adapt(object, IItemLabelProviderClass);

    return 
      itemLabelProvider != null ?
        getImageFromObject(itemLabelProvider.getImage(object)) :
        getDefaultImage(object);
  }

  protected Image getDefaultImage(Object object)
  {
    String image = "full/obj16/GenericValue";
    if (object instanceof String)
    {
      image = "full/obj16/TextValue";
    }
    else if (object instanceof Boolean)
    {
      image = "full/obj16/BooleanValue";
    }
    else if (object instanceof Float || object instanceof Double)
    {
      image = "full/obj16/RealValue";
    }
    else if (object instanceof Integer || object instanceof Short || object instanceof Long || object instanceof Byte)
    {
      image = "full/obj16/RealValue";
    }

    return getImageFromObject(EMFEditPlugin.INSTANCE.getImage(image));
  }

  protected Image getImageFromObject(Object object)
  {
    return ExtendedImageRegistry.getInstance().getImage(object);
  }

  /**
   * This implements {@link ILabelProvider}.getText by forwarding it to an object that implements 
   * {@link IItemLabelProvider#getText IItemLabelProvider.getText}
   */
  public String getText(Object object) 
  {
    // Get the adapter from the factory.
    //
    IItemLabelProvider itemLabelProvider = (IItemLabelProvider)adapterFactory.adapt(object, IItemLabelProviderClass);

    return
      itemLabelProvider != null ?
        itemLabelProvider.getText(object) :
        object == null ? 
          "" :
          object.toString();
  }

  /**
   * This implements {@link org.eclipse.jface.viewers.IFontProvider}.getFont by forwarding it to an object that implements 
   * {@link org.eclipse.emf.edit.provider.IItemFontProvider#getFont IItemFontProvider.getFont}
   */
  public Font getFont(Object object) 
  {
    // Get the adapter from the factory.
    //
    IItemFontProvider itemFontProvider = (IItemFontProvider)adapterFactory.adapt(object, IItemFontProviderClass);

    return 
      itemFontProvider != null ?
        getFontFromObject(itemFontProvider.getFont(object)) :
        null;
  }

  protected Font getFontFromObject(Object object)
  {
    return object == null ? null : ExtendedFontRegistry.INSTANCE.getFont(defaultFont, object);
  }

  /**
   * This implements {@link org.eclipse.jface.viewers.IColorProvider}.getForeground by forwarding it to an object that implements 
   * {@link org.eclipse.emf.edit.provider.IItemColorProvider#getForeground IItemColorProvider.getForeground}
   */
  public Color getForeground(Object object) 
  {
    // Get the adapter from the factory.
    //
    IItemColorProvider itemColorProvider = (IItemColorProvider)adapterFactory.adapt(object, IItemColorProviderClass);

    return 
      itemColorProvider != null ?
        getColorFromObject(itemColorProvider.getForeground(object)) :
        null;
  }

  /**
   * This implements {@link org.eclipse.jface.viewers.IColorProvider}.getBackground by forwarding it to an object that implements 
   * {@link org.eclipse.emf.edit.provider.IItemColorProvider#getBackground IItemColorProvider.getBackground}
   */
  public Color getBackground(Object object) 
  {
    // Get the adapter from the factory.
    //
    IItemColorProvider itemColorProvider = (IItemColorProvider)adapterFactory.adapt(object, IItemColorProviderClass);

    return 
      itemColorProvider != null ?
        getColorFromObject(itemColorProvider.getBackground(object)) :
        null;
  }

  protected Color getColorFromObject(Object object)
  {
    return object == null ? null : ExtendedColorRegistry.INSTANCE.getColor(defaultForeground, defaultBackground, object);
  }

  /**
   * This implements {@link ITableLabelProvider}.getColumnImage by forwarding it to an object that implements 
   * {@link ITableItemLabelProvider#getColumnImage ITableItemLabelProvider.getColumnImage}
   * or failing that, an object that implements 
   * {@link IItemLabelProvider#getImage IItemLabelProvider.getImage}
   * where the columnIndex is ignored.
   */
  public Image getColumnImage(Object object, int columnIndex)
  {
    // Get the adapter from the factory.
    //
    ITableItemLabelProvider tableItemLabelProvider = (ITableItemLabelProvider)adapterFactory.adapt(object, ITableItemLabelProviderClass);

    // No image is a good default.
    //
    Image result = null;

    // Now we could check that the adapter implements interface ITableItemLabelProvider.
    //
    if (tableItemLabelProvider  != null)
    {
      // And delegate the call.
      //
      result = getImageFromObject(tableItemLabelProvider.getColumnImage(object, columnIndex));
    }
    // Otherwise, we could check that the adapter implements interface IItemLabelProvider.
    //
    else 
    {
      IItemLabelProvider itemLabelProvider = (IItemLabelProvider)adapterFactory.adapt(object, IItemLabelProviderClass);
      if (itemLabelProvider != null)
      {
        // And delegate the call.
        //
        result = getImageFromObject(itemLabelProvider.getImage(object));
      }
    }

    return result;
  }

  /**
   * This implements {@link ITableLabelProvider}.getColumnText by forwarding it to an object that implements 
   * {@link ITableItemLabelProvider#getColumnText ITableItemLabelProvider.getColumnText}
   * or failing that, an object that implements 
   * {@link IItemLabelProvider#getText IItemLabelProvider.getText}
   * where the columnIndex are is ignored.
   */
  public String getColumnText(Object object, int columnIndex)
  {
    // Get the adapter from the factory.
    //
    ITableItemLabelProvider tableItemLabelProvider = (ITableItemLabelProvider)adapterFactory.adapt(object, ITableItemLabelProviderClass);

    // Now we could check that the adapter implements interface ITableItemLabelProvider.
    //
    if (tableItemLabelProvider != null)
    {
      // And delegate the call.
      //
      return tableItemLabelProvider.getColumnText(object, columnIndex);
    }
    // Otherwise, we could check that the adapter implements interface IItemLabelProvider.
    //
    else 
    {
      IItemLabelProvider itemLabelProvider = (IItemLabelProvider)adapterFactory.adapt(object, IItemLabelProviderClass);
      if (itemLabelProvider != null)
      {
        // And delegate the call.
        //
        return itemLabelProvider.getText(object);
      }
      // If there is a column object, just convert it to a string.
      //
      else if (object != null)
      {
        return object.toString();
      }
      else
      {
      return "";
      }
    }
  }

  /**
   * This implements {@link ITableFontProvider}.getFont by forwarding it to an object that implements 
   * {@link ITableItemFontProvider#getFont ITableItemFontProvider.getFont}
   * or failing that, an object that implements 
   * {@link IItemFontProvider#getFont IItemFontProvider.getFont}
   * where the columnIndex is ignored.
   */
  public Font getFont(Object object, int columnIndex)
  {
    // Get the adapter from the factory.
    //
    ITableItemFontProvider tableItemFontProvider = (ITableItemFontProvider)adapterFactory.adapt(object, ITableItemFontProviderClass);

    // No font is a good default.
    //
    Font result = null;

    // Now we could check that the adapter implements interface ITableItemFontProvider.
    //
    if (tableItemFontProvider  != null)
    {
      // And delegate the call.
      //
      result = getFontFromObject(tableItemFontProvider.getFont(object, columnIndex));
    }
    // Otherwise, we could check that the adapter implements interface IItemFontProvider.
    //
    else
    {
      IItemFontProvider itemFontProvider = (IItemFontProvider)adapterFactory.adapt(object, IItemFontProviderClass);
      if (itemFontProvider != null)
      {
        // And delegate the call.
        //
        result = getFontFromObject(itemFontProvider.getFont(object));
      }
    }

    return result;
  }

  /**
   * This implements {@link ITableColorProvider}.getForeground by forwarding it to an object that implements 
   * {@link ITableItemColorProvider#getForeground ITableItemColorProvider.getForeground}
   * or failing that, an object that implements 
   * {@link IItemColorProvider#getForeground IItemColorProvider.getForeground}
   * where the columnIndex is ignored.
   */
  public Color getForeground(Object object, int columnIndex)
  {
    // Get the adapter from the factory.
    //
    ITableItemColorProvider tableItemColorProvider = (ITableItemColorProvider)adapterFactory.adapt(object, ITableItemColorProviderClass);

    // No color is a good default.
    //
    Color result = null;

    // Now we could check that the adapter implements interface ITableItemColorProvider.
    //
    if (tableItemColorProvider  != null)
    {
      // And delegate the call.
      //
      result = getColorFromObject(tableItemColorProvider.getForeground(object, columnIndex));
    }
    // Otherwise, we could check that the adapter implements interface IItemColorProvider.
    //
    else
    {
      IItemColorProvider itemColorProvider = (IItemColorProvider)adapterFactory.adapt(object, IItemColorProviderClass);
      if (itemColorProvider != null)
      {
        // And delegate the call.
        //
        result = getColorFromObject(itemColorProvider.getForeground(object));
      }
    }
 
    return result;
  }

  /**
   * This implements {@link ITableColorProvider}.getBackground by forwarding it to an object that implements 
   * {@link ITableItemColorProvider#getBackground ITableItemColorProvider.getBackground}
   * or failing that, an object that implements 
   * {@link IItemColorProvider#getBackground IItemColorProvider.getBackground}
   * where the columnIndex is ignored.
   */
  public Color getBackground(Object object, int columnIndex)
  {
    // Get the adapter from the factory.
    //
    ITableItemColorProvider tableItemColorProvider = (ITableItemColorProvider)adapterFactory.adapt(object, ITableItemColorProviderClass);
 
    // No color is a good default.
    //
    Color result = null;
 
    // Now we could check that the adapter implements interface ITableItemColorProvider.
    //
    if (tableItemColorProvider  != null)
    {
      // And delegate the call.
      //
      result = getColorFromObject(tableItemColorProvider.getBackground(object, columnIndex));
    }
    // Otherwise, we could check that the adapter implements interface IItemColorProvider.
    //
    else
    {
      IItemColorProvider itemColorProvider = (IItemColorProvider)adapterFactory.adapt(object, IItemColorProviderClass);
      if (itemColorProvider != null)
      {
        // And delegate the call.
        //
        result = getColorFromObject(itemColorProvider.getBackground(object));
      }
    }

    return result;
  }

  /**
   * Returns whether this label provider fires {@link ILabelProviderListener#labelProviderChanged(LabelProviderChangedEvent) update notifications}.
   */
  public boolean isFireLabelUpdateNotifications()
  {
    return isFireLabelUpdateNotifications;
  }

  /**
   * Sets whether this label provider fires {@link ILabelProviderListener#labelProviderChanged(LabelProviderChangedEvent) update notifications}.
   */
  public void setFireLabelUpdateNotifications(boolean isFireLabelUpdateNotifications)
  {
    this.isFireLabelUpdateNotifications = isFireLabelUpdateNotifications;
  }

  public void fireLabelProviderChanged()
  {
    for (ILabelProviderListener labelProviderListener : labelProviderListeners)
    {
      labelProviderListener.labelProviderChanged(new LabelProviderChangedEvent(this));
    }
  }

  public void notifyChanged(Notification notification)
  {
    if (isFireLabelUpdateNotifications())
    {
      if (!(notification instanceof IViewerNotification) || ((IViewerNotification)notification).isLabelUpdate())
      {
        for (ILabelProviderListener labelProviderListener : labelProviderListeners)
        {
          labelProviderListener.labelProviderChanged(new LabelProviderChangedEvent(this, notification.getNotifier()));
        }
      }
    }
  }
}
