/*
 * Created on 17 oct. 06
 * By Gabriel DROMARD
 */

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;

import javax.swing.JPanel;
import javax.swing.border.AbstractBorder;

/**
 * A class which implements a shadow border.
 *
 * <p>Code Example: </p>
 * <pre>
 *        JPanel root = new JPanel();
 *        // Generate 4 example for each TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT and BOTTOM_RIGHT configurations
 *        for(int i=0; i<4; ++i) {
 *            JPanel test = new JPanel();
 *            test.setPreferredSize(new Dimension(100, 100));
 *            test.setBackground(Color.WHITE);
 *            test.setOpaque(false);
 *            test.setBorder(new ShadowBorder(Color.GRAY, Color.GRAY, 10, i));
 *            root.add(test);
 *        }
 *        //SwingHelper.openInFrame(root);
 *        JFrame frame = new JFrame();
 *        frame.getContentPane().setLayout(new BorderLayout());
 *        frame.getContentPane().add(root, BorderLayout.CENTER);
 *        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
 *        frame.pack();
 *        frame.setVisible(true);
 * </pre>
 */
public class ShadowBorder extends AbstractBorder {
    private int shadowWidth = 10;
    private int inset = 5;
    protected Color lineBorderColor = null;
    protected Color shadowColor = null;
    protected int type = 2;
    public static final int TOP_LEFT = 0;
    public static final int TOP_RIGHT = 1;
    public static final int BOTTOM_LEFT = 2;
    public static final int BOTTOM_RIGHT = 3;
    
    /**
     * Auto-generated main method to display this
     * JPanel inside a new JFrame.
     */
    public static void main(String[] args) {
        JPanel root = new JPanel();
        for(int i=0; i<4; ++i) {
            JPanel test = new JPanel();
            test.setPreferredSize(new Dimension(100, 100));
            test.setBackground(Color.WHITE);
            test.setOpaque(false);
            test.setBorder(new ShadowBorder(Color.GRAY, Color.GRAY, 10, i));
            root.add(test);
        }
        SwingHelper.openInFrame(root);
    }

    /**
     * Creates a shadow border with the specified shadow width and whose
     * colors will be derived from the background color of the
     * component passed into the paintBorder method.
     * @param shadowWidth the length of shadow
     * @param
     * @param
     * @param
     */
    public ShadowBorder(Color shadowColor, Color lineBorderColor, int shadowWidth, int type) {
        super();
        this.shadowWidth = shadowWidth;
        this.shadowColor = shadowColor;
        this.lineBorderColor = lineBorderColor;
        this.type = type;
    }

    /**
     * Returns the insets of the border.
     * @param c the component for which this border insets value applies
     */
    public Insets getBorderInsets(Component c) {
        int hPosition = shadowWidth;
        int vPosition = shadowWidth;
       
        if(type == TOP_LEFT) {
            hPosition = 0;
            vPosition = 0;
        } else if(type == TOP_RIGHT) {
            hPosition = shadowWidth;
            vPosition = 0;
        } else if(type == BOTTOM_LEFT) {
            hPosition = 0;
            vPosition = shadowWidth;
        } else if(type == BOTTOM_RIGHT) {
            hPosition = shadowWidth;
            vPosition = shadowWidth;
        }
        return new Insets((shadowWidth - hPosition)+inset, (shadowWidth - vPosition)+inset, hPosition+inset, vPosition+inset);
    }

    /**
     * Reinitialize the insets parameter with this Border's current Insets.
     * @param c the component for which this border insets value applies
     * @param insets the object to be reinitialized
     */
    public Insets getBorderInsets(Component c, Insets insets) {
        insets.left = insets.top = insets.right = insets.bottom = shadowWidth+2;
        return insets;
    }

    /**
     * Returns the shadow color of the border
     * when rendered on the specified component.  If no shadow
     * color was specified at instantiation, the shadow color
     * is derived from the specified component's background color.
     * @param c the component for which the shadow may be derived
     */
    public Color getShadowColor(Component c) {
        Color shadow = getShadowColor();
        if(shadow == null) shadow = c.getBackground().darker().darker();
        return shadow;
    }

    /**
     * Returns the shadow color of the border.
     * Will return null if no shadow color was specified at instantiation.
     */
    public Color getShadowColor() {
        return shadowColor;
    }

    /**
     * Returns whether or not the border is opaque.
     */
    public boolean isBorderOpaque() {
        return true;
    }


    /**
     * Paints the border for the specified component with the specified position and size.
     * @param c the component for which this border is being painted
     * @param g the paint graphics
     * @param x the x position of the painted border
     * @param y the y position of the painted border
     * @param width the width of the painted border
     * @param height the height of the painted border
     */
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
        int hPosition = shadowWidth;
        int vPosition = shadowWidth;
       
        if(type == TOP_LEFT) {
            hPosition = 0;
            vPosition = 0;
        } else if(type == TOP_RIGHT) {
            hPosition = shadowWidth;
            vPosition = 0;
        } else if(type == BOTTOM_LEFT) {
            hPosition = 0;
            vPosition = shadowWidth;
        } else if(type == BOTTOM_RIGHT) {
            hPosition = shadowWidth;
            vPosition = shadowWidth;
        }
        width = width - 1;
        height = height - 1;
        Graphics2D gg = (Graphics2D) g;
        // backup old color to be able to restore it at the end of paint
        Color oldColor = gg.getColor();
        // Translate position
        gg.translate(x, y);
        // paint shadow
        paintShadow(c, gg, getShadowColor(c), hPosition, vPosition, width-shadowWidth, height-shadowWidth, shadowWidth);
        // Fill with background color the panel
        gg.setColor(c.getBackground());
        gg.fillRect(shadowWidth - hPosition, shadowWidth - vPosition, width-shadowWidth, height-shadowWidth);
        // Draw line border
        if (lineBorderColor != null) {
           gg.setColor(lineBorderColor);
           gg.drawRect(shadowWidth - hPosition, shadowWidth - vPosition, width-shadowWidth, height-shadowWidth);
        }
        // Reset position
        gg.translate(-x, -y);
        // Re put oldColor
        gg.setColor(oldColor);
    }
    
    
    /**
     *
     * @param c           the component for which this border is being painted
     * @param gg          the paint graphics
     * @param shadowColor The color of the shadow
     * @param x           the x position of the painted border
     * @param y           the y position of the painted border
     * @param width       the width of the painted border
     * @param height      the height of the painted border
     * @param shadowWidth The shadow width
     */
    public static void paintShadow(Component c, Graphics2D gg, Color shadowColor, int x, int y, int width, int height, int shadowWidth) {
        // Set anti aliasing
        gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING , RenderingHints.VALUE_ANTIALIAS_ON);
        // Draw shadow
        AlphaComposite ac = null;
        gg.setColor(shadowColor);
        // Initial shadow width:
        int sw = width - 2*shadowWidth;
        // Initial shadow height:
        int sh = height - 2*shadowWidth;
        // Draw shadow
        for(int i=0; i<shadowWidth; ++i) {
            ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f/(i+1));
            gg.setComposite(ac);
            gg.fillRoundRect(x+shadowWidth-i, y+shadowWidth-i, sw + 2*i, sh + 2*i, i*2, i*2);
        }
        // Reset transparency
        ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1);
        gg.setComposite(ac);
    }
}