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{
	using WinForms = System.Windows.Forms;
	
	public sealed class HotKeyManager : Component{
		private Window window;
		WindowInteropHelper helper;
		public HotKeyCollection hotKeys;
		
		public HotKeyManager(Window window){
			if(window == null){
				throw new ArgumentNullException();
			}
			this.window = window;
			this.helper = new WindowInteropHelper(window);
			this.hotKeys = new HotKeyCollection(this);
			this.window.Closed += delegate{
				this.Dispose();
			};
			ComponentDispatcher.ThreadPreprocessMessage += this.ThreadPreprocessMessage;
		}
		
		private void ThreadPreprocessMessage(ref MSG msg, ref bool handled){
			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");
			}
			RegisterHotKey(this.helper.Handle, hotkey.Id, (int)hotkey.Modifiers, (int)hotkey.Key);
		}
		
		private void Unregister(HotKey hotkey){
			if(this.window == null){
				throw new ObjectDisposedException("HotKeyManager");
			}
			UnregisterHotKey(this.helper.Handle, hotkey.Id);
		}
		
		protected override void Dispose(bool disposing){
			if(this.window != null){
				ComponentDispatcher.ThreadPreprocessMessage -= this.ThreadPreprocessMessage;
				foreach(HotKey hotkey in this.hotKeys){
					this.Unregister(hotkey);
				}
				this.window = null;
			}
		}
		
		public class HotKeyCollection : ICollection<HotKey>{
			private List<HotKey> collection = new List<HotKey>();
			private HotKeyManager manager;
			
			internal HotKeyCollection(HotKeyManager manager){
				this.manager = manager;
			}
			
			public void Add(ModifierKeys modifiers, WinForms::Keys key, EventHandler executed){
				this.Add(new HotKey(modifiers, key, executed));
			}
			
			public void Add(HotKey item){
				this.manager.Register(item);
				this.collection.Add(item);
			}
			
			public bool Remove(HotKey hotkey){
				this.manager.Unregister(hotkey);
				return this.collection.Remove(hotkey);
			}
			
			public void Clear(){
				foreach(HotKey hotkey in this){
					this.manager.Unregister(hotkey);
				}
				this.collection.Clear();
			}
			
			public bool Contains(HotKey hotkey){
				return this.collection.Contains(hotkey);
			}
			
			public void CopyTo(HotKey[] array, int arrayIndex){
				this.collection.CopyTo(array, arrayIndex);
			}
			
			IEnumerator IEnumerable.GetEnumerator(){
				return this.GetEnumerator();
			}
			
			public IEnumerator<HotKey> GetEnumerator(){
				return this.collection.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);
		
		public int Id{get; private set;}
		public ModifierKeys Modifiers{get; private set;}
		public WinForms::Keys Key{get; private set;}
		public event EventHandler Executed;
		
		public HotKey(ModifierKeys modifiers, WinForms::Keys key, EventHandler executed){
			this.Id = (int)GlobalAddAtom(this.GetHashCode().ToString());
			this.Modifiers = modifiers;
			this.Key = key & WinForms::Keys.KeyCode;
			this.Executed += executed;
		}
		
		public void Execute(){
			this.Executed(this, EventArgs.Empty);
		}
		
		~HotKey(){
			this.Id = GlobalDeleteAtom((ushort)this.Id);
		}
	}
	
}