/*******************************************************************************
 * Copyright  2015 rzorzorzo@users.sf.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package org.rzo.yajsw.os.ms.win.w32;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.rzo.yajsw.os.ms.win.w32.DummyWindow.MyUser32.MSG;
import org.rzo.yajsw.os.ms.win.w32.DummyWindow.MyUser32.WNDCLASSEX;
import org.rzo.yajsw.os.ms.win.w32.DummyWindow.MyUser32.WNDPROC;

import com.sun.jna.Callback;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.ptr.PointerByReference;

import jnacontrib.jna.Options;

// TODO: Auto-generated Javadoc
/**
 * The Class DummyWindow.
 */
public class DummyWindow
{

	/**
	 * The Interface MyKernel32.
	 */
	public interface MyKernel32 extends Kernel32
	{

		/** The INSTANCE. */
		MyKernel32 INSTANCE = (MyKernel32) Native.loadLibrary("Kernel32",
				MyKernel32.class);

		/*
		 * HMODULE WINAPI GetModuleHandle( __in LPCTSTR lpModuleName );
		 */
		/**
		 * Gets the module handle a.
		 * 
		 * @param lpModuleName
		 *            the lp module name
		 * 
		 * @return the pointer
		 */
		Pointer GetModuleHandleA(String lpModuleName);

		/*
		 * BOOL WINAPI GetModuleHandleEx( __in DWORD dwFlags, __in LPCTSTR
		 * lpModuleName, __out HMODULE phModule );
		 */
		/**
		 * Gets the module handle ex a.
		 * 
		 * @param dwFlags
		 *            the dw flags
		 * @param lpModuleName
		 *            the lp module name
		 * @param phModule
		 *            the ph module
		 * 
		 * @return true, if successful
		 */
		boolean GetModuleHandleExA(int dwFlags, String lpModuleName,
				PointerByReference phModule);

		/**
		 * Global add atom a.
		 * 
		 * @param key
		 *            the key
		 * 
		 * @return the int
		 */
		int GlobalAddAtomA(String key);

	}// Kernel32

	/**
	 * The Interface MyUser32.
	 */
	public abstract interface MyUser32 extends
			com.sun.jna.platform.win32.User32
	{
		// Method declarations, constant and structure definitions go here

		/** The INSTANCE. */
		MyUser32 INSTANCE = (MyUser32) Native.loadLibrary("User32",
				MyUser32.class);

		/**
		 * Register hot key.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * @param id
		 *            the id
		 * @param fsModifiers
		 *            the fs modifiers
		 * @param vk
		 *            the vk
		 * 
		 * @return the int
		 */
		int RegisterHotKey(Pointer hWnd, int id, int fsModifiers, int vk);

		/*
		 * BOOL UnregisterHotKey( HWND hWnd, int id );
		 */
		/**
		 * Unregister hot key.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * @param id
		 *            the id
		 * 
		 * @return the int
		 */
		boolean UnregisterHotKey(Pointer hWnd, int id);

		/*
		 * LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam,
		 * LPARAM lParam );
		 */
		/**
		 * The Interface WNDPROC.
		 */
		interface WNDPROC extends Callback
		{

			/**
			 * Callback.
			 * 
			 * @param hwnd
			 *            the hwnd
			 * @param uMsg
			 *            the u msg
			 * @param wParam
			 *            the w param
			 * @param lParam
			 *            the l param
			 * 
			 * @return the int
			 */
			int callback(Pointer hwnd, int uMsg, int wParam, int lParam);
		}

		/** The W m_ hotkey. */
		int WM_HOTKEY = 786;

		/*
		 * typedef struct { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int
		 * cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR
		 * hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR
		 * lpszClassName; HICON hIconSm; } WNDCLASSEX,PWNDCLASSEX;
		 */
		/** The C s_ hredraw. */
		int CS_HREDRAW = 2;

		/** The C s_ vredraw. */
		int CS_VREDRAW = 1;

		/**
		 * The Class WNDCLASSEX.
		 */
		static class WNDCLASSEX extends Structure
		{

			/** The cb size. */
			public int cbSize = size();

			/** The style. */
			public int style = 0;

			/** The lpfn wnd proc. */
			public WNDPROC lpfnWndProc;

			/** The cb cls extra. */
			public int cbClsExtra = 0;

			/** The cb wnd extra. */
			public int cbWndExtra = 0;

			/** The h instance. */
			public Pointer hInstance;

			/** The h icon. */
			public Pointer hIcon = null;

			/** The h cursor. */
			public Pointer hCursor = null;

			/** The hbr background. */
			public Pointer hbrBackground = null;

			/** The lpsz menu name. */
			public String lpszMenuName = null;

			/** The lpsz class name. */
			public String lpszClassName = "JavaDummyWnd";

			/** The h icon sm. */
			public Pointer hIconSm = null;

			@Override
			protected List getFieldOrder()
			{
				return Arrays.asList(new String[] { "cbSize", "style",
						"lpfnWndProc", "cbClsExtra", "cbWndExtra", "hInstance",
						"hIcon", "hCursor", "hbrBackground", "lpszMenuName",
						"lpszClassName", "hIconSm" });
			}
		}

		/*
		 * ATOM RegisterClassEx( CONST WNDCLASSEXlpwcx );
		 */
		/**
		 * Register class ex a.
		 * 
		 * @param lpwcx
		 *            the lpwcx
		 * 
		 * @return the int
		 */
		int RegisterClassExA(WNDCLASSEX lpwcx);

		/*
		 * BOOL UnregisterClass( LPCTSTR lpClassName, HINSTANCE hInstance );
		 */
		/**
		 * Unregister class w.
		 * 
		 * @param lpClassName
		 *            the lp class name
		 * @param hInstance
		 *            the h instance
		 * 
		 * @return the int
		 */
		int UnregisterClassW(String lpClassName, Pointer hInstance);

		/*
		 * HWND CreateWindowEx( DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR
		 * lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight,
		 * HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam );
		 */

		/** The W s_ overlappedwindow. */
		int WS_OVERLAPPEDWINDOW = 0xcf0000;

		/** The W s_ e x_ clientedge. */
		int WS_EX_CLIENTEDGE = 512;

		/** The W s_ overlapped. */
		int WS_OVERLAPPED = 0;

		/** The W s_ visible. */
		int WS_VISIBLE = 0x10000000;

		/**
		 * Creates the window ex a.
		 * 
		 * @param dwExStyle
		 *            the dw ex style
		 * @param lpClassName
		 *            the lp class name
		 * @param lpWindowName
		 *            the lp window name
		 * @param dwStyle
		 *            the dw style
		 * @param x
		 *            the x
		 * @param y
		 *            the y
		 * @param nWidth
		 *            the n width
		 * @param nHeight
		 *            the n height
		 * @param hWndParent
		 *            the h wnd parent
		 * @param hMenu
		 *            the h menu
		 * @param hInstance
		 *            the h instance
		 * @param lpParam
		 *            the lp param
		 * 
		 * @return the pointer
		 */
		Pointer CreateWindowExA(int dwExStyle, String lpClassName,
				String lpWindowName, int dwStyle, int x, int y, int nWidth,
				int nHeight, Pointer hWndParent, Pointer hMenu,
				Pointer hInstance, Pointer lpParam);

		/*
		 * LONG_PTR SetWindowLongPtr( HWND hWnd, int nIndex, LONG_PTR dwNewLong
		 * );
		 */
		/**
		 * Sets the window long a.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * @param nIndex
		 *            the n index
		 * @param dwNewLong
		 *            the dw new long
		 * 
		 * @return the pointer
		 */
		Pointer SetWindowLongA(Pointer hWnd, int nIndex, Pointer dwNewLong);

		/*
		 * LONG_PTR GetWindowLongPtr( HWND hWnd, int nIndex );
		 */
		/**
		 * Gets the window long a.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * @param nIndex
		 *            the n index
		 * 
		 * @return the pointer
		 */
		Pointer GetWindowLongA(Pointer hWnd, int nIndex);

		/** The GWL p_ userdata. */
		int GWLP_USERDATA = -21;

		/*
		 * LRESULT DefWindowProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM
		 * lParam );
		 */
		/**
		 * Def window proc a.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * @param Msg
		 *            the msg
		 * @param wParam
		 *            the w param
		 * @param lParam
		 *            the l param
		 * 
		 * @return the int
		 */
		int DefWindowProcA(Pointer hWnd, int Msg, int wParam, int lParam);

		/*
		 * BOOL ShowWindow( HWND hWnd, int nCmdShow );
		 */
		/**
		 * Show window.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * @param nCmdShow
		 *            the n cmd show
		 * 
		 * @return the int
		 */
		int ShowWindow(Pointer hWnd, int nCmdShow);

		/** The S w_ hide. */
		int SW_HIDE = 0;

		/** The S w_ show. */
		int SW_SHOW = 5;

		/** The S w_ shownormal. */
		int SW_SHOWNORMAL = 1;

		/*
		 * BOOL UpdateWindow( HWND hWnd // handle to window );
		 */
		/**
		 * Update window.
		 * 
		 * @param hWnd
		 *            the h wnd
		 * 
		 * @return the int
		 */
		int UpdateWindow(Pointer hWnd // handle to window
		);

		/*
		 * typedef struct { HWND hwnd; UINT message; WPARAM wParam; LPARAM
		 * lParam; DWORD time; POINT pt; } MSG,PMSG;
		 */
		/**
		 * The Class MSG.
		 */
		class MSG extends Structure
		{

			/** The hwnd. */
			public Pointer hwnd;

			/** The message. */
			public int message;

			/** The w param. */
			public int wParam;

			/** The l param. */
			public int lParam;

			/** The time. */
			public int time;

			/** The pt. */
			public POINT pt;

			@Override
			protected List getFieldOrder()
			{
				return Arrays.asList(new String[] { "hwnd", "message",
						"wParam", "lParam", "time", "pt" });
			}

		}

		/*
		 * BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT
		 * wMsgFilterMax );
		 */
		/**
		 * Gets the message a.
		 * 
		 * @param lpMsg
		 *            the lp msg
		 * @param hWnd
		 *            the h wnd
		 * @param wMsgFilterMin
		 *            the w msg filter min
		 * @param wMsgFilterMax
		 *            the w msg filter max
		 * 
		 * @return the int
		 */
		int GetMessageA(MSG lpMsg, Pointer hWnd, int wMsgFilterMin,
				int wMsgFilterMax);

		/*
		 * LRESULT DispatchMessage( const MSGlpmsg );
		 */
		/**
		 * Dispatch message a.
		 * 
		 * @param lpmsg
		 *            the lpmsg
		 * 
		 * @return the int
		 */
		int DispatchMessageA(MSG lpmsg);

	} // user32

	/**
	 * The Interface MyGdi32.
	 */
	public interface MyGdi32 extends GDI32
	{

		/** The INSTANCE. */
		MyGdi32 INSTANCE = (MyGdi32) Native.loadLibrary("gdi32", MyGdi32.class,
				Options.UNICODE_OPTIONS);

		/*
		 * HGDIOBJ GetStockObject( int fnObject // stock object type );
		 */
		/**
		 * Gets the stock object.
		 * 
		 * @param fnObject
		 *            the fn object
		 * 
		 * @return the pointer
		 */
		Pointer GetStockObject(int fnObject);

		/** The BLAC k_ brush. */
		int BLACK_BRUSH = 4;

	}// Gdi32

	/**
	 * The Class CallbackMessage.
	 */
	static class CallbackMessage
	{

		/** The _u msg. */
		int _uMsg;

		/** The _w param. */
		int _wParam;

		/** The _l param. */
		int _lParam;

		/**
		 * Instantiates a new callback message.
		 * 
		 * @param uMsg
		 *            the u msg
		 * @param wParam
		 *            the w param
		 * @param lParam
		 *            the l param
		 */
		CallbackMessage(int uMsg, int wParam, int lParam)
		{
			_uMsg = uMsg;
			_wParam = wParam;
			_lParam = lParam;
		}
	}

	/** The _instance. */
	static DummyWindow _instance;

	/** The _listners. */
	static Map _listners = new MultiValueMap();

	/** The _wnd proc. */
	WndProc _wndProc = new WndProc();

	/** The _hinstance. */
	Pointer _hinstance;

	/** The _h wnd. */
	Pointer _hWnd;

	/** The _wnd class. */
	WNDCLASSEX _wndClass = new WNDCLASSEX();

	/** The _queue. */
	static LinkedBlockingQueue _queue = new LinkedBlockingQueue();

	/** The _hot keys. */
	static BidiMap _hotKeys = new DualHashBidiMap();

	/** The _semaphore. */
	Semaphore _semaphore = new Semaphore(0);

	/**
	 * The Class HotKey.
	 */
	public class HotKey
	{

		/**
		 * Instantiates a new hot key.
		 * 
		 * @param wParam
		 *            the w param
		 * @param lParam
		 *            the l param
		 */
		public HotKey(int wParam, int lParam)
		{
			_wParam = wParam;
			_lParam = lParam;
		}

		/** The _w param. */
		int _wParam;

		/** The _l param. */
		int _lParam;

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode()
		{
			return _wParam | _lParam;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj)
		{
			return (obj instanceof HotKey && ((HotKey) obj)._lParam == _lParam && ((HotKey) obj)._lParam == _lParam);
		}
	}

	/**
	 * Instance.
	 * 
	 * @return the dummy window
	 */
	public static synchronized DummyWindow instance()
	{
		if (_instance == null)
		{
			_instance = new DummyWindow();
			_instance.init();
		}
		return _instance;
	}

	/**
	 * The Class WndProc.
	 */
	static class WndProc extends Structure implements WNDPROC
	{

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.rzo.yajsw.os.ms.win.xp.DummyWindow.MyUser32.WNDPROC#callback(
		 * com.sun.jna.Pointer, int, int, int)
		 */
		public int callback(Pointer hWnd, int uMsg, int wParam, int lParam)
		{
			// System.out.println("callback " + uMsg + " " + wParam + " " +
			// lParam + " " + "ptr" + " "+ hWnd);
			if (_listners.get(new Integer(uMsg)) != null)
			{
				CallbackMessage msg = new CallbackMessage(uMsg, wParam, lParam);
				_queue.offer(msg);
			}
			int res = MyUser32.INSTANCE.DefWindowProcA(hWnd, uMsg, wParam,
					lParam);
			// System.out.println(">" + res);
			return res;
		}

		/**
		 * Instantiates a new wnd proc.
		 */
		WndProc()
		{
			super();
			allocateMemory(4);
		}

		@Override
		protected List getFieldOrder()
		{
			return new ArrayList();
		}
	}

	/**
	 * The Interface WndListner.
	 */
	public interface WndListner
	{

		/**
		 * Execute.
		 * 
		 * @param uMsg
		 *            the u msg
		 * @param wParam
		 *            the w param
		 * @param lParam
		 *            the l param
		 * 
		 * @return the int
		 */
		int execute(int uMsg, int wParam, int lParam);
	}

	/**
	 * Adds the listner.
	 * 
	 * @param uMsg
	 *            the u msg
	 * @param listner
	 *            the listner
	 */
	public void addListner(Integer uMsg, WndListner listner)
	{
		synchronized (_listners)
		{
			_listners.put(uMsg, listner);
		}
	}

	/**
	 * Removes the listner.
	 * 
	 * @param listner
	 *            the listner
	 */
	public void removeListner(WndListner listner)
	{
		synchronized (_listners)
		{
			_listners.remove(listner);
		}

	}

	/**
	 * Inits the.
	 */
	private void init()
	{
		Runnable r = new Runnable()
		{
			public void run()
			{
				_hinstance = MyKernel32.INSTANCE.GetModuleHandleA(null);

				_wndClass.lpfnWndProc = _wndProc;
				_wndClass.hInstance = _hinstance;
				if (MyUser32.INSTANCE.RegisterClassExA(_wndClass) == 0)
					return;
				_hWnd = MyUser32.INSTANCE.CreateWindowExA(0,
						_wndClass.lpszClassName, _wndClass.lpszClassName, 0, 0,
						0, 0, 0, null, null, _hinstance, null);
				MyUser32.INSTANCE.ShowWindow(_hWnd, MyUser32.SW_HIDE);
				MyUser32.INSTANCE.UpdateWindow(_hWnd);

				MSG msg = new MSG();
				msg.size();
				int ret;
				do
				{
					ret = MyUser32.INSTANCE.GetMessageA(msg, _hWnd, 0, 0);
					// System.out.println("get message "+msg);
					if (msg.message == WindowsXPKeyboard.WND_REGISTER_HOTKEY)
					{
						int id = MyKernel32.INSTANCE.GlobalAddAtomA("JHOTKEY-"
								+ msg.wParam + "-" + msg.lParam);
						int ret2 = MyUser32.INSTANCE.RegisterHotKey(_hWnd, id,
								msg.wParam, msg.lParam);
						// System.out.println("register: "+id);
						if (ret2 != 0)
							_hotKeys.put(new Integer(id), new HotKey(
									msg.wParam, msg.lParam));
						// System.out.println("register "+ret2);
						_semaphore.release();
					}
					else if (msg.message == WindowsXPKeyboard.WND_UNREGISTER_HOTKEY)
					{
						HotKey k = new HotKey(msg.wParam, msg.lParam);
						Integer id = (Integer) _hotKeys.getKey(k);
						// System.out.println("unregister: "+id);
						if (id != null)
							if (MyUser32.INSTANCE.UnregisterHotKey(_hWnd,
									id.intValue()))
								_hotKeys.remove(id);
						_semaphore.release();

					}
					else
						MyUser32.INSTANCE.DispatchMessageA(msg);
				}
				while (ret != 0);
			}
		};
		Runnable r2 = new Runnable()
		{
			public void run()
			{
				CallbackMessage msg;
				try
				{
					while (true)
					{
						msg = (CallbackMessage) _queue.take();
						// System.out.println("Message queue: "+msg._uMsg);
						Collection listners = (Collection) _listners
								.get(new Integer(msg._uMsg));
						if (listners != null)
							for (Iterator it = listners.iterator(); it
									.hasNext();)
							{
								WndListner listner = (WndListner) it.next();
								listner.execute(msg._uMsg, msg._wParam,
										msg._lParam);
							}
					}
				}
				catch (Exception e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}
		};
		new Thread(r2).start();
		new Thread(r).start();
		_semaphore.release();

	}

	/**
	 * Wait termination.
	 * 
	 * @throws InterruptedException
	 *             the interrupted exception
	 */
	void waitTermination() throws InterruptedException
	{
		// System.out.println("+ wait termination "+
		// System.currentTimeMillis());
		_semaphore.acquire();
		// System.out.println("- wait termination "+
		// System.currentTimeMillis());
	}
}
