using System;
using System.Drawing;
using System.ComponentModel;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D=Microsoft.DirectX.Direct3D;

using System.Windows.Forms;


//http://pluralsight.com/wiki/default.aspx/Craig.DirectX/FontBasicsTutorial.html?diff=y
namespace DarkStrideToolbox
{
	//This is a wrapper to the DX DSMesh object.  Their are all kinds of things it does for you that the DSMesh object doesn't.
	//As an example it will easily load itself from a file or enable you to create some common shapes.  This is a VERY 
	//helpfull class and I would recogmend never useing the DX DSMesh class without this inherited version.
	public class DSMesh
	{
		private enum enumMeshType
		{
			Empty,
			Sphere,
			Cylinder,
			Text,
			Rectangle,
			Load
		};

		//This struct holds a DX vertex.
		private struct Vertex
		{
			public Vector3 p;
			public Vector3 n;
			public float tu, tv;
			public static readonly VertexFormats Format = VertexFormats.Position | VertexFormats.Normal | VertexFormats.Texture1;
		};

		// Custom D3D vertex format used by the vertex buffer
		private struct MeshVertex
		{
			public Vector3 p;       // vertex position
			public Vector3 n;       // vertex normal

			public static readonly VertexFormats Format = VertexFormats.Position | VertexFormats.Normal;
		};

		#region Constants
		private const uint m_cNumberVertsX = 32;      // Number of vertices in the wall DSMesh along X
		private const uint m_cNumberVertsZ = 32;      // Number of vertices in the wall DSMesh along Z
		private const int m_cNumTriangles = (int)((m_cNumberVertsX-1)*(m_cNumberVertsZ-1)*2);	  // Number of triangles in the wall DSMesh
		#endregion

		#region Properties
		private Direct3D.Mesh m_oMesh = null; // Our DSMesh object in sysmem
		private Direct3D.Material[] m_oMeshMaterials = null; // Materials for our DSMesh
		private Texture[] m_oMeshTextures = null; // Textures for our DSMesh
		private Device m_oDevice = null; //The device we are rendering to

		private Vector3 m_vMeshBounds = new Vector3( 0,0,0 );

		private enumMeshType m_nType = enumMeshType.Empty;
		private object[] m_oaProperties = null;
		#endregion


		//We do two things here, first we reference the Vertex object so the damn compiler stops complaining.  And second
		//We store the device now when the DSMesh object is created instead of in EVERY function.
		public DSMesh( Device oDevice )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.DSMesh";
			Vertex oVertex;
			
			try
			{
				m_oDevice = oDevice;

				oVertex = new Vertex();
				oVertex.p = new Vector3( 0,0,0 );
				oVertex.n = new Vector3( 0,0,0 );
				oVertex.tu = 0;
				oVertex.tv = 0;
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}
		}

		public void OnResetDevice()
		{
			if( m_nType == enumMeshType.Sphere )
			{
				CreateMeshSphere( (double)m_oaProperties[0],
								  (int)m_oaProperties[1],
								  (int)m_oaProperties[2],
								  (System.Drawing.Color)m_oaProperties[3],
								  (string)m_oaProperties[4] );
			}
			else if( m_nType == enumMeshType.Cylinder )
			{
				CreateMeshCylinder( (double)m_oaProperties[0],
								    (double)m_oaProperties[1],
								    (double)m_oaProperties[2],
								    (int)m_oaProperties[3],
								    (int)m_oaProperties[4],
									(System.Drawing.Color)m_oaProperties[5],
									(string)m_oaProperties[6] );
			}
			else if( m_nType == enumMeshType.Text )
			{
				CreateMeshText( (string)m_oaProperties[0],
								(int)m_oaProperties[1],
								(float)m_oaProperties[2],
								(Color)m_oaProperties[3] );
			}
			else if( m_nType == enumMeshType.Rectangle )
			{
				CreateMeshRectangle( (double)m_oaProperties[0],
								     (double)m_oaProperties[1],
								     (System.Drawing.Color)m_oaProperties[2],
									 (string)m_oaProperties[3] );
			}
			else if( m_nType == enumMeshType.Load )
			{
				Load( (string)m_oaProperties[0] );
			}			
		}


		private Direct3D.Material CreateMaterial( System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMaterial";
			Material oMtrl;

			try
			{
				oMtrl = new Material();
				oMtrl.Ambient = cColor;
				oMtrl.Diffuse = cColor;
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}
			return( oMtrl );
		}


		public void Render( Matrix matWorld )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.Render";

			try
			{
				m_oDevice.SetTransform( TransformType.World,matWorld );

				Render();

				m_oDevice.SetTransform( TransformType.World,Matrix.Identity );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}
		}
		public void Render()
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.Render";

			try
			{
				//Meshes are divided into subsets, one for each material. Render them in a loop
				for( int i=0; i<m_oMeshMaterials.Length; i++ )
				{
					// Set the material and texture for this subset
					m_oDevice.Material = m_oMeshMaterials[i];
					if( m_oMeshTextures != null )
					{
						m_oDevice.SetTexture(0, m_oMeshTextures[i]);
					}
        
					// Draw the DSMesh subset
					m_oMesh.DrawSubset(i);
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}
		}
		

		//Create a DSMesh the shape of a sphere
		public void CreateMeshSphere( double nRadius )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshSphere";

			try
			{
				CreateMeshSphere( .25f,20,20,System.Drawing.Color.White,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}
		}
		public void CreateMeshSphere( double nRadius,System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshSphere";

			try
			{
				CreateMeshSphere( nRadius,20,20,cColor,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}
		}
		public void CreateMeshSphere( double nRadius,System.Drawing.Color cColor,string sTextureFileName )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshSphere";

			try
			{
				CreateMeshSphere( nRadius,20,20,cColor,sTextureFileName );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshSphere( double nRadius,int nSlices,int nStacks,System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshSphere";

			try
			{
				CreateMeshSphere( nRadius,nSlices,nStacks,cColor,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshSphere( double nRadius,int nSlices,int nStacks,System.Drawing.Color cColor,string sTextureFileName )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshSphere";

			try
			{
				m_nType = enumMeshType.Sphere;
				m_oaProperties = new object[]{ nRadius,nSlices,nStacks,cColor,sTextureFileName };

				// Create Sphere meshes
				m_oMesh = Direct3D.Mesh.Sphere( m_oDevice,(float)nRadius,nSlices,nStacks );

				// Set up a material
				m_oMeshMaterials = new Material[1];
				m_oMeshMaterials[0] = CreateMaterial(cColor);

				//Setup the texture
				if( sTextureFileName.Length > 0 )
				{
					m_oMeshTextures = new Texture[1];
					m_oMeshTextures[0] = TextureLoader.FromFile( m_oDevice,sTextureFileName );
				}
				else
				{
					m_oMeshTextures = null;
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}


		//Create a DSMesh the shape of a cylinder
		public void CreateMeshCylinder( double nBottomRadius,double nLength )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshCylinder";

			try
			{
				CreateMeshCylinder( 0,nBottomRadius,nLength,20,20,System.Drawing.Color.White,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshCylinder( double nBottomRadius,double nLength,System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshCylinder";

			try
			{
				CreateMeshCylinder( 0,nBottomRadius,nLength,20,20,cColor,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshCylinder( double nTopRadius,double nBottomRadius,double nLength,System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshCylinder";

			try
			{
				CreateMeshCylinder( nTopRadius,nBottomRadius,nLength,20,20,cColor,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshCylinder( double nTopRadius,double nBottomRadius,double nLength,System.Drawing.Color cColor,string sTextureFileName )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshCylinder";

			try
			{
				CreateMeshCylinder( nTopRadius,nBottomRadius,nLength,20,20,cColor,sTextureFileName );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshCylinder( double nTopRadius,double nBottomRadius,double nLength,int nSlices,int nStacks,System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshCylinder";

			try
			{
				CreateMeshCylinder( nTopRadius,nBottomRadius,nLength,nSlices,nStacks,cColor,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshCylinder( double nTopRadius,double nBottomRadius,double nLength,int nSlices,int nStacks,System.Drawing.Color cColor,string sTextureFileName )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshCylinder";

			try
			{
				m_nType = enumMeshType.Cylinder;
				m_oaProperties = new object[]{ nTopRadius,nBottomRadius,nLength,nSlices,nStacks,cColor,sTextureFileName };

				// Create Cylinder meshes
				m_oMesh = Direct3D.Mesh.Cylinder(m_oDevice, (float)nTopRadius, (float)nBottomRadius, (float)nLength, nSlices, nStacks);

				// Set up a material
				m_oMeshMaterials = new Material[1];
				m_oMeshMaterials[0] = CreateMaterial(cColor);

				//Setup the texture
				if( sTextureFileName.Length > 0 )
				{
					m_oMeshTextures = new Texture[1];
					m_oMeshTextures[0] = TextureLoader.FromFile( m_oDevice,sTextureFileName );
				}
				else
				{
					m_oMeshTextures = null;
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}


		//Text Meshes
		public void CreateMeshText( string sText,int nFontSize,Color oColor )
		{
			CreateMeshText( sText,nFontSize,.3f,oColor );
		}
		public void CreateMeshText( string sText,int nFontSize,float nThickness,Color oColor )
		{
			System.Drawing.Font oFont = null;
			GlyphMetricsFloat[] oaGlyphMetrics = null;


			m_nType = enumMeshType.Text;
			m_oaProperties = new object[]{ sText,nFontSize,nThickness,oColor };

			//Create the font
			oFont = new System.Drawing.Font( "Arial",nFontSize );

			//Create the mesh
			m_oMesh = Mesh.TextFromFont( m_oDevice,oFont,sText, 0.001f, nThickness,out oaGlyphMetrics );
			m_vMeshBounds = ComputeMeshBounds( oaGlyphMetrics ); 
			m_vMeshBounds.Z = nThickness;

			// Set up a material
			m_oMeshMaterials = new Material[1];
			m_oMeshMaterials[0] = CreateMaterial( oColor );

			//Set our texture
			m_oMeshTextures = new Texture[1];
		}
		private Vector3 ComputeMeshBounds( GlyphMetricsFloat[] oaGlyphs )
		{
			float nMaxX = 0; 
			float nMaxY = 0; 
			float nOffsetY = 0; 
			float nY = 0;

			
			foreach( GlyphMetricsFloat oLoopGlyph in oaGlyphs )
			{
				nMaxX += oLoopGlyph.CellIncX; 
				nY = nOffsetY + oLoopGlyph.BlackBoxY; 

				if( nY > nMaxY )
				{
					nMaxY = nY; 
				}
				nOffsetY += oLoopGlyph.CellIncY; 
			}

			return( new Vector3( nMaxX,nMaxY,0) ); 
		} 


		//Create a DSMesh the shape of a rectangle
		public void CreateMeshRectangle( double nWidth,double nHeight )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshRectangle";

			try
			{
				CreateMeshRectangle( nWidth,nHeight,System.Drawing.Color.White,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshRectangle( double nWidth,double nHeight,System.Drawing.Color cColor )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshRectangle";

			try
			{
				CreateMeshRectangle( nWidth,nHeight,cColor,"" );
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}
		public void CreateMeshRectangle( double nWidth,double nHeight,System.Drawing.Color cColor,string sTextureFileName )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.CreateMeshRectangle";
			MeshVertex[] vaVertexes;
			float dX = 1.0f/(m_cNumberVertsX-1);
			float dZ = 1.0f/(m_cNumberVertsZ-1);
			uint k = 0;
			ushort[] pIndex;
			int[] pdwAdjacency;

			try
			{
				m_nType = enumMeshType.Rectangle;
				m_oaProperties = new object[]{ nWidth,nHeight,cColor,sTextureFileName };


				//Create a square grid m_cNumberVertsX*m_cNumberVertsZ for rendering the wall
				m_oMesh = new Direct3D.Mesh(m_cNumTriangles, m_cNumTriangles*3,0,MeshVertex.Format, m_oDevice);

				//Fill in the grid vertex data
				vaVertexes = (MeshVertex[])m_oMesh.LockVertexBuffer(typeof(MeshVertex), 0, m_cNumTriangles * 3);
				for (uint z=0; z < (m_cNumberVertsZ-1); z++)
				{
					for (uint x=0; x < (m_cNumberVertsX-1); x++)
					{
						vaVertexes[k].p  = new Vector3((float)nWidth * x*dX, 0.0f, (float)nHeight * z*dZ);
						vaVertexes[k].n  = new Vector3(0.0f, 1.0f, 0.0f);
						k++;
						vaVertexes[k].p  = new Vector3((float)nWidth * x*dX, 0.0f, (float)nHeight * (z+1)*dZ);
						vaVertexes[k].n  = new Vector3(0.0f, 1.0f, 0.0f);
						k++;
						vaVertexes[k].p  = new Vector3((float)nWidth * (x+1)*dX, 0.0f, (float)nHeight * (z+1)*dZ);
						vaVertexes[k].n  = new Vector3(0.0f, 1.0f, 0.0f);
						k++;
						vaVertexes[k].p  = new Vector3((float)nWidth * x*dX, 0.0f, (float)nHeight * z*dZ);
						vaVertexes[k].n  = new Vector3(0.0f, 1.0f, 0.0f);
						k++;
						vaVertexes[k].p  = new Vector3((float)nWidth * (x+1)*dX, 0.0f, (float)nHeight * (z+1)*dZ);
						vaVertexes[k].n  = new Vector3(0.0f, 1.0f, 0.0f);
						k++;
						vaVertexes[k].p  = new Vector3((float)nWidth * (x+1)*dX, 0.0f, (float)nHeight * z*dZ);
						vaVertexes[k].n  = new Vector3(0.0f, 1.0f, 0.0f);
						k++;
					}
				}
				m_oMesh.UnlockVertexBuffer();

				// Fill in index data			
				pIndex = (ushort[])m_oMesh.LockIndexBuffer(typeof(ushort), 0, m_cNumTriangles * 3);
				for( ushort iIndex = 0; iIndex < m_cNumTriangles * 3; iIndex++ )
				{
					pIndex[iIndex] = iIndex;
				}
				m_oMesh.UnlockIndexBuffer();

				// Eliminate redundant vertices
				pdwAdjacency = new int[3 * m_cNumTriangles];
				WeldEpsilons we = new WeldEpsilons();
				m_oMesh.GenerateAdjacency(0.01f, pdwAdjacency);
				m_oMesh.WeldVertices(WeldEpsilonsFlags.WeldAll, we, pdwAdjacency);

				// Optimize the DSMesh
				m_oMesh = m_oMesh.Optimize(MeshFlags.OptimizeCompact | MeshFlags.OptimizeVertexCache | 
					MeshFlags.VbDynamic | MeshFlags.VbWriteOnly, pdwAdjacency);

				// Set up a material
				m_oMeshMaterials = new Direct3D.Material[1];
				m_oMeshMaterials[0] = CreateMaterial(cColor);

				//Setup the texture
				if( sTextureFileName.Length > 0 )
				{
					m_oMeshTextures = new Texture[1];
					m_oMeshTextures[0] = TextureLoader.FromFile( m_oDevice,sTextureFileName );
				}
				else
				{
					m_oMeshTextures = null;
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}


		//Load in a DSMesh from a file
		public void Load( string sMeshFileName )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.Load";
			string sPath = "";
			int nWhere = -1;
			ExtendedMaterial[] oaMaterials = null;

			try
			{
				m_nType = enumMeshType.Load;
				m_oaProperties = new object[]{ sMeshFileName };

				// Load the DSMesh from the specified file
				m_oMesh = Direct3D.Mesh.FromFile( sMeshFileName,MeshFlags.SystemMemory, m_oDevice, out oaMaterials );
				//Get the path
				nWhere = sMeshFileName.LastIndexOf( "\\" );
				sPath = sMeshFileName.Substring( 0,nWhere+1 );

				if (m_oMeshTextures == null)
				{
					//We need to extract the material properties and texture names 
					m_oMeshTextures  = new Texture[oaMaterials.Length];
					m_oMeshMaterials = new Direct3D.Material[oaMaterials.Length];
					for( int i=0; i<oaMaterials.Length; i++ )
					{
						m_oMeshMaterials[i] = oaMaterials[i].Material3D;
						// Set the ambient color for the material (D3DX does not do this)
						m_oMeshMaterials[i].Ambient = m_oMeshMaterials[i].Diffuse;
						if( oaMaterials[i].TextureFilename != null )
						{
							// Create the texture, first we need to get the path name
							m_oMeshTextures[i] = TextureLoader.FromFile(m_oDevice, sPath + oaMaterials[i].TextureFilename);
						}
					}
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}


		//Resize a DSMesh by a facter.  Unfortunitly this function is somewhat unstable, I do not know why yet.
		public void Resize( double nResize )
		{
			const string sRoutineName = "DarkStrideToolbox.DSMesh.Resize";
			VertexBuffer oVertBuff = m_oMesh.VertexBuffer;
			Vertex[] oaVertexes = null;
			Vertex oForSize = new Vertex();
			int nNumVert = m_oMesh.NumberVertices;
			int nSize = System.Runtime.InteropServices.Marshal.SizeOf( oForSize );
			int nStart = 0;
			
			try
			{
				//Grab the first 30 verticies
				while( nStart < m_oMesh.NumberVertices )
				{
					oaVertexes = (Vertex[])oVertBuff.Lock( nSize*nStart, typeof(Vertex), 0, nNumVert );

					for( int n=0 ; n<oaVertexes.Length ; n++ )
					{
						oaVertexes[ n ].n.Multiply( (float)nResize );
						oaVertexes[ n ].p.Multiply( (float)nResize );
					}

					oVertBuff.Unlock();

					nStart += nNumVert;
					if( nStart+nNumVert > m_oMesh.NumberVertices )
					{
						nNumVert = nStart+nNumVert - m_oMesh.NumberVertices;																		
					}
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( sRoutineName + " Failed.",oEx );
			}		
		}



		#region Properties
		public Vector3 MeshBounds
		{
			get
			{
				if( m_nType == enumMeshType.Text )
				{
					return( m_vMeshBounds );
				}
				else
				{
					throw new System.Exception( "MeshBounds are not implimented yet for this mesh type." );
				}
			}
		}
		#endregion
	}
}
