//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.Generic;
using System.Text;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;

namespace DXGfxLib
{
    /// <summary>
    /// This class features a very raw and basic skybox. It loads everything from an x file.
    /// </summary>
    public class SkyBox : MeshObject
    {
        protected const string effectFileName = "skyBox.fx";

        protected string savedFileName;

        protected float dim = 0.0f;

        public SkyBox() : base()
        {
            dim = 160.0f;

            worldAABBox = new AABBox(new BBox(new Vector3(0.0f, 0.0f, 0.0f), dim));
        }

        public SkyBox(string file, float extent) : base()
        {
            if (DXGfxManager.GetGlobalInstance().d3dDevice == null)
            {
                throw new Exception("DXGfxManager.d3dDevice is null, creation of a skybox is impossible without a valid device!");
            }
            Init(DXGfxManager.GetGlobalInstance().d3dDevice, file, extent);
        }

        /// <summary>
        /// SkyBox constructor. You have to indicate with which d3d device you have to create everything and also
        /// you have to specify the x file to load and the desired dimensions for the skybox you are going to create.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="file"></param>
        /// <param name="extent"></param>
        public SkyBox(Device d3ddevice, string file, float extent) : base()
        {
            Init(d3ddevice, file, extent);
        }

        public void Init(Device d3ddevice, string file, float extent)
        {
            dim = extent;

            worldAABBox = new AABBox(new BBox(new Vector3(0.0f, 0.0f, 0.0f), dim));
            LoadFromFile(d3ddevice, file);
        }

        /// <summary>
        /// This will ensure associated DX resources are disposed!
        /// </summary>
        public override void Dispose()
        {
            if (originalMesh != null)
                originalMesh.Dispose();
        }

        /// <summary>
        /// We use the Update method to ensure that the Skybox is always centered on the camera position.
        /// </summary>
        /// <param name="frustrum"></param>
        /// <param name="appTime"></param>
        /// <param name="elapsedTime"></param>
        public override void Update(Frustrum frustrum, double appTime, float elapsedTime)
        {
            base.Update(frustrum, appTime, elapsedTime);

            // We are not going to change the orientation of the skybox but we want its center to
            // be always at the camera position.
            Position = MathUtil.ExtractPosition(Matrix.Invert(frustrum.matView));
        }

        /// <summary>
        /// Load the skybox from an .x file. The .x file should contain a cube on which the sky textures are mapped internally.
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public override void LoadFromFile(Device d3ddevice, string fileName)
        {
            ExtendedMaterial[] materials = null;

            originalMesh = Mesh.FromFile(fileName, MeshFlags.Managed, d3ddevice, out materials);

            if (meshTextures == null)
            {
                // We need to extract the material properties and texture names 
                meshTextures = new Texture[materials.Length];
                meshMaterials = new Material[materials.Length];
                for (int i = 0; i < materials.Length; i++)
                {
                    meshMaterials[i] = materials[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, materials[i].TextureFilename);
                }
            }

            Matrix mat = Matrix.Identity;
            mat.Scale(dim, dim, dim);
            WorldMat = mat;

            associatedEffectGroup.Set(d3ddevice, effectFileName);
        }

        /// <summary>
        /// A SkyBox is meant to be always visible, we don't check whether we are in the frustrum or not, we always register
        /// to the scene to be rendered.
        /// </summary>
        /// <param name="frustrum"></param>
        /// <param name="objectsToBeRendered"></param>
        public override void AttachForRendering(Frustrum frustrum, List<IDrawable> objectsToBeRendered)
        {
            objectsToBeRendered.Add(this);
        }

        /// <summary>
        /// This method perform rendering of the skybox.
        /// </summary>
        /// <param name="d3ddevice"></param>
        public override void Draw(Device d3ddevice)
        {
            d3ddevice.SetTransform(TransformType.World, WorldMat);

            // Meshes are divided into subsets, one for each material. Render them in
            // a loop
            for (int i = 0; i < meshMaterials.Length; i++)
            {
                // Set the material and texture for this subset
                d3ddevice.Material = meshMaterials[i];
                d3ddevice.SetTexture(i, meshTextures[i]);

                // Draw the mesh subset
                originalMesh.DrawSubset(i);
            }
        }
    }
}
