import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Random;

import junit.framework.TestCase;

import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.util.PAffineTransform;
import edu.umd.cs.piccolo.util.PBounds;

public class PerformanceTests extends TestCase {
	
	private static PerformanceLog log = new PerformanceLog();
	private static int NUMBER_NODES = 20000;

	public PerformanceTests(String name) {
		super(name);
	}
		
	public void testRunPerformanceTests() {
		// three times to warm up JVM
		for (int i = 0; i < 3; i++) {
			addNodes();
			copyNodes();
			createNodes();
			createPaths();
			fullIntersectsNodes();
			memorySizeOfNodes();
			//removeNodes();
			translateNodes();
			costOfNoBoundsCache();
//			renderSpeed();
			if (i != 2) {
				log.clear();
			}
		}
		log.writeLog();
	}
	
	public void createNodes() { 	
		PNode[] nodes = new PNode[NUMBER_NODES];

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
		}		
		log.endTest("Create " + NUMBER_NODES + " new nodes");		
	}
	
	public void createPaths() { 	
		PNode[] nodes = new PNode[NUMBER_NODES];

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = PPath.createRectangle(0, 0, 100, 80);
		}		
		log.endTest("Create " + NUMBER_NODES + " new rect paths");		
		
		Random r = new Random();
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i].translate(r.nextFloat() * 300, r.nextFloat() * 300);
		}		
	}
	
	public void addNodes() {
		PNode parent = new PNode();
		PNode[] nodes = new PNode[NUMBER_NODES];

		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
		}		
		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			parent.addChild(nodes[i]);
		}		
		log.endTest("Add " + NUMBER_NODES + " nodes to a new parent");
	}
	
	public void removeNodes() {
		PNode parent = new PNode();
		PNode[] nodes = new PNode[NUMBER_NODES];
		ArrayList list = new ArrayList();

		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
		}		
		
		for (int i = 0; i < NUMBER_NODES; i++) {
			parent.addChild(nodes[i]);
			list.add(nodes[i]);
		}		

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {	
			parent.removeChild(nodes[i]);	
		}
		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() front to back");
		
		parent.addChildren(list);
				
		log.startTest();
		for (int i = NUMBER_NODES - 1; i >= 0; i--) {	
			parent.removeChild(i);
		}
		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() back to front by index");

		log.startTest();
//		for (int i = NUMBER_NODES - 1; i >= 0; i--) {	
//			parent.removeChild(nodes[i]);	
//		}
		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() back to front by object, TO_SLOW");

		parent.addChildren(list);
				
		log.startTest();
		parent.removeChildren(list);		
		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChildren()");

		parent.addChildren(list);
				
		log.startTest();
		parent.removeAllChildren(); 	
		log.endTest("Remove " + NUMBER_NODES + " nodes using removeAllChildren()");
	}

	public void translateNodes() {
		PNode parent = new PNode();
		PNode[] nodes = new PNode[NUMBER_NODES];
		PBounds b = new PBounds();
		Random r = new Random();
		
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
			nodes[i].setBounds(1000 * r.nextFloat(), 1000 * r.nextFloat(), 100, 80);
			parent.addChild(nodes[i]);
			nodes[i].getFullBoundsReference();
		}
		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i].translate(1000 * r.nextFloat(), 1000 * r.nextFloat());
			nodes[i].scale(1000 * r.nextFloat());			
//			nodes[i].translateBy(100.01, 100.2);
//			nodes[i].scaleBy(0.9);
		}		
		log.endTest("Translate " + NUMBER_NODES + " nodes, not counting repaint or validate layout");
				
		log.startTest();
		//parent.validateFullBounds(); now protected.
		parent.getFullBoundsReference(); // calls validateFullBounds as a side effect.
		log.endTest("Validate Layout after translate " + NUMBER_NODES + " nodes");
		
		log.startTest();
		parent.validateFullPaint();
		log.endTest("Validate Paint after translate " + NUMBER_NODES + " nodes");
		
		log.startTest();
		parent.computeFullBounds(b);
		log.endTest("Parent compute bounds of " + NUMBER_NODES + " children nodes");
	}
	
	public void fullIntersectsNodes() {
		PNode parent = new PNode();
		PNode[] nodes = new PNode[NUMBER_NODES];
		PBounds b = new PBounds(0, 50, 100, 20);
		
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
			parent.addChild(nodes[i]);
		}
		
		//parent.validateFullBounds(); // now protected
		parent.getFullBoundsReference(); // calls validateFullBounds as a side effect.
		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i].fullIntersects(b);
		}		
		log.endTest("Do fullIntersects test for " + NUMBER_NODES + " nodes");
	}
	
	public void memorySizeOfNodes() {
		PNode[] nodes = new PNode[NUMBER_NODES];
		Runtime.getRuntime().gc();
		long startTotalMemory = Runtime.getRuntime().totalMemory();
		long startFree = Runtime.getRuntime().freeMemory();
		long endFree;
		long endTotal;
		
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
		}
		
		Runtime.getRuntime().gc();
		endFree = Runtime.getRuntime().freeMemory();
		endTotal = Runtime.getRuntime().totalMemory();
		
		log.addEntry("Approximate k used by " + NUMBER_NODES + " nodes", ((endTotal - startTotalMemory) + (startFree - endFree)) / 1024);
		nodes[0].getPaint();
	}
	
	public void copyNodes() {
		PNode parent = new PNode();
		PNode[] nodes = new PNode[NUMBER_NODES];

		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
			parent.addChild(nodes[i]);
		}		
		
		log.startTest();
		parent.clone();
		log.endTest("Copy/Serialize " + NUMBER_NODES + " nodes");		
	}	
	
	public void costOfNoBoundsCache() {
		PNode[] nodes = new PNode[NUMBER_NODES];
		PBounds[] bounds = new PBounds[NUMBER_NODES];
		PBounds pickRect = new PBounds(0, 0, 1, 1);
		Random r = new Random();
				
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i] = new PNode();
			nodes[i].translate(1000 * r.nextFloat(), 1000 * r.nextFloat());
			nodes[i].scale(1000 * r.nextFloat());	
			bounds[i] = new PBounds(1000 * r.nextFloat(), 1000 * r.nextFloat(), 100, 80);
		}

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			bounds[i].intersects(pickRect);
		}		
		log.endTest("Do intersects test for " + NUMBER_NODES + " bounds");
			
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			nodes[i].localToParent(bounds[i]);
		}		
		log.endTest("Transform " + NUMBER_NODES + " bounds from local to parent");
		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			pickRect.add(bounds[i]);
		}		
		log.endTest("Sum " + NUMBER_NODES + " bounds");
		
		PBounds b = new PBounds(r.nextDouble(), r.nextDouble(), r.nextDouble(), r.nextDouble());
		log.startTest();
		for (int i = 0; i < NUMBER_NODES * 10; i++) {
			b.clone();
		}				
		log.endTest("Clone " + NUMBER_NODES * 10 + " PBounds");

	}
	
	public void renderSpeed() {
		Random r = new Random();
		PAffineTransform at = new PAffineTransform();
		at.setScale(r.nextFloat());
		at.translate(r.nextFloat(), r.nextFloat());

		try {
			log.startTest();
			for (int i = 0; i < NUMBER_NODES; i++) {
				at.createInverse();
			}		
			log.endTest("Create inverse transform " + NUMBER_NODES + " times");
		} catch (NoninvertibleTransformException e) {
		}

		int height = 400;
		int width = 400;

		double scale1 = 0.5;
		double scale2 = 2;
		boolean scaleFlip = true;

		PAffineTransform transorm1 = new PAffineTransform();
		//transorm1.scale(0.5, 0.5);
		transorm1.translate(0.5, 10.1);
		PAffineTransform transorm2 = null;
		
		try {
			transorm2 = new PAffineTransform(transorm1.createInverse());
		} catch (NoninvertibleTransformException e) {}
			
			
		GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
		BufferedImage result = (BufferedImage)graphicsConfiguration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
		Graphics2D g2 = result.createGraphics();

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			if (scaleFlip) {
				g2.scale(scale2, scale2);
				scaleFlip = !scaleFlip;
			} else {
				g2.scale(scale1, scale1);
				scaleFlip = !scaleFlip;
			}
		}		
		log.endTest("Scale graphics context " + NUMBER_NODES + " times");

		g2.setTransform(new AffineTransform());
		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			g2.translate(0.5, 0.5);
		}		
		log.endTest("Translate graphics context " + NUMBER_NODES + " times");

		g2.setTransform(new AffineTransform());

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			if (scaleFlip) {
				g2.transform(transorm1);
				scaleFlip = !scaleFlip;
			} else {
				g2.transform(transorm2);
				scaleFlip = !scaleFlip;
			}
		}		
		log.endTest("Transform graphics context " + NUMBER_NODES + " times");		

	
		Rectangle2D rect = new Rectangle2D.Double(0, 0, 100, 80);
		GeneralPath path = new GeneralPath(rect);
		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			g2.fill(rect);
		}		
		log.endTest("Fill " + NUMBER_NODES + " rects"); 	

		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			g2.getTransform().getScaleX();
		}		
		log.endTest("Call g2.getTransform() " + NUMBER_NODES + " times");		

		
		log.startTest();
		for (int i = 0; i < NUMBER_NODES; i++) {
			g2.fill(path);
		}		
		log.endTest("Fill " + NUMBER_NODES + " paths"); 	
	}	
}
