/*
 * @(#)PlafPaintUtils.java
 *
 * $Date: 2014-12-30 18:41:58 -0800 (Tue, 30 Dec 2014) $
 *
 * Copyright (c) 2011 by Jeremy Wood.
 * All rights reserved.
 *
 * The copyright of this software is owned by Jeremy Wood. 
 * You may not use, copy or modify this software, except in  
 * accordance with the license agreement you entered into with  
 * Jeremy Wood. For details see accompanying license terms.
 * 
 * This software is probably, but not necessarily, discussed here:
 * https://javagraphics.java.net/
 * 
 * That site should also contain the most recent official version
 * of this software.  (See the SVN repository for more details.)
 */
package com.bric.plaf;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import java.util.Hashtable;

import javax.swing.SwingConstants;
import javax.swing.UIManager;

import com.bric.util.JVM;

/** Some static methods for some common painting functions.
 *
 * @author Jeremy Wood
 **/
public class PlafPaintUtils {

	/** Four shades of white, each with increasing opacity. */
	final static Color[] whites = new Color[] {
			new Color(255,255,255,50),
			new Color(255,255,255,100),
			new Color(255,255,255,150)
	};
	
	/** Four shades of black, each with increasing opacity. */
	final static Color[] blacks = new Color[] {
			new Color(0,0,0,50),
			new Color(0,0,0,100),
			new Color(0,0,0,150)
	};
	
	/** @return the color used to indicate when a component has
	 * focus.  By default this uses the color (64,113,167), but you can
	 * override this by calling:
	 * <BR><code>UIManager.put("focusRing",customColor);</code>
	 */
	public static Color getFocusRingColor() {
		Object obj = UIManager.getColor("Focus.color");
		if(obj instanceof Color)
			return (Color)obj;
		obj = UIManager.getColor("focusRing");
		if(obj instanceof Color)
			return (Color)obj;
		return new Color(64,113,167);
	}
	
	/** Paints 3 different strokes around a shape to indicate focus.
	 * The widest stroke is the most transparent, so this achieves a nice
	 * "glow" effect.
	 * <P>The catch is that you have to render this underneath the shape,
	 * and the shape should be filled completely.
	 * 
	 * @param g the graphics to paint to
	 * @param shape the shape to outline
	 * @param pixelSize the number of pixels the outline should cover.
	 */
	public static void paintFocus(Graphics2D g,Shape shape,int pixelSize) {
		paintFocus(g, shape, pixelSize, getFocusRingColor(), true);
	}
	
	/** Paints 3 different strokes around a shape to indicate focus.
	 * The widest stroke is the most transparent, so this achieves a nice
	 * "glow" effect.
	 * <P>The catch is that you have to render this underneath the shape,
	 * and the shape should be filled completely.
	 * 
	 * @param g the graphics to paint to
	 * @param shape the shape to outline
	 * @param pixelSize the number of pixels the outline should cover.
	 * @param focusColor the color of the focus ring to paint
	 * @param changeRenderingHints if true then the rendering hints will be modified, if false they will be left in tact
	 */
	public static void paintFocus(Graphics2D g,Shape shape,int pixelSize,Color focusColor,boolean changeRenderingHints) {
		g = (Graphics2D)g.create();
		try {
			Color[] focusArray = new Color[] {
				new Color(focusColor.getRed(), focusColor.getGreen(), focusColor.getBlue(), 235*focusColor.getAlpha()/255 ),
				new Color(focusColor.getRed(), focusColor.getGreen(), focusColor.getBlue(), 130*focusColor.getAlpha()/255 ),
				new Color(focusColor.getRed(), focusColor.getGreen(), focusColor.getBlue(), 80*focusColor.getAlpha()/255 )	
			};
			if(changeRenderingHints) {
				if(JVM.usingQuartz) {
					g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
					g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
				} else {
					g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
					g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
				}
			}
			
			g.setStroke(new BasicStroke(2*pixelSize+1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
			g.setColor(focusArray[2]);
			g.draw(shape);
			if(2*pixelSize+1>0) {
				g.setStroke(new BasicStroke(2*pixelSize-2+1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
				g.setColor(focusArray[1]);
				g.draw(shape);
			}
			if(2*pixelSize-4+1>0) {
				g.setStroke(new BasicStroke(2*pixelSize-4+1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
				g.setColor(focusArray[0]);
				g.draw(shape);
			}
		} finally {
			g.dispose();
		}
	}
	
	/** Uses translucent shades of white and black to draw highlights
	 * and shadows around a rectangle, and then frames the rectangle
	 * with a shade of gray (120).
	 * <P>This should be called to add a finishing touch on top of
	 * existing graphics.
	 * @param g the graphics to paint to.
	 * @param r the rectangle to paint.
	 */
	public static void drawBevel(Graphics2D g,Rectangle r) {
		g.setStroke(new BasicStroke(1));
		drawColors(blacks,g, r.x, r.y+r.height, r.x+r.width, r.y+r.height, SwingConstants.SOUTH);
		drawColors(blacks,g, r.x+r.width, r.y, r.x+r.width, r.y+r.height, SwingConstants.EAST);

		drawColors(whites,g, r.x, r.y, r.x+r.width, r.y, SwingConstants.NORTH);
		drawColors(whites,g, r.x, r.y, r.x, r.y+r.height, SwingConstants.WEST);
		
		g.setColor(new Color(120, 120, 120));
		g.drawRect(r.x, r.y, r.width, r.height);
	}
	
	private static void drawColors(Color[] colors,Graphics g,int x1,int y1,int x2,int y2,int direction) {
		for(int a = 0; a<colors.length; a++) {
			g.setColor(colors[colors.length-a-1]);
			if(direction==SwingConstants.SOUTH) {
				g.drawLine(x1, y1-a, x2, y2-a);
			} else if(direction==SwingConstants.NORTH) {
				g.drawLine(x1, y1+a, x2, y2+a);
			} else if(direction==SwingConstants.EAST) {
				g.drawLine(x1-a, y1, x2-a, y2);
			} else if(direction==SwingConstants.WEST) {
				g.drawLine(x1+a, y1, x2+a, y2);
			}
		}
	}
	
	/** The table used to store vertical gradients. */
	private static Hashtable<String, TexturePaint> verticalGradients;
	
	/** Create a vertical gradient.  This gradient is stored in a
	 * table and reused throughout the rest of this session.
	 * 
	 * @param name an identifying key for this gradient (used to cache it).
	 * @param height the height of the gradient
	 * @param y the y offset of the gradient
	 * @param positions the fractional positions of each color (between [0,1]).
	 * @param colors one color for each position.
	 * @return the vertical gradient.
	 */
	synchronized static Paint getVerticalGradient(String name,
			int height,int y,
			float[] positions,
			Color[] colors) {
		if(verticalGradients==null) {
			verticalGradients = new Hashtable<String, TexturePaint>();
		}
		
		String key = name+" "+height+" "+y;
		TexturePaint paint = verticalGradients.get(key);
		if(paint==null) {
			height = Math.max(height, 1); //before a component is laid out, it may be 0x0
			BufferedImage bi = new BufferedImage(1,height,BufferedImage.TYPE_INT_ARGB);
			int[] array = new int[height];
			for(int a = 0; a<array.length; a++) {
				float f = a;
				f = f/((array.length-1));
				boolean hit = false;
				findMatch : for(int b = 1; b<positions.length; b++) {
					if(f>=positions[b-1] && f<positions[b]) {
						float p = (f-positions[b-1])/(positions[b]-positions[b-1]);
						array[a] = tween(colors[b-1],colors[b],p).getRGB();
						hit = true;
						break findMatch;
					}
				}
				if(!hit)
					array[a] = colors[colors.length-1].getRGB();
			}
			bi.getRaster().setDataElements(0, 0, 1, height, array);
			paint = new TexturePaint( bi, new Rectangle(0,y,1,height) );
			verticalGradients.put(key,paint);
		}
		return paint;
	}
	
	/** Tweens between the two arguments. */
	private static Color tween(Color c1,Color c2,float p) {
		int r1 = c1.getRed();
		int g1 = c1.getGreen();
		int b1 = c1.getBlue();
		int a1 = c1.getAlpha();
		
		int r2 = c2.getRed();
		int g2 = c2.getGreen();
		int b2 = c2.getBlue();
		int a2 = c2.getAlpha();
		
		return new Color( (int)(r1*(1-p)+r2*p),
				(int)(g1*(1-p)+g2*p),
				(int)(b1*(1-p)+b2*p),
				(int)(a1*(1-p)+a2*p) 
		);
	}

	private static Hashtable<String, TexturePaint> checkers;
	public static TexturePaint getCheckerBoard(int checkerSize) {
		return getCheckerBoard( checkerSize, Color.white, Color.lightGray );
	}
	public static TexturePaint getCheckerBoard(int checkerSize,Color color1,Color color2) {
		String key = checkerSize+" "+color1.toString()+" "+color2.toString();
		if(checkers==null)
			checkers = new Hashtable<String, TexturePaint>();
		TexturePaint paint = checkers.get(key);
		if(paint==null) {
			BufferedImage bi = new BufferedImage(2*checkerSize, 2*checkerSize, BufferedImage.TYPE_INT_RGB);
			Graphics2D g = bi.createGraphics();
			g.setColor(color1);
			g.fillRect(0,0,2*checkerSize,2*checkerSize);
			g.setColor(color2);
			g.fillRect(0,0,checkerSize,checkerSize);
			g.fillRect(checkerSize,checkerSize,checkerSize,checkerSize);
			g.dispose();
			paint = new TexturePaint(bi,new Rectangle(0,0,bi.getWidth(),bi.getHeight()));
			checkers.put(key, paint);
		}
		return paint;
	}
}
