//BSD License  
//DXGfx® - http://www.eteractions.com
//Copyright (c) 2005
//by Guillaume Randon
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software
//and associated documentation files (the "Software"), to deal in the Software without restriction,
//including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial
//portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;
using System.Drawing;
using System.IO;

namespace DXGfxLib
{
    /// <summary>
    /// This structure is used to store material information.
    /// </summary>
    public struct MaterialFromObj
    {
        public string name;

        public Vector3 vAmbient;
        public Vector3 vDiffuse;
        public Vector3 vSpecular;

        public int nShininess;
        public float fAlpha;

        public bool bSpecular;

        public string strTexture;
        public Texture texture;
    }

    /// <summary>
    /// This class is used to load .obj file (wavefront format). It is not finished yet. It expect to read vertices with
    /// normal and texture information. If the file you pass to that class lack these information the object that will be
    /// displayed in the end will be ugly to say the least.
    /// </summary>
    public class ObjectFromObj : MeshObject
    {
        /// <summary>
        /// Number of faces of the geometry.
        /// </summary>
        private int numFaces = 0;
        /// <summary>
        /// Number of indices.
        /// </summary>
        private int numIndices = 0;
        /// <summary>
        /// Do we have normals associated with our vertices?
        /// </summary>
        private bool weHaveNormals = false;
        /// <summary>
        /// Default material used for rendering.
        /// </summary>
        private Material defaultMat;

        /// <summary>
        /// List of materials associated with the object.
        /// </summary>
        private List<MaterialFromObj> materials = null;

        /// <summary>
        /// A default constructor.
        /// </summary>
        public ObjectFromObj()
        {
            materials = new List<MaterialFromObj>();
            // Default material
            defaultMat = new Material();
            defaultMat.Ambient = Color.Aqua;
            defaultMat.Diffuse = Color.Azure;
            defaultMat.Emissive = Color.Blue;
            defaultMat.Specular = Color.Brown;
        }

        /// <summary>
        /// This method performs the work needed to load the data from the .obj and .mtl files.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="wavefrontObjFile"></param>
        public void Load(Device d3ddevice, string wavefrontObjFile)
        {
            // Load our file
            string meshFile = Utility.FindMediaFile(wavefrontObjFile);
            string mtlFile = "";

            // Store the current folder, then switch to the folder where the media was found
            string currentFolder = System.IO.Directory.GetCurrentDirectory();
            System.IO.Directory.SetCurrentDirectory(new System.IO.FileInfo(meshFile).Directory.FullName);

            List<Int16> indices = new List<Int16>();
            List<Vector2> texCoords = new List<Vector2>();
            List<Vector3> normals = new List<Vector3>();
            List<int> attributes = new List<int>();
            List<CustomVertex.PositionNormalTextured> vertices = new List<CustomVertex.PositionNormalTextured>();

            int currSubset = 0;
    
            string line;

            //Pass the file path and file name to the StreamReader constructor
            StreamReader sr = new StreamReader(meshFile);

			//Read the first line of text
			line = sr.ReadLine();

			//Continue to read until you reach end of file
			while (line != null) 
			{
                if(line.StartsWith("#"))
                {
                    // Nothing to read, these are comments.
                }
                else if(line.StartsWith("vt"))
                {
                    // Vertext texcoord.
                    string tmp = line.Substring(3);

                    string[] values = FilteredSplit(tmp, null);

                    texCoords.Add(new Vector2(float.Parse(values[0]),
                                                float.Parse(values[1])));
                }
                else if(line.StartsWith("vn"))
                {
                    // Normals
                    string tmp = line.Substring(3);

                    string[] values = FilteredSplit(tmp, null);

                    normals.Add(new Vector3(float.Parse(values[0]),
                                                float.Parse(values[1]),
                                                float.Parse(values[2])));
                }
                else if (line.StartsWith("v"))
                {
                    // Positions.
                    string tmp = line.Substring(2);

                    string[] values = FilteredSplit(tmp, null);

                    CustomVertex.PositionNormalTextured vertex = new CustomVertex.PositionNormalTextured();
                    vertex.Position = new Vector3(float.Parse(values[0]),
                                                float.Parse(values[1]),
                                                float.Parse(values[2]));
                    vertices.Add(vertex);
                }
                else if(line.StartsWith("f"))
                {
                    // Face
                    int iPosition, iTexCoord, iNormal;
                    iPosition = iTexCoord = iNormal = 0;
                    CustomVertex.PositionNormalTextured vertex;

                    string tmp = line.Substring(2);
                    string[] values = FilteredSplit(tmp, null);
                    if (values.Length == 3)
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            string[] indexes = values[i].Split('/');

                            iPosition = int.Parse(indexes[0]);
                            vertex = vertices[iPosition - 1];

                            indices.Add((Int16)(iPosition - 1));
                            numIndices++;

                            if (indexes.Length > 1)
                            {
                                iTexCoord = int.Parse(indexes[1]);
                                vertex.Tu = texCoords[iTexCoord - 1].X;
                                vertex.Tv = texCoords[iTexCoord - 1].Y;

                                if (indexes.Length > 2)
                                {
                                    weHaveNormals = true;
                                    iNormal = int.Parse(indexes[2]);
                                    vertex.Nx = normals[iNormal - 1].X;
                                    vertex.Ny = normals[iNormal - 1].Y;
                                    vertex.Nz = normals[iNormal - 1].Z;
                                }
                            }
                        }
                        numFaces++;

                        attributes.Add(currSubset);
                    }
                    if (values.Length == 4)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            string[] indexes = values[i].Split('/');

                            iPosition = int.Parse(indexes[0]);
                            vertex = vertices[iPosition - 1];

                            if (i == 3)
                            {
                                indices.Add(indices[indices.Count - 3]);
                                indices.Add(indices[indices.Count - 1]);
                                indices.Add((Int16)(iPosition - 1));
                                numIndices = +3;
                                numFaces++;
                                attributes.Add(currSubset);
                            }
                            else
                            {
                                indices.Add((Int16)(iPosition - 1));
                                numIndices++;
                            }
                            if (indexes.Length > 1)
                            {
                                iTexCoord = int.Parse(indexes[1]);
                                vertex.Tu = texCoords[iTexCoord - 1].X;
                                vertex.Tv = texCoords[iTexCoord - 1].Y;

                                if (indexes.Length > 2)
                                {
                                    weHaveNormals = true;
                                    iNormal = int.Parse(indexes[2]);
                                    vertex.Nx = normals[iNormal - 1].X;
                                    vertex.Ny = normals[iNormal - 1].Y;
                                    vertex.Nz = normals[iNormal - 1].Z;
                                }
                            }
                        }
                        numFaces++;

                        attributes.Add(currSubset);
                    }
                }
                else if(line.StartsWith("mtllib"))
                {
                    mtlFile = line.Substring(7);

                    // Then load it now so that we have our materials ready
                    LoadMtl(d3ddevice, mtlFile);
                }
                else if(line.StartsWith("usemtl"))
                {
                    bool found = false;

                    string matName = line.Substring(7);

                    for (int i = 0; i < materials.Count; i++)
                    {
                        if (matName.Equals(materials[i].name))
                        {
                            found = true;
                            currSubset = i;
                        }
                    }

                    if (!found)
                    {
                        throw new Exception("Materials are already loaded so we should have it!");
                    }
                }

				//Read the next line
				line = sr.ReadLine();
			}

			//close the file
			sr.Close();

            Mesh ourMesh = new Mesh(numFaces, vertices.Count, MeshFlags.Managed, CustomVertex.PositionNormalTextured.Format,
                d3ddevice);

            GraphicsStream dstVertices = ourMesh.LockVertexBuffer(LockFlags.NoSystemLock);
            dstVertices.Write(vertices.ToArray());
            ourMesh.UnlockVertexBuffer();

            GraphicsStream dstIndices = ourMesh.LockIndexBuffer(LockFlags.NoSystemLock);
            dstIndices.Write(indices.ToArray());
            ourMesh.UnlockIndexBuffer();

            int[] dstAttributes = ourMesh.LockAttributeBufferArray(LockFlags.NoSystemLock);
            attributes.CopyTo(dstAttributes);
            ourMesh.UnlockAttributeBuffer();

            if (!weHaveNormals)
            {
                ourMesh.ComputeNormals();
            }

            
            base.Attach(ourMesh);

            // Now revert to the folder we were in initially
            System.IO.Directory.SetCurrentDirectory(currentFolder);
        }

        /// <summary>
        /// This method is used to load information stored in .mtl files referenced by the .obj file.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="file"></param>
        public void LoadMtl(Device d3ddevice, string file)
        {
            MaterialFromObj currentMaterial = new MaterialFromObj();
            bool first = true;

            //Pass the file path and file name to the StreamReader constructor
            StreamReader sr = new StreamReader(file);

            //Read the first line of text
            string line = sr.ReadLine();

            //Continue to read until you reach end of file
            while (line != null)
            {
                if (line.StartsWith("#"))
                {
                    // Nothing to read, these are comments.
                }
                else if (line.StartsWith("newmtl"))
                {
                    if (!first)
                    {
                        materials.Add(currentMaterial);
                        currentMaterial = new MaterialFromObj();
                    }
                    first = false;
                    currentMaterial.name = line.Substring(7);
                }
                else if (line.StartsWith("Ka"))
                {
                    string tmp = line.Substring(3);

                    string[] values = FilteredSplit(tmp, null);

                    
                    currentMaterial.vAmbient = new Vector3(float.Parse(values[0]),
                                                float.Parse(values[1]),
                                                float.Parse(values[2]));
                }
                else if (line.StartsWith("Kd"))
                {
                    string tmp = line.Substring(3);

                    string[] values = FilteredSplit(tmp, null);

                    currentMaterial.vDiffuse = new Vector3(float.Parse(values[0]),
                            float.Parse(values[1]),
                            float.Parse(values[2]));
                }

                else if (line.StartsWith("Ks"))
                {
                    string tmp = line.Substring(3);

                    string[] values = FilteredSplit(tmp, null);

                    currentMaterial.vSpecular = new Vector3(float.Parse(values[0]),
                            float.Parse(values[1]),
                            float.Parse(values[2]));
                }
                else if (line.StartsWith("map_Kd"))
                {
                    string tmp = line.Substring(7);

                    //currentMaterial.texture = ResourceCache.GetGlobalInstance().CreateTextureFromFile(d3ddevice, tmp);

                }

                //Read the next line
                line = sr.ReadLine();
            }
            materials.Add(currentMaterial);

            //close the file
            sr.Close();
        }

        /// <summary>
        /// Actual rendering of the tile. As always for the render method and as for the attach method, that method
        /// is called when needed on objects by the scene manager.
        /// </summary>
        public override void Draw(Device d3ddevice)
        {
            d3ddevice.SetTransform(TransformType.World, worldMat);
            d3ddevice.Material = defaultMat;
            for (int i = 0; i < materials.Count; i++)
            {
                d3ddevice.SetTexture(0, materials[i].texture);
                originalMesh.DrawSubset(i);
            }
        }

        /// <summary>
        /// This method is used to split string in a list of strings based on the separator passed to hte method.
        /// </summary>
        /// <param name="strIn"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public string[] FilteredSplit(string strIn, char[] separator)
        {
            string[] valuesUnfiltered = strIn.Split(separator);

            // Sometime if we have a white space at the beginning of the string, split
            // will remove an empty string. Let's remove that.
            List<string> listOfValues = new List<string>();
            foreach (string str in valuesUnfiltered)
            {
                if (str != "")
                {
                    listOfValues.Add(str);
                }
            }
            string[] values = listOfValues.ToArray();

            return values;
        }
    }
}
