/*
 * org.openmicroscopy.shoola.util.ui.ColouredButtonUI
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006 University of Dundee. All rights reserved.
 *
 *
 * 	This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program 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 General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */

package org.openmicroscopy.shoola.util.ui;




//Java imports
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import javax.swing.JComponent;
import javax.swing.plaf.basic.BasicButtonUI;

//Third-party libraries

//Application-internal dependencies
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.painter.CompoundPainter;
import org.jdesktop.swingx.painter.MattePainter;
import org.jdesktop.swingx.painter.Painter;
import org.openmicroscopy.shoola.util.ui.colour.HSV;

/** 
 * Basic UI for coloured button.
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * 				<a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author	Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * 				<a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * <small>
 * (<b>Internal version:</b> $Revision: $ $Date: $)
 * </small>
 * @since OME2.2
 */
class ColouredButtonUI
	extends BasicButtonUI
{

	/** Identifies the matte painter. */
	private static final int MATTE = 0;
	
	/** Identifies the specular painter. */
	private static final int SPEC = 1;
		
	/** The stroke of the graphics context. */
	private static final Stroke	STROKE = new BasicStroke(1.0f);
	
	/** The default value for width or height. */
	private static final int DEFAULT_SIZE = 32;
	
	/** The default insets for the painter. */
	private static final Insets INSETS = new Insets(3, 3, 3, 3);
	
    /** Current Colour of the button. */
    private Color           colour;

    /** Reference to parent button. */
    private final ColouredButton  button;
    
    /** The button's size, used by paint to draw onto component. */
    private Rectangle       buttonRect;
    
    /** 
     * HSV colour user to determine the start(top) of the gradient in the
     * button.
     */
    private HSV             gradientStartHSV;
    
    /** The Color conversion from HSV used in the paintGradient command.*/
    private Color           gradientStartRGB;
    
    /** 
     * HSV colour user to determine the End(bottom) of the gradient in the
     * button.
     */
    private HSV             gradientEndHSV;
    
    /** The Color conversion from HSV used in the paintGradient command.*/
    private Color           gradientEndRGB;
    
    /** 
     * If true the buttons will have a grey mask painted on top of their
     * button face. 
     */
    private boolean         greyedOut;
    
    /** The index of the derived font. */
    private int             fontIndex;
    
    /** Show the button borders? */
    private boolean 		paintBorderInsets;
    
    /** The button face painter. */
    private Painter 		buttonFacePainter;

    /** The button face painter. */
    private Painter 		selectedButtonFacePainter;

    /** The grey-mask face painter. */
    private Painter 		greyMaskPainter;
    
    /** The grey-mask face painter. */
    private Painter 		selectedGreyMaskPainter;
    
    /**
     * This method calculates the start and end colours for the gradient on
     * the face of the button. 
     */
    private void setGradientColours()
    {
        final HSV col = new HSV(colour);
         // top gradient value from HSV model. 
        float topGradientValue = col.getValue();
        // bottom gradient value from HSV model. 
        float bottomGradientValue = col.getValue();
        // top and bottom gradient saturation from HSV model. 
        float topGradientSaturation, bottomGradientSaturation;
        
        // if colour greyscale(achromatic) don't touch saturation
        if (col.getSaturation() == 0) {
            topGradientSaturation = bottomGradientSaturation = 
                col.getSaturation();
            // A check to see what gives greatest increase, +0.3 or *1.3 
            // and set topGradientValue to that.
            topGradientValue = col.getValue()+0.3f;
            if (col.getValue()*1.3f > topGradientValue)
                topGradientValue = col.getValue()*1.3f;
            if (topGradientValue>1) topGradientValue = 1;
            // Set bottomGradientValue to 75% of colour value.
            bottomGradientValue = col.getValue()*0.75f;      
        } else {
            // We're in a colour space. 
            // Increase topGradientValue to 1.5 * value of colour face.
            topGradientValue = col.getValue()*1.5f;
            if (topGradientValue>1) topGradientValue = 1;
            topGradientSaturation = col.getSaturation()*0.6f;
            bottomGradientSaturation = col.getSaturation();
        }
        
        gradientStartHSV = new HSV(col.getHue(), topGradientSaturation,
                            topGradientValue, 1.0f);
        gradientStartRGB = gradientStartHSV.toColorA();

        gradientEndHSV = new HSV(col.getHue(),
                    bottomGradientSaturation, bottomGradientValue, 1.0f);
        gradientEndRGB = gradientEndHSV.toColorA();
    }
    
    /**
     * This method calculates the size of the button's text. It then
     * renders the text in the centre of the button, it also changes the
     * colour of the text to maximize the contrast between it and the 
     * background. 
     * 
     * @param g Graphics2D drawing context.
     */
    private void drawText(final Graphics2D g)
    {
        final HSV col = new HSV(colour);
        //Font fnt = button.getFont();
        //fnt = fnt.deriveFont(fontIndex, 10);
        //g.setFont(fnt);
        final FontMetrics fm = g.getFontMetrics();
        
        // Using the font metrics, centre the text in the button face.
        final int x = (int) ((buttonRect.width/2.0f)-
        		fm.stringWidth(button.getText())/2.0f);
        final int y = (int) ((buttonRect.height/2.0f) + 
                (fm.getHeight()-fm.getDescent())/2.0f);
        
        // If the button face is dark or does not contrast well with the 
        // black text turn the text white.
        if (col.getValue() < 0.6 || (col.getHue() > 0.6 && 
           col.getSaturation() > 0.7) || greyedOut)
            g.setPaint(Color.white);
        else
            g.setPaint(Color.black);
        g.drawString(button.getText(), x, y);
    }
    
    /**
     * Draws a beveled border for an unselected Button, the top and left 
     * bevels will be lighter than the bottom and Right.
     * 
     * @param g Graphics2D render context.
     */
    private void drawBorder(Graphics2D g)
    {
        Color borderColour;
        borderColour = gradientStartRGB.brighter();
     
        // Set the colour of the top, left bevels to be a lighter colour
        // than the gradient at that same corner.
        g.setPaint(borderColour);
        
        // Draw the bevel, it is drawn as four line from: topleft to 
        // topright, and topleft to bottom left. 
        g.drawLine(0, 0, 0, (int) buttonRect.getHeight());
        g.drawLine(0, 0, (int) buttonRect.getWidth(), 0);
        g.drawLine(1, 1, 1, (int) buttonRect.getHeight()-1);
        g.drawLine(1, 1, (int) buttonRect.getWidth()-1, 1);
        
        borderColour = gradientEndRGB.darker();
        // Set the colour of the top, left bevels to be a lighter colour
        // than the gradient at that same corner.
        g.setPaint(borderColour);
        
        // Draw the bevel, it is drawn as four line from: bottomleft to 
        // bottom right, and bottomright to top left. 
        g.drawLine((int) buttonRect.getWidth()-1, 0, 
            (int) buttonRect.getWidth()-1, (int) buttonRect.getHeight()-1);
        g.drawLine(0, (int) buttonRect.getHeight()-1,
            (int) buttonRect.getWidth()-1, (int) buttonRect.getHeight()-1);
        g.drawLine((int) buttonRect.getWidth()-2, 2,
            (int) buttonRect.getWidth()-2, (int) buttonRect.getHeight()-2);
            g.drawLine(1, (int) buttonRect.getHeight()-2,
            (int) buttonRect.getWidth()-1, (int) buttonRect.getHeight()-2);
    }
    
    /**
     * Draws a bevelled border for a Grey unselected Button, the top and left 
     * bevels will be lighter than the bottom and Right. The Bevels will
     * be the greyscale equivalent of the face colour.
     * 
     * @param g Graphics2D render context.
     */
    private void drawGreyBorder(Graphics2D g)
    {
        Color borderColour;
        HSV borderColourHSV = new HSV(0, 0, gradientStartHSV.getValue(), 0.8f);
        borderColour = borderColourHSV.toColorA();
       
        // Set the colour of the top, left bevels to be a lighter colour
        // than the grey mask of the gradient at that same corner.
        g.setPaint(borderColour);
        g.setStroke(STROKE);
       
        // Draw the bevel, it is drawn as four line from: topleft to 
        // topright, and topleft to bottom left. 
        final int height = (int) buttonRect.getHeight();
        final int width = (int) buttonRect.getWidth();
        g.drawLine(0, 0, 0, height);
        g.drawLine(0, 0, width, 0);
        g.drawLine(1, 1, 1, height-1);
        g.drawLine(1, 1, width-1, 1);
        borderColourHSV = new HSV(0, 0, gradientEndHSV.getValue(), 0.8f);
        borderColour = borderColourHSV.toColorA();
        
        // Set the colour of the bottom, right bevels to be a darker colour
        // than the grey mask of the gradient at that same corner.
        g.setPaint(borderColour.darker().darker().darker());
       
        // Draw the bevel, it is drawn as four line from: bottomleft to 
        // bottom right, and bottomright to top left. 
        g.drawLine(width-1, 0, width-1, height-1);
        g.drawLine(0, height-1, width-1, height-1);
        g.drawLine(width-2, 2, width-2, height-2);
        g.drawLine(1, height-2, width-1, height-2);
    }
    
    /**
     * Draws a bevelled border for a Grey selected Button, the top and left 
     * bevels will be darker than the bottom and Right. The border will be
     * drawn using greyscale equivalent of the face colour. 
     * 
     * @param g Graphics2D render context.
     */
    private void drawGreySelectedBorder(Graphics2D g)
    {
        Color borderColour;
        HSV borderColourHSV = new HSV(0, 0, gradientEndHSV.getValue(), 0.8f);
        borderColour = borderColourHSV.toColorA();
      
        // Set the colour of the top, left bevels to be a lighter colour
        // than the grey mask of the gradient at that same corner.
        g.setPaint(borderColour.darker().darker().darker());
        g.setStroke(STROKE);
        
        // Draw the bevel, it is drawn as four line from: topleft to 
        // topright, and topleft to bottom left. 
        int height = (int) buttonRect.getHeight();
        int width = (int) buttonRect.getWidth();
        g.drawLine(0, 0, 0, height);
        g.drawLine(0, 0, width, 0);
        g.drawLine(1, 1, 1, height-1);
        g.drawLine(1, 1, width-1, 1);
        borderColourHSV = new HSV(0, 0, gradientStartHSV.getValue(), 0.8f);
        borderColour = borderColourHSV.toColorA();
        
        // Set the colour of the bottom, right bevels to be a darker colour
        // than the grey mask of the gradient at that same corner.
        g.setPaint(borderColour.darker());
        
       // Draw the bevel, it is drawn as four line from: bottomleft to 
       // bottom right, and bottomright to top left. 
        g.drawLine(width-1, 0, width-1, height-1);
        g.drawLine(0, height-1, width-1, height-1);
        g.drawLine(width-2, 2, width-2, height-2);
        g.drawLine(1, height-2, width-1, height-2);
    }
    
    /**
     * Draws a bevelled border for a selected Button, the top and left 
     * bevels will be darker than the bottom and Right.
     * 
     * @param g Graphics2D render context.
     */
    private void drawSelectedBorder(Graphics2D g)
    {
        Color borderColour;
        
        borderColour = gradientEndRGB.darker();
        HSV col = new HSV(borderColour);
        g.setPaint(col.toColorA());
        g.setStroke(STROKE);
        
        // Draw the bevel, it is drawn as four line from: topleft to 
        // topright, and topleft to bottom left.
        int height = (int) buttonRect.getHeight();
        int width = (int) buttonRect.getWidth();
        g.drawLine(0, 0, 0, height);
        g.drawLine(0, 0, width, 0);
        g.drawLine(1, 1, 1, height-1);
        g.drawLine(1, 1, width-1, 1);

        borderColour = gradientStartRGB;
        col = new HSV(borderColour);
        col.setSaturation(col.getSaturation()*0.8f);
        borderColour = col.toColorA();

        // Set the colour of the bottom, right bevels to be a darker colour
        // than the grey mask of the gradient at that same corner.
        g.setPaint(borderColour);
        
        // Draw the bevel, it is drawn as four line from: bottomleft to 
            // bottom right, and bottomright to top left. 
        g.drawLine(width-1, 0, width-1, (int) buttonRect.getHeight()-1);
        g.drawLine(0, height-1, width-1, height-1);
        g.drawLine(width-2, 2, width-2, height-2);
        g.drawLine(1, width-2, width-1, height-2);
    }
    
    /**
     * Paints the square, beveled button with gradient fill on to the 
     * Graphics context.
     * 
     * @param g Graphics context.
     */
    private void paintSquareButton(Graphics2D g)
    {
        
        // If the button is selected draw selected button face.  
        // Check to see if it's greyed out, if not draw border else
        // draw mask and draw the grey mask selected border. 
        if (button.isSelected()) 
        {
        	if (button.isEnabled()) {
        		if (!greyedOut) 
                {
        			invokePainter(g, selectedButtonFacePainter);
                	drawSelectedBorder(g);
                }
                else
                {
                	invokePainter(g, selectedGreyMaskPainter);
                    drawGreySelectedBorder(g);
                }
        	} else {
        		invokePainter(g, selectedGreyMaskPainter);
                drawGreySelectedBorder(g);
        	}
        } 
        else 
        {
            // If the button is not selected draw unselected button face.  
            // Check to see if it's greyed out, if not draw border else
            // draw mask and draw the grey mask unselected border.
        	if (button.isEnabled()) {
        		if (greyedOut) 
                { 
        			invokePainter(g, greyMaskPainter);
                    drawGreyBorder(g);
                }
                else 
                {
                	invokePainter(g, buttonFacePainter);
                	drawBorder(g);    
                }
        	} else {
        		invokePainter(g, greyMaskPainter);
                drawGreyBorder(g);
        	}
            
        }
         // Draw text in centre of button.
        drawText(g);
    }

    /** Creates painters for the different button options. */
    private void createPainters()
    {
    	buttonFacePainter = getPainter(colour, SPEC);
    	selectedButtonFacePainter = getPainter(colour, MATTE);
    	greyMaskPainter = getPainter(Color.gray, SPEC);
    	selectedGreyMaskPainter = getPainter(Color.gray, MATTE);
    }

    /**
     * Creates the painters for the colour, and adds specular highlight if the
     * passed parameter is <code>true</code>. 
     * 
     * @param colour see above.
     * @param spec see above.
     * @return see above.
     */
    private Painter<JXButton> getPainter(Color colour, int spec) 
	{
		int startX = (int) (getWidth()*0.2);
		int startY = 6;
		int colourStartX = (int) (getWidth()*0.3);
		int colourStartY = 18;
		int matteEndX = 10;
		int matteEndY = 18;
		
		Color c = colour.brighter();
		MattePainter gradientWhite = new MattePainter(
			    new GradientPaint(new Point2D.Double(0.0, 0.0), c,
			    new Point2D.Double(matteEndX, matteEndY), Color.white));
		/*
		MattePainter gradientBrighterColour = new MattePainter(
			    new GradientPaint(new Point2D.Double(0.0, 0.0), c,
			    new Point2D.Double(matteEndX, matteEndY), colour));
		*/
		MattePainter gradientBrighterDarker = new MattePainter(
			    new GradientPaint(new Point2D.Double(0.0, 0.0), c,
			    new Point2D.Double(matteEndX, matteEndY), colour.darker()));
		
		
		//We cannot use this
		/*org.apache.batik.ext.awt.RadialGradientPaint rp =
			new org.apache.batik.ext.awt.RadialGradientPaint(new 
				Point2D.Double(startX,startY), radius, 
				new Point2D.Double(colourStartX, colourStartY),
	                new float[] { 0.0f, 0.5f },
	                new Color[] { new Color(1.0f, 1.0f, 1.0f, 0.4f),
	                    new Color(1.0f, 1.0f, 1.0f, 0.0f) } );*/
		
		HSV newHSV = new HSV(colour);
		float colourAlpha = 0.5f;
		if (newHSV.getHue()>(100.0/360.0) && newHSV.getHue()<(150.0/360.0))
			colourAlpha = 0.7f;
		
		MattePainter gradientLight = new MattePainter(
			    new GradientPaint(new Point2D.Double(startX, startY), 
			    		new Color(1.0f, 1.0f, 1.0f, colourAlpha),
			    new Point2D.Double(colourStartX, colourStartY),  
			    new Color(1.0f, 1.0f, 1.0f, 0.0f) ));
		switch (spec) {
			case SPEC:
				return new CompoundPainter<JXButton>(gradientWhite, 
						gradientBrighterDarker, gradientLight);
			case MATTE:
				return new CompoundPainter<JXButton>( gradientWhite, 
		    			gradientBrighterDarker);
		}
		return null;
	}

    /**
     * Draws the painter on the graphics context.
     * 
     * @param g 	The graphics context.
     * @param painter 	The painter to draw.
     */
    private void invokePainter(Graphics g, Painter painter) 
    {
        if (painter == null) return;
        
        Graphics2D g2d = (Graphics2D) g.create();

        if (!isPaintBorderInsets()) 
        	painter.paint(g2d, this, getWidth(), getHeight());
        else 
        {
        	g2d.translate(INSETS.left, INSETS.top);
        	painter.paint(g2d, this, getWidth()-INSETS.left-INSETS.right,
        			getHeight()-INSETS.top-INSETS.bottom);
        }

    }
    
    /**
     * Creates a new instance.
     * 
     * @param b Reference to parent Button. Mustn't be <code>null</code>.
     * @param c Colour of the button. Mustn't be <code>null</code>.
     */
    ColouredButtonUI(ColouredButton b, Color c)
    {
    	if (b == null) throw new IllegalArgumentException("No button.");
        if (c == null) throw new IllegalArgumentException("No color.");
        button = b;
        greyedOut = false;
        fontIndex = Font.PLAIN;
        setColor(c);
        uninstallListeners(b);
    }
    
    /**
     * Buttons can be greyed out, representing that the current model is
     * in greyscale, not RGB, HSV. This is done by adding an alpha blended
     * grey mask over the button. This method sets, unsets buttons to be
     * greyed out.
     * 
     * @param greyedOut Pass <code>true</code> to gray out the button,
     *                  <code>false</code> otherwise.
     */
    void setGrayedOut(boolean greyedOut) { this.greyedOut = greyedOut; }
     
    /**
     * Sets the colour of the button. 
     * 
     * @param c Color to set. Mustn't be <code>null</code>.
     */
    void setColor(Color c) 
    { 
    	if (c == null) throw new IllegalArgumentException("No color.");
    	this.colour = c; 
    	setGradientColours();
    	createPainters();
    }
   
    /**
     * Sets the index of the derived font used to paint the text.
     * 
     * @param fontIndex The font index. 
     */
    void setDeriveFont(int fontIndex) { this.fontIndex = fontIndex; }
    
    /**
     * Overridden to paint the button and to renders the text in the centre of
     * the button.
     * @see BasicButtonUI#paint(Graphics, JComponent)
     */
    public void paint(Graphics og, JComponent comp)
    {
        Graphics2D g = (Graphics2D) og;
        buttonRect = new Rectangle(comp.getWidth(), comp.getHeight());
        paintSquareButton(g);
    }
    
    /** 
     * Returns <code>true</code> if the button paints borders, 
     * <code>false</code> otherwise.
     * 
     * @return see above.
     */
    boolean isPaintBorderInsets() { return paintBorderInsets; }

    /**
     * Sets to <code>true</code> if the button paints borders, to
     * <code>false</code> otherwise.
     * 
     * @param pb The value to set.
     */
    void setPaintBorderInsets(boolean pb) { paintBorderInsets = pb; }

    /** 
     * Returns the width of the components.
     * 
     * @return See above.
     */
    protected int getWidth()
    {
    	return Math.max(button.getWidth(), DEFAULT_SIZE);
    }
    
    /** 
     * Returns the height of the components.
     * 
     * @return See above.
     */
    protected int getHeight()
    {
    	return Math.max(button.getHeight(), DEFAULT_SIZE);
    }
    
}
