/**
 * 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.image.transition;

import java.awt.Dimension;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.pump.geom.RectangularTransform;
import com.pump.geom.ShapeBounds;
import com.pump.geom.ShapeStringUtils;
import com.pump.geom.ShapeUtils;
import com.pump.geom.TransformUtils;

/**
 * In this transition the current frame splits apart into shrinking stars that
 * spin off towards a distant point. Here are playback samples:
 * <p>
 * <table summary="Sample Animations of StarsTransition2D" cellspacing="50"
 * border="0">
 * <tr>
 * <td align="center"><img src=
 * "https://github.com/mickleness/pumpernickel/raw/master/resources/transition/StarsTransition2D/StarsLeft.gif"
 * alt="Stars Left">
 * <p>
 * Stars Left</td>
 * <td align="center"><img src=
 * "https://github.com/mickleness/pumpernickel/raw/master/resources/transition/StarsTransition2D/StarsRight.gif"
 * alt="Stars Right">
 * <p>
 * Stars Right</td>
 * </tr>
 * </table>
 * 
 */
public class StarsTransition2D extends AbstractClippedTransition2D {

	/**
	 * @return the transitions that should be used to demonstrate this
	 *         transition.
	 */
	public static Transition[] getDemoTransitions() {
		return new Transition[] { new StarsTransition2D(LEFT),
				new StarsTransition2D(RIGHT) };
	}

	static GeneralPath[] star = new GeneralPath[] { createStar(1.5f),
			createStar(1.6f), createStar(1.7f), createStar(1.8f),
			createStar(1.9f), createStar(2f), createStar(2.1f),
			createStar(2.2f), createStar(2.3f) };

	/**
	 * Creates a 5-pointed star. There are 2 distances involved. The distance
	 * from the center to middle edge is 1, and the distance from the center to
	 * the tips of the star is r2.
	 * 
	 * @param r2
	 * @return
	 */
	private static GeneralPath createStar(float r2) {
		GeneralPath p = new GeneralPath();
		double angle = 0;
		double k = Math.PI * 2 / 10;
		p.moveTo((float) (Math.cos(angle)), (float) (Math.sin(angle)));
		for (int a = 0; a < 5; a++) {
			p.lineTo((float) (r2 * Math.cos(angle + k)),
					(float) (r2 * Math.sin(angle + k)));
			angle += Math.PI * 2.0 / 5.0;
			p.lineTo((float) (Math.cos(angle)), (float) (Math.sin(angle)));
		}
		p.closePath();
		return p;
	}

	int type = RIGHT;

	/**
	 * Create a new stars transition that moves to the right.
	 * 
	 */
	public StarsTransition2D() {
		this(RIGHT);
	}

	/**
	 * Create a new stars transition.
	 * 
	 * @param type
	 *            must be LEFT or RIGHT.
	 */
	public StarsTransition2D(int type) {
		if (!(type == LEFT || type == RIGHT)) {
			throw new IllegalArgumentException(
					"This transition must use type RIGHT or LEFT");
		}
		this.type = type;
	}

	@Override
	public String toString() {
		if (type == RIGHT) {
			return "Stars Right";
		} else {
			return "Stars Left";
		}
	}

	protected void fit(Path2D p, float length, float centerX, float centerY,
			Path2D path, Dimension size, float progress) {
		Rectangle2D r = p.getBounds2D();
		AffineTransform t = new AffineTransform();
		t.translate(-r.getX() - r.getWidth() / 2,
				-r.getY() - r.getHeight() / 2);
		t.rotate((1 - progress) * 1);
		double scaleProgress = Math.pow(progress, 3) * .75f;
		t.scale(length / r.getWidth() * (.02 + 1.8 * scaleProgress),
				length / r.getWidth() * (.02 + 1.8 * scaleProgress));
		p.transform(t);

		if (progress > 1)
			progress = 1;
		if (progress < 0)
			progress = 0;
		Point2D endPoint = ShapeUtils.getPoint(path, 1);
		Point2D startPoint = ShapeUtils.getPoint(path, progress);
		Rectangle2D pathBounds = ShapeBounds.getBounds(path);
		AffineTransform pathTransform = RectangularTransform.create(pathBounds,
				new Rectangle2D.Float(0, 0, size.width + 100, size.height));
		pathTransform.transform(endPoint, endPoint);
		pathTransform.transform(startPoint, startPoint);
		r = p.getBounds();
		t.setToTranslation(
				-r.getCenterX() + centerX - endPoint.getX() + startPoint.getX(),
				-r.getCenterY() + centerY - endPoint.getY()
						+ startPoint.getY());

		p.transform(t);
	}

	Path2D[] paths = new Path2D[] { ShapeStringUtils.createPath(
			"m 82.604 6.405 c 81.496 6.405 58.748 5.967 57.234 5.937 c 48.657 5.767 39.783 5.605 30.4 11.819 c 19.367 19.125 9.915 39.783 23.713 50.988 c 35.754 60.766 50.748 54.184 53.807 47.734 c 56.105 42.887 49.464 38.223 45.159 38.223"),
			ShapeStringUtils.createPath(
					"m 130.936 47.089 c 130.636 46.6 113.679 45.149 103.386 45.364 c 94.251 45.555 88.013 49.832 82.977 54.875 c 75.353 62.51 70.458 72.743 73.292 82.281 c 76.126 91.818 93.239 89.414 93.239 89.414 c 93.239 89.414 101.796 85.728 100.734 78.276 c 99.683 70.903 96.561 71.393 93.124 71.393 c 84.366 71.393 85.661 83.327 94.277 78.651"),
			ShapeStringUtils.createPath(
					"m 124.379 23.124 c 124.044 24.216 107.26 20.206 97.997 21.225 c 88.734 22.245 74.072 27.614 64.329 38.119 c 54.586 48.624 52.078 53.184 52.683 61.27 c 53.288 69.356 71.622 78.91 77.935 66.901") };

	@Override
	public Shape[] getShapes(float progress, Dimension size) {
		progress = 1 - progress;

		GeneralPath star1 = new GeneralPath(star[8]);
		GeneralPath star2 = new GeneralPath(star[5]);
		GeneralPath star3 = new GeneralPath(star[8]);
		GeneralPath star4 = new GeneralPath(star[5]);
		GeneralPath star5 = new GeneralPath(star[7]);
		GeneralPath star6 = new GeneralPath(star[5]);
		GeneralPath star7 = new GeneralPath(star[8]);
		GeneralPath star8 = new GeneralPath(star[6]);

		Random random = new Random(2);

		star1.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star2.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star3.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star4.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star5.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star6.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star7.transform(AffineTransform.getRotateInstance(random.nextDouble()));
		star8.transform(AffineTransform.getRotateInstance(random.nextDouble()));

		float big = (Math.min(size.width, size.height)) * .7f;
		float base1 = (float) (Math.pow(progress, 2.2) * .5f + 0f / 8f * .3f);
		float base2 = (float) (Math.pow(progress, 2.2) * .5f + 1f / 8f * .3f);
		float base3 = (float) (Math.pow(progress, 2.2) * .5f + 2f / 8f * .3f);
		float base4 = (float) (Math.pow(progress, 2.2) * .5f + 3f / 8f * .3f);
		float base5 = (float) (Math.pow(progress, 2.2) * .5f + 4f / 8f * .3f);
		float base6 = (float) (Math.pow(progress, 2.2) * .5f + 5f / 8f * .3f);
		float base7 = (float) (Math.pow(progress, 2.2) * .5f + 6f / 8f * .3f);
		float base8 = (float) (Math.pow(progress, 2.2) * .5f + 7f / 8f * .3f);
		float progress1 = (progress - base1) / (1 - base1);
		float progress2 = (progress - base2) / (1 - base2);
		float progress3 = (progress - base3) / (1 - base3);
		float progress4 = (progress - base4) / (1 - base4);
		float progress5 = (progress - base5) / (1 - base5);
		float progress6 = (progress - base6) / (1 - base6);
		float progress7 = (progress - base7) / (1 - base7);
		float progress8 = (progress - base8) / (1 - base8);
		List<GeneralPath> v = new ArrayList<GeneralPath>();

		if (progress1 > 0) {
			fit(star1, big, size.width * 2f / 3f, size.height * 3f / 4f,
					paths[0], size, progress1 * 2);
			v.add(star1);
		}
		if (progress2 > 0) {
			fit(star2, big, size.width * 7f / 8f, size.height * 1f / 5f,
					paths[1], size, progress2 * 2);
			v.add(star2);
		}
		if (progress3 > 0) {
			fit(star3, big, size.width * 1f / 6f, size.height * 2.2f / 5f,
					paths[2], size, progress3 * 2);
			v.add(star3);
		}
		if (progress4 > 0) {
			fit(star4, big, size.width * 3.1f / 6f, size.height * 1.2f / 5f,
					paths[0], size, progress4 * 2);
			v.add(star4);
		}
		if (progress5 > 0) {
			fit(star5, big, size.width * 1.9f / 6f, size.height * 4.2f / 5f,
					paths[1], size, progress5 * 2);
			v.add(star5);
		}
		if (progress6 > 0) {
			fit(star6, big, size.width * 13f / 15f, size.height * 4.3f / 5f,
					paths[2], size, progress6 * 2);
			v.add(star6);
		}
		if (progress7 > 0) {
			fit(star7, big, size.width * 2f / 5f, size.height * 2.4f / 5f,
					paths[0], size, progress7 * 2);
			v.add(star7);
		}
		if (progress8 > 0) {
			fit(star8, big, size.width * 3f / 6f, size.height * 2f / 5f,
					paths[2], size, progress8 * 2);
			v.add(star8);
		}

		Shape[] shapes = v.toArray(new Shape[v.size()]);
		if (type == LEFT) {
			AffineTransform flipHorizontal = TransformUtils
					.createAffineTransform(0, 0, 0, size.height, size.width, 0,
							size.width, 0, size.width, size.height, 0, 0);
			for (int a = 0; a < shapes.length; a++) {
				if (shapes[a] instanceof GeneralPath) {
					((GeneralPath) shapes[a]).transform(flipHorizontal);
				} else {
					shapes[a] = flipHorizontal
							.createTransformedShape(shapes[a]);
				}
			}
		}
		return shapes;
	}

	@Override
	public float getStrokeWidth(float progress, Dimension size) {
		return 2 + 7 * (1 - progress);
	}

	@Override
	public Transition2DInstruction[] getInstructions(float progress,
			Dimension size) {
		// This transition was written, um, backwards. So the simplest solution
		// is to
		// meddle with the resulting frames...
		Transition2DInstruction[] instr = super.getInstructions(progress, size);
		for (int a = 0; a < instr.length; a++) {
			if (instr[a] instanceof ImageInstruction) {
				ImageInstruction i = (ImageInstruction) instr[a];
				i.isFirstFrame = !i.isFirstFrame;
			}
		}
		return instr;
	}

}