/*
 * $Id: WrappingProvider.java,v 1.19 2009/01/20 16:22:34 kschaefe Exp $
 *
 * Copyright 2007 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.renderer;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.tree.DefaultMutableTreeNode;

import org.jdesktop.swingx.rollover.RolloverRenderer;
import org.jdesktop.swingx.treetable.TreeTableNode;


/**
 * Wrapping ComponentProvider for usage in tree rendering. Handles the icon
 * itself, delegates the node content to the wrappee. Value-based icon and
 * content mapping can be configured by custom <code>IconValue</code>s and
 * <b>StringValue</b>, respectively.
 * <p>
 * 
 * An example of how to configure a file tree by using the system icons and
 * display names
 * 
 * <pre><code>
 * StringValue sv = new StringValue() {
 * 
 *     public String getString(Object value) {
 *         if (value instanceof File) {
 *             return FileSystemView.getFileSystemView().getSystemDisplayName(
 *                     (File) value);
 *         }
 *         return TO_STRING.getString(value);
 *     }
 * 
 * };
 * IconValue iv = new IconValue() {
 * 
 *     public Icon getIcon(Object value) {
 *         if (value instanceof File) {
 *             return FileSystemView.getFileSystemView().getSystemIcon(
 *                     (File) value);
 *         }
 *         return null;
 *     }
 * };
 * TreeCellRenderer r = new DefaultTreeRenderer(iv, sv);
 * tree.setCellRenderer(r);
 * treeTable.setTreeCellRenderer(r);
 * </code></pre>
 * 
 * PENDING: ui specific focus rect variation (draw rect around icon) missing
 * <p>
 */
public class WrappingProvider extends 
    ComponentProvider<WrappingIconPanel>  implements RolloverRenderer {

    protected ComponentProvider wrappee;
    private boolean unwrapUserObject;

    /**
     * Instantiates a WrappingProvider with default delegate provider.
     * 
     */
    public WrappingProvider() {
        this((ComponentProvider) null);
    }

    /**
     * Instantiates a WrappingProvider with default wrappee, configured
     * to use the wrappeeStringValue. Uses the 
     * given IconValue to configure the icon. 
     * 
     * @param iconValue the IconValue to use for configuring the icon.
     * @param wrappeeStringValue the StringValue to use in the wrappee.
     */
    public WrappingProvider(IconValue iconValue, StringValue wrappeeStringValue) {
        this(iconValue, wrappeeStringValue, true);
    }

    /**
     * Instantiates a WrappingProvider with default wrappee. Uses the 
     * given IconValue to configure the icon. 
     * 
     * @param iconValue the IconValue to use for configuring the icon.
     */
    public WrappingProvider(IconValue iconValue) {
        this(iconValue, null);
    }
   
    /**
     * Instantiates a WrappingProvider with default wrappee configured
     * with the given StringValue. 
     * 
     * PENDING: we have a slight semantic glitch compared to super because
     * the given StringValue is <b>not</b> for use in this provider but for use 
     * in the wrappee!
     * 
     * @param wrappeeStringValue the StringValue to use in the wrappee.
     */
    public WrappingProvider(StringValue wrappeeStringValue) {
        this(null, wrappeeStringValue);
    }

    /**
     * Instantiates a WrappingProvider with the given delegate
     * provider for the node content. If null, a default 
     * LabelProvider will be used. 
     * 
     * @param delegate the provider to use as delegate
     */
    public WrappingProvider(ComponentProvider delegate) {
        this(delegate, true);
    }
    
    /**
     * Instantiates a WrappingProvider with the given delegate
     * provider for the node content and unwrapUserObject property. 
     * If the delegate is null, a default LabelProvider will be used. 
     * 
     * @param delegate the provider to use as delegate
     * @param unwrapUserObject a flag indicating whether this provider
     * should auto-unwrap the userObject from the context value. 
     */
    public WrappingProvider(ComponentProvider delegate, boolean unwrapUserObject) {
        this(null, delegate, unwrapUserObject);
    }

    /**
     * Instantiates a WrappingProvider with the given delegate
     * provider for the node content and unwrapUserObject property. 
     * If the delegate is null, a default LabelProvider will be used. 
     * 
     * @param iv the icon converter to use for this provider
     * @param delegate the provider to use as delegate
     * @param unwrapUserObject a flag indicating whether this provider
     *          should auto-unwrap the userObject from the context value. 
     */
    public WrappingProvider(IconValue iv, ComponentProvider delegate, boolean unwrapUserObject) {
        super(iv != null ? (new MappedValue(null, iv)) : StringValues.EMPTY);
        setWrappee(delegate);
        setUnwrapUserObject(unwrapUserObject);
    }
    
    /**
     * Instantiates a WrappingProvider with the given delegate
     * provider for the node content and unwrapUserObject property. 
     * If the delegate is null, a default LabelProvider will be used. 
     * 
     * @param iv the icon converter to use for this provider
     * @param delegateStringValue the StringValue to use in the wrappee.
     * @param unwrapUserObject a flag indicating whether this provider
     *          should auto-unwrap the userObject from the context value. 
     */
    public WrappingProvider(IconValue iv, StringValue delegateStringValue, boolean unwrapUserObject) {
        this(iv, (ComponentProvider) null, unwrapUserObject);
        getWrappee().setStringValue(delegateStringValue);
    }
    
    /**
     * Sets the given provider as delegate for the node content. 
     * If the delegate is null, a default LabelProvider is set.<p>
     * 
     *  PENDING: rename to setDelegate?
     *  
     * @param delegate the provider to use as delegate. 
     */
    public void setWrappee(ComponentProvider delegate) {
        if (delegate == null) {
            delegate = new LabelProvider();
        }
        this.wrappee = delegate;
    }

    /**
     * Returns the delegate provider used to render the node content.
     * 
     * @return the provider used for rendering the node content.
     */
    public ComponentProvider getWrappee() {
        return wrappee;
    }
    
    /**
     * Sets the unwrapUserObject property. If true, this provider 
     * replaces a context value of type XXNode with its user object before
     * delegating to the wrappee. Otherwise the value is passed as-is always.<p>
     * 
     * The default value is true.
     * 
     * @param unwrap
     * @see #getUnwrapUserObject()
     */
    public void setUnwrapUserObject(boolean unwrap) {
        this.unwrapUserObject = unwrap;
    }
    
    /**
     * Returns a boolean indicating whether this provider tries to unwrap 
     * a userObject from a tree/table/node type value before delegating the
     * context. 
     * 
     * @return a flag indicating the auto-unwrap property.
     * 
     * @see #setUnwrapUserObject(boolean)
     */
    public boolean getUnwrapUserObject() {
        return unwrapUserObject;
    }
    
    /**
     * {@inheritDoc} <p>
     * 
     * Overridden to comply to contract: returns the string representation as 
     * provided by the wrappee (as this level has no string rep). Must do the
     * same unwrapping magic as in configuring the rendering component if the
     * unwrapUserObject property is true. <p>
     * 
     * 
     * @param value the Object to get a String representation for.
     * 
     * @see #setUnwrapUserObject(boolean)
     * @see #getUnwrappedValue(Object)
     */
    @Override
    public String getString(Object value) {
        value = getUnwrappedValue(value);
        return wrappee.getString(value);
    }

    /**
     * Returns the value as it should be passed to the delegate. If the unwrapUserObject
     * property is true, tries return a userObject as appropriate for the value type.
     * Returns the given value itself, ff the property is false or the type does 
     * not support the notion of userObject<p>
     * 
     * Here: unwraps userObject of DefaultMutableTreeNode and TreeTableNode.<p>
     * 
     * @param value the value to possibly unwrap
     * @return the userObject if the value has an appropriate type and the 
     *   unwrapUserObject property is true, otherwise returns the value unchanged.
     *   
     * @see #setUnwrapUserObject(boolean)
     * @see #getString(Object)
     * @see #getRendererComponent(CellContext)  
     */
    protected Object getUnwrappedValue(Object value) {
        if (!getUnwrapUserObject()) return value;
        if (value instanceof DefaultMutableTreeNode) {
            value = ((DefaultMutableTreeNode) value).getUserObject();
        } else if (value instanceof TreeTableNode) {
            TreeTableNode node = (TreeTableNode) value;
            value = node.getUserObject();
        }
        return value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public WrappingIconPanel getRendererComponent(CellContext context) {
        if (context != null) {
            rendererComponent.setComponent(wrappee.rendererComponent);
            Object oldValue = adjustContextValue(context);
            // PENDING JW: sequence of config?
            // A - first wrappee, then this allows to override configure/format methods
            // of this class and overrule the wrappee
            // B - first this, then wrappee allows overrule by overriding getRendererComp
            // would take control from wrappee (f.i. Hyperlink foreground)
            super.getRendererComponent(context);
            wrappee.getRendererComponent(context);
            restoreContextValue(context, oldValue);
            return rendererComponent;
        }
        // PENDING JW: Findbugs barking [NP] Load of known null value
        // probably can move the return rendererComponent from the if
        // to here (the contract is to return the comp as-is if the
        // context is null) - so we can do it here instead of delegating
        // to super?
        return super.getRendererComponent(context);
    }

    /**
     * Restores the context value to the old value.
     * 
     * @param context the CellContext to restore.
     * @param oldValue the value to restore the context to.
     */
    protected void restoreContextValue(CellContext context, Object oldValue) {
        context.replaceValue(oldValue);
    }

    /**
     * Replace the context's value with the userobject if the value is a type
     * supporting the notion of userObject and this provider's unwrapUserObject
     * property is true. Otherwise does nothing.<p>
     * 
     * Subclasses may override but must guarantee to return the original 
     * value for restoring. 
     * 
     * @param context the context to adjust
     * @return the old context value
     * 
     * @see #setUnwrapUserObject(boolean)
     * @see #getString(Object)
     */
    protected Object adjustContextValue(CellContext context) {
        Object oldValue = context.getValue();
        if (getUnwrapUserObject()) {
            context.replaceValue(getUnwrappedValue(oldValue));
        }
        return oldValue;
    }

    @Override
    protected void configureState(CellContext context) {
        rendererComponent.setBorder(BorderFactory.createEmptyBorder());
    }

//    /**
//     * @return
//     */
//    private boolean isBorderAroundIcon() {
//        return Boolean.TRUE.equals(UIManager.get("Tree.drawsFocusBorderAroundIcon"));
//    }

    @Override
    protected WrappingIconPanel createRendererComponent() {
        return new WrappingIconPanel();
    }

    /**
     * {@inheritDoc} <p>
     * 
     * Here: implemented to set the icon.
     */
    @Override
    protected void format(CellContext context) {
        rendererComponent.setIcon(getValueAsIcon(context));
    }

    /**
     * {@inheritDoc} <p>
     * 
     * Overridden to fallback to the default icons supplied by the 
     * context if super returns null.
     *   
     */
    @Override
    protected Icon getValueAsIcon(CellContext context) {
        Icon icon = super.getValueAsIcon(context);
        if (icon == null) {
            return context.getIcon();
        }
        return IconValue.NULL_ICON == icon ? null : icon;
    }
    
    //----------------- implement RolloverController
    

    /**
     * {@inheritDoc}
     */
    public void doClick() {
        if (isEnabled()) {
            ((RolloverRenderer) wrappee).doClick(); 
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isEnabled() {
        return (wrappee instanceof RolloverRenderer) && 
           ((RolloverRenderer) wrappee).isEnabled();
    }


    
}
