using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics.CodeAnalysis;

namespace WeifenLuo.WinFormsUI.Docking
{
	internal interface IContentFocusManager
	{
		void Activate(IDockContent content);
		void GiveUpFocus(IDockContent content);
		void AddToList(IDockContent content);
		void RemoveFromList(IDockContent content);
	}

	partial class DockPanel
	{
		private interface IFocusManager
		{
			void SuspendFocusTracking();
			void ResumeFocusTracking();
			bool IsFocusTrackingSuspended { get; }
			IDockContent ActiveContent { get; }
			DockPane ActivePane { get; }
			IDockContent ActiveDocument { get; }
			DockPane ActiveDocumentPane { get; }
		}

		private class FocusManagerImpl : Component, IContentFocusManager, IFocusManager
		{
			private class HookEventArgs : EventArgs
			{
				[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
				public int HookCode;
				[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
				public IntPtr wParam;
				public IntPtr lParam;
			}

			private class LocalWindowsHook : IDisposable
			{
				// Internal properties
				private IntPtr m_hHook = IntPtr.Zero;
				private NativeMethods.HookProc m_filterFunc = null;
				private Win32.HookType m_hookType;

				// Event delegate
				public delegate void HookEventHandler(object sender, HookEventArgs e);

				// Event: HookInvoked 
				public event HookEventHandler HookInvoked;
				protected void OnHookInvoked(HookEventArgs e)
				{
					if (HookInvoked != null)
						HookInvoked(this, e);
				}

				public LocalWindowsHook(Win32.HookType hook)
				{
					m_hookType = hook;
					m_filterFunc = new NativeMethods.HookProc(this.CoreHookProc);
				}

				// Default filter function
				public IntPtr CoreHookProc(int code, IntPtr wParam, IntPtr lParam)
				{
					if (code < 0)
						return NativeMethods.CallNextHookEx(m_hHook, code, wParam, lParam);

					// Let clients determine what to do
					HookEventArgs e = new HookEventArgs();
					e.HookCode = code;
					e.wParam = wParam;
					e.lParam = lParam;
					OnHookInvoked(e);

					// Yield to the next hook in the chain
					return NativeMethods.CallNextHookEx(m_hHook, code, wParam, lParam);
				}

				// Install the hook
				public void Install()
				{
					if (m_hHook != IntPtr.Zero)
						Uninstall();

					int threadId = NativeMethods.GetCurrentThreadId();
					m_hHook = NativeMethods.SetWindowsHookEx(m_hookType, m_filterFunc, IntPtr.Zero, threadId);
				}

				// Uninstall the hook
				public void Uninstall()
				{
					if (m_hHook != IntPtr.Zero)
					{
						NativeMethods.UnhookWindowsHookEx(m_hHook);
						m_hHook = IntPtr.Zero;
					}
				}

				~LocalWindowsHook()
				{
					Dispose(false);
				}

				public void Dispose()
				{
					Dispose(true);
					GC.SuppressFinalize(this);
				}

				protected virtual void Dispose(bool disposing)
				{
					Uninstall();
				}
			}

			private LocalWindowsHook m_localWindowsHook;
			private LocalWindowsHook.HookEventHandler m_hookEventHandler;

			public FocusManagerImpl(DockPanel dockPanel)
			{
				m_dockPanel = dockPanel;
				m_localWindowsHook = new LocalWindowsHook(Win32.HookType.WH_CALLWNDPROCRET);
				m_hookEventHandler = new LocalWindowsHook.HookEventHandler(HookEventHandler);
				m_localWindowsHook.HookInvoked += m_hookEventHandler;
				m_localWindowsHook.Install();
			}

			private DockPanel m_dockPanel;
			public DockPanel DockPanel
			{
				get { return m_dockPanel; }
			}

			private bool m_disposed = false;
			protected override void Dispose(bool disposing)
			{
				lock (this)
				{
					if (!m_disposed && disposing)
					{
						m_localWindowsHook.Dispose();
						m_disposed = true;
					}

					base.Dispose(disposing);
				}
			}

			private IDockContent m_contentActivating = null;
			private IDockContent ContentActivating
			{
				get { return m_contentActivating; }
				set { m_contentActivating = value; }
			}

			public void Activate(IDockContent content)
			{
				if (IsFocusTrackingSuspended)
				{
					ContentActivating = content;
					return;
				}

				if (content == null)
					return;
				DockContentHandler handler = content.DockHandler;
				if (handler.Form.IsDisposed)
					return; // Should not reach here, but better than throwing an exception
				if (ContentContains(content, handler.ActiveWindowHandle))
					NativeMethods.SetFocus(handler.ActiveWindowHandle);
				if (!handler.Form.ContainsFocus)
				{
					if (!handler.Form.SelectNextControl(handler.Form.ActiveControl, true, true, true, true))
						// Since DockContent Form is not selectalbe, use Win32 SetFocus instead
						NativeMethods.SetFocus(handler.Form.Handle);
				}
			}

			private List<IDockContent> m_listContent = new List<IDockContent>();
			private List<IDockContent> ListContent
			{
				get { return m_listContent; }
			}
			public void AddToList(IDockContent content)
			{
				if (ListContent.Contains(content) || IsInActiveList(content))
					return;

				ListContent.Add(content);
			}

			public void RemoveFromList(IDockContent content)
			{
				if (IsInActiveList(content))
					RemoveFromActiveList(content);
				if (ListContent.Contains(content))
					ListContent.Remove(content);
			}

			private IDockContent m_lastActiveContent = null;
			private IDockContent LastActiveContent
			{
				get { return m_lastActiveContent; }
				set { m_lastActiveContent = value; }
			}

			private bool IsInActiveList(IDockContent content)
			{
				return !(content.DockHandler.NextActive == null && LastActiveContent != content);
			}

			private void AddLastToActiveList(IDockContent content)
			{
				IDockContent last = LastActiveContent;
				if (last == content)
					return;

				DockContentHandler handler = content.DockHandler;

				if (IsInActiveList(content))
					RemoveFromActiveList(content);

				handler.PreviousActive = last;
				handler.NextActive = null;
				LastActiveContent = content;
				if (last != null)
					last.DockHandler.NextActive = LastActiveContent;
			}

			private void RemoveFromActiveList(IDockContent content)
			{
				if (LastActiveContent == content)
					LastActiveContent = content.DockHandler.PreviousActive;

				IDockContent prev = content.DockHandler.PreviousActive;
				IDockContent next = content.DockHandler.NextActive;
				if (prev != null)
					prev.DockHandler.NextActive = next;
				if (next != null)
					next.DockHandler.PreviousActive = prev;

				content.DockHandler.PreviousActive = null;
				content.DockHandler.NextActive = null;
			}

			public void GiveUpFocus(IDockContent content)
			{
				DockContentHandler handler = content.DockHandler;
				if (!handler.Form.ContainsFocus)
					return;

				if (IsFocusTrackingSuspended)
					DockPanel.DummyControl.Focus();

				if (LastActiveContent == content)
				{
					IDockContent prev = handler.PreviousActive;
					if (prev != null)
						Activate(prev);
					else if (ListContent.Count > 0)
						Activate(ListContent[ListContent.Count - 1]);
				}
				else if (LastActiveContent != null)
					Activate(LastActiveContent);
				else if (ListContent.Count > 0)
					Activate(ListContent[ListContent.Count - 1]);
			}

			private static bool ContentContains(IDockContent content, IntPtr hWnd)
			{
				Control control = Control.FromChildHandle(hWnd);
				for (Control parent = control; parent != null; parent = parent.Parent)
					if (parent == content.DockHandler.Form)
						return true;

				return false;
			}

			private int m_countSuspendFocusTracking = 0;
			public void SuspendFocusTracking()
			{
				m_countSuspendFocusTracking++;
				m_localWindowsHook.HookInvoked -= m_hookEventHandler;
			}

			public void ResumeFocusTracking()
			{
				if (m_countSuspendFocusTracking > 0)
					m_countSuspendFocusTracking--;

				if (m_countSuspendFocusTracking == 0)
				{
					if (ContentActivating != null)
					{
						Activate(ContentActivating);
						ContentActivating = null;
					}
					m_localWindowsHook.HookInvoked += m_hookEventHandler;
					if (!InRefreshActiveWindow)
						RefreshActiveWindow();
				}
			}

			public bool IsFocusTrackingSuspended
			{
				get { return m_countSuspendFocusTracking != 0; }
			}

			// Windows hook event handler
			private void HookEventHandler(object sender, HookEventArgs e)
			{
				Win32.Msgs msg = (Win32.Msgs)Marshal.ReadInt32(e.lParam, IntPtr.Size * 3);

				if (msg == Win32.Msgs.WM_KILLFOCUS)
				{
					IntPtr wParam = Marshal.ReadIntPtr(e.lParam, IntPtr.Size * 2);
					DockPane pane = GetPaneFromHandle(wParam);
					if (pane == null)
						RefreshActiveWindow();
				}
				else if (msg == Win32.Msgs.WM_SETFOCUS)
					RefreshActiveWindow();
			}

			private DockPane GetPaneFromHandle(IntPtr hWnd)
			{
				Control control = Control.FromChildHandle(hWnd);

				IDockContent content = null;
				DockPane pane = null;
				for (; control != null; control = control.Parent)
				{
					content = DockPanel.Contents.Find(control);
					if (content != null)
						content.DockHandler.ActiveWindowHandle = hWnd;

					if (content != null && content.DockHandler.DockPanel == DockPanel)
						return content.DockHandler.Pane;

					pane = control as DockPane;
					if (pane != null && pane.DockPanel == DockPanel)
						break;
				}

				return pane;
			}

			private bool m_inRefreshActiveWindow = false;
			private bool InRefreshActiveWindow
			{
				get { return m_inRefreshActiveWindow; }
			}

			private void RefreshActiveWindow()
			{
				SuspendFocusTracking();
				m_inRefreshActiveWindow = true;

				DockPane oldActivePane = ActivePane;
				IDockContent oldActiveContent = ActiveContent;
				IDockContent oldActiveDocument = ActiveDocument;

				SetActivePane();
				SetActiveContent();
				SetActiveDocumentPane();
				SetActiveDocument();
				DockPanel.AutoHideWindow.RefreshActivePane();

				ResumeFocusTracking();
				m_inRefreshActiveWindow = false;

				if (oldActiveContent != ActiveContent)
					DockPanel.OnActiveContentChanged(EventArgs.Empty);
				if (oldActiveDocument != ActiveDocument)
					DockPanel.OnActiveDocumentChanged(EventArgs.Empty);
				if (oldActivePane != ActivePane)
					DockPanel.OnActivePaneChanged(EventArgs.Empty);
			}

			private DockPane m_activePane = null;
			public DockPane ActivePane
			{
				get { return m_activePane; }
			}

			private void SetActivePane()
			{
				DockPane value = GetPaneFromHandle(NativeMethods.GetFocus());
				if (m_activePane == value)
					return;

				if (m_activePane != null)
					m_activePane.SetIsActivated(false);

				m_activePane = value;

				if (m_activePane != null)
					m_activePane.SetIsActivated(true);
			}

			private IDockContent m_activeContent = null;
			public IDockContent ActiveContent
			{
				get { return m_activeContent; }
			}

			internal void SetActiveContent()
			{
				IDockContent value = ActivePane == null ? null : ActivePane.ActiveContent;

				if (m_activeContent == value)
					return;

				if (m_activeContent != null)
					m_activeContent.DockHandler.IsActivated = false;

				m_activeContent = value;

				if (m_activeContent != null)
				{
					m_activeContent.DockHandler.IsActivated = true;
					if (!DockHelper.IsDockStateAutoHide((m_activeContent.DockHandler.DockState)))
						AddLastToActiveList(m_activeContent);
				}
			}

			private DockPane m_activeDocumentPane = null;
			public DockPane ActiveDocumentPane
			{
				get { return m_activeDocumentPane; }
			}

			private void SetActiveDocumentPane()
			{
				DockPane value = null;

				if (ActivePane != null && ActivePane.DockState == DockState.Document)
					value = ActivePane;

				if (value == null && DockPanel.DockWindows != null)
				{
					if (ActiveDocumentPane == null)
						value = DockPanel.DockWindows[DockState.Document].DefaultPane;
					else if (ActiveDocumentPane.DockPanel != DockPanel || ActiveDocumentPane.DockState != DockState.Document)
						value = DockPanel.DockWindows[DockState.Document].DefaultPane;
					else
						value = ActiveDocumentPane;
				}

				if (m_activeDocumentPane == value)
					return;

				if (m_activeDocumentPane != null)
					m_activeDocumentPane.SetIsActiveDocumentPane(false);

				m_activeDocumentPane = value;

				if (m_activeDocumentPane != null)
					m_activeDocumentPane.SetIsActiveDocumentPane(true);
			}

			private IDockContent m_activeDocument = null;
			public IDockContent ActiveDocument
			{
				get { return m_activeDocument; }
			}

			private void SetActiveDocument()
			{
				IDockContent value = ActiveDocumentPane == null ? null : ActiveDocumentPane.ActiveContent;

				if (m_activeDocument == value)
					return;

				m_activeDocument = value;
			}
		}

		private IFocusManager FocusManager
		{
			get { return m_focusManager; }
		}

		internal IContentFocusManager ContentFocusManager
		{
			get { return m_focusManager; }
		}

		internal void SaveFocus()
		{
			DummyControl.Focus();
		}

		[Browsable(false)]
		public IDockContent ActiveContent
		{
			get { return FocusManager.ActiveContent; }
		}

		[Browsable(false)]
		public DockPane ActivePane
		{
			get { return FocusManager.ActivePane; }
		}

		[Browsable(false)]
		public IDockContent ActiveDocument
		{
			get { return FocusManager.ActiveDocument; }
		}

		[Browsable(false)]
		public DockPane ActiveDocumentPane
		{
			get { return FocusManager.ActiveDocumentPane; }
		}

		private static readonly object ActiveDocumentChangedEvent = new object();
		[LocalizedCategory("Category_PropertyChanged")]
		[LocalizedDescription("DockPanel_ActiveDocumentChanged_Description")]
		public event EventHandler ActiveDocumentChanged
		{
			add { Events.AddHandler(ActiveDocumentChangedEvent, value); }
			remove { Events.RemoveHandler(ActiveDocumentChangedEvent, value); }
		}
		protected virtual void OnActiveDocumentChanged(EventArgs e)
		{
			EventHandler handler = (EventHandler)Events[ActiveDocumentChangedEvent];
			if (handler != null)
				handler(this, e);
		}

		private static readonly object ActiveContentChangedEvent = new object();
		[LocalizedCategory("Category_PropertyChanged")]
		[LocalizedDescription("DockPanel_ActiveContentChanged_Description")]
		public event EventHandler ActiveContentChanged
		{
			add { Events.AddHandler(ActiveContentChangedEvent, value); }
			remove { Events.RemoveHandler(ActiveContentChangedEvent, value); }
		}
		protected void OnActiveContentChanged(EventArgs e)
		{
			EventHandler handler = (EventHandler)Events[ActiveContentChangedEvent];
			if (handler != null)
				handler(this, e);
		}

		private static readonly object ActivePaneChangedEvent = new object();
		[LocalizedCategory("Category_PropertyChanged")]
		[LocalizedDescription("DockPanel_ActivePaneChanged_Description")]
		public event EventHandler ActivePaneChanged
		{
			add { Events.AddHandler(ActivePaneChangedEvent, value); }
			remove { Events.RemoveHandler(ActivePaneChangedEvent, value); }
		}
		protected virtual void OnActivePaneChanged(EventArgs e)
		{
			EventHandler handler = (EventHandler)Events[ActivePaneChangedEvent];
			if (handler != null)
				handler(this, e);
		}
	}
}
