package vgp.iterate.pythagoreanTree;

import java.awt.Color;
import jv.geom.PgElementSet;
import jv.number.PdColor;
import jv.number.PuInteger;
import jv.project.PgGeometryIf;
import jv.project.PjProject;
import jv.project.PvCameraIf;
import jv.project.PvDisplayIf;
import jv.vecmath.PdMatrix;
import jv.vecmath.PdVector;
import jv.vecmath.PuMath;
import jv.vecmath.PuReflect;

/**
 * Computes a Pythagorean tree which is recursively generated from a simple trunk.
 * @author		Andreas Haferburg, Konrad Polthier
 * @version		04.11.06, 2.00 revised (kp) Full revision (new functionality and sync with JavaView).<br>
 *			      29.03.06, 1.00 created (ah)
 * @since      JavaView 3.95
 */
public class PjPythagoreanTree extends PjProject {
	/** Age of tree determines number of iterations and size of tree. */
	protected	PuInteger			m_age;
	/** Flag if triangle above trunk is a right triangle, or can be modified. */
	private		boolean				m_bRightTriangle;
	/** Flag if trunk of tree is a square, or can be modified. */
	private		boolean				m_bSquareTrunk;
	/** Vector from the mid point of the triangle base to the triangle tip */
	private		PdVector				m_diff;
	/** Map m_trunk to the left side. */
	private		PdMatrix				m_left;
	/** Map m_trunk to the right side. */
	private		PdMatrix				m_right;
	/** Lower part of the tree */
	protected	PgElementSet		m_trunk;
	/** Upper part of the tree */
	protected	PgElementSet		m_tree;
	/** Color of trunk. */
	protected	PdColor				m_colTrunk;
	/** Color of leafs. */
	protected	PdColor				m_colLeaf;
	
	/** Creates a new instance of PjPythagoreanTree */
	public PjPythagoreanTree() {
		super("Pythagorean Tree");
		
		m_trunk		= new PgElementSet(2);
		m_trunk.addUpdateListener(this);
		m_tree		= new PgElementSet(2);
		m_diff		= new PdVector(2);
		m_left		= new PdMatrix(3);
		m_right		= new PdMatrix(3);
		
		m_colTrunk	= new PdColor("Color of Trunk", this);
		m_colLeaf	= new PdColor("Color of Leafs", this);
		
		m_age			= new PuInteger("Age of Tree", this);
		
		if (getClass() == PjPythagoreanTree.class)
		  init();
	}
	
	/** Initialization. */
	public void init() {
		super.init();

		m_bRightTriangle	= true;
		m_bSquareTrunk		= true;
		
		m_colTrunk.setColor(new Color(159.f/256.f, 19.f/256.f, 5.f/256.f));
		m_colLeaf.setColor(new Color(7.f/256.f, 147.f/256.f, 36.f/256.f));
		m_trunk.setName("Pythagorean Tree Trunk");
		m_trunk.showVertices(true);
		m_trunk.setGlobalElementColor(m_colTrunk.getColor());
		m_trunk.setNumVertices(5);
		m_trunk.setNumElements(2);
		m_trunk.setVertex(0, 0.,0.);
		m_trunk.setVertex(1, 2.57921,1.93016);
		m_trunk.setVertex(2, 4.,0.);
		m_trunk.setVertex(3, 0.,-4.);
		m_trunk.setVertex(4, 4.,-4.);
		m_trunk.setElement(0,0,1,2);
		m_trunk.setElement(1,0,2,4,3);
		m_tree.setName("Pythagorean Tree");
		m_tree.showElementColors(true);
		
		m_age.setDefBounds(1, 15, 1, 2);
		m_age.setDefValue(3);
		m_age.init();
		setAge(m_age.getDefValue());
	}
	
	/** Initializes display, geometries. */
	public void start() {
		super.start();

		getDisplay().selectCamera(PvCameraIf.CAMERA_ORTHO_XY);
		getDisplay().setMajorMode(PvDisplayIf.MODE_PICK);
		m_trunk.update(m_trunk);
		
		addGeometry(m_tree);
		addGeometry(m_trunk);
		selectGeometry(m_trunk);
	}
	/**
	 * Reset project to initial state by calling project.init().
	 * Must call project.update(project) after calling reset().
	 */
	public void reset() {
		init();
		m_trunk.update(m_trunk);
	}
	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		if (event == this) {
			return super.update(this);
		} else if (event == m_trunk) {
			computeMatrices();
			computeTreeVertices(m_age.getValue());
			m_tree.update(m_tree);
			return update(this);
		} else if (event == m_age) {
			setAge(m_age.getValue());
			m_tree.update(m_tree);
			return update(this);
		} else if (event == m_colTrunk) {
			computeTreeConnectivity(m_age.getValue());
			m_trunk.setGlobalElementColor(m_colTrunk.getColor());
			m_trunk.update(m_trunk);
			return true;
		} else if (event == m_colLeaf) {
			computeTreeConnectivity(m_age.getValue());
			m_tree.update(m_tree);
			return true;
		}
		return super.update(event);
	}	
	/**
	 * Computes the matrices m_left and m_right that map the m_trunk to the left 
	 * and right side of the tree. Matrices must be updated when m_trunk has changed.
	 */	
	private void computeMatrices() {
		PdVector [] vertex = m_trunk.getVertices();
		double a = vertex[4].dist(vertex[3]); // triangle base
		double c = vertex[1].dist(vertex[2]); // triangle right
		double d = vertex[1].dist(vertex[0]); // triangle left
		
		double alpha = PdVector.angle(vertex[2], vertex[0], vertex[1])/180.*Math.PI;
		if (vertex[1].m_data[1] > vertex[0].m_data[1])
			alpha *= -1.;
		computeMatrix(m_right, vertex[4], c/a, alpha, vertex[2]);
		double beta = PdVector.angle(vertex[0], vertex[1], vertex[2])/180.*Math.PI;
		if (vertex[1].m_data[1] < vertex[0].m_data[1])
			beta *= -1.;
		computeMatrix(m_left, vertex[3], d/a, beta, vertex[0]);
	}
	
	/**
	 * Computes the 3x3-matrix that moves to the pivot, then scales, then rotates 
	 * by alpha, and finally moves to the origin.
	 * @param		matrix	Output matrix.
	 * @param		pivot		Pivot for scale and rotation.
	 * @param		scale		Scale factor.
	 * @param		alpha		Angle of the rotation.
	 * @param		origin	New origin.
	 */
	private void computeMatrix(PdMatrix matrix, PdVector pivot, double scale, double alpha, PdVector origin) {
		// set the pivot
		PdVector translation	= PdVector.copyNew(pivot); 
		translation.multScalar(-1);
		matrix.copy(PuReflect.translate(translation));
		
		// scale
		matrix.leftMult(PuReflect.scale(scale, 2));
		
		// rotate
		matrix.leftMult(PuReflect.rotateXY(alpha, 2));
		
		// set origin
		matrix.leftMult(PuReflect.translate(origin));
	}
	
	
	/**
	 * Computes the position of the vertices of the tree of the specified age.
	 * Method assumes that trunk has been copied into the tree,
	 * and that number of vertices has been allocated elsewhere.
	 * 
	 * @param		age		Number of years of this tree, ie. number of iterations.
	 */
	private void computeTreeVertices(int age) {
		m_tree.setNumVertices(3*((1<<age)-1));
		PdVector [] vertex	= m_tree.getVertices();
		// Assign year==0 from trunk.
		for (int i=0; i<3; i++)
			vertex[i].copy(m_trunk.getVertex(i));
		
		// Assign other years using matrices m_left and m_right.
		for (int year=1; year<age; year++) {
			int n = 1<<(year-1);		// prev year length
			int i = n-1;				// prev year starts here
			n *= 3;						// num vertices in the prev year
			int parent = 3*i;			// first vertex of prev year
			int l = parent+n;			// first vertex of this year
			int r = l+n;				// first vertex of the second half of this year
			for (int counter=0; counter<n; counter++, l++, r++, parent++) {
				// first half of this year = m_left * prev year
				vertex[l].leftMultAffin(m_left, vertex[parent], true); 
				// second half of this year = m_right * prev year
				vertex[r].leftMultAffin(m_right, vertex[parent], true);
			}
		}
	}
	
	/**
	 * Computes connectivity and colors of tree of the specified age.
	 * Method assumes that trunk has been copied into the tree,
	 * and that number of vertices has been allocated elsewhere.
	 * 
	 * @param		age		Number of years of this tree, ie. number of iterations.
	 */
	private void computeTreeConnectivity(int age) {
		int numTriangles = (1<<age)-1;
		m_tree.setNumElements(2*numTriangles-2);
		for (int year=1; year<age; year++) {
			int n = 1<<(year-1);		// prev year length
			int i = n-1;				// prev year starts here
			int j = 2*n-1;				// year starts here
			int elem	= 2*j-2; 
			int tri	= 3*j;			// first vertex of the next triangle
		
			Color trunk			= m_colTrunk.getColor();
			Color leaf			= m_colLeaf.getColor();
			float g;
			g = year/(age-1.f);
			Color triColor		= PdColor.blend(1.f-g, trunk, g, leaf);
			g = (year-.5f)/(age-1.f);
			Color quadColor	= PdColor.blend(1.f-g, trunk, g, leaf);
			for (int k=0; k<n ;k++) {
				int edge = 3*i; // first vertex of the left edge
				m_tree.setElementColor(elem, triColor);
				m_tree.setElement(elem++, tri, tri+1, tri+2); // left tri
				m_tree.setElementColor(elem, quadColor);
				m_tree.setElement(elem++, tri, tri+2, edge+1, edge); // left quad
				edge++; // first vertex of the right edge
				tri+=3;
				m_tree.setElementColor(elem, triColor);
				m_tree.setElement(elem++, tri, tri+1, tri+2); // right tri
				m_tree.setElementColor(elem, quadColor);
				m_tree.setElement(elem++, tri, tri+2, edge+1, edge); // right quad
				tri+=3;
				i++;
			}
		}
	}

	/**
	 * Drags a picked vertex of m_trunk. 
	 * @param		geom				Picked geometry on which vertex lies
	 * @param		picked			Index of picked vertex in vertex array of geometry
	 * @param		pickedVertex	3d coordinates of vertex position
	 * @see			jv.project.PvPickListenerIf
	 */
	public void pickVertex(PgGeometryIf geom, int picked, PdVector pickedVertex) {
		if (geom != m_trunk)
			return;
		// Check if top vertex of triangle or one of the base vertices of rectangle is picked.
		if (picked != 1) {
			// One of the four vertices of the rectangular part of trunk is picked.
			int [] havingTheSameXCooAs = {3, 1, 4, 0, 2};
			int [] havingTheSameYCooAs = {2, 1, 0, 4, 3};
			int [] opposingOf = {4 ,1, 3, 2, 0};
			PdVector [] vertex = m_trunk.getVertices();
			if (m_bSquareTrunk) {
				PdVector diff	= PdVector.subNew(vertex[opposingOf[picked]], vertex[picked]);
				double d			= Math.abs(diff.m_data[0])-Math.abs(diff.m_data[1]);
				diff.m_data[0] -= PuMath.sign(diff.m_data[0])*d/2.;
				diff.m_data[1] += PuMath.sign(diff.m_data[1])*d/2.;
				vertex[picked].sub(vertex[opposingOf[picked]], diff);
			}		
			vertex[havingTheSameXCooAs[picked]].m_data[0] = vertex[picked].m_data[0];
			vertex[havingTheSameYCooAs[picked]].m_data[1] = vertex[picked].m_data[1];
			// make triangle similar (works if m_bRightTriangle)
			vertex[1].blend(0.5, vertex[0], 0.5, vertex[2], 1., m_diff);
		}
		assureRightTriangle();
	}

	/**
	 * Returns the age of the tree which is the number of consecutive branches.
	 * @return	the age of the tree.
	 */
	public int getAge() { return m_age.getValue(); }

	/**
	 * Sets the age of the tree which is the number of consecutive branches.
	 * Main allocation method, allocates tree element set.
	 * @param	age	New age of the tree.
	 */
	public void setAge(int age) {
		if (age < 1)
			return;
		m_age.setValue(age);
		computeTreeConnectivity(age);
		computeTreeVertices(age);
	}
	
	/**
	 * Makes sure the triangle is right angled if m_bRightTriangle is true.
	 * Recompute m_diff.
	 */
	private void assureRightTriangle() {
		PdVector [] vertex	= m_trunk.getVertices();
		double r					= vertex[0].dist(vertex[2])/2.; // triangle base
		PdVector mid			= PdVector.addNew(vertex[0], vertex[2]);
		mid.multScalar(.5);
		m_diff.sub(vertex[1], mid);
		if (m_bRightTriangle) {
			m_diff.setLength(r);
			vertex[1].add(mid, m_diff);
		}
	}

	/**
	 * Returns true if the triangle above trunk is kept right.
	 * @return		<code>true</code> if the triangle is kept right.
	 */
	public boolean isEnabledRightTriangle() {
		return m_bRightTriangle;
	}
	/**
	 * Determines if the triangle above trunk is a right triangle.
	 * @param		bRightTriangle			use right triangle above trunk
	 */
	public void setEnabledRightTriangle(boolean bRightTriangle) {
		m_bRightTriangle = bRightTriangle;
		if (bRightTriangle) {
			assureRightTriangle();
		}
	}

	/**
	 * Returns true if the trunk is kept square.
	 * @return		<code>true</code> if the rectangle is kept square.
	 */
	public boolean isEnabledSquareTrunk() {
		return m_bSquareTrunk;
	}

	/**
	 * Determines if the trunk should be a square.
	 * @param		bSquareTrunk		use square trunk
	 */
	public void setEnabledSquareTrunk(boolean bSquareTrunk) {
		m_bSquareTrunk = bSquareTrunk;
		if (bSquareTrunk) {
			pickVertex(m_trunk, 0, null);
		}
	}
}
