/*
* Copyright (c) 2007-2009 SlimDX Group
* 
* 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.ComponentModel;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using SampleFramework.Properties;

namespace SampleFramework
{
	/// <summary>
	/// Implements a specialized window for games and rendering.
	/// </summary>
	public class GameWindow : Form
	{
		const int DefaultWidth = 800;
		const int DefaultHeight = 600;
		const string DefaultTitle = "Game";

		Size cachedSize;
		bool minimized;
		bool maximized;
		bool inSizeMove;

		/// <summary>
		/// Occurs when the application is suspended.
		/// </summary>
		public event EventHandler Suspend;

		/// <summary>
		/// Occurs when the application is resumed.
		/// </summary>
		public event EventHandler Resume;

		/// <summary>
		/// Occurs when the user resizes the window.
		/// </summary>
		public event EventHandler UserResized;

		/// <summary>
		/// Occurs when the screen on which the window resides is changed.
		/// </summary>
		public event EventHandler ScreenChanged;

		/// <summary>
		/// Occurs when the application is activated.
		/// </summary>
		public event EventHandler ApplicationActivated;

		/// <summary>
		/// Occurs when the application is deactivated.
		/// </summary>
		public event EventHandler ApplicationDeactivated;

		/// <summary>
		/// Occurs when the system is suspended.
		/// </summary>
		public event EventHandler SystemSuspend;

		/// <summary>
		/// Occurs when the system is resumed.
		/// </summary>
		public event EventHandler SystemResume;

		/// <summary>
		/// Occurs when a screen saver is about to be activated.
		/// </summary>
		public event CancelEventHandler Screensaver;

		/// <summary>
		/// Gets a value indicating whether this instance is minimized.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is minimized; otherwise, <c>false</c>.
		/// </value>
		public bool IsMinimized
		{
			get { return minimized; }
		}

		/// <summary>
		/// Gets a value indicating whether this instance is maximized.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is maximized; otherwise, <c>false</c>.
		/// </value>
		public bool IsMaximized
		{
			get { return maximized; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether System Menu is enabled.
		/// </summary>
		/// <value><c>true</c> if System Menu is enabled; otherwise, <c>false</c>.</value>
		public bool EnableSystemMenu			// #28200 2012.5.1 yyagi
		{
			get;
			set;
		}
		public string strMessage				// #28821 2014.1.23 yyagi
		{
			get;
			private set;
		}
		public bool IsReceivedMessage
		{
			get;
			set;
		}

		private Screen m_Screen;
		/// <summary>
		/// Gets the screen on which the window resides.
		/// </summary>
		/// <value>The screen.</value>
		public Screen Screen
		{
			get { return m_Screen; }
			private set { m_Screen = value; }
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GameWindow"/> class.
		/// </summary>
		public GameWindow()
		{
			MinimumSize = new Size(200, 200);

			Screen = ScreenFromHandle(Handle);

			//Icon = GetDefaultIcon();
			Text = GetDefaultTitle();
			strMessage = "";
		}

		/// <summary>
		/// Raises the <see cref="E:Suspend"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnSuspend(EventArgs e)
		{
			if (Suspend != null)
				Suspend(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:Resume"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnResume(EventArgs e)
		{
			if (Resume != null)
				Resume(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:UserResized"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnUserResized(EventArgs e)
		{
			if (UserResized != null)
				UserResized(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:ScreenChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnScreenChanged(EventArgs e)
		{
			if (ScreenChanged != null)
				ScreenChanged(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:ApplicationActivated"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnApplicationActivated(EventArgs e)
		{
			if (ApplicationActivated != null)
				ApplicationActivated(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:ApplicationDeactivated"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnApplicationDeactivated(EventArgs e)
		{
			if (ApplicationDeactivated != null)
				ApplicationDeactivated(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:SystemSuspend"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnSystemSuspend(EventArgs e)
		{
			if (SystemSuspend != null)
				SystemSuspend(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:SystemResume"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		protected virtual void OnSystemResume(EventArgs e)
		{
			if (SystemResume != null)
				SystemResume(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:Screensaver"/> event.
		/// </summary>
		/// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
		protected virtual void OnScreensaver(CancelEventArgs e)
		{
			if (Screensaver != null)
				Screensaver(this, e);
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Form.Load"/> event.
		/// </summary>
		/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			cachedSize = Size;
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Form.ResizeBegin"/> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.EventArgs"/> that contains the event data.</param>
		protected override void OnResizeBegin(EventArgs e)
		{
			base.OnResizeBegin(e);

			// suspend any processing until we are done being minimized
			inSizeMove = true;
			cachedSize = Size;
			OnSuspend(EventArgs.Empty);
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Form.ResizeEnd"/> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.EventArgs"/> that contains the event data.</param>
		protected override void OnResizeEnd(EventArgs e)
		{
			base.OnResizeEnd(e);

			// check for screen and size changes
			OnUserResized(EventArgs.Empty);
			UpdateScreen();
			inSizeMove = false;

			// resume application processing
			OnResume(EventArgs.Empty);
		}


		#region #23510 2010.11.14 yyagi add: 縦横比固定でのウインドウサイズ変更 定数定義 from http://www.vcskicks.com/maintain-aspect-ratio.php
		//double so division keeps decimal points
		const double widthRatio = SampleFramework.GameWindowSize.Width;
		const double heightRatio = SampleFramework.GameWindowSize.Height;
		const int WM_SIZING = 0x214;
		const int WMSZ_LEFT = 1;
		const int WMSZ_RIGHT = 2;
		const int WMSZ_TOP = 3;
		const int WMSZ_TOPLEFT = 4;
		const int WMSZ_TOPRIGHT = 5;
		const int WMSZ_BOTTOM = 6;
		const int WMSZ_BOTTOMLEFT = 7;
		const int WMSZ_BOTTOMRIGHT = 8;

		public struct RECT
		{
			public int Left;
			public int Top;
			public int Right;
			public int Bottom;
		}
		#endregion



		/// <summary>
		/// Handles raw window messages.
		/// </summary>
		/// <param name="m">The raw message.</param>
		[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
		protected override void WndProc(ref Message m)
		{
			if (m.Msg == WindowConstants.WM_SIZE)
			{
				if (m.WParam == WindowConstants.SIZE_MINIMIZED)
				{
					minimized = true;
					maximized = false;

					OnSuspend(EventArgs.Empty);
				}
				else
				{
					Rectangle client = NativeMethods.GetClientRectangle(m.HWnd);
					if (client.Height == 0)
					{
						// rapidly clicking the task bar to minimize and restore a window
						// can cause a WM_SIZE message with SIZE_RESTORED when 
						// the window has actually become minimized due to rapid change
						// so just ignore this message
					}
					else if (m.WParam == WindowConstants.SIZE_MAXIMIZED)
					{
						if (minimized)
							OnResume(EventArgs.Empty);

						minimized = false;
						maximized = true;

						OnUserResized(EventArgs.Empty);
						UpdateScreen();
					}
					else if (m.WParam == WindowConstants.SIZE_RESTORED)
					{
						if (minimized)
							OnResume(EventArgs.Empty);

						minimized = false;
						maximized = false;

						if (!inSizeMove && Size != cachedSize)
						{
							OnUserResized(EventArgs.Empty);
							UpdateScreen();
							cachedSize = Size;
						}
					}
				}
			}
			else if (m.Msg == WindowConstants.WM_ACTIVATEAPP)
			{
				if (m.WParam != IntPtr.Zero)
					OnApplicationActivated(EventArgs.Empty);
				else
					OnApplicationDeactivated(EventArgs.Empty);
			}
			else if (m.Msg == WindowConstants.WM_POWERBROADCAST)
			{
				if (m.WParam == WindowConstants.PBT_APMQUERYSUSPEND)
				{
					OnSystemSuspend(EventArgs.Empty);
					m.Result = (IntPtr)1;
					return;
				}
				else if (m.WParam == WindowConstants.PBT_APMRESUMESUSPEND)
				{
					OnSystemResume(EventArgs.Empty);
					m.Result = (IntPtr)1;
					return;
				}
			}
			else if (m.Msg == WindowConstants.WM_SYSCOMMAND)
			{
				long wparam = m.WParam.ToInt64() & 0xFFF0;
				if (wparam == WindowConstants.SC_MONITORPOWER || wparam == WindowConstants.SC_SCREENSAVE)
				{
					CancelEventArgs e = new CancelEventArgs();
					OnScreensaver(e);
					if (e.Cancel)
					{
						m.Result = IntPtr.Zero;
						return;
					}
				}
				#region #28200 2012.5.1 yyagi: Disable system menu
				if ((m.WParam.ToInt32() & 0xFFFF) == 0xF100 && !EnableSystemMenu)	// SC_KEYMENU
				{
					m.Result = IntPtr.Zero;
					return;
				}
				#endregion
				#region #23510 2010.11.13 yyagi: reset to 640x480
				if ((m.WParam.ToInt32() & 0xFFFF) == MENU_VIEW)
				{
					base.ClientSize = new Size(SampleFramework.GameWindowSize.Width, SampleFramework.GameWindowSize.Height);
					this.OnResizeEnd(new EventArgs());		// #23510 2010.11.20 yyagi: to set window size to Config.ini
				}
				else
					if ((m.WParam.ToInt32() & 0xFFFF) == MENU_VIEW - 1)
					{
						base.ClientSize = new Size(1280, 720);
						this.OnResizeEnd(new EventArgs());		// #23510 2010.11.20 yyagi: to set window size to Config.ini
					}
					else
						if ((m.WParam.ToInt32() & 0xFFFF) == MENU_VIEW - 2)
						{
							base.ClientSize = new Size(896, 504);
							this.OnResizeEnd(new EventArgs());		// #23510 2010.11.20 yyagi: to set window size to Config.ini
						}
						else
							if ((m.WParam.ToInt32() & 0xFFFF) == MENU_VIEW - 3)
							{
								base.ClientSize = new Size(640, 480);
								this.OnResizeEnd(new EventArgs());		// #23510 2010.11.20 yyagi: to set window size to Config.ini
							}
				#endregion
			}
			#region #28821 2014.1.23 yyagi (WM_COPYDATA)
			else if (m.Msg == WindowConstants.WM_COPYDATA)
			{
				//Trace.WriteLine( "FDK;msg received" );
				COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
				strMessage = Marshal.PtrToStringUni(cds.lpData);
				IsReceivedMessage = true;
				//Trace.WriteLine( "FDK;msg=" + strMessage + ", len=" + strMessage.Length + ", truelen=" + cds.cbData );
			}
			#endregion
			#region #23510 2010.11.16 yyagi add: 縦横比固定でのウインドウサイズ変更 from http://d.hatena.ne.jp/iselix/20080917/1221666614 http://hp.vector.co.jp/authors/VA016117/sizing.html
			else if (m.Msg == WM_SIZING)
			{
				RECT rc = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
				int w = rc.Right - rc.Left - (Size.Width - ClientSize.Width);
				int h = rc.Bottom - rc.Top - (Size.Height - ClientSize.Height);
				int dw = (int)(h * widthRatio / heightRatio + 0.5) - w;
				int dh = (int)(w / (widthRatio / heightRatio) + 0.5) - h;

				switch (m.WParam.ToInt32())
				{
					case WMSZ_LEFT:
					case WMSZ_RIGHT:
						rc.Bottom += dh;
						break;
					case WMSZ_TOP:
					case WMSZ_BOTTOM:
						rc.Right += dw;
						break;
					case WMSZ_BOTTOMRIGHT:
						if (dw > 0)
						{
							rc.Right += dw;
						}
						else
						{
							rc.Bottom += dh;
						}
						break;
					case WMSZ_TOPLEFT:
						if (dw > 0)
						{
							rc.Left -= dw;
						}
						else
						{
							rc.Top -= dh;
						}
						break;
					case WMSZ_TOPRIGHT:
						if (dw > 0)
						{
							rc.Right += dw;
						}
						else
						{
							rc.Top -= dh;
						}
						break;
					case WMSZ_BOTTOMLEFT:
						if (dw > 0)
						{
							rc.Left -= dw;
						}
						else
						{
							rc.Bottom += dh;
						}
						break;
					case 9:		// #32383 2013.11.2 yyagi; exitting maximized window by using Aero snap
						break;
					default:
						throw new ArgumentOutOfRangeException("value", "Illegal WM_SIZING value.");
				}
				Marshal.StructureToPtr(rc, m.LParam, true);
			}
			#endregion


			base.WndProc(ref m);
		}

		void UpdateScreen()
		{
			Screen current = Screen.FromHandle(Handle);
			if (Screen == null || Screen.DeviceName != current.DeviceName)
			{
				Screen = current;
				if (Screen != null)
					OnScreenChanged(EventArgs.Empty);
			}
		}

		static Screen ScreenFromHandle(IntPtr windowHandle)
		{
			Rectangle rectangle = NativeMethods.GetWindowRectangle(windowHandle);

			Screen bestScreen = null;
			int mostArea = 0;
			foreach (Screen screen in Screen.AllScreens)
			{
				Rectangle r = Rectangle.Intersect(rectangle, screen.Bounds);
				int area = r.Width * r.Height;

				if (area > mostArea)
				{
					mostArea = area;
					bestScreen = screen;
				}
			}

			if (bestScreen == null)
				bestScreen = Screen.PrimaryScreen;
			return bestScreen;
		}

		static string GetAssemblyTitle(Assembly assembly)
		{
			if (assembly == null)
				return null;

			AssemblyTitleAttribute[] customAttributes = (AssemblyTitleAttribute[])assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), true);
			if (customAttributes != null && customAttributes.Length > 0)
				return customAttributes[0].Title;

			return null;
		}

		static string GetDefaultTitle()
		{
			string assemblyTitle = GetAssemblyTitle(Assembly.GetEntryAssembly());
			if (!string.IsNullOrEmpty(assemblyTitle))
				return assemblyTitle;

			try
			{
				Uri uri = new Uri(Application.ExecutablePath);
				return Path.GetFileNameWithoutExtension(uri.LocalPath);
			}
			catch (ArgumentNullException)
			{
				// swallow the exception
			}
			catch (UriFormatException)
			{
				// swallow the exception
			}

			return DefaultTitle;
		}

		static Icon GetDefaultIcon()
		{
			return (Icon)Resources.sdx_icon_black.Clone();
		}

		#region システムメニューに"1920x1080", "1280x720", "640x480" を追加 #23510 2010.11.13 yyagi add: to set "1920x1020","1280x720","640x480" menu in systemmenu. See also http://cs2ch.blog123.fc2.com/blog-entry-80.html
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		private struct MENUITEMINFO
		{
			public uint cbSize;
			public uint fMask;
			public uint fType;
			public uint fState;
			public uint wID;
			public IntPtr hSubMenu;
			public IntPtr hbmpChecked;
			public IntPtr hbmpUnchecked;
			public IntPtr dwItemData;
			public string dwTypeData;
			public uint cch;
			public IntPtr hbmpItem;
		}

		[DllImport("user32", ExactSpelling = true)]
		private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
		[DllImport("user32", CharSet = CharSet.Auto)]
		private static extern bool InsertMenuItem(IntPtr hMenu, uint uItem, bool fByPosition, ref MENUITEMINFO lpmii);

		private const uint MENU_VIEW = 0x9999;
		private const uint MFT_SEPARATOR = 0x00000800;
		private const uint MIIM_FTYPE = 0x00000100;
		private const uint MIIM_STRING = 0x00000040;
		private const uint MIIM_ID = 0x00000002;

		protected override void OnCreateControl()
		{
			base.OnCreateControl();

			//システムメニューのハンドル取得   
			IntPtr hSysMenu = GetSystemMenu(this.Handle, false);

			//セパレーターの挿入   
			MENUITEMINFO item1 = new MENUITEMINFO();
			item1.cbSize = (uint)Marshal.SizeOf(item1);
			item1.fMask = MIIM_FTYPE;
			item1.fType = MFT_SEPARATOR;
			InsertMenuItem(hSysMenu, 5, true, ref item1);

			//メニュー項目(1920x1080)の挿入   
			MENUITEMINFO item2 = new MENUITEMINFO();
			item2.cbSize = (uint)Marshal.SizeOf(item2);
			item2.fMask = MIIM_STRING | MIIM_ID;
			item2.wID = MENU_VIEW;
			item2.dwTypeData = "&" + SampleFramework.GameWindowSize.Width.ToString() + "x" + SampleFramework.GameWindowSize.Height.ToString();
			InsertMenuItem(hSysMenu, 6, true, ref item2);

			//メニュー項目(1280x720)の挿入   
			MENUITEMINFO item3 = new MENUITEMINFO();
			item3.cbSize = (uint)Marshal.SizeOf(item3);
			item3.fMask = MIIM_STRING | MIIM_ID;
			item3.wID = MENU_VIEW - 1;
			item3.dwTypeData = "&1280x720";
			InsertMenuItem(hSysMenu, 6, true, ref item3);

			//メニュー項目(896x504)の挿入   
			MENUITEMINFO item4 = new MENUITEMINFO();
			item4.cbSize = (uint)Marshal.SizeOf(item4);
			item4.fMask = MIIM_STRING | MIIM_ID;
			item4.wID = MENU_VIEW - 2;
			item4.dwTypeData = "&896x504";
			InsertMenuItem(hSysMenu, 6, true, ref item4);

			//メニュー項目(640x480)の挿入   
			MENUITEMINFO item5 = new MENUITEMINFO();
			item5.cbSize = (uint)Marshal.SizeOf(item4);
			item5.fMask = MIIM_STRING | MIIM_ID;
			item5.wID = MENU_VIEW - 3;
			item5.dwTypeData = "&640x480";
			InsertMenuItem(hSysMenu, 6, true, ref item5);
		}
		#endregion

		private void InitializeComponent()
		{
			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GameWindow));
			this.SuspendLayout();
			// 
			// GameWindow
			// 
			resources.ApplyResources(this, "$this");
			this.Name = "GameWindow";
			this.ResumeLayout(false);

		}
	}
}
