package j3deditor.bin.hierarchy;

import java.awt.Color;

import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;


/**
 * Represents a vertex of a polygonal mesh.
 * Doesn't contain any vertex data, manipulates vertex data
 * contained in <code>J3DeGeometryArray</code> referenced by
 * this vertex.
 * 
 * @author Risto Seene
 * 
 * @see j3deditor.bin.hierarchy.J3DeGeometryArray
 */
public class Vertex{

	/** Specifies that this vertex is currently hidden. */
	public static final int HIDDEN = -1;

	/** Specifies that this vertex is in normal state. */
	public static final int NORMAL = 0;

	/** Specifies that this vertex is currently selected. */
	public static final int SELECTED = 1;

	/** Specifies that this vertex can be transformed. */
	public static final int TRANSFORMABLE = 2;

	/** Positional coordinate state. */
	public static final int COORDINATE_VERTEX_STATE = 10;

	/** Texture coordinate state. */
	public static final int TEXTURE_VERTEX_STATE = 11;

	private J3DeGeometryArray g;

	/** index of the vertex */
	private int index;

	/** positional coordinate state of this vertex */
	private int state;

	/** texture coordinate state of this vertex */
	private int texState;

	/** Used by various classes that manipulate the data of multiple vertices. */
	public boolean inUse = false;


	/**
	 * Creates an inastance of <code>Vertex</code> which represents
	 * <code>index</code>-th vertex in the specified <code>J3DeGeometryArray</code>.
	 * 
	 * @param g
	 * 	<code>J3DeGeometryArray</code> the data of this vertex is in.
	 * @param index
	 * 	index of this vertex in <code>J3DeGeometryArray</code>
	 */
	protected Vertex(J3DeGeometryArray g, int index){
		this.g = g;
		this.index = index;
		this.state = 0;
		this.texState = -1;
	}

	/**
	 * Copies all vertex data from the specified vertex to this vertex,
	 * including index and reference to <code>J3DeGeometryArray</code>.
	 * 
	 * @param vertex
	 * 	vertex which data will be copied to this vertex
	 */
	protected void set(Vertex vertex){
		this.g = vertex.getGeometry();
		this.index = vertex.getIndex();
		this.setData(vertex);
	}

	/**
	 * Copies all vertex data from the specified vertex to this vertex,
	 * excluding index and reference to <code>J3DeGeometryArray</code>.
	 * 
	 * @param vertex
	 * 	vertex which data will be copied to this vertex
	 */
	protected void setData(Vertex vertex){
		this.state = vertex.getState(Vertex.COORDINATE_VERTEX_STATE);
		this.texState = vertex.getState(Vertex.TEXTURE_VERTEX_STATE);
		this.setCoordinates(vertex.getCoordinates());
		for(int i = 0; i < this.g.getTexCoordSetCount(); i++){
			this.setTexCoordinates(vertex.getTexCoordinates(i), i);
		}
		if(this.g.hasNormals()){
			this.setNormal(vertex.getNormal());
		}
		if(this.g.hasColor()){
			this.setColor(vertex.getColor());
		}
	}

	/**
	 * Returns the index of this vertex.
	 * 
	 * @return the index of this vertex
	 */
	public int getIndex(){
		return index;
	}

	/**
	 * Sets the index of this vertex.
	 * 
	 * @param index
	 * 	new index
	 */
	protected void setIndex(int index){
		this.index = index;
	}

	/**
	 * Returns the positional coordinates of this vertex (X, Y, Z).
	 * 
	 * @return the positional coordinates of this vertex
	 */
	public float[] getCoordinates(){
		return g.getVertexCoordinates(this.index, new int[]{ 0, 1, 2 });
	}

	/**
	 * Returns the positional coordinates of this vertex.
	 * 
	 * @return the positional coordinates of this vertex
	 */
	public Point3f getCoordinate(){
		float[] coords = getCoordinates();
		return new Point3f(coords[0], coords[1], coords[2]);
	}

	/**
	 * Returns the X-coordinate of this vertex.
	 * 
	 * @return the X-coordinate of this vertex
	 */
	public float getX(){
		return g.getVertexCoordinates(this.index, new int[]{ 0 })[0];
	}

	/**
	 * Returns the Y-coordinate of this vertex.
	 * 
	 * @return the Y-coordinate of this vertex
	 */
	public float getY(){
		return g.getVertexCoordinates(this.index, new int[]{ 1 })[0];
	}

	/**
	 * Returns the Z-coordinate of this vertex.
	 * 
	 * @return the Z-coordinate of this vertex
	 */
	public float getZ(){
		return g.getVertexCoordinates(this.index, new int[]{ 2 })[0];
	}


	/**
	 * Returns texture coordinates of this vertex in the specified texture coordinate set.
	 * Number of coordinate axes depends on the type of texture coordinates.
	 * 
	 * @param texCoordSet
	 * 	texture coordinate set
	 * @return texture coordinates of this vertex in the specified texture coordinate set
	 */
	public float[] getTexCoordinates(int texCoordSet){
	          int[] axes = new int[g.getTexCoordType()];
	          for(int i=0; i<axes.length; i++){
	                    axes[i] = i;
	          }
		return g.getTextureCoordinates(this.index, axes, texCoordSet);
	}

	/**
	 * Returns a texture coordinate of this vertex in the specified texture coordinate set.
	 * 
	 * @param axis
	 * 	coordinate axis: S, T, R or Q
	 * @param texCoordSet
	 * 	texture coordinate set
	 * @return a texture coordinate of this vertex in the specified texture coordinate set
	 */
	public float getTex(int axis, int texCoordSet){
		return g.getTextureCoordinates(this.index, new int[]{ axis }, texCoordSet)[0];
	}


	/**
	 * Changes the state of this vertex.
	 * 
	 * @param state
	 * 	<code>HIDDEN</code>, <code>NORMAL</code>, <code>SELECTED</code> or <code>TRANSFORMABLE</code>
	 * @param type
	 * 	state type: <code>COORDINATE_VERTEX_STATE</code> or <code>TEXTURE_VERTEX_STATE</code>
	 */
	public void setState(int state, int type){
		if(type == Vertex.COORDINATE_VERTEX_STATE){
			this.state = state;
			if(state > -1 && state < 2){
				this.texState = this.state - 1;
			}
			else {
				this.texState = -1;
			}
		}
		else if(type == Vertex.TEXTURE_VERTEX_STATE){
			this.texState = state;
		}
	}

	/**
	 * Returns the state of this vertex.
	 * 
	 * @param type
	 * 	state type: <code>COORDINATE_VERTEX_STATE</code> or <code>TEXTURE_VERTEX_STATE</code>
	 * @return the state of this vertex
	 */
	public int getState(int type){
		if(type == Vertex.COORDINATE_VERTEX_STATE){
			return this.state;
		}
		else if(type == Vertex.TEXTURE_VERTEX_STATE){
			return this.texState;
		}

		return 0;
	}


	/**
	 * Sets the positional coordinates of this vertex.
	 * 
	 * @param coordinates
	 * 	new position of this vertex
	 */
	public void setCoordinates(float[] coordinates){
		g.setVertexCoordinates(this.index, coordinates, new int[]{ 0, 1, 2 });
	}

	/**
	 * Sets the positional coordinates of this vertex.
	 * 
	 * @param coordinates
	 *        new position of this vertex
	 */
	public void setCoordinate(Point3f coordinates){
		float[] coords = new float[3];
		coordinates.get(coords);
		this.setCoordinates(coords);
	}

	/**
	 * Sets the texture coordinates of this vertex.
	 * 
	 * @param coordinates
	 * 	new texture coordinates
	 * @param texCoordSet
	 * 	texture coordinate set
	 */
	public void setTexCoordinates(float[] coordinates, int texCoordSet){
		int[] axes = new int[Math.min(g.getTexCoordType(), coordinates.length)];
		for(int i=0; i<axes.length; i++){
			axes[i] = i;
		}
		g.setTextureCoordinates(this.index, coordinates, axes, texCoordSet);
	}

	/**
	 * Sets a texture coordinate of this vertex.
	 * 
	 * @param coordinate
	 * 	new texture coordinate
	 * @param axis
	 * 	coordinate axis: S, T, R or Q
	 * @param texCoordSet
	 * 	texture coordinate set
	 */
	public void setTex(float coordinate, int axis, int texCoordSet){
		g.setTextureCoordinates(this.index, new float[]{ coordinate }, new int[]{ axis }, texCoordSet);
	}

	/**
	 * Translates this vertex.
	 * 
	 * @param xyz
	 * 	change of position on each axis
	 */
	public void translate(float[] xyz){
		float[] coords = g.getVertexCoordinates(this.index, new int[]{ 0, 1, 2 });
		coords[0] += xyz[0];
		coords[1] += xyz[1];
		coords[2] += xyz[2];
		g.setVertexCoordinates(this.index, coords, new int[]{ 0, 1, 2 });
	}

	/**
	 * Changes texture coordinates of this vertex.
	 * 
	 * @param xy
	 * 	change of coordinates on S and T axis
	 * @param texCoordSet
	 * 	texture coordinate set
	 */
	public void translateTex(float[] xy, int texCoordSet){
		float[] coords = g.getTextureCoordinates(this.index, new int[]{ 0, 1 }, texCoordSet);
		coords[0] += xy[0];
		coords[1] += xy[1];
		g.setTextureCoordinates(this.index, coords, new int[]{ 0, 1 }, texCoordSet);
	}

	/**
	 * Sets the X coordinate of this vertex.
	 * 
	 * @param x
	 * 	new X coordinate
	 */
	public void setX(float x){
		g.setVertexCoordinates(this.index, new float[]{ x }, new int[]{ 0 });
	}

	/**
	 * Changes the X coordinate of this vertex.
	 * 
	 * @param x
	 * 	change of position on X axis
	 */
	public void translateX(float x){
		setX(getX() + x);
	}


	/**
	 * Sets the Y coordinate of this vertex.
	 * 
	 * @param y
	 *        new Y coordinate
	 */
	public void setY(float y){
		g.setVertexCoordinates(this.index, new float[]{ y }, new int[]{ 1 });
	}

	/**
	 * Changes the Y coordinate of this vertex.
	 * 
	 * @param y
	 *        change of position on Y axis
	 */
	public void translateY(float y){
		setY(getY() + y);
	}


	/**
	 * Sets the Z coordinate of this vertex.
	 * 
	 * @param z
	 *        new Z coordinate
	 */
	public void setZ(float z){
		g.setVertexCoordinates(this.index, new float[]{ z }, new int[]{ 2 });
	}

	/**
	 * Changes the Z coordinate of this vertex.
	 * 
	 * @param z
	 *        change of position on Z axis
	 */
	public void translateZ(float z){
		setZ(getZ() + z);
	}


	/**
	 * Returns true if the Object o is of type Vertex and all of the positional
	 * coordinates of o are equal to the ones in this vertex.
	 * 
	 * @param o
	 * 	the Object with which the comparison is made
	 * @return true if the Object o is of type Vertex and all of the positional
	 * 	coordinates of o are equal to the ones in this vertex
	 */
	public boolean equals(Object o){
		if(o instanceof Vertex){
			Vertex v = (Vertex)o;
			return (getX() == v.getX() && getY() == v.getY() && getZ() == v.getZ());
		}
		return false;
	}

	/**
	 * Returns the String representation of this vertex.
	 * 
	 * @return the String representation of this vertex
	 */
	public String toString(){
		String vertex = "Vertex(" + this.index + "): [" + this.getX() + " " + this.getY() + " " + this.getZ() + "]";
		if(g.hasColor()){
			float[] color = this.getColor();
			vertex = vertex + ", COLOR:[";
			for(int i=0; i<color.length; i++){
				vertex = vertex + color[i];
				if(i < color.length - 1){
					vertex = vertex + " ";
				}
			}
			vertex = vertex + "]";
		}
		if(g.getTexCoordType() > 0){
			StringBuffer tex = new StringBuffer();
			for(int i=0; i<g.getTexCoordSetCount(); i++){
				tex.append("[");
				for(int j=0; j<g.getTexCoordType(); j++){
					tex.append(this.getTex(j, i));
					if(j < g.getTexCoordType() - 1){
						tex.append(" ");
					}
				}
				tex.append("]");
				if(i < g.getTexCoordSetCount() - 1){
					tex.append(" ");
				}
			}
			vertex = vertex + ", TEXTURE:[" + tex.toString() + "]";
		}
		if(g.hasNormals()){
			Vector3f normal = this.getNormal();
		          vertex = vertex + ", NORMAL:[" + normal.getX() + " " + normal.getY() + " " + normal.getZ() + "]";
		}
		
		return vertex;
			
	}

	/**
	 * Returns <code>J3DeGeometryArray</code>.
	 * 
	 * @return <code>J3DeGeometryArray</code>
	 */
	protected J3DeGeometryArray getGeometry(){
		return this.g;
	}


	/**
	 * Returns the normal vector of this vertex.
	 * 
	 * @return the normal vector of this vertex
	 */
	public Vector3f getNormal(){
		return new Vector3f(g.getNormal(this.index));
	}

	/**
	 * Sets the normal vector of this vertex.
	 * 
	 * @param normal
	 * 	normal vector
	 */
	public void setNormal(Vector3f normal){
		normal.normalize();
		float[] coordinates = new float[3];
		normal.get(coordinates);
		g.setNormal(this.index, coordinates);
	}


	/**
	 * Returns the color of this vertex.
	 * 
	 * @return the color of this vertex
	 */
	public float[] getColor(){
		return this.g.getColor(this.index);
	}

	/**
	 * Sets the color of this vertex.
	 * 
	 * @param color
	 * 	R, G and B if <code>COLOR_3</code><br/>
	 *        R, G, B and A if <code>COLOR_3</code>
	 */
	public void setColor(float[] color){
		this.g.setColor(this.index, color);
	}

	/**
	 * Returns the RGB color of this vertex.
	 * 
	 * @return the RGB color of this vertex
	 */
	public Color getRGB(){
		if(this.g.hasColor()){
			float[] clr = this.getColor();
			return new Color((int)(255 * clr[0]), (int)(255 * clr[1]), (int)(255 * clr[2]));
		}
		return null;
	}

	/**
	 * Sets the RGB color of this vertex.
	 * 
	 * @param color
	 * 	<code>Color</code> with R, G and B components
	 */
	public void setRGB(Color color){
		if(this.g.hasColor()){
			float[] clr = this.getColor();
			clr[0] = ((float)color.getRed()) / 255;
			clr[1] = ((float)color.getGreen()) / 255;
			clr[2] = ((float)color.getBlue()) / 255;
			this.setColor(clr);
		}
	}

	/**
	 * Returns the alpha component of the color of this vertex if available.
	 * 
	 * @return the alpha component of the color of this vertex if available
	 */
	public float getAlpha(){
		if(this.g.getColorType() == 4){
			float[] color = this.getColor();
			return color[3];
		}
		return 0;
	}

	/**
	 * Sets the alpha component of the color of this vertex if vertex color has alpha component.
	 * 
	 * @param alpha
	 * 	new value of alpha component
	 */
	public void setAlpha(float alpha){
		if(this.g.getColorType() == 4){
			if(alpha < 0){
				alpha = 0;
			}
			else if(alpha > 1){
				alpha = 1;
			}

			float[] color = this.getColor();
			color[3] = alpha;
			this.setColor(color);
		}
	}

}
