/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.tool;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;

/**
 * Component which draws a 2D pie chart with a drop shadow, based on supplied
 * data.
 */
public class PieChart extends JComponent /* implements Accessible*/ {
    /**
     * Create an empty pie chart.  Using this constructor is not recommended.
     */
    private PieChart() {
    }

    /**
     * Constructs a pie chart with the given initial distribution and colors.
     *
     * @param dist the numerical values to be represented in the pie.  it is
     *        assumed that the sum of these numbers equals 100% of the pie.
     * @param colors The colors that should be used to paint each of the pie
     *        slices, positionally corresponding to the values in the first
     *        argument.
     * @throws IllegalArgumentException If the length of the first parameter
     *         does not equal the second.
     * @throws NullPointerException if either argument is null
     * @see #setValue
     */
    public PieChart(int[] dist, Color[] colors) {
	this();
	setValue(dist, colors);
    }

    // could provide methods which take percentage, floats, floats, long, etc...

    /**
     * Sets the pie chart to the given distribution and corresponding colors.
     *
     * @param dist the numerical values to be represented in the pie.  it is
     *        assumed that the sum of these numbers equals 100% of the pie.
     * @param c The colors that should be used to paint each of the pie slices,
     *          positionally corresponding to the values in the first argument.
     * @throws IllegalArgumentException If the length of the first parameter
     *         does not equal the second.
     * @throws NullPointerException if either argument is null
     */
    public void setValue(int[] dist, Color[] c) {
	if (dist.length != c.length)
	    throw new IllegalArgumentException();

	colors = c;
	slices = dist;
	repaint();
    }

    protected void paintComponent(Graphics g) {
	Image image = new BufferedImage(getWidth(), getHeight(),
				  BufferedImage.TYPE_INT_ARGB);
	Graphics2D imageG = (Graphics2D)image.getGraphics();
	paintPie(imageG);
	imageG.dispose();
	
	g.drawImage(image, 0, 0, null);
	g.dispose();
    }

    private void paintPie(Graphics2D g2d) {
	if (isOpaque()) {
	    g2d.setColor(getBackground());
	    g2d.fillRect(0,0, getWidth(), getHeight());
	}

	if (slices == null || slices.length == 0)
	    return;

	// now paint pie slices
	float sum = 0.0f;
	int minIndex = 0, maxIndex = 0;
	for (int i = 0; i < slices.length; i++) {
	    sum += slices[i];
	    if (slices[i] > slices[maxIndex])
		maxIndex = i;
	    if (slices[i] > slices[minIndex])
		minIndex = i;
	}

	// do not paint shadow if there is no pie
	if (sum < 0.1f)	    // floating point inaccuracy...
	    return;

	// perhaps use create with a clip that excludes the border area??
	Insets inset = getInsets();

	// this is critical - pie can look ugly without
	g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			     RenderingHints.VALUE_ANTIALIAS_ON);

	int maxDia = Math.min((getWidth() - inset.left - inset.right),
			      (getHeight() - inset.top - inset.bottom));
	float targetDia = (float)maxDia * 0.8f;


	// coords of point defining the box which defines the arcs
	float boxX = inset.left + (0.1f  * (maxDia - targetDia));

	float boxY = inset.top + (0.1f  * (maxDia - targetDia));
	if (getWidth() > getHeight()) {
	}
	else {
	    // vertical centering
	    boxY = ((float)(getHeight() / 2)) - (0.5f * targetDia);
	}

	// center of gradient circle
	float gradx = boxX + OFFSETX;
	float grady = boxY + OFFSETY;

	// the effectiveness of the gradient will be determined by the fg and
	// bg colors, combined with whether or not this component is opaque
	/*
	GradientPaint grad = new GradientPaint(
					(float)(gradx-xshift),
					(float)(grady-yshift),
					Color.black,
					(float)(gradx+xshift),
					(float)(grady+yshift),
					getBackground());
	*/
	GradientPaint grad = new GradientPaint(
					0.0f, 0.0f,
					Color.decode("#dddddd"),
					(float)getWidth(), (float)getHeight(),
					getBackground());
	g2d.setPaint(grad);
	g2d.fill(new Ellipse2D.Float(gradx, grady,
				     targetDia*0.98f, targetDia*0.98f));

	float[] dist = new float[slices.length];
	for (int i = 0; i < slices.length; i++) {
	    if (slices[i] == 0)
		dist[i] = 0.0f;
	    else if (slices[i] == sum)
		dist[i] = 360.0f;
	    else
		dist[i] = 360.0f * (((float)slices[i]) / sum);
	}

	// post process to provide slices of minimal viewable size
	for (int i = 0; i < dist.length; i++) {
	    if (dist[i] > 0.0f && dist[i] < MIN_SLICE) {
		dist[maxIndex] = dist[maxIndex] - (MIN_SLICE - dist[i]);
		// XXX should recalculate maxIndex
		dist[i] = MIN_SLICE;
	    }
	}

	float pos = 90.0f;	// position on circle
	for (int i = 0; i < slices.length; i++) {
	    if (dist[i] > 0.0) {
		g2d.setPaint(colors[i]);
		g2d.fill(new Arc2D.Float(boxX, boxY, targetDia, targetDia,
					 pos, -1.0f*dist[i], Arc2D.PIE));

		// special outline for white slices
		if (colors[i].equals(Color.WHITE)) {
		    g2d.setPaint(Color.BLACK);

		    if (dist[i] == 360.0f)
			g2d.draw(new Ellipse2D.Float(boxX, boxY, targetDia, targetDia));
		    else
			g2d.draw(new Arc2D.Float(boxX, boxY, targetDia, targetDia,
						 pos, -1.0f*dist[i], Arc2D.PIE));
		}
		else { }

		pos -= dist[i];
	    }
	}   // for

	/*
	// creates a border for the whole pie
	g2d.setStroke(new BasicStroke(1.5f));
	g2d.draw(new Ellipse2D.Float(boxX, boxY,
				     targetDia, targetDia));
	*/
    }

    private int[] slices;
    private Color[] colors;
    private static final int OFFSETX = 7;
    private static final int OFFSETY = 7;
    private static final float GRAD_ANGLE = 45.0f;
    private static final float MIN_SLICE = 2.0f;	    // min. of 3 degrees
}
