package demos.dualDepthPeeling;


// Translated from C++ Version:
// nvModel.h - Model support class
//
// The nvModel class implements an interface for a multipurpose model
// object. This class is useful for loading and formatting meshes
// for use by OpenGL. It can compute face normals, tangents, and
// adjacency information. The class supports the obj file format.
//
// Author: Evan Hart
// Email: sdkfeedback@nvidia.com
//
// Copyright (c) NVIDIA Corporation. All rights reserved.
////////////////////////////////////////////////////////////////////////////////


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Vector;

import com.jogamp.common.util.IOUtil;


public class Model {
	//
	// Enumeration of primitive types
	//
	//////////////////////////////////////////////////////////////
	public enum PrimType {
		eptNone(0x0),
		eptPoints(0x1),
		eptEdges(0x2),
		eptTriangles(0x4),
		eptTrianglesWithAdjacency(0x8),
		eptAll(0xf);

		PrimType( int val )
		{
			m_iVal = val;
		}
		int m_iVal = 0;
	};

	public static final int NumPrimTypes = 4;

	public Model CreateModel()
	{
		return new Model();
	}

	public Model()
	{
		posSize_ = 0;
		pOffset_ = -1;
		nOffset_ = -1;
		vtxSize_ = 0;
		openEdges_ = 0;
	}


	public class IdxSet {
		Integer pIndex = 0;
		Integer nIndex = 0;

		boolean lessThan ( IdxSet rhs ) 
		{
			if (pIndex < rhs.pIndex)
				return true;
			else if (pIndex == rhs.pIndex) {
				if (nIndex < rhs.nIndex)
					return true;
			}
			return false;
		}
	};


	//
	// loadModelFromFile
	//
	//    This function attempts to determine the type of
	//  the filename passed as a parameter. If it understands
	//  that file type, it attempts to parse and load the file
	//  into its raw data structures. If the file type is
	//  recognized and successfully parsed, the function returns
	//  true, otherwise it returns false.
	//
	//////////////////////////////////////////////////////////////
	public boolean loadModelFromFile( Class<?> context, String file ) {
        URLConnection conn = IOUtil.getResource(context, file);        
		if ( conn != null )
		{
			BufferedReader input = null;
			try {

				input = new BufferedReader(new InputStreamReader(conn.getInputStream()));
				String line = null;
				float[] val = new float[4];
				int[][] idx = new int[3][3];
				boolean hasNormals = false;

				while ( (line = input.readLine()) != null) {
					switch (line.charAt(0)) {
					case '#':
						break;

					case 'v':
						switch (line.charAt(1)) {

						case ' ':
							line = line.substring( line.indexOf( " " ) + 1 );
							//vertex, 3 or 4 components
							val[0] = Float.valueOf( line.substring( 0, line.indexOf( " " ) ) );
							line = line.substring( line.indexOf( " " ) + 1 );
							val[1] = Float.valueOf( line.substring( 0, line.indexOf( " " ) ) );
							line = line.substring( line.indexOf( " " ) + 1 );
							val[2] = Float.valueOf( line );
							positions_.add( val[0]);
							positions_.add( val[1]);
							positions_.add( val[2]);
							break;

						case 'n':
							//normal, 3 components
							line = line.substring( line.indexOf( " " ) + 1 );
							val[0] = Float.valueOf( line.substring( 0, line.indexOf( " " ) ) );
							line = line.substring( line.indexOf( " " ) + 1 );
							val[1] = Float.valueOf( line.substring( 0, line.indexOf( " " ) ) );
							line = line.substring( line.indexOf( " " ) + 1 );
							val[2] = Float.valueOf( line );
							normals_.add( val[0]);
							normals_.add( val[1]);
							normals_.add( val[2]);
							break;
						}
						break;

					case 'f':
						//face
						line = line.substring( line.indexOf( " " ) + 2 );
						
						idx[0][0] = Integer.valueOf( line.substring( 0, line.indexOf("//") ) ).intValue();
						line = line.substring( line.indexOf( "//" ) + 2 );
						idx[0][1] = Integer.valueOf( line.substring( 0, line.indexOf(" ") ) ).intValue();

						{ 
							//This face has vertex and normal indices

							// in .obj, f v1 .... the vertex index used start from 1, so -1 here
							//remap them to the right spot
							idx[0][0] = (idx[0][0] > 0) ? (idx[0][0] - 1) : ((int)positions_.size() - idx[0][0]);
							idx[0][1] = (idx[0][1] > 0) ? (idx[0][1] - 1) : ((int)normals_.size() - idx[0][1]);

							//grab the second vertex to prime
							line = line.substring( line.indexOf( " " ) + 1 );
							idx[1][0] = Integer.valueOf( line.substring( 0, line.indexOf("//") ) );
							line = line.substring( line.indexOf( "//" ) + 2 );
							idx[1][1] = Integer.valueOf( line.substring( 0, line.indexOf(" ") ) );

							//remap them to the right spot
							idx[1][0] = (idx[1][0] > 0) ? (idx[1][0] - 1) : ((int)positions_.size() - idx[1][0]);
							idx[1][1] = (idx[1][1] > 0) ? (idx[1][1] - 1) : ((int)normals_.size() - idx[1][1]);

							//grab the third vertex to prime
							line = line.substring( line.indexOf( " " ) + 1 );
							idx[2][0] = Integer.valueOf( line.substring( 0, line.indexOf("//") ) );
							line = line.substring( line.indexOf( "//" ) + 2 );
							idx[2][1] = Integer.valueOf( line );
							{
								//remap them to the right spot
								idx[2][0] = (idx[2][0] > 0) ? (idx[2][0] - 1) : ((int)positions_.size() - idx[2][0]);
								idx[2][1] = (idx[2][1] > 0) ? (idx[2][1] - 1) : ((int)normals_.size() - idx[2][1]);

								//add the indices
								for (int ii = 0; ii < 3; ii++) {
									pIndex_.add( idx[ii][0]);
									nIndex_.add( idx[ii][1]);
								}

								//prepare for the next iteration, the num 0 does not change.
								idx[1][0] = idx[2][0];
								idx[1][1] = idx[2][1];
							}
							hasNormals = true;
						}
						break;

					default:
						break;
					};								
				}			
				//post-process data
				//free anything that ended up being unused
				if (!hasNormals) {
					normals_.clear();
					nIndex_.clear();
				}

				posSize_ = 3;
				return true;
				
			} catch (FileNotFoundException kFNF) {
				System.err.println("Unable to find the shader file " + file);
			} catch (IOException kIO) {
				System.err.println("Problem reading the shader file " + file);
			} catch (NumberFormatException kIO) {
				System.err.println("Problem reading the shader file " + file);
			} finally {
                IOUtil.close(input, false);
			}
		}
		return false;
	}

	//
	//  compileModel
	//
	//    This function takes the raw model data in the internal
	//  structures, and attempts to bring it to a format directly
	//  accepted for vertex array style rendering. This means that
	//  a unique compiled vertex will exist for each unique
	//  combination of position, normal, tex coords, etc that are
	//  used in the model. The prim parameter, tells the model
	//  what type of index list to compile. By default it compiles
	//  a simple triangle mesh with no connectivity. 
	//
	public void compileModel( )
	{
		boolean needsTriangles = true;

		HashMap<IdxSet, Integer> pts = new HashMap<IdxSet, Integer>();
		vertices_ = FloatBuffer.allocate( (pIndex_.size() + nIndex_.size()) * 3 );
		indices_ = IntBuffer.allocate( pIndex_.size() );
		for ( int i = 0; i < pIndex_.size(); i++  ) {
			IdxSet idx = new IdxSet();
			idx.pIndex = pIndex_.elementAt(i);

			if ( normals_.size() > 0)
				idx.nIndex = nIndex_.elementAt(i);
			else
				idx.nIndex = 0;

			if ( !pts.containsKey(idx) ) {
				if (needsTriangles)
					indices_.put( pts.size());

				pts.put( idx, pts.size() );

				//position, 
				vertices_.put( positions_.elementAt(idx.pIndex*posSize_));
				vertices_.put( positions_.elementAt(idx.pIndex*posSize_ + 1));
				vertices_.put( positions_.elementAt(idx.pIndex*posSize_ + 2));

				//normal
				if (normals_.size() > 0) {
					vertices_.put( normals_.elementAt(idx.nIndex*3));
					vertices_.put( normals_.elementAt(idx.nIndex*3 + 1));
					vertices_.put( normals_.elementAt(idx.nIndex*3 + 2));
				}

			}
			else {                	
				if (needsTriangles)
					indices_.put( pts.get(idx) );
			}
		}

		//create selected prim

		//set the offsets and vertex size
		pOffset_ = 0; //always first
		vtxSize_ = posSize_;
		if ( hasNormals()) {
			nOffset_ = vtxSize_;
			vtxSize_ += 3;
		}
		else {
			nOffset_ = -1;
		}
		vertices_.rewind();
		indices_.rewind();
	}

	//
	//  computeBoundingBox
	//
	//    This function returns the points defining the axis-
	//  aligned bounding box containing the model.
	//
	//////////////////////////////////////////////////////////////
	public void computeBoundingBox( float[] minVal, float[] maxVal)
	{
		if ( positions_.isEmpty())
			return;
		
		for ( int i = 0; i < 3; i++ )
		{
			minVal[i] = 1e10f;
			maxVal[i] = -1e10f;
		}


		for ( int i = 0; i < positions_.size(); i+= 3 ) {
			float x = positions_.elementAt(i);
			float y = positions_.elementAt(i+1);
			float z = positions_.elementAt(i+2);
			minVal[0] = Math.min( minVal[0], x );
			minVal[1] = Math.min( minVal[1], y );
			minVal[2] = Math.min( minVal[2], z );
			maxVal[0] = Math.max( maxVal[0], x );
			maxVal[1] = Math.max( maxVal[1], y );
			maxVal[2] = Math.max( maxVal[2], z );
		}
	}


	public void clearNormals()
	{
		normals_.clear();
		nIndex_.clear();
	}


	public boolean hasNormals()  {
		return normals_.size() > 0;
	}


	public int getPositionSize()  {
		return posSize_;
	}

	public int getNormalSize()  {
		return 3;
	}

	public Vector<Float> getPositions()  {
		return ( positions_.size() > 0) ? positions_ : null;
	}

	public Vector<Float> getNormals()  {
		return ( normals_.size() > 0) ? normals_ : null;
	}

	public Vector<Integer> getPositionIndices()  {
		return ( pIndex_.size() > 0) ? pIndex_: null;
	}

	public Vector<Integer> getNormalIndices()  {
		return ( nIndex_.size() > 0) ? nIndex_ : null;
	}

	public int getPositionCount()  {
		return (posSize_ > 0) ? (int)positions_.size() / posSize_ : 0;
	}

	public int getNormalCount()  {
		return (int)normals_.size() / 3;
	}

	public int getIndexCount()  {
		return (int)pIndex_.size();
	}

	public FloatBuffer getCompiledVertices()  {
		return vertices_;
	}

	public IntBuffer getCompiledIndices( )  {
		return indices_;
	}

	public int getCompiledPositionOffset()  {
		return pOffset_;
	}

	public int getCompiledNormalOffset()  {
		return nOffset_;
	}


	public int getCompiledVertexSize()  {
		return vtxSize_;
	}

	public int getCompiledVertexCount()  {
		return ((pIndex_.size() + nIndex_.size()) * 3);
	}

	public int getCompiledIndexCount( )  {
		return pIndex_.size();
	}

	public int getOpenEdgeCount()  {
		return openEdges_;
	}



	Vector<Float> positions_ = new Vector<Float>();
	Vector<Float> normals_ = new Vector<Float>();
	int posSize_;

	Vector<Integer> pIndex_ = new Vector<Integer>();
	Vector<Integer> nIndex_ = new Vector<Integer>();

	//data structures optimized for rendering, compiled model
	IntBuffer indices_ = null;
	FloatBuffer vertices_ = null;  
	int pOffset_;
	int nOffset_;
	int vtxSize_ = 0;

	int openEdges_;
};