using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;

namespace DXGfxLib
{
    /// <summary>
    /// FrameworkMesh is very a very convenient class when you only want to load a Mesh. This class does a bit the same thing
    /// except that it does only what I want and I might extend it in the future. Still basically it does pretty much the
    /// same thing as FrameworkMesh at this stage. It loads mesh geometry and textures.
    /// Also most of the time you will ask the ressource manager to get a MeshRessource for you and it will only load it once.
    /// Refcounting for a ressource might be desirable and I might end doing something just like that. At this stage it is not
    /// done though.
    /// It is easy to do but I still have to decide if I really need a ressource manager in this library or not.
    /// </summary>
    public class MeshRessource
    {
        /// <summary>
        /// Name of our mesh ressource.
        /// </summary>
        string name;

        /// <summary>
        /// The filename associated with the MeshRessource.
        /// </summary>
        string fileName;

        /// <summary>
        /// We keep a mesh object associated with our Scene object!
        /// </summary>
        protected Mesh originalMesh = null;

        /// <summary>
        /// Yep this is the Mesh in local memory (default pool).
        /// </summary>
        protected Mesh localMemMesh = null;

        /// <summary>
        /// The extended materials associated with our mesh. I keep that information because for exemple if you want to move
        /// a file it is interesting to move also the files it depends on!
        /// </summary>
        protected ExtendedMaterial[] extendedMaterials;

        /// <summary>
        /// Same as above but for effects.
        /// </summary>
        protected EffectInstance[] effectIntances;

        protected GraphicsStream adjacency = null;

        /// <summary>
        /// If we are given material we want to keep that information. It is used among other things for
        /// rendering!
        /// </summary>
        public Material[] meshMaterials;

        /// <summary>
        /// If we are given materials to render our mesh we will need to load the associated textures!
        /// </summary>
        public Texture[] meshTextures;

        /// <summary>
        /// The effects that were referenced in the X file.
        /// </summary>
        public Effect[] effects;

        /// <summary>
        /// A default radius for our bounding sphere.
        /// </summary>
        public float bSphereRadius = 1.0f;

        /// <summary>
        /// A default barycenter for our vertices.
        /// </summary>
        public Vector3 barycenter = new Vector3();

        /// <summary>
        /// The bounding box of the geometry in the Mesh local coordinate space.
        /// </summary>
        public BBox localBoundingBox = new BBox();

        /// <summary>
        /// Extends of the geomtry still in the mesh local coordinate space.
        /// </summary>
        public Vector3 extents = new Vector3();

        /// <summary>
        /// A constructor that will load everything from the provided file. This needs DXGfxManager to be initialized and to
        /// reference a valid D3D device.
        /// </summary>
        /// <param name="fileName"></param>
        public MeshRessource(string fileName)
        {
            if (DXGfxManager.GetGlobalInstance().d3dDevice == null)
            {
                throw new Exception("Manager does not have any reference to any valid d3ddevice, this constructor can not be executed");
            }
            LoadFromFile(DXGfxManager.GetGlobalInstance().d3dDevice, fileName);
        }

        /// <summary>
        /// Another constructor. That one does not need DXGfxManager to be initialized.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="fileName"></param>
        public MeshRessource(Device d3ddevice, string fileName)
        {
            LoadFromFile(d3ddevice, fileName);
        }

        /// <summary>
        /// This method is used by both constructor to perform the work of loading the information from the .X file and then
        /// loading all the textures it might depends on.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="fileName"></param>
        public void LoadFromFile(Device d3ddevice, string fileName)
        {
            this.fileName = fileName;
            this.name = StringUtil.GetName(fileName);

            originalMesh = Mesh.FromFile(fileName, MeshFlags.SystemMemory, d3ddevice, out adjacency, out extendedMaterials, out effectIntances);

            originalMesh.OptimizeInPlace(MeshFlags.OptimizeVertexCache | MeshFlags.OptimizeCompact |
                    MeshFlags.OptimizeAttributeSort, adjacency);

            originalMesh.ComputeNormals();

            localMemMesh = originalMesh.Clone((originalMesh.Options.Value & ~MeshFlags.SystemMemory),
                originalMesh.VertexFormat, d3ddevice);

            // We need to extract the material properties and texture names 
            meshTextures = new Texture[extendedMaterials.Length];
            meshMaterials = new Material[extendedMaterials.Length];
            for (int i = 0; i < extendedMaterials.Length; i++)
            {
                meshMaterials[i] = extendedMaterials[i].Material3D;
                // Set the ambient color for the material (D3DX does not do this)
                meshMaterials[i].Ambient = meshMaterials[i].Diffuse;

                // Create the texture
                meshTextures[i] = ResourceCache.GetGlobalInstance().CreateTextureFromFile(d3ddevice,
                                                                                  extendedMaterials[i].TextureFilename);
            }

            localBoundingBox = CalculateLocalBBox(originalMesh);
            extents = localBoundingBox.Size();

            // Find the mesh's center, then generate a centering matrix
            using (VertexBuffer vb = originalMesh.VertexBuffer)
            {
                using (GraphicsStream stm = vb.Lock(0, 0, LockFlags.NoSystemLock))
                {
                    try
                    {
                        this.bSphereRadius = Geometry.ComputeBoundingSphere(stm,
                            originalMesh.NumberVertices, originalMesh.VertexFormat, out this.barycenter);
                    }
                    finally
                    {
                        vb.Unlock();
                    }
                }
            }
        }

        /// <summary>
        /// The Draw method!
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="subsetId"></param>
        /// <param name="setMeshMaterial"></param>
        public virtual void Draw(Device d3ddevice, int subsetId, bool setMeshMaterial)
        {
            if (setMeshMaterial)
            {
                d3ddevice.Material = meshMaterials[subsetId];
                d3ddevice.SetTexture(0, meshTextures[subsetId]);
            }
            localMemMesh.DrawSubset(subsetId);
        }

        /// <summary>
        /// Another Draw method.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="subsetId"></param>
        public virtual void Draw(Device d3ddevice, int subsetId)
        {
            Draw(d3ddevice, subsetId, true);
        }

        /// <summary>
        /// Idem.
        /// </summary>
        /// <param name="d3ddevice"></param>
        public virtual void Draw(Device d3ddevice)
        {
            for (int i = 0; i < meshMaterials.Length; i++)
            {
                Draw(d3ddevice, i);
            }
        }

        /// <summary>
        /// This will calculate a local bounding box based on associated geometry.
        /// </summary>
        /// <param name="meshToUse"></param>
        /// <returns></returns>
        public BBox CalculateLocalBBox(Mesh meshToUse)
        {
            return AABBox.CalculateLocalBBox(meshToUse);
        }

        /// <summary>
        /// This will return the copy of the Mesh we keep in system memory (that copy will remain there even if the D3D device is
        /// invalidated.
        /// </summary>
        /// <returns></returns>
        public Mesh GetDXMesh()
        {
            return originalMesh;
        }
    }
}
