/**
 * This software is released as part of the Pumpernickel project.
 * 
 * All com.pump resources in the Pumpernickel project are distributed under the
 * MIT License:
 * https://github.com/mickleness/pumpernickel/raw/master/License.txt
 * 
 * More information about the Pumpernickel project is available here:
 * https://mickleness.github.io/pumpernickel/
 */
package com.pump.icon;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;

import javax.swing.Icon;
import javax.swing.SwingConstants;

import com.pump.blog.ResourceSample;
import com.pump.geom.ShapeStringUtils;

/**
 * This is a large square grayscale icon depicting two joined eighth notes.
 * <p>
 * This should replicate the icon observed on Mac OS X 10.7.4 for audio files.
 * <p>
 * Also this icon contains a <code>PlayToggleIcon</code> (which is set to zero
 * opacity initially) that can be used to emulated the playback controls
 * available in Mac OS X 10.7.
 * 
 * <!-- ======== START OF AUTOGENERATED SAMPLES ======== -->
 * <p>
 * Here are some samples:
 * <table summary=
 * "Resource&#160;Samples&#160;for&#160;com.pump.swing.resources.MusicIcon">
 * <tr>
 * <td></td>
 * <td><img src=
 * "https://github.com/mickleness/pumpernickel/raw/master/resources/samples/MusicIcon/24x24.png"
 * alt="new&#160;com.pump.swing.resources.MusicIcon(24)"></td>
 * <td><img src=
 * "https://github.com/mickleness/pumpernickel/raw/master/resources/samples/MusicIcon/32x32.png"
 * alt="new&#160;com.pump.swing.resources.MusicIcon(32)"></td>
 * <td><img src=
 * "https://github.com/mickleness/pumpernickel/raw/master/resources/samples/MusicIcon/64x64.png"
 * alt="new&#160;com.pump.swing.resources.MusicIcon(64)"></td>
 * <td><img src=
 * "https://github.com/mickleness/pumpernickel/raw/master/resources/samples/MusicIcon/128x128.png"
 * alt="new&#160;com.pump.swing.resources.MusicIcon(128)"></td>
 * </tr>
 * <tr>
 * <td></td>
 * <td>24x24</td>
 * <td>32x32</td>
 * <td>64x64</td>
 * <td>128x128</td>
 * </tr>
 * <tr>
 * </tr>
 * </table>
 * <!-- ======== END OF AUTOGENERATED SAMPLES ======== -->
 */
@ResourceSample(sample = { "new com.pump.swing.resources.MusicIcon(24)",
		"new com.pump.swing.resources.MusicIcon(32)",
		"new com.pump.swing.resources.MusicIcon(64)",
		"new com.pump.swing.resources.MusicIcon(128)" }, names = { "24x24",
				"32x32", "64x64", "128x128" })
public class MusicIcon implements Icon {

	/**
	 * This is a circular play/pause icon, typically displayed in the center of
	 * a <code>MusicIcon</code>. This also supports an optional circular track
	 * that depicts sound completion from 0% to 100% by painting an arc that
	 * completes clockwise from the 12:00 position.
	 * <p>
	 * The is associated with the <code>MusicIcon</code> by convention, but this
	 * could be applied to other settings, too.
	 */
	public static class PlayToggleIcon implements Icon {
		public final static int DEFAULT_WIDTH = 48;
		final static Icon pauseIcon = new PauseIcon(12, 12, Color.white);
		final static Icon playIcon = new TriangleIcon(SwingConstants.EAST, 12,
				12, Color.white);
		final static Icon pressedPauseIcon = new PauseIcon(12, 12,
				new Color(160, 160, 160));
		final static Icon pressedPlayIcon = new TriangleIcon(
				SwingConstants.EAST, 12, 12, new Color(160, 160, 160));

		int width, height;
		boolean pauseVisible;
		float completion = 0;
		float trackSize = 0;
		float opacity = 1;
		boolean pressed = false;

		/** Create a <code>PlayToggleIcon</code> that is 48x48 pixels. */
		public PlayToggleIcon() {
			this(DEFAULT_WIDTH, DEFAULT_WIDTH);
		}

		/**
		 * Create a square <code>PlayToggleIcon</code> of the given dimensions.
		 * 
		 * @param width
		 *            the width and height.
		 */
		public PlayToggleIcon(int width) {
			this(width, width);
		}

		/**
		 * Create a <code>PlayToggleIcon</code>.
		 * 
		 * @param width
		 *            the width.
		 * @param height
		 *            the height.
		 */
		public PlayToggleIcon(int width, int height) {
			this.width = width;
			this.height = height;
		}

		public int getIconHeight() {
			return height;
		}

		/**
		 * @return the track completion (a float from [0,1]).
		 */
		public float getTrackCompletion() {
			return completion;
		}

		/**
		 * Set the track completion (a float from [0, 1]).
		 * 
		 * @param f
		 *            the completion (from [0,1])
		 */
		public void setTrackCompletion(float f) {
			if (f < 0 || f > 1)
				throw new IllegalArgumentException("The track completion (" + f
						+ ") must be between zero and one.");
			completion = f;
		}

		/**
		 * @return the track size (a float from [0,1]).
		 */
		public float getTrackSize() {
			return trackSize;
		}

		/**
		 * Set the track size (a float from [0,1]).
		 * 
		 * @param f
		 *            the new track size (from [0,1])
		 */
		public void setTrackSize(float f) {
			if (f < 0 || f > 1)
				throw new IllegalArgumentException("The track size (" + f
						+ ") must be between zero and one.");
			trackSize = f;
		}

		/**
		 * @return the opacity (a float from [0,1]).
		 */
		public float getOpacity() {
			return opacity;
		}

		/**
		 * Set the opacity (a float from [0,1]).
		 * 
		 * @param f
		 *            the new opacity (from [0,1])
		 */
		public void setOpacity(float f) {
			if (f < 0 || f > 1)
				throw new IllegalArgumentException("The opacity (" + f
						+ ") must be between zero and one.");
			opacity = f;
		}

		public int getIconWidth() {
			return width;
		}

		/**
		 * @return true if the pause icon is visible (instead of the play icon).
		 */
		public boolean isPauseIconVisible() {
			return pauseVisible;
		}

		/**
		 * Control whether the pause icon (instead of the play icon) is visible.
		 * 
		 * @param b
		 *            whether the pause icon is visible
		 */
		public void setPauseIconVisible(boolean b) {
			pauseVisible = b;
		}

		/**
		 * @return true if the play/pause icon is rendered as pressed. (When
		 *         pressed, those icons are a little bit darker.)
		 */
		public boolean isPressed() {
			return pressed;
		}

		/**
		 * Controls whether the play/pause icon is rendered as pressed. (When
		 * pressed, those icons are a little bit darker.)
		 * 
		 * @param b
		 *            the pressed state
		 */
		public void setPressed(boolean b) {
			pressed = b;
		}

		private Ellipse2D ellipse = new Ellipse2D.Float();
		private Arc2D arc = new Arc2D.Float();
		private BufferedImage image;

		public void paintIcon(Component c, Graphics g0, int x, int y) {
			if (opacity == 0)
				return;

			Graphics2D g = (Graphics2D) g0.create();

			if (opacity != 1) {
				if (image == null || image.getWidth() != getIconWidth()
						|| image.getHeight() != getIconHeight()) {
					image = new BufferedImage(width, height,
							BufferedImage.TYPE_INT_ARGB);
				}
				Graphics2D g2 = image.createGraphics();
				g2.setComposite(AlphaComposite.Clear);
				g2.fillRect(0, 0, image.getWidth(), image.getHeight());
				g2.setComposite(AlphaComposite.SrcOver);
				paintIcon(g2);
				g2.dispose();

				g.setComposite(AlphaComposite
						.getInstance(AlphaComposite.SRC_OVER, opacity));
				g.drawImage(image, x, y, null);
			} else {
				g.translate(x, y);
				paintIcon(g);

				if (image != null) {
					image.flush();
					image = null;
				}
			}

			g.dispose();
		}

		/**
		 * Paint the icon at full opacity.
		 */
		private void paintIcon(Graphics2D g) {
			double sx = ((double) getIconWidth()) / ((double) DEFAULT_WIDTH);
			double sy = ((double) getIconHeight()) / ((double) DEFAULT_WIDTH);
			g.scale(sx, sy);
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
					RenderingHints.VALUE_STROKE_PURE);

			float centerX = (DEFAULT_WIDTH) / 2f;
			float centerY = (DEFAULT_WIDTH) / 2f;

			float radiusInner = 14f;
			float radiusOuter = 8 * getTrackSize() + radiusInner;

			// the shadow:
			ellipse.setFrameFromCenter(centerX, centerY,
					centerX + radiusOuter + 3, centerY + radiusOuter + 3);
			g.setStroke(new BasicStroke(1));
			g.setColor(new Color(0, 0, 0, 20));
			g.draw(ellipse);

			ellipse.setFrameFromCenter(centerX, centerY,
					centerX + radiusOuter + 2, centerY + radiusOuter + 2);
			g.setColor(new Color(0, 0, 0, 50));
			g.draw(ellipse);

			ellipse.setFrameFromCenter(centerX, centerY,
					centerX + radiusOuter + 1, centerY + radiusOuter + 1);
			g.setColor(new Color(0, 0, 0, 90));
			g.draw(ellipse);

			// the fill
			ellipse.setFrameFromCenter(centerX, centerY, centerX + radiusInner,
					centerY + radiusInner);
			g.setColor(Color.black);
			g.fill(ellipse);

			// the icon:
			if (pauseVisible) {
				Icon currentIcon = pressed ? pressedPauseIcon : pauseIcon;
				currentIcon.paintIcon(null, g,
						(int) (centerX - currentIcon.getIconWidth() / 2),
						(int) (centerY - currentIcon.getIconHeight() / 2));
			} else {
				Icon currentIcon = pressed ? pressedPlayIcon : playIcon;
				currentIcon.paintIcon(null, g,
						(int) (centerX - currentIcon.getIconWidth() / 2) + 2,
						(int) (centerY - currentIcon.getIconHeight() / 2));
			}

			float diff = radiusOuter - radiusInner;
			float radiusMiddle = (radiusOuter + radiusInner) / 2;
			arc.setArc(centerX - radiusMiddle, centerY - radiusMiddle,
					2 * radiusMiddle, 2 * radiusMiddle, 90, -completion * 360,
					Arc2D.OPEN);
			g.setColor(new Color(128, 128, 128, 200));
			g.setStroke(new BasicStroke(diff, BasicStroke.CAP_BUTT,
					BasicStroke.JOIN_MITER));
			g.draw(arc);
			arc.setAngleExtent((1 - completion) * 360);
			g.setColor(new Color(0, 0, 0, 100));
			g.setStroke(new BasicStroke(diff, BasicStroke.CAP_BUTT,
					BasicStroke.JOIN_MITER));
			g.draw(arc);

			// the border:
			ellipse.setFrameFromCenter(centerX, centerY, centerX + radiusInner,
					centerY + radiusInner);
			g.setStroke(new BasicStroke(2));
			g.setColor(Color.white);
			g.draw(ellipse);

			ellipse.setFrameFromCenter(centerX, centerY, centerX + radiusOuter,
					centerY + radiusOuter);
			g.setStroke(new BasicStroke(1.2f));
			g.draw(ellipse);
		}

	}

	protected static Shape createEighthNotesShape() {
		Path2D circle1 = ShapeStringUtils.createPath(
				"m 111.113 175.531 c 111.173 184.2 102.183 191.65 91.034 192.171 c 79.885 192.692 70.798 186.087 70.738 177.419 c 70.678 168.75 79.668 161.3 90.817 160.779 c 101.966 160.257 111.053 166.862 111.113 175.531 z");
		Path2D circle2 = ShapeStringUtils.createPath(
				"m 173.579 164.139 c 173.638 172.808 165.467 180.258 154.318 180.779 c 143.169 181.3 134.082 174.695 134.022 166.027 c 133.962 157.358 142.952 149.908 154.101 149.387 c 165.25 148.865 173.519 155.47 173.579 164.139 z");
		Path2D polygon = ShapeStringUtils.createPath(
				"m 99.31 73.824 l 173.592 62.328 l 173.592 163.322 l 164.175 174.311 l 164.175 95.261 l 111.188 103.947 l 111.188 175.11 l 99.31 183.286 l 99.31 73.824 z");

		Area sum = new Area();
		sum.add(new Area(circle1));
		sum.add(new Area(circle2));
		sum.add(new Area(polygon));
		return sum;
	}

	Shape quarterNotesShape = createEighthNotesShape();
	int width, height;
	public static final int DEFAULT_WIDTH = 256;

	PlayToggleIcon playToggleIcon;

	/**
	 * Create a <code>MusicIcon</code> that is 256x256, with a
	 * <code>PlayToggleIcon</code> that is 48x48.
	 */
	public MusicIcon() {
		this(DEFAULT_WIDTH, DEFAULT_WIDTH, PlayToggleIcon.DEFAULT_WIDTH,
				PlayToggleIcon.DEFAULT_WIDTH);
	}

	/**
	 * Create a square <code>MusicIcon</code>, with a
	 * <code>PlayToggleIcon</code> that is 48x48.
	 * 
	 * @param width
	 *            the width and height of this icon.
	 */
	public MusicIcon(int width) {
		this(width, width, Math.min(width, PlayToggleIcon.DEFAULT_WIDTH),
				Math.min(width, PlayToggleIcon.DEFAULT_WIDTH));
	}

	/**
	 * Create a <code>MusicIcon</code>.
	 * 
	 * @param width
	 *            the width of this icon.
	 * @param height
	 *            the height of this icon.
	 * @param playButtonWidth
	 *            the width of the <code>PlayToggleIcon</code>.
	 * @param playButtonHeight
	 *            the height of the <code>PlayToggleIcon</code>.
	 */
	public MusicIcon(int width, int height, int playButtonWidth,
			int playButtonHeight) {
		this.width = width;
		this.height = height;
		playToggleIcon = new PlayToggleIcon(playButtonWidth, playButtonHeight);
		playToggleIcon.setOpacity(0);
	}

	public int getIconHeight() {
		return height;
	}

	public int getIconWidth() {
		return width;
	}

	/**
	 * @return a <code>PlayToggleIcon</code> that is overlayed on this
	 *         <code>MusicIcon</code>. By default this icon control has an
	 *         opacity of zero, so it is never painted.
	 */
	public PlayToggleIcon getPlayToggleIcon() {
		return playToggleIcon;
	}

	public void paintIcon(Component c, Graphics g0, int x, int y) {
		Graphics2D g = (Graphics2D) g0.create();

		double scaleX = ((double) getIconWidth()) / ((double) DEFAULT_WIDTH);
		double scaleY = ((double) getIconHeight()) / ((double) DEFAULT_WIDTH);
		g.translate(x, y);
		g.scale(scaleX, scaleY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		g.setPaint(new RadialGradientPaint(new Point2D.Float(128, -20), 270,
				new float[] { 0, .3f, .6f, .9f },
				new Color[] { new Color(140, 140, 140),
						new Color(100, 100, 100), new Color(50, 50, 50),
						Color.black }));
		g.fillRect(0, 0, 256, 256);

		// the shadow:
		for (int a = 0; a < 3; a++) {
			g.setColor(new Color(0, 0, 0, 20 * a + 30));
			g.draw(quarterNotesShape);
			g.setStroke(new BasicStroke(a + 1, BasicStroke.CAP_ROUND,
					BasicStroke.JOIN_ROUND));
		}

		// the shape
		g.setPaint(new GradientPaint(80, 190, new Color(105, 122, 141), 170, 65,
				new Color(182, 186, 194)));
		g.fill(quarterNotesShape);

		// a light highlight
		Graphics2D g2 = (Graphics2D) g.create();
		g2.clip(quarterNotesShape);
		g2.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND,
				BasicStroke.JOIN_ROUND));
		for (int a = 1; a <= 3; a++) {
			g2.translate(0, 1);
			g2.setColor(new Color(255, 255, 255, 60 - 15 * a));
			g2.draw(quarterNotesShape);
		}
		g2.dispose();
		g.dispose();

		g = (Graphics2D) g0.create();
		playToggleIcon.paintIcon(c, g,
				x + getIconWidth() / 2 - playToggleIcon.getIconWidth() / 2,
				y + getIconHeight() / 2 - playToggleIcon.getIconHeight() / 2);
		g.dispose();
	}
}