using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace Icarus.Renderer
{
	/// <summary>
	/// This is an abstract game window, which can be extended either to use DirectX or GDI for rendering the content.
	/// </summary>
	public abstract class GameWindow : System.Windows.Forms.Form
	{
		#region Window Properties
		/// <summary>
		/// The default size of the game window.
		/// </summary>
		public static Size DefaultWindowSize 
		{
			get { return new Size(800, 600); }
		}

		/// <summary>
		/// Gets or sets the title of the window, which will be used when in windowed mode, and also will be displayed in the task bar when the game is minimized.
		/// </summary>
		public string WindowTitle 
		{
			get { return this.Text; }
			set { this.Text = value; }
		}

		/// <summary>
		/// The size of the window.
		/// </summary>
		private Size m_WindowSize;

		/// <summary>
		/// Gets or sets the size of the window.
		/// </summary>
		public Size WindowSize 
		{
			get { return m_WindowSize; }
			set { m_WindowSize = value; }
		}

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

		/// <summary>
		/// Gets or sets the windowed mode operation.
		/// </summary>
		public bool Windowed 
		{
			get { return m_Windowed; }
			set 
			{
				if (Windowed != value) 
				{
					if (value) 
					{
						//We have gone windowed
						this.goWindowed();
					} 
					else 
					{
						//We have gone fullscreen
						this.goFullscreen();
					}

					m_Windowed = value;
				}
			}
		}

		/// <summary>
		/// Whether or not we would like to cap the frame rate.
		/// </summary>
		private bool m_CapFrameRate = true;

		/// <summary>
		/// Gets or sets whether or not we would like to cap the frame rate.
		/// </summary>
		/// <remarks>
		/// If this is set to false, then we will update the screen as fast as possible. Otherwise, we will update the screen at most as fast as the FrameRate property says. The value is true by default.
		/// </remarks>
		public bool CapFrameRate 
		{
			get { return m_CapFrameRate; }
			set { m_CapFrameRate = value; }
		}

		/// <summary>
		/// How many frames per second should we render if we are capping the frame rate?
		/// </summary>
		private int m_FramesPerSecond;

		/// <summary>
		/// The delay, in milliseconds, between frames.
		/// </summary>
		private TimeSpan m_FrameDelay;

		/// <summary>
		/// Gets or sets how many frames per second we should strive to render.
		/// </summary>
		public int FrameRate 
		{
			get { return m_FramesPerSecond; }
			set 
			{
				m_FramesPerSecond = value;
				m_FrameDelay = new TimeSpan(0, 0, 0, 0, 1000 / m_FramesPerSecond);
			}
		}

		/// <summary>
		/// This will be set to true when we are trying to kill the window, so that the rendering thread has a chance to quit gracefully.
		/// </summary>
		private bool m_TimeToDie = false;

		/// <summary>
		/// The thread that updates the game graphics.
		/// </summary>
		private Thread m_GraphicsUpdateThread;

		#endregion

		#region Constructors
		/// <summary>
		/// Creates a new window with default parameters.
		/// </summary>
		public GameWindow() : 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 GameWindow(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 GameWindow(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 GameWindow(string title, bool windowed, Size initialSize) 
		{ 
			this.WindowTitle = title;
			this.WindowSize = initialSize;
			this.FrameRate = 60;

			//This makes the window auto-scale based on font size.  Since we aren't adding
			//controls we probably don't need to turn it off, but it doesn't hurt.
			this.AutoScale = false;

			//Background colour
			this.BackColor = Color.Black;

			//Center this window
			this.StartPosition = FormStartPosition.CenterScreen;

			//We need to do this in the beginning, because sometimes windowed parameter will be the same as the default value of the parameter, so the call won't be made in the property setting.
			this.m_Windowed = windowed;
			if (windowed) 
			{
				//We have gone windowed
				this.goWindowed();
			} 
			else 
			{
				//We have gone fullscreen
				this.goFullscreen();
			}

			//Set the style thinger:
			this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

			//Perform any other special initializations we need to do:
			this.SpecialInitialize();

			//Start rendering thread:
			m_GraphicsUpdateThread = new Thread(new ThreadStart(RenderFunction));
			m_GraphicsUpdateThread.Start();
//			m_GraphicsUpdateThread.Suspend();
		}
		#endregion

		#region Public Functions

        /// <summary>
        /// Destroy data in use due to Closing Event
        /// </summary>
        /// <param name="e">Arguments from the Event</param>
		protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
		{
			//First, we want to catch the closing event and kill the rendering thread:
			this.m_TimeToDie = true;
			this.m_GraphicsUpdateThread.Join();

			//Now that the rendering thread is dead, can close:
			base.OnClosing (e);
		}

        /// <summary>
        /// Take action due to change in Visibility
        /// </summary>
        /// <param name="e">Arguments from the Event</param>
		protected override void OnVisibleChanged(EventArgs e)
		{
			base.OnVisibleChanged (e);
//			if (this.Visible) 
//			{
//				this.m_GraphicsUpdateThread.Resume();
//			} 
//			else 
//			{
//				this.m_GraphicsUpdateThread.Suspend();
//			}
		}
        
        /// <summary>
        /// Taje action due to Window resize
        /// </summary>
        /// <param name="e">Arguments from the Event</param>
		protected override void OnResize(EventArgs e)
		{
			base.OnResize (e);

//			if (this.m_GraphicsUpdateThread != null)
//			{
//				if (this.ClientSize.IsEmpty) 
//				{
//					this.m_GraphicsUpdateThread.Suspend();
//				} 
//				else 
//				{
//					if (this.m_GraphicsUpdateThread.ThreadState == ThreadState.Suspended)
//					{
//						this.m_GraphicsUpdateThread.Resume();
//					}
//				}
//			}

//			Console.WriteLine("Resize: {0}", e);
//			Console.WriteLine("\tLocation: {0}", this.ClientRectangle.Location);
//			Console.WriteLine("\tSize: {0}; empty - {1}", this.ClientSize, this.ClientSize.IsEmpty);
		}


		#endregion

		#region Private Functions
		/// <summary>
		/// Performs whatever functionality needs to be done to go windowed.
		/// </summary>
		private void goWindowed() 
		{
			//Save original visibility, so we don't accidentally show the window if we don't have to.
			bool originallyVisible = this.Visible;

			//Hide the window, make all changes, then show it.  Just for smoother presentation
			if (originallyVisible) { this.Hide(); }

			//Restore from original window size:
			this.ClientSize = this.WindowSize;

			//We don't want to always be the front-most window in Windowed mode
			this.TopMost = false;

			//Make a pretty window border, and don't allow resizing the window:
			this.FormBorderStyle = FormBorderStyle.Fixed3D;

			//We want to have the maximize / minimize buttons available to the user:
			this.MaximizeBox = true;
			this.MinimizeBox = true;

			//Place the window in the center of the screen
			this.CenterToScreen();

			//If the window was originally shown, show it again:
			if (originallyVisible) { this.Show(); }

			//Do anything the specific implementation requires:
			this.SpecialGoWindowed();
		}

		/// <summary>
		/// Performs whatever functionality needs to be done to go full-screen.
		/// </summary>
		private void goFullscreen() 
		{
			//Save original visibility, so we don't accidentally show the window if we don't have to.
			bool originallyVisible = this.Visible;

			//Hide the window, make all changes, then show it.  Just for smoother presentation
			if (originallyVisible) { this.Hide(); }

			//Save the old client size:
			this.WindowSize = this.ClientSize;

			//Get the rectangle describing the primary display
			Rectangle screenBounds = Screen.PrimaryScreen.Bounds;

			//Set our window to cover the entire display
			this.ClientSize = screenBounds.Size;

			//Remove the title bar and borders and things
			this.FormBorderStyle = FormBorderStyle.None;

			//Appear above all other windows
			this.TopMost = true;

			//Center the window so it covers the whole screen
			this.CenterToScreen();

			//If the window was originally shown, show it again:
			if (originallyVisible) { this.Show(); }

			//Do anything the specific implementation requires:
			this.SpecialGoFullscreen();
		}

		/// <summary>
		/// This is the function run by the 
		/// </summary>
		private void RenderFunction() 
		{
			while (!this.m_TimeToDie)
			{
				if (!this.Visible)
				{
					//We are invisible. I'll just kinda sleep:
					Thread.Sleep(m_FrameDelay);
				}
				else
				{
					if (!this.CapFrameRate)
					{
						//We're not capping frame rate, just update as fast as possible:
						this.UpdateWindow();
					}
					else
					{
						DateTime start = DateTime.Now;

						this.UpdateWindow();

						TimeSpan timeTaken = DateTime.Now - start;
						if (timeTaken.CompareTo(m_FrameDelay) < 0)
						{
							Thread.Sleep(m_FrameDelay - timeTaken);
						}
					}
				}
			}
		}
		#endregion

		#region Abstract Functions - Override Those!
		/// <summary>
		/// This function will be called when all the constructor chains are through, and will finish up the initializations.
		/// </summary>
		protected abstract void SpecialInitialize();

		/// <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 abstract void SpecialGoWindowed();

		/// <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 abstract void SpecialGoFullscreen();

		/// <summary>
		/// This function will be called at every frame.
		/// </summary>
		protected abstract void UpdateWindow();
		#endregion
	}
}
