package vgp.tutor.sizeEdge;

import java.awt.Color;

import jv.geom.PgElementSet;
import jv.number.PuDouble;
import jv.object.PsConfig;
import jv.project.PjProject;
import jv.vecmath.PdVector;

/**
 * Tutorial project demonstrates how to adjust the individual thickness
 * of element edges.
 * 
 * @author		Konrad Polthier
 * @version		04.11.06, 2.00 revised (kp) Moved to vgp.tutor.<br>
 *					05.07.06, 1.00 created (kp) Based on similar applet vgp.tutor.sizeEdge.
 */
public class PjSizeEdge extends PjProject implements Runnable {
	/** Individual edge color is computed from edge size. */
	public			final static int				COLOR_SIZE		= 0;
	/** Individual edge color is computed from edge index. */
	public			final static int				COLOR_INDEX		= 1;
	/** Element set whose edges are displayed with varying thickness. */
	protected		PgElementSet					m_geom;
	/** Rate of change of thickness of each polygon. */
	protected		PdVector							m_delSize;
	/** Thread created in auto-rotate mode. */
	protected		transient Thread				m_thread;
	/** Rate of change of edge sizes respectively edge colors. */
	protected		PuDouble							m_speed;
	/** Global edge size of base geometry. */
	protected		PuDouble							m_size;
	/** Flag if individual edge colors are shown. */
	protected		boolean							m_bShowEdgeColors;
	/** Flag if individual edge labels are shown. */
	protected		boolean							m_bShowEdgeLabels;
	/** Flag if individual edge sizes are shown. */
	protected		boolean							m_bShowEdgeSizes;
	/** Flag if individual edge color is computed either from edge size of edge index. */
	protected		int								m_colorType;

	public PjSizeEdge() {
		super("Size of Element Edges");
		m_geom		= new PgElementSet(3);
		m_geom.setParent(this);

		m_delSize			= new PdVector();
		m_size				= new PuDouble(PsConfig.getMessage(true, 84000, "Edge Size"), this);
		m_speed				= new PuDouble(PsConfig.getMessage(true, 84000, "Speed of Change"), this);
		
		if (getClass() == PjSizeEdge.class) {
			init();
		}
	}
	public void init() {
		super.init();
		m_colorType			= COLOR_INDEX;
		m_bShowEdgeColors	= true;
		m_bShowEdgeLabels	= false;
		m_bShowEdgeSizes	= true;
		
		m_geom.computeTorus(10, 10, 2., 1.);

		m_geom.setEnabledEdges(true);
		m_geom.makeEdgeStars();

		m_geom.setGlobalEdgeSize(8.);
		m_geom.showVertices(false);
		
		m_geom.showEdgeColors(m_bShowEdgeColors);
		m_geom.showEdgeLabels(m_bShowEdgeLabels);
		m_geom.showEdgeSizes(m_bShowEdgeSizes);

		m_size.setDefBounds(0., 20., 1., 2.);
		m_size.setDefValue(m_geom.getGlobalEdgeSize());
		m_size.init();

		m_speed.setDefBounds(0., 1.5, 0.05, 0.1);
		m_speed.setDefValue(1.);
		m_speed.init();
	}
	public void start() {
		compute();
		m_geom.update(m_geom);

		addGeometry(m_geom);
		selectGeometry(m_geom);
		super.start();
	}
	/**
	 * Create element edges with initial thickness and the rate of chance.
	 */
	public void compute() {
		// Compute random edge sizes.
		computeEdgeSizes();
		// Compute polygon colors based on the current size
		computeEdgeColors();
	}
	/**
	 * Compute random size of edges.
	 */
	private void computeEdgeSizes() {
		int nop				= m_geom.getNumEdgeStars();
		// Compute vector (-1 or +1) for each polygon which determines
		// how the polygon size changes in each time step.
		m_delSize.setSize(nop);
		for (int i=0; i<nop; i++) {
			if (Math.random() > .5)
				m_delSize.setEntry(i, 1.);
			else
				m_delSize.setEntry(i, -1.);
		}

		// Assign a random initial size of each polygon
		m_geom.assureEdgeSizes();
		PdVector sizes		= m_geom.getEdgeSizes();
		for (int i=0; i<nop; i++) {
			sizes.setEntry(i, Math.random());
			// m_geom.setEdgeSize(i, Math.random());
		}
	}
	/**
	 * Compute polygon colors based on the current size or index of edge.
	 */
	private void computeEdgeColors() {
		m_geom.assureEdgeColors();
		Color [] color		= m_geom.getEdgeColors();
		PdVector sizes		= m_geom.getEdgeSizes();
		int nop				= m_geom.getNumEdgeStars();
		for (int i=0; i<nop; i++) {
			float col	= (float)(sizes.getEntry(i));
			if (m_colorType == COLOR_INDEX)
				col		= i/(nop-1.f);
			color[i]		= new Color(Color.HSBtoRGB(0.83333f*(1.f-col), 1.f, 1.f));
		}
	}
	/**
	 * Change size of each vertex slightly along its element vector.
	 * If an element reaches the boundary then do a reflection like billard.
	 */
	public void changeEdgeSize(double speed) {
		speed		/= 20.;
		m_geom.assureEdgeSizes();
		PdVector sizes		= m_geom.getEdgeSizes();
		int nop				= m_geom.getNumEdgeStars();
		for (int i=0; i<nop; i++) {
			double size = sizes.getEntry(i);
			double incr = m_delSize.getEntry(i)*speed;
			if (incr>0 && size+incr > 1. ||
				 incr<0 && size+incr < 0.) {
				incr = -incr;
				m_delSize.setEntry(i, -m_delSize.getEntry(i));
			}
			sizes.setEntry(i, size+incr);
		}
		computeEdgeColors();
	}
	/**
	 * Determine if edge colors are shown.
	 */
	public void showEdgeColors(boolean flag) {
		m_bShowEdgeColors		= flag;
		m_geom.showEdgeColors(flag);
	}
	/**
	 * Determine if edge sizes are shown.
	 */
	public void showEdgeSizes(boolean flag) {
		m_bShowEdgeSizes		= flag;
		m_geom.showEdgeSizes(flag);
	}
	/**
	 * Determine if edge labels are shown.
	 */
	public void showEdgeLabels(boolean flag) {
		m_bShowEdgeLabels		= flag;
		m_geom.showEdgeLabels(flag);
	}
	/**
	 * Determine if edge color is computed based on edge size or edge index.
	 */
	public void setColorType(int colorType) {
		m_colorType		= colorType;
		computeEdgeColors();
	}
	/** Helper variable to avoid update loops. */
	protected	boolean		m_bIsUpdateSender	= false;
	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		if (m_bIsUpdateSender)
			return true;
		if (event == this) {
			m_geom.showEdgeColors(m_bShowEdgeColors);
			m_geom.showEdgeLabels(m_bShowEdgeLabels);
			m_geom.showEdgeSizes(m_bShowEdgeSizes);
			m_bIsUpdateSender	= true;
			m_geom.update(m_geom);
			m_bIsUpdateSender	= false;
			return true;
		} else if (event == m_geom) {
			return update(this);
		} else if (event == m_size) {
			m_geom.setGlobalEdgeSize(m_size.getValue());
			return update(this);
		} else if (event == m_speed) {
			return update(this);
		}
		return super.update(event);
	}
	/** Start animation by creating a new thread */
	public void startAnim() {
		if (m_thread != null)
			return;
		m_thread = new Thread(this, PsConfig.getProgram()+": "+"SizeEdges");
		m_thread.setPriority(Thread.NORM_PRIORITY);
		m_thread.start();
	}
	
	/** Stop animation by removing the thread. */
	public void stopAnim() {
		m_thread = null;
	}
	/** Do the animation by incrementing the polygon thickness. */
	public void run() {
		while(m_thread != null) {
			changeEdgeSize(m_speed.getValue());
			m_geom.update(m_geom);
			try {
				Thread.sleep(30);
			} catch(InterruptedException e) {}
		}
	}
}
