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

namespace DXGfxLib
{
    /// <summary>
    /// This class is used to get some water displayed on screen.
    /// </summary>
    public class Water : TexturedPlane
    {
        /// <summary>
        /// A texture object to keep our bumpmap.
        /// </summary>
        private Texture bumpMap = null;
        /// <summary>
        /// A matrix to generate the bumpmap.
        /// </summary>
		private Matrix bumpMapMatrix = Matrix.Zero;

        /// <summary>
        /// Texture that is going to be use to render our water!
        /// </summary>
        public Texture waterTexture = null;

        public Plane reflectPlane;

        public Water()
        {
        }

        public Water(int width, int heigth, int numVertX, int numVertZ, string texturefile)
        {
            if (DXGfxManager.GetGlobalInstance().d3dDevice == null)
            {
                throw new Exception("DXGfxManager.d3dDevice is null, creation of water is impossible without a valid device!");
            }
            Init(DXGfxManager.GetGlobalInstance().d3dDevice, width, heigth, numVertX, numVertZ, texturefile);
        }

        /// <summary>
        /// The default constructor. You should provide a valid d3ddevice, dimensions, number of vertices in the grid
        /// and the texture to be applied to the surface.
        /// </summary>
        /// <param name="d3ddevice"></param>
        /// <param name="width"></param>
        /// <param name="heigth"></param>
        /// <param name="numVertX"></param>
        /// <param name="numVertZ"></param>
        /// <param name="texturefile"></param>
        public Water(Device d3ddevice, int width, int heigth, int numVertX, int numVertZ, string texturefile)
        {
            Init(d3ddevice, width, heigth, numVertX, numVertZ, texturefile);
        }

        public override void Init(Device d3ddevice, int width, int heigth, int numVertX, int numVertZ, string texturefile)
        {

            base.Init(d3ddevice, width, heigth, numVertX, numVertZ, texturefile);

            // First load our texture.
            if (File.Exists(texturefile))
            {
                waterTexture = ResourceCache.GetGlobalInstance().CreateTextureFromFile(d3ddevice, texturefile);
            }
            else
            {
                throw new ArgumentException("File :" + texturefile + "doesn't not exist");
            }

            bumpMap = this.CreateBumpMap(d3ddevice, 256, 256, Format.V8U8);

            reflectPlane = Microsoft.DirectX.Plane.FromPointNormal(new Vector3(0, 0, 0), new Vector3(0, 1, 0));
        }

        public override void LoadFromFile(Device d3ddevice, string fileName)
        {
            Init(d3ddevice, 2000, 2000, 10, 10, fileName);
        }

		/// <summary>
		/// When attached to the scene manager this method will be called automatically by the scene manager. If you
		/// make your own implementation of IWater you can use this method to update any internal value you might use
		/// to model your water.
		/// </summary>
		/// <param name="time"></param>
        public override void Update(Frustrum frustrum, double appTime, float elapsedTime)
		{
            base.Update(frustrum, appTime, elapsedTime);

			// Setup the bump matrix
			// Min r is 0.04 if amplitude is 32 to miss temporal aliasing
			// Max r is 0.16 for amplitude is 8 to miss spatial aliasing
			float r = 0.04f;
            bumpMapMatrix.M11 = r * (float)System.Math.Cos(appTime * 2.0f);
            bumpMapMatrix.M12 = -r * (float)System.Math.Sin(appTime * 2.0f);
            bumpMapMatrix.M21 = r * (float)System.Math.Sin(appTime * 2.0f);
            bumpMapMatrix.M22 = r * (float)System.Math.Cos(appTime * 2.0f);

            Matrix invMatView = Matrix.Invert(frustrum.matView);
            Vector3 pos = MathUtil.ExtractPosition(invMatView);
            Vector3 ypr = MathUtil.YawPitchRollFromMat(invMatView);

            Matrix desiredWorldMat = Matrix.RotationYawPitchRoll(ypr.X, 0, 0);
            desiredWorldMat.M41 = pos.X;
            desiredWorldMat.M43 = pos.Y;

            worldMat = desiredWorldMat;
		}

        /// <summary>
        /// Render everything when called.
        /// </summary>
        /// <param name="d3ddevice"></param>
        public override void Draw(Device d3ddevice)
        {
            // The way we built our water, it does look down, let's change that.
            Matrix useMat = Matrix.Multiply(Matrix.RotationX((float)(Math.PI)), worldMat);
            d3ddevice.SetTransform(TransformType.World, useMat);

            // Set up texture stage 0's states for the bumpmap
            d3ddevice.SetTexture(0, bumpMap);
            d3ddevice.SamplerState[0].AddressU = TextureAddress.Clamp;
            d3ddevice.SamplerState[0].AddressV = TextureAddress.Clamp;
            d3ddevice.TextureState[0].BumpEnvironmentMaterial00 = bumpMapMatrix.M11;
            d3ddevice.TextureState[0].BumpEnvironmentMaterial01 = bumpMapMatrix.M12;
            d3ddevice.TextureState[0].BumpEnvironmentMaterial10 = bumpMapMatrix.M21;
            d3ddevice.TextureState[0].BumpEnvironmentMaterial11 = bumpMapMatrix.M22;
            d3ddevice.TextureState[0].ColorOperation = TextureOperation.BumpEnvironmentMap;
            d3ddevice.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
            d3ddevice.TextureState[0].ColorArgument2 = TextureArgument.Current;


            d3ddevice.SetTexture(1, waterTexture);

            d3ddevice.TextureState[1].ColorArgument1 = TextureArgument.TextureColor;
            d3ddevice.TextureState[1].ColorOperation = TextureOperation.SelectArg1;

			// Set up projected texture coordinates
			// tu = (0.8x + 0.5z) / z
			// tv = (0.8y - 0.5z) / z
			Matrix mat = Matrix.Zero;
			mat.M11 = 0.8f; mat.M12 = 0.0f; mat.M13 = 0.0f;
			mat.M21 = 0.0f; mat.M22 = 0.8f; mat.M23 = 0.0f;
			mat.M31 = 0.5f; mat.M32 =-0.5f; mat.M33 = 1.0f;
			mat.M41 = 0.0f; mat.M42 = 0.0f; mat.M43 = 0.0f;

            d3ddevice.Transform.Texture1 = mat;
            d3ddevice.TextureState[1].TextureTransform = TextureTransform.Count3 | TextureTransform.Projected;
            d3ddevice.TextureState[1].TextureCoordinateIndex = (int)TextureCoordinateIndex.CameraSpacePosition | 1;

			d3ddevice.VertexFormat = CustomVertex.PositionTextured.Format;
            d3ddevice.SetTransform(TransformType.World, useMat);
            originalMesh.DrawSubset(0);
			d3ddevice.SamplerState[0].AddressU = TextureAddress.Wrap;
			d3ddevice.SamplerState[0].AddressV = TextureAddress.Wrap;
			d3ddevice.TextureState[0].ColorOperation = TextureOperation.Modulate;
			d3ddevice.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
			d3ddevice.TextureState[0].ColorArgument2 = TextureArgument.Current;
			d3ddevice.TextureState[1].ColorOperation = TextureOperation.Disable;
		}

		/// <summary>
		/// Creates a bumpmap from a surface
		/// </summary>
		private Texture CreateBumpMap(Device d3ddevice, int width, int height, Format bumpformat)
		{
			Texture bumpMapTexture = null;

			// Check if the device can create the format
			//			if (Manager.CheckDeviceFormat(Caps.AdapterOrdinal, Caps.DeviceType, graphicsSettings.DisplayMode.Format, 0, ResourceType.Textures, bumpformat) == false)
			//				return null;

			// Create the bumpmap texture
			bumpMapTexture = new Microsoft.DirectX.Direct3D.Texture(d3ddevice, width, height, 1, 0, bumpformat, Pool.Managed);

			int pitch;
			// Lock the surface and write in some bumps for the waves
			GraphicsStream dst = bumpMapTexture.LockRectangle(0, 0, out pitch);

			byte bumpUCoord, bumpVCoord;
			for (int y=0; y<height; y++)
			{
				for (int x=0; x<width; x++)
				{
					float fx = x/(float)width - 0.5f;
					float fy = y/(float)height - 0.5f;

					float r = (float)System.Math.Sqrt(fx*fx + fy*fy);

                    // 300 - 150 - 140
					bumpUCoord  = (byte)(64 * (float)System.Math.Cos(1200.0f * r) * (float)System.Math.Exp(-r * 5.0f));
					bumpUCoord += (byte)(32 * (float)System.Math.Cos(950.0f * (fx + fy)));
					bumpUCoord += (byte)(16 * (float)System.Math.Cos(690.0f * (fx * 0.85f - fy)));

					bumpVCoord  = (byte)(64 * (float)System.Math.Sin(1200.0f * r) * (float)System.Math.Exp(-r * 5.0f));
					bumpVCoord += (byte)(32 * (float)System.Math.Sin(950.0f * (fx + fy)));
					bumpVCoord += (byte)(16 * (float)System.Math.Sin(690.0f * (fx * 0.85f - fy)));

					dst.Write(bumpUCoord);
					dst.Write(bumpVCoord);
				}
			}
			bumpMapTexture.UnlockRectangle(0);
			return bumpMapTexture;
		}
    }
}
