/*
 * $Id: IconBorder.java 4147 2012-02-01 17:13:24Z kschaefe $
 *
 * Copyright 2004 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.border;

import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.io.Serializable;

import javax.swing.Icon;
import javax.swing.SwingConstants;
import javax.swing.border.Border;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.icon.EmptyIcon;

/**
 * {@code IconBorder} creates a border that places an {@code Icon} in the border
 * on the horizontal axis. The border does not add any additional insets other
 * than the inset required to produce the space for the icon. If additional
 * insets are required, users should create a
 * {@link javax.swing.border.CompoundBorder compund border}.
 * <p>
 * This border is useful when attempting to add {@code Icon}s to pre-existing
 * components without requiring specialty painting.
 * 
 * @author Amy Fowler
 * @author Karl Schaefer
 * 
 * @version 1.1
 */
@JavaBean
public class IconBorder implements Border, Serializable {

    /**
     * An empty icon.
     */
    public static final Icon EMPTY_ICON = new EmptyIcon();
    private int padding;
    private Icon icon;
    private int iconPosition;
    private Rectangle iconBounds = new Rectangle();
    
    /**
     * Creates an {@code IconBorder} with an empty icon in a trailing position
     * with a padding of 4.
     * 
     * @see #EMPTY_ICON
     */
    public IconBorder() {
        this(null);
    }

    /**
     * Creates an {@code IconBorder} with the specified icon in a trailing
     * position with a padding of 4.
     * 
     * @param validIcon
     *            the icon to set. This may be {@code null} to represent an
     *            empty icon.
     * @see #EMPTY_ICON
     */
    public IconBorder(Icon validIcon) {
        this(validIcon, SwingConstants.TRAILING);
    }

    /**
     * Creates an {@code IconBorder} with the specified constraints and a
     * padding of 4.
     * 
     * @param validIcon
     *            the icon to set. This may be {@code null} to represent an
     *            empty icon.
     * @param iconPosition
     *            the position to place the icon relative to the component
     *            contents. This must be one of the following
     *            {@code SwingConstants}:
     *            <ul>
     *            <li>{@code LEADING}</li>
     *            <li>{@code TRAILING}</li>
     *            <li>{@code EAST}</li>
     *            <li>{@code WEST}</li>
     *            </ul>
     * @throws IllegalArgumentException
     *             if {@code iconPosition} is not a valid position.
     * @see #EMPTY_ICON
     */
    public IconBorder(Icon validIcon, int iconPosition) {
        this(validIcon, iconPosition, 4);
    }
    
    /**
     * Creates an {@code IconBorder} with the specified constraints. If
     * {@code validIcon} is {@code null}, {@code EMPTY_ICON} is used instead.
     * If {@code padding} is negative, then the border does not use padding.
     * 
     * @param validIcon
     *            the icon to set. This may be {@code null} to represent an
     *            empty icon.
     * @param iconPosition
     *            the position to place the icon relative to the component
     *            contents. This must be one of the following
     *            {@code SwingConstants}:
     *            <ul>
     *            <li>{@code LEADING}</li>
     *            <li>{@code TRAILING}</li>
     *            <li>{@code EAST}</li>
     *            <li>{@code WEST}</li>
     *            </ul>
     * @param padding
     *            the padding to surround the icon with. All non-positive values
     *            set the padding to 0.
     * @throws IllegalArgumentException
     *             if {@code iconPosition} is not a valid position.
     * @see #EMPTY_ICON
     */
    public IconBorder(Icon validIcon, int iconPosition, int padding) {
        setIcon(validIcon);
        setPadding(padding);
        setIconPosition(iconPosition);
    }

    private boolean isValidPosition(int position) {
        boolean result = false;
        
        switch (position) {
        case SwingConstants.LEADING:
        case SwingConstants.TRAILING:
        case SwingConstants.EAST:
        case SwingConstants.WEST:
            result = true;
            break;
        default:
            result = false;
        }
        
        return result;
    }
    
    /**
     * {@inheritDoc}
     */
    public Insets getBorderInsets(Component c) {
        int horizontalInset = icon.getIconWidth() + (2 * padding);
        int iconPosition = bidiDecodeLeadingTrailing(c.getComponentOrientation(), this.iconPosition);
        if (iconPosition == SwingConstants.EAST) {
            return new Insets(0, 0, 0, horizontalInset);
        }
        return new Insets(0, horizontalInset, 0, 0);
    }

    /**
     * Sets the icon for this border.
     * 
     * @param validIcon
     *            the icon to set.  This may be {@code null} to represent an
     *            empty icon.
     * @see #EMPTY_ICON
     */
    public void setIcon(Icon validIcon) {
        this.icon = validIcon == null ? EMPTY_ICON : validIcon;
    }
    
    /**
     * This border is not opaque.
     * 
     * @return always returns {@code false}
     */
    public boolean isBorderOpaque() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public void paintBorder(Component c, Graphics g, int x, int y, int width,
        int height) {
        int iconPosition = bidiDecodeLeadingTrailing(c.getComponentOrientation(), this.iconPosition);
        if (iconPosition == SwingConstants.NORTH_EAST) {
            iconBounds.y = y + padding;
            iconBounds.x = x + width - padding - icon.getIconWidth();
        } else if (iconPosition == SwingConstants.EAST) {    // EAST
            iconBounds.y = y
                + ((height - icon.getIconHeight()) / 2);
            iconBounds.x = x + width - padding - icon.getIconWidth();
        } else if (iconPosition == SwingConstants.WEST) {
            iconBounds.y = y
                + ((height - icon.getIconHeight()) / 2);
            iconBounds.x = x + padding;
        }
        iconBounds.width = icon.getIconWidth();
        iconBounds.height = icon.getIconHeight();
        icon.paintIcon(c, g, iconBounds.x, iconBounds.y);
    }

    /**
     * Returns EAST or WEST depending on the ComponentOrientation and 
     * the given postion LEADING/TRAILING this method has no effect for other
     * position values
     */
    private int bidiDecodeLeadingTrailing(ComponentOrientation c, int position) {
        if(position == SwingConstants.TRAILING) {
            if(!c.isLeftToRight()) {
                return SwingConstants.WEST;
            }
            return SwingConstants.EAST;
        }
        if(position == SwingConstants.LEADING) {
            if(c.isLeftToRight()) {
                return SwingConstants.WEST;
            }
            return SwingConstants.EAST;
        }
        return position;
    }

    /**
     * Gets the padding surrounding the icon.
     * 
     * @return the padding for the icon. This value is guaranteed to be
     *         nonnegative.
     */
    public int getPadding() {
        return padding;
    }

    /**
     * Sets the padding around the icon.
     * 
     * @param padding
     *            the padding to set. If {@code padding < 0}, then
     *            {@code padding} will be set to {@code 0}.
     */
    public void setPadding(int padding) {
        this.padding = padding < 0 ? 0 : padding;
    }

    /**
     * Returns the position to place the icon (relative to the component contents).
     * 
     * @return one of the following {@code SwingConstants}:
     *        <ul>
     *          <li>{@code LEADING}</li>
     *          <li>{@code TRAILING}</li>
     *          <li>{@code EAST}</li>
     *          <li>{@code WEST}</li>
     *        </ul>
     */
    public int getIconPosition() {
        return iconPosition;
    }

    /**
     * Sets the position to place the icon (relative to the component contents).
     * 
     * @param iconPosition must be one of the following {@code SwingConstants}:
     *        <ul>
     *          <li>{@code LEADING}</li>
     *          <li>{@code TRAILING}</li>
     *          <li>{@code EAST}</li>
     *          <li>{@code WEST}</li>
     *        </ul>
     * @throws IllegalArgumentException
     *             if {@code iconPosition} is not a valid position.
     */
    public void setIconPosition(int iconPosition) {
        if (!isValidPosition(iconPosition)) {
            throw new IllegalArgumentException("Invalid icon position");
        }
        this.iconPosition = iconPosition;
    }

}
