// 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.
//
// Copyright (c) 2004 - 2006 Novell, Inc.
//
// Authors:
//	Peter Bartok	pbartok@novell.com
//

// COMPLETE

#undef DebugRunLoop

using Microsoft.Win32;
using System;
using System.Drawing;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
#if NET_2_0
using System.Windows.Forms.VisualStyles;
#endif

namespace System.Windows.Forms {
	public sealed class Application {
		internal class MWFThread {
			#region Fields
			private ApplicationContext	context;
			private bool			messageloop_started;
			private int			thread_id;

			private static Hashtable	threads = new Hashtable();
			#endregion	// Fields

			#region Constructors
			private MWFThread() {
			}
			#endregion	// Constructors

			#region Properties
			public ApplicationContext Context {
				get {
					return context;
				}

				set {
					context = value;
				}
			}

			public bool MessageLoop {
				get {
					return messageloop_started;
				}

				set {
					messageloop_started = value;
				}
			}


			public static int LoopCount {
				get {
					IEnumerator	e;
					int		loops;
					MWFThread	thread;

					e = threads.Values.GetEnumerator();
					loops = 0;

					while (e.MoveNext()) {
						thread = (MWFThread)e.Current;
						if (thread != null && thread.messageloop_started) {
							loops++;
						}
					}

					return loops;
				}
			}

			public static MWFThread Current {
				get {
					MWFThread	thread;

					thread = null;
					lock (threads) {
						thread = (MWFThread)threads[Thread.CurrentThread.GetHashCode()];
						if (thread == null) {
							thread = new MWFThread();
							thread.thread_id = Thread.CurrentThread.GetHashCode();
							threads[thread.thread_id] = thread;
						}
					}

					return thread;
				}
			}
			#endregion	// Properties

			#region Methods
			public void Exit() {
				if (context != null) {
					context.ExitThread();
				}
				context = null;

				if (Application.ThreadExit != null) {
					Application.ThreadExit(null, EventArgs.Empty);
				}

				if (LoopCount == 0) {
					if (Application.ApplicationExit != null) {
						Application.ApplicationExit(null, EventArgs.Empty);
					}
				}
				lock (threads) {
					threads[thread_id] = null;
				}
			}
			#endregion	// Methods
		}

		private static bool			browser_embedded	= false;
		private static InputLanguage		input_language		= InputLanguage.CurrentInputLanguage;
		private static string			safe_caption_format	= "{1} - {0} - {2}";
		private static ArrayList		message_filters		= new ArrayList();
#if NET_2_0
		private static VisualStyleState visual_style_state = VisualStyleState.ClientAndNonClientAreasEnabled;
#endif

		private Application () {
		}

		#region Private Methods
		private static void CloseForms(Thread thread) {
			Control		c;
			IEnumerator	control;
			bool		all;

			#if DebugRunLoop
				Console.WriteLine("   CloseForms({0}) called", thread);
			#endif
			if (thread == null) {
				all = true;
			} else {
				all = false;
			}

			control = Control.controls.GetEnumerator();

			while (control.MoveNext()) {
				c = (Control)control.Current;
				if (c is Form) {
					if (all || (thread == c.creator_thread)) {
						if (c.IsHandleCreated) {
							XplatUI.PostMessage(c.Handle, Msg.WM_CLOSE_INTERNAL, IntPtr.Zero, IntPtr.Zero);
						}
						#if DebugRunLoop
							Console.WriteLine("      Closing form {0}", c);
						#endif
					}
				}
			}

		}
		#endregion	// Private methods

		#region Public Static Properties
		public static bool AllowQuit {
			get {
				return browser_embedded;
			}
		}

		public static string CommonAppDataPath {
			get {
				return Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
			}
		}

		public static RegistryKey CommonAppDataRegistry {
			get {
				RegistryKey	key;

				key = Registry.LocalMachine.OpenSubKey("Software\\" + Application.CompanyName + "\\" + Application.ProductName + "\\" + Application.ProductVersion, true);

				return key;
			}
		}

		public static string CompanyName {
			get {
				AssemblyCompanyAttribute[] attrs = (AssemblyCompanyAttribute[]) Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), true);
				
				if ((attrs != null) && attrs.Length>0) {
					return attrs[0].Company;
				}

				return Assembly.GetEntryAssembly().GetName().Name;
			}
		}

		public static CultureInfo CurrentCulture {
			get {
				return Thread.CurrentThread.CurrentUICulture;
			}

			set {
				
				Thread.CurrentThread.CurrentUICulture=value;
			}
		}

		public static InputLanguage CurrentInputLanguage {
			get {
				return input_language;
			}

			set {
				input_language=value;
			}
		}

		public static string ExecutablePath {
			get {
				return Assembly.GetEntryAssembly().Location;
			}
		}

		public static string LocalUserAppDataPath {
			get {
				return Path.Combine(Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), CompanyName), ProductName), ProductVersion);
			}
		}

		public static bool MessageLoop {
			get {
				return MWFThread.Current.MessageLoop;
			}
		}

		public static string ProductName {
			get {
				AssemblyProductAttribute[] attrs = (AssemblyProductAttribute[]) Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), true);
				
				if ((attrs != null) && attrs.Length>0) {
					return attrs[0].Product;
				}

				return Assembly.GetEntryAssembly().GetName().Name;
			}
		}

		public static string ProductVersion {
			get {
				String version;

				version = Assembly.GetEntryAssembly().GetName().Version.ToString();

				return version;
			}
		}

		public static string SafeTopLevelCaptionFormat {
			get {
				return safe_caption_format;
			}

			set {
				safe_caption_format=value;
			}
		}

		public static string StartupPath {
			get {
				return Path.GetDirectoryName(Application.ExecutablePath);
			}
		}

		public static string UserAppDataPath {
			get {
				return Path.Combine(Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName), ProductName), ProductVersion);
			}
		}

		public static RegistryKey UserAppDataRegistry {
			get {
				RegistryKey	key;

				key = Registry.CurrentUser.OpenSubKey("Software\\" + Application.CompanyName + "\\" + Application.ProductName + "\\" + Application.ProductVersion, true);

				return key;
			}
		}

#if NET_2_0
		public static bool RenderWithVisualStyles {
		      get {
				if (VisualStyleInformation.IsSupportedByOS)
				{
					if (!VisualStyleInformation.IsEnabledByUser)
						return false;
				  
					if (!XplatUI.ThemesEnabled)
						return false;
				  
					if (Application.VisualStyleState == VisualStyleState.ClientAndNonClientAreasEnabled)
						return true;
				  
					if (Application.VisualStyleState == VisualStyleState.ClientAreaEnabled)
				  		return true;
				}
				
				return false;
		      }
		}

		public static VisualStyleState VisualStyleState {
			get { return Application.visual_style_state; }
			set { Application.visual_style_state = value; }
		}
#endif

		#endregion

		#region Public Static Methods
		public static void AddMessageFilter(IMessageFilter value) {
			lock (message_filters) {
				message_filters.Add(value);
			}
		}

		public static void DoEvents() {
			XplatUI.DoEvents();
		}

		public static void EnableVisualStyles() {
			XplatUI.EnableThemes();
		}

#if NET_2_0
		//
		// If true, it uses GDI+, performance reasons were quoted
		//
		static internal bool use_compatible_text_rendering = true;
		
		public static void SetCompatibleTextRenderingDefault (bool defaultValue)
		{
			use_compatible_text_rendering = defaultValue;
		}
#endif

		public static void Exit() {
			CloseForms(null);

			// FIXME - this needs to be fired when they're all closed
			// But CloseForms uses PostMessage, so it gets fired before
			// We need to wait on something...
			if (ApplicationExit != null) {
				ApplicationExit(null, EventArgs.Empty);
			}
		}

		public static void ExitThread() {
			CloseForms(Thread.CurrentThread);
			MWFThread.Current.Exit();
		}

		public static ApartmentState OleRequired() {
			//throw new NotImplementedException("OLE Not supported by this System.Windows.Forms implementation");
			return ApartmentState.Unknown;
		}

		public static void OnThreadException(Exception t) {
			if (Application.ThreadException != null) {
				Application.ThreadException(null, new ThreadExceptionEventArgs(t));
				return;
			}

			if (SystemInformation.UserInteractive) {
				Form form = new ThreadExceptionDialog (t);
				form.ShowDialog ();
			} else {
				Console.WriteLine (t.ToString ());
			}
		}

		public static void RemoveMessageFilter(IMessageFilter filter) {
			lock (message_filters) {
				message_filters.Remove(filter);
			}
		}

		public static void Run() {
			RunLoop(false, new ApplicationContext());
		}

		public static void Run(Form mainForm) {
			RunLoop(false, new ApplicationContext(mainForm));
		}

		public static void Run(ApplicationContext context) {
			RunLoop(false, context);
		}

		internal static void RunLoop(bool Modal, ApplicationContext context) {
			Queue		toplevels;
			IEnumerator	control;
			MSG		msg;
			Object		queue_id;
			MWFThread	thread;


			thread = MWFThread.Current;

			msg = new MSG();

			if (context == null) {
				context = new ApplicationContext();
			}

			thread.Context = context;

			if (context.MainForm != null) {
				context.MainForm.is_modal = Modal;
				context.MainForm.context = context;
				context.MainForm.closing = false;
				context.MainForm.Visible = true;	// Cannot use Show() or scaling gets confused by menus
				// FIXME - do we need this?
				//context.MainForm.PerformLayout();
				context.MainForm.Activate();
			}

			#if DebugRunLoop
				Console.WriteLine("Entering RunLoop(Modal={0}, Form={1})", Modal, context.MainForm != null ? context.MainForm.ToString() : "NULL");
			#endif

			if (Modal) {
				Control c;

				toplevels = new Queue();
				control = Control.controls.GetEnumerator();

				while (control.MoveNext()) {

					c = (Control)control.Current;
					if (c is Form && (c != context.MainForm)) {
						if (c.IsHandleCreated && XplatUI.IsEnabled(c.Handle)) {
							#if DebugRunLoop
								Console.WriteLine("      Disabling form {0}", c);
							#endif
							XplatUI.EnableWindow(c.Handle, false);
							toplevels.Enqueue(c);
						}
					}
				}
				// FIXME - need activate?
				/* make sure the MainForm is enabled */
				XplatUI.EnableWindow (context.MainForm.Handle, true);
				XplatUI.SetModal(context.MainForm.Handle, true);
			} else {
				toplevels = null;
			}

			queue_id = XplatUI.StartLoop(Thread.CurrentThread);
			thread.MessageLoop = true;

			while (XplatUI.GetMessage(queue_id, ref msg, IntPtr.Zero, 0, 0)) {
				lock (message_filters) {
					if (message_filters.Count > 0) {
						Message	m;
						bool	drop;

						drop = false;
						m = Message.Create(msg.hwnd, (int)msg.message, msg.wParam, msg.lParam);
						for (int i = 0; i < message_filters.Count; i++) {
							if (((IMessageFilter)message_filters[i]).PreFilterMessage(ref m)) {
								// we're dropping the message
								drop = true;
								break;
							}
						}
						if (drop) {
							continue;
						}
					}
				}

				switch((Msg)msg.message) {
					case Msg.WM_KEYDOWN:
					case Msg.WM_SYSKEYDOWN:
					case Msg.WM_CHAR:
					case Msg.WM_SYSCHAR:
					case Msg.WM_KEYUP:
					case Msg.WM_SYSKEYUP: {
						Message m;
						Control c;

						m = Message.Create(msg.hwnd, (int)msg.message, msg.wParam, msg.lParam);
						c = Control.FromHandle(msg.hwnd);
						if ((c != null) && !c.PreProcessMessage(ref m)) {
							goto default;
						}
						break;
					}
					default: {
						XplatUI.TranslateMessage(ref msg);
						XplatUI.DispatchMessage(ref msg);
						break;
					}
				}

				// Handle exit, Form might have received WM_CLOSE and set 'closing' in response
				if ((context.MainForm != null) && (context.MainForm.closing || (Modal && !context.MainForm.Visible))) {
					if (!Modal) {
						XplatUI.PostQuitMessage(0);
					} else {
						break;
					}
				}
			}
			#if DebugRunLoop
				Console.WriteLine("   RunLoop loop left");
			#endif

			thread.MessageLoop = false;
			XplatUI.EndLoop(Thread.CurrentThread);

			if (Modal) {
				Control c;

				context.MainForm.Hide();
				context.MainForm.is_modal = false;

				while (toplevels.Count>0) {
					#if DebugRunLoop
						Console.WriteLine("      Re-Enabling form form {0}", toplevels.Peek());
					#endif
					c = (Control)toplevels.Dequeue();
					if (c.IsHandleCreated) {
						XplatUI.EnableWindow(c.window.Handle, true);
					}
				}
				#if DebugRunLoop
					Console.WriteLine("   Done with the re-enable");
				#endif
				if (context.MainForm.IsHandleCreated) {
					XplatUI.SetModal(context.MainForm.Handle, false);
				}
				#if DebugRunLoop
					Console.WriteLine("   Done with the SetModal");
				#endif
			}

			#if DebugRunLoop
				Console.WriteLine("Leaving RunLoop(Modal={0}, Form={1})", Modal, context.MainForm != null ? context.MainForm.ToString() : "NULL");
			#endif
			if (context.MainForm != null) {
				context.MainForm.context = null;
			}

			if (!Modal) {
				thread.Exit();
			}
		}

		#endregion	// Public Static Methods

		#region Events
		public static event EventHandler	ApplicationExit;

		public static event EventHandler	Idle {
			add {
				XplatUI.Idle += value;
			}
			remove {
				XplatUI.Idle -= value;
			}
		}

		public static event EventHandler	ThreadExit;
		public static event ThreadExceptionEventHandler	ThreadException;
		#endregion	// Events
	}
}
