using System;
using System.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

using Icarus.Renderer.Animation;

namespace Icarus.Renderer
{

	/// <summary>
	/// This is the delegate that can be used to render something.
	/// </summary>
	public delegate void RenderFunction(Icarus.Graphics.Animation.IRenderParameters parameters);

	/// <summary>
	/// This is a DirectX implementation of the game window.
	/// </summary>
	public class D3DGameWindow : GameWindow
	{
		private D3DRenderParameters mRenderParameters;

		#region Constructors
		/// <summary>
		/// Creates a new window with default parameters.
		/// </summary>
		public D3DGameWindow() : this("") { }

		/// <summary>
		/// Creates a new window with the specified title and other parameters - default.
		/// </summary>
		/// <param name="title">The title of the window</param>
		public D3DGameWindow(string title) : this(title, true) { }

		/// <summary>
		/// Creates a new window.
		/// </summary>
		/// <param name="title">The title of the window</param>
		/// <param name="windowed">True if we want to run in windowed mode, and false if we want to run in fullscreen mode</param>
		public D3DGameWindow(string title, bool windowed) : this(title, windowed, GameWindow.DefaultWindowSize) { }

		/// <summary>
		/// Creates a new window.
		/// </summary>
		/// <param name="title">The title of the window</param>
		/// <param name="windowed">True if we want to run in windowed mode, and false if we want to run in fullscreen mode</param>
		/// <param name="initialSize">The initial size of the window</param>
		public D3DGameWindow(string title, bool windowed, Size initialSize) : base(title, windowed, initialSize)
		{
			mRenderParameters = new D3DRenderParameters(new Sprite(Direct3DSettings.Instance.RenderingDevice));
		}
		#endregion

		#region Event Callbacks
		/// <summary>
		/// CancelResize - This is an event handler used to turn off the automatic
		/// resize handling that Managed D3D does behind the scenes
		/// </summary>
		/// <param name="sender">not used</param>
		/// <param name="e">not used</param>
		protected void CancelResize(object sender, System.ComponentModel.CancelEventArgs e) 
		{
			e.Cancel = true;
		}

		/// <summary>
		/// OnDeviceLost - The device has been lost.  Some resources will need
		///   to be freed to allow the device to be Reset.
		/// </summary>
		/// <param name="sender">not used</param>
		/// <param name="e">not used</param>
		protected void OnDeviceLost(object sender, EventArgs e)
		{
			//Nothing much to do for now...
		}
      
		/// <summary>
		/// OnDeviceReset - The device has been successfully Reset.
		///   Settings lost by the Reset should be set here and any resources
		///   that were freed in OnDeviceLost should be reallocated.
		/// </summary>
		/// <param name="sender">not used</param>
		/// <param name="e">not used</param>
		protected void OnDeviceReset(object sender, EventArgs e)
		{
			//Nothing much to do for now...
		}
		#endregion

		#region Game Window Overrides
		/// <summary>
		/// This function will be called when all the constructor chains are through, and will finish up the initializations.
		/// </summary>
		protected override void SpecialInitialize() 
		{
			//Initialize D3D rendering:
			Direct3DSettings.Instance.InitializeFor(this);

			//Turn off automatic resize handling:
			Direct3DSettings.Instance.RenderingDevice.DeviceResizing += new System.ComponentModel.CancelEventHandler(this.CancelResize);

			//Register event handlers for lost and reset devices:
			Direct3DSettings.Instance.RenderingDevice.DeviceLost += new EventHandler(OnDeviceLost);
			Direct3DSettings.Instance.RenderingDevice.DeviceReset += new EventHandler(OnDeviceReset);
		}

		/// <summary>
		/// This function should be overridden in the specific implementation of the window to figure out what special things need to be done when we go windowed.
		/// </summary>
		protected override void SpecialGoWindowed() 
		{
			//Tell the D3D configuration that we have gone windowed:
			Direct3DSettings.Instance.WindowedMode = true;
		}

		/// <summary>
		/// This function should be overridden in the specific implementation of the window to figure out what special things need to be done when we go full-screen.
		/// </summary>
		protected override void SpecialGoFullscreen() 
		{
			//Tell the D3D configuration that we have gone fullscreen:
			Direct3DSettings.Instance.WindowedMode = false;
		}

		/// <summary>
		/// This function will be called at every frame.
		/// </summary>
		protected override void UpdateWindow() 
		{
			int result;

			//Check if the device is cooperating with us. :)
			if (Direct3DSettings.Instance.RenderingDevice.CheckCooperativeLevel(out result))
			{
				try
				{
					this.Render();
				}
				catch(DeviceLostException)
				{
					Direct3DSettings.Instance.RenderingDevice.CheckCooperativeLevel(out result);
				}
				catch(DeviceNotResetException)
				{
					Direct3DSettings.Instance.RenderingDevice.CheckCooperativeLevel(out result);
				}

			}
			
			if (result == (int) ResultCode.DeviceNotReset)
			{
				Direct3DSettings.Instance.ResetDevice();
			}
		}
		#endregion

		#region Render Function
		/// <summary>
		/// The function used to render stuff.
		/// </summary>
		private RenderFunction mRenderer;

		/// <summary>
		/// The function to render stuff.
		/// </summary>
		public RenderFunction RenderCallback
		{
			get { return mRenderer; }
			set { mRenderer = value; }
		}
		#endregion

        /// <summary>
        /// Render the scene to screen
        /// </summary>
		protected virtual void Render()
		{
			Direct3DSettings.Instance.RenderingDevice.Clear(ClearFlags.Target, Color.Black, 0, 0);

			Direct3DSettings.Instance.RenderingDevice.BeginScene();

			mRenderParameters.TargetSprite.Begin(SpriteFlags.DoNotSaveState | SpriteFlags.AlphaBlend);

			this.RenderCallback(mRenderParameters);

			mRenderParameters.TargetSprite.Flush();
			mRenderParameters.TargetSprite.End();

			mRenderParameters.TargetSprite.Transform = Matrix.Identity;

			Direct3DSettings.Instance.RenderingDevice.EndScene();

			Direct3DSettings.Instance.RenderingDevice.Present();
		}
	}
}
