/*
	$Id: HotKeyManager.cs 48 2010-01-27 15:52:20Z catwalk $
*/
using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using System.Windows.Input;
using System.Linq;

namespace Hiyoko.Forms{
	public sealed class HotKeyManager : DisposableObject{
		private Window window;
		private IntPtr handle;
		public HotKeyCollection hotKeys;
		private IList<HotKey> delayList = new List<HotKey>(0);
		
		public HotKeyManager(Window window){
			if(window == null){
				throw new ArgumentNullException();
			}
			
			this.window = window;
			this.hotKeys = new HotKeyCollection(this);
			
			var handle = new WindowInteropHelper(window).Handle;
			if(handle != IntPtr.Zero){
				this.handle = handle;
			}else{
				this.window.Loaded += this.Window_Loaded;
			}
			
			// イベント
			this.window.Closed += delegate{
				this.Dispose();
			};
			ComponentDispatcher.ThreadPreprocessMessage += this.ThreadPreprocessMessage;
		}
		
		private void Window_Loaded(object sender, RoutedEventArgs e){
			this.handle = new WindowInteropHelper(this.window).Handle;
			if(this.handle == IntPtr.Zero){
				throw new Win32Exception();
			}
			
			foreach(HotKey key in this.delayList){
				this.Register(key);
			}
			this.delayList = null;
		}
		
		private void ThreadPreprocessMessage(ref MSG msg, ref bool handled){
			if(msg.hwnd == this.handle){
				const int WM_HOTKEY = 0x312;
				if(msg.message == WM_HOTKEY){
					int id = (int)msg.wParam;
					HotKey hotkey;
					
					if(this.hotKeys.ToDictionary(hk => hk.Id).TryGetValue(id, out hotkey)){
						hotkey.Execute();
						handled = true;
					}
				}
			}
		}
		
		[DllImport("user32.dll")] 
		[return: MarshalAs(UnmanagedType.Bool)] 
		private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc); 
 		
		[DllImport("user32.dll")] 
		[return: MarshalAs(UnmanagedType.Bool)] 
		private static extern bool UnregisterHotKey(IntPtr hWnd, int id); 
		
		private void Register(HotKey hotkey){
			if(this.window == null){
				throw new ObjectDisposedException("HotKeyManager");
			}
			if(this.handle == IntPtr.Zero){
				this.delayList.Add(hotkey);
			}else{
				//MessageBox.Show(this.handle.ToString() + "\nRegister : " + hotkey.Id);
				if(!RegisterHotKey(this.handle, hotkey.Id, (int)hotkey.Modifiers, KeyInterop.VirtualKeyFromKey(hotkey.Key))){
					throw new Win32Exception();
				}
			}
		}
		
		private void Unregister(HotKey hotkey){
			if(this.window == null){
				throw new ObjectDisposedException("HotKeyManager");
			}
			if(this.handle == IntPtr.Zero){
				this.delayList.Remove(hotkey);
			}else{
				//MessageBox.Show(this.handle.ToString() + "\nUnregister : " + hotkey.Id);
				if(!UnregisterHotKey(this.handle, hotkey.Id)){
					throw new Win32Exception();
				}
			}
		}
		
		protected override void Dispose(bool disposing){
			try{
				if(this.window != null){
					ComponentDispatcher.ThreadPreprocessMessage -= this.ThreadPreprocessMessage;
					foreach(HotKey hotkey in this.hotKeys){
						this.Unregister(hotkey);
					}
					this.window = null;
				}
			}finally{
				base.Dispose(disposing);
			}
		}
		
		public class HotKeyCollection : ICollection<HotKey>{
			private IDictionary<int, HotKey> collection = new Dictionary<int, HotKey>();
			private HotKeyManager manager;
			
			internal HotKeyCollection(HotKeyManager manager){
				this.manager = manager;
			}
			
			public void Add(Key key, EventHandler executed){
				this.Add(key, ModifierKeys.None, executed);
			}
			
			public void Add(Key key, ModifierKeys modifiers, EventHandler executed){
				this.Add(new HotKey(key, modifiers, executed));
			}
			
			public void Add(HotKey item){
				if(item == null){
					throw new ArgumentNullException();
				}
				this.collection.Add(item.Id, item);
				this.manager.Register(item);
			}
			
			public bool Remove(HotKey hotkey){
				if(hotkey == null){
					throw new ArgumentNullException();
				}
				this.manager.Unregister(hotkey);
				return this.collection.Remove(hotkey.Id);
			}
			
			public void Clear(){
				foreach(HotKey hotkey in this){
					this.manager.Unregister(hotkey);
				}
				this.collection.Clear();
			}
			
			public bool Contains(HotKey hotkey){
				if(hotkey == null){
					throw new ArgumentNullException();
				}
				return this.collection.ContainsKey(hotkey.Id);
			}
			
			public void CopyTo(HotKey[] array, int arrayIndex){
				if(array == null){
					throw new ArgumentNullException();
				}
				this.collection.Values.CopyTo(array, arrayIndex);
			}
			
			IEnumerator IEnumerable.GetEnumerator(){
				return this.GetEnumerator();
			}
			
			public IEnumerator<HotKey> GetEnumerator(){
				return this.collection.Values.GetEnumerator();
			}
			
			public int Count{
				get{
					return this.collection.Count;
				}
			}
			
			public bool IsReadOnly{
				get{
					return false;
				}
			}
		}
		
		public HotKeyCollection HotKeys{
			get{
				if(this.window == null){
					throw new ObjectDisposedException("HotKeyManager");
				}
				return this.hotKeys;
			}
		}
	}
	
	public class HotKey{
		[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
		private static extern ushort GlobalAddAtom(string str);
		
		[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
		private static extern ushort GlobalDeleteAtom(ushort nAtom);
		
		internal int Id{get; private set;}
		public ModifierKeys Modifiers{get; private set;}
		public Key Key{get; private set;}
		public event EventHandler Executed;
		
		public HotKey(Key key, ModifierKeys modifiers, EventHandler executed){
			this.Id = (int)GlobalAddAtom(this.GetHashCode().ToString());
			this.Modifiers = modifiers;
			this.Key = key;
			this.Executed += executed;
		}
		
		public void Execute(){
			this.Executed(this, EventArgs.Empty);
		}
		
		~HotKey(){
			this.Id = GlobalDeleteAtom((ushort)this.Id);
		}
	}
	
}