/*
* 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( 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 );

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