package com.jmex.font3d.math;

import com.jme.math.FastMath;
import com.jme.math.Vector3f;


/**
 * Used to do a triangulation of a complex polygon.
 * Please note that you should make sure all of these vertices are two-manifold, 
 * if they are not the triangulation will fail with nullpointers.
 * 
 * @author emanuel
 */
public class TriangulationVertex extends PlanarVertex
{
	// Easy access pointers
	//TriangulationVertex prev_vert,next_vert;
	//TriangulationEdge   ingoing_edge,outgoing_edge;
	
	public enum VertexType
	{
		START,
		END,
		SPLIT,
		MERGE,
		REGULAR_RIGHT,
		REGULAR_LEFT,
		UNSET
	}
	VertexType vert_type = VertexType.UNSET;
	public boolean is_left_chain = false;
	
	TriangulationVertex(int i, Vector3f p)
	{
		super(i, p);
	}
	
	boolean yLessThan(PlanarVertex vertex)
	{
		return (point.y == vertex.point.y ? point.x > vertex.point.x : point.y < vertex.point.y);
	}
	
	VertexType getType()
	{
		if(vert_type == VertexType.UNSET)
		{
			System.out.println("PANIX !!! WE HAVE WRONG TYPE AND SUCH !");
		}
		return vert_type;
	}
	
	@Override
	public String toString()
	{
		return "[indx:"+index+",("+point.x+","+point.y+"),type:"+vert_type+"]";
	}

	public void initializeType()
	{
		// Find the previous and next vertex
		TriangulationEdge outgoing_edge = getOutGoingEdge();
		TriangulationEdge ingoing_edge  = getInGoingEdge();
		TriangulationVertex prev_vert = (TriangulationVertex) ingoing_edge.getOrigin();
		TriangulationVertex next_vert = (TriangulationVertex) outgoing_edge.getTwin().getOrigin();
		
		if(prev_vert.yLessThan(this) && next_vert.yLessThan(this))
		{
			// Are we at the top but are we start/split
			Vector3f v1 = prev_vert.point;
			Vector3f v2 = point;
			Vector3f v  = next_vert.point;
			float turnang = (v2.x - v1.x) * (v.y - v1.y) - (v.x - v1.x) * (v2.y - v1.y);
			if(turnang > 0)
			{
				vert_type = VertexType.START;
			}
			else
			{
				vert_type = VertexType.SPLIT;
			}
		}
		else if(yLessThan(prev_vert) && yLessThan(next_vert))
		{
			// We are at the bottom, but are we end/merge ?
			Vector3f v1 = prev_vert.point;
			Vector3f v2 = point;
			Vector3f v  = next_vert.point;
			float turnang = (v2.x - v1.x) * (v.y - v1.y) - (v.x - v1.x) * (v2.y - v1.y);
			if(turnang > 0)
			{
				vert_type = VertexType.END;
			}
			else
			{
				vert_type = VertexType.MERGE;
			}
		}
		else if(prev_vert.yLessThan(this))
		{
			// Regular on the right side
			vert_type = VertexType.REGULAR_RIGHT;
		}
		else if(next_vert.yLessThan(this))
		{
			// Regular on the right side
			vert_type = VertexType.REGULAR_LEFT;
		}
		else
		{
			System.out.println("PNIX: we are none of the above types !!!!");
			System.out.println("GetType: (prev:"+prev_vert+",this:"+this+",next:"+next_vert);
		}
	}

	public boolean checkAllEdges()
	{
		// This one is used for sanity (HACK: hardcoded value)
		int sanity_check = 10000;
		int edgecount = 0;
		
		// Walk around our-selves with tmp = outgoing; tmp = tmp.prev.twin (clockwise)
		PlanarEdge tmp = getFirstEdge();
		float anglesum = 0;
		float anglimit = FastMath.TWO_PI+FastMath.FLT_EPSILON*2;
		edgecount = 0;
		if(tmp != null)
		{
			sanity_check = 10000;
			do
			{
				// Test that tmp has us as origin
				if(tmp.getOrigin() != this)
				{
					throw new GeometricException("edge "+tmp+" does not have a correct origin");
				}
				// Test surface links (clockwise)
				PlanarEdge tmp2 = tmp;
				//String debugString = "[";
				do
				{
					//debugString += " -> "+tmp2;
					if(tmp2.isRealEdge() != tmp2.getPrev().isRealEdge())
					{
						//System.out.println("VERT: "+tmp2.getOrigin());
						System.out.println("Edge1:"+tmp2);
						System.out.println("Edge2:"+tmp2.getPrev());
						//System.out.println("Tour: "+debugString+" -> "+tmp2.getPrev());
						throw new GeometricException("Bound two edges, one real one unreal, that is not possible in a closed polygon");
					}
					tmp2 = tmp2.getPrev();
					if(sanity_check-- <= 0)
						throw new GeometricException("Sanity check !");
				}
				while(tmp2 != tmp);
				anglesum += tmp.getTwin().getNext().angleCounterClockWise(tmp);
				edgecount++;
				if(anglesum > anglimit)
				{
					System.out.println("HERE ARE MY EDGES");
					printEdges();
					throw new GeometricException("The sum of angles between edges exceeded 2 PI ("+anglesum+" > "+anglimit+") on this vert: "+this);
				}
				tmp = tmp.getTwin().getNext();
			}
			while(tmp != getFirstEdge());
			//System.out.println("anglesum:"+anglesum+",edgecount:"+edgecount);
		}

		// Walk around our-selves with tmp = outgoing; tmp = tmp.twin.next(counter-clockwise)
		tmp = getFirstEdge();
		anglesum = 0;
		edgecount = 0;
		if(tmp != null)
		{
			sanity_check = 10000;
			edgecount = 0;
			do
			{
				// Test that tmp has us as origin
				if(tmp.getOrigin() != this)
				{
					throw new GeometricException("edge "+tmp+" does not have a correct origin");
				}
				// Test surface links (counter-clockwise)
				PlanarEdge tmp2 = tmp;
				do
				{
					if(tmp2.isRealEdge() != tmp2.getNext().isRealEdge())
					{
						System.out.println("VERT: "+tmp2.getOrigin());
						System.out.println("Edge1:"+tmp2);
						System.out.println("Edge2:"+tmp2.getNext());
						throw new GeometricException("Bound two edges, one real one unreal, that is not possible in a closed polygon");
					}
					tmp2 = tmp2.getNext();
					if(sanity_check-- <= 0)
						throw new GeometricException("Sanity check !");
				}
				while(tmp2 != tmp);
				
				anglesum += tmp.angleCounterClockWise(tmp.getPrev().getTwin());
				edgecount++;
				if(anglesum > anglimit)
				{
					throw new GeometricException("The sum of angles between edges exceeded 2 PI ("+anglesum+" > "+anglimit+") on this vert: "+this);
				}
				tmp = tmp.getPrev().getTwin();
			}
			while(tmp != getFirstEdge());
			//System.out.println("anglesum:"+anglesum+",edgecount:"+edgecount);
		}

		return true;
	}

	/**
	 * This method returns the first and best real edge going out of this vertex, there should be only one before the triangulation.
	 * 
	 * @return
	 */
	public TriangulationEdge getOutGoingEdge()
	{
		if(getFirstEdge() == null)
			return null;
		TriangulationEdge next = (TriangulationEdge) getFirstEdge(); 
		do
		{
			if(next.isRealEdge())
				return next;
			next = (TriangulationEdge) next.getTwin().getNext();
		}
		while(next != getFirstEdge()); // Only one round
		return null;
	}

	/**
	 * This method returns the first and best real edge going in to this vertex, there should be only one before the triangulation.
	 * 
	 * @return
	 */
	public TriangulationEdge getInGoingEdge()
	{
		if(getFirstEdge() == null)
			return null;
		TriangulationEdge next = (TriangulationEdge) getFirstEdge(); 
		do
		{
			if(next.getTwin().isRealEdge())
				return (TriangulationEdge) next.getTwin();
			next = (TriangulationEdge) next.getTwin().getNext();
		}
		while(next != getFirstEdge()); // Only one round
		return null;
	}
}
