using System;

using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;

using Icarus.Utilities.Graphics;

namespace Icarus.Renderer
{
	/// <summary>
	/// Summary description for Direct3DSettings.
	/// </summary>
	public class Direct3DSettings
	{
		#region Singleton Implementation
		/// <summary>
		/// Singleton Implementation.
		/// </summary>
		private static Direct3DSettings mg_Instance = new Direct3DSettings();

		/// <summary>
		/// Singleton Implementation.
		/// </summary>
		private Direct3DSettings() 
		{
		}

		/// <summary>
		/// Singleton Implementation.
		/// </summary>
		public static Direct3DSettings Instance 
		{
			get { return mg_Instance; }
		}
		#endregion

		/// <summary>
		/// Should we render collision boxes.
		/// </summary>
		private bool mRenderCollisionBoxes = false;

		/// <summary>
		/// Gets or sets whether we should render collision boxes.
		/// </summary>
		public bool RenderCollisionBoxes
		{
			get { return mRenderCollisionBoxes; }
			set { mRenderCollisionBoxes = value; }
		}

		/// <summary>
		/// This is the virtual device through which we will be doing the rendering.
		/// </summary>
		private Device m_RenderingDevice;

		/// <summary>
		/// Gets the device through which we will be doing the rendering.
		/// </summary>
		public Device RenderingDevice 
		{
			get { return m_RenderingDevice; }
		}

		/// <summary>
		/// Returns the intended usage of textures when rendering.
		/// </summary>
		/// <remarks>
		/// Ok, this bit is kinda funky. DirectX documentation says that when we create a texture, it's Usage parameter should match the corresponding device's CreateFlags. So when we get around to actually creating devices, it'll probably be a good idea to fill this function in.
		/// </remarks>
		public Usage TextureUsage 
		{
			get 
			{
				//TODO: Fill me in. For what to fill me in with, see field comment.
				return Usage.None;
			}
		}

		/// <summary>
		/// Whether we are operating in fullscreen or windowed mode.
		/// </summary>
		private bool m_WindowedMode = false;

		/// <summary>
		/// Gets or sets whether we are operating in fullscreen or windowed mode.
		/// </summary>
		public bool WindowedMode 
		{
			get { return m_WindowedMode; }
			set 
			{
				UpdateWindowedMode(value);
				m_WindowedMode = value;
			}
		}

		/// <summary>
		/// How many frames do we skip between animation frames. 1 means at every graphics updated, we go to the next animation frame.
		/// </summary>
		private int m_AnimationDelay = 1;

		/// <summary>
		/// How many frames do we skip between animation frames. 1 means at every graphics updated, we go to the next animation frame.
		/// </summary>
		public int AnimationDelay 
		{
			get { return m_AnimationDelay; }
			set 
			{
				m_AnimationDelay = value;
				if (m_AnimationDelay < 1) m_AnimationDelay = 1;
			}
		}

		/// <summary>
		/// The scroll margin. See field description for more.
		/// </summary>
		private int mScrollMargin = 30;

		/// <summary>
		/// Gets or sets the scroll margin. If the player character comes to within this many pixels of any edge of the screen, the view will be recentered on him.
		/// </summary>
		public int ScrollMargin
		{
			get { return mScrollMargin; }
			set { mScrollMargin = Math.Abs(value); }
		}

		private System.Drawing.Size mMaximumTextureSize;

		/// <summary>
		/// Returns the maximum texture size allowed by the device.
		/// </summary>
		public System.Drawing.Size MaximumTextureSize
		{
			get
			{
				return mMaximumTextureSize;
			}
		}

		/// <summary>
		/// This is the control that we will be rendering to.
		/// </summary>
		private D3DGameWindow mParentWindow;

		/// <summary>
		/// Gets the control that we are rendering to.
		/// </summary>
		public D3DGameWindow ParentWindow
		{
			get { return mParentWindow; }
		}

		/// <summary>
		/// Resets the device.
		/// </summary>
		public void ResetDevice() 
		{
			if (this.RenderingDevice != null) 
			{
				this.RenderingDevice.Reset(m_PresentationParameters);
//				m_RenderingDevice.RenderState.AlphaBlendEnable = true;
//				m_RenderingDevice.RenderState.SourceBlend = Blend.SourceAlpha;
//				m_RenderingDevice.RenderState.DestinationBlend = Blend.InvSourceAlpha;
//				m_RenderingDevice.RenderState.CullMode = Cull.None;
//				m_RenderingDevice.RenderState.Lighting = false;
//				m_RenderingDevice.RenderState.ZBufferEnable = false;
			}
		}

		/// <summary>
		/// Updates the windowed mode.
		/// </summary>
		/// <param name="v">The new value of the windowed mode.</param>
		private void UpdateWindowedMode(bool v) 
		{
			//Make sure we only update this if the windowed mode is not already correct:
			if (WindowedMode != v) 
			{
				if (v) 
				{
					//We are windowed. The window will determine all there is to know by itself:
					m_PresentationParameters.Windowed = true;
					m_PresentationParameters.BackBufferWidth = 0;
					m_PresentationParameters.BackBufferHeight = 0;
					m_PresentationParameters.FullScreenRefreshRateInHz = 0;
					m_PresentationParameters.BackBufferFormat = Format.Unknown;
				} 
				else 
				{
					//We are full-screen. Need to figure things out on our own:
					DisplayMode mode = DirectXUtilities.Instance.FindCurrentDisplayMode();
					m_PresentationParameters.Windowed = false;
					m_PresentationParameters.BackBufferWidth = mode.Width;
					m_PresentationParameters.BackBufferHeight = mode.Height;
					m_PresentationParameters.FullScreenRefreshRateInHz = mode.RefreshRate;
					m_PresentationParameters.BackBufferFormat = mode.Format;
				}
				
				//Now that presentation parameters are updated, we need to reset the device:
				this.ResetDevice();
			}
		}

		/// <summary>
		/// The presentation parameters for the rendering device.
		/// </summary>
		private PresentParameters m_PresentationParameters = new PresentParameters();

		/// <summary>
		/// Initializes the presentation parameters.
		/// </summary>
		private void InitializePresentationParameters() 
		{
			//No Z (Depth) buffer or Stencil buffer
			m_PresentationParameters.EnableAutoDepthStencil = false;

			//1 Back buffer for double-buffering
			m_PresentationParameters.BackBufferCount = 1;

			//Should make the back buffer lockable so we can render to it:
			m_PresentationParameters.PresentFlag = PresentFlag.LockableBackBuffer;

			//Set our Window as the Device Window
			m_PresentationParameters.DeviceWindow = mParentWindow;

			//Tie VSync directly to refresh rate (as opposed to every other refresh etc):
			m_PresentationParameters.PresentationInterval = PresentInterval.Immediate;//.One;

			//Discard old frames for better performance
			m_PresentationParameters.SwapEffect = SwapEffect.Discard;
		}

		/// <summary>
		/// Initializes a rendering device to display contents in the control passed in.
		/// </summary>
		/// <param name="renderWindow">
		/// This is the control where the stuff rendered to the device will be redirected.
		/// </param>
		public void InitializeFor(D3DGameWindow renderWindow) 
		{
			this.mParentWindow = renderWindow;

			//Initialize the presentation parameters:
			InitializePresentationParameters();

			//Set default windowed mode:
			this.WindowedMode = true;

			//Create the rendering device:
			m_RenderingDevice = new Device(
				0, //Use the default rendering device
				DeviceType.Hardware, //Use hardware rendering
				renderWindow, //Render to the control passed in

				//TODO: What should the behavior flags be?
				CreateFlags.MixedVertexProcessing, //Behavior flags
				m_PresentationParameters); //Presentation parameters

//			m_RenderingDevice.RenderState.AlphaBlendEnable = true;
//			m_RenderingDevice.RenderState.SourceBlend = Blend.SourceAlpha;
//			m_RenderingDevice.RenderState.DestinationBlend = Blend.InvSourceAlpha;
//			m_RenderingDevice.RenderState.CullMode = Cull.None;
//			m_RenderingDevice.RenderState.Lighting = false;
//			m_RenderingDevice.RenderState.ZBufferEnable = false;

			mMaximumTextureSize = new System.Drawing.Size(
				this.RenderingDevice.DeviceCaps.MaxTextureWidth,
				this.RenderingDevice.DeviceCaps.MaxTextureHeight);
		}
	}
}
