#include "SysInfo_in.h"

using namespace Upp;

struct KeyCodes {
	String key; 
	int code;
}; 

#if defined(PLATFORM_WIN32) || defined (PLATFORM_WIN64)

Array <String> GetWinRegSubkeys(const String& key, HKEY base) {
	HKEY hkey;
	Array <String> subkeys;
	if(RegOpenKeyEx(base, key, 0, KEY_READ, &hkey) != ERROR_SUCCESS)
		return subkeys;
	char temp[_MAX_PATH];
	dword len;
	for(dword dw = 0; len = sizeof(temp), RegEnumKeyEx(hkey, dw, temp, &len, 0, 0, 0, 0) == ERROR_SUCCESS; dw++)
		subkeys.Add(temp);
	RegCloseKey(hkey);
	return subkeys;
}

void Mouse_LeftDown() 	{mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);}
void Mouse_LeftUp()		{mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);}
void Mouse_MiddleDown()	{mouse_event(MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0, 0);}
void Mouse_MiddleUp()	{mouse_event(MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0);}
void Mouse_RightDown()	{mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);}
void Mouse_RightUp()	{mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);}


bool Mouse_SetPos(long xMove, long yMove, int64 windowId) {
    long left, top, right, bottom;
    
    if (windowId != 0) {
        Window_GetRect(windowId, left, top, right, bottom);
        xMove = xMove + left;
        yMove = yMove + top;
    }
    SetCursorPos(xMove, yMove);
    
    return true;
}

bool Mouse_GetPos(long &x, long &y) {
    POINT p;
    
    GetCursorPos (&p);
    x = p.x;
    y = p.y;
    
    return true;
}

KeyCodes keyCodes[60] = {
	"NUMPAD7", 	VK_NUMPAD7, 	"BACK", 	VK_BACK, 
	"NUMPAD8", 	VK_NUMPAD8,		"TAB", 		VK_TAB,
	"NUMPAD9", 	VK_NUMPAD9, 	"RETURN", 	VK_RETURN,
	"MULTIPLY", VK_MULTIPLY, 	"SHIFT",	VK_SHIFT,
	"ADD",		VK_ADD, 	 	"CONTROL",	VK_CONTROL,
	"SEPARATOR", VK_SEPARATOR,	"MENU", 	VK_MENU,
	"SUBTRACT", VK_SUBTRACT, 	"PAUSE", 	VK_PAUSE,
	"DECIMAL",	VK_DECIMAL,		"CAPITAL", 	VK_CAPITAL,
	"DIVIDE",	VK_DIVIDE, 		"ESCAPE",	VK_ESCAPE,
	"F1", 		VK_F1, 			"SPACE", 	VK_SPACE,
	"F2", 		VK_F2,	 		"END",		VK_END,
	"F3",		VK_F3, 			"HOME",		VK_HOME,
	"F4",		VK_F4, 			"LEFT", 	VK_LEFT,
	"F5",		VK_F5,		 	"UP", 		VK_UP,
	"F6",		VK_F6,		 	"RIGHT",	VK_RIGHT,
	"F7",		VK_F7,		 	"DOWN",		VK_DOWN,
	"F8",		VK_F8,		 	"PRINT",	VK_PRINT,
	"F9",		VK_F9,		 	"SNAPSHOT",	VK_SNAPSHOT,
	"F10",		VK_F10,		 	"INSERT",	VK_INSERT,
	"F11",		VK_F11,		 	"DELETE",	VK_DELETE,
	"F12",		VK_F12,		 	"LWIN",		VK_LWIN,
	"NUMLOCK",	VK_NUMLOCK,	 	"RWIN",		VK_RWIN,
	"SCROLL",	VK_SCROLL,	 	"NUMPAD0",	VK_NUMPAD0,
	"LSHIFT",	VK_LSHIFT,	 	"NUMPAD1", 	VK_NUMPAD1,
	"RSHIFT",	VK_RSHIFT,	 	"NUMPAD2",	VK_NUMPAD2,
	"LCONTROL",	VK_LCONTROL, 	"NUMPAD3",	VK_NUMPAD3,
	"RCONTROL",	VK_RCONTROL, 	"NUMPAD4",	VK_NUMPAD4,
	"LMENU",	VK_LMENU, 		"NUMPAD5",	VK_NUMPAD5,
	"RMENU",	VK_RMENU, 		"NUMPAD6",	VK_NUMPAD6,
	/*"PGUP", 	XK_Page_Up, 	"PGDOWN", 	XK_Page_Down
	"CAPSLOCK", XK_Caps_Lock, 	"BACKSPACE",XK_BackSpace	*/
	""
};

void PressKeyVK(int keyVK, bool hold = false, bool release = false, bool compatible = false) {    
    long nScan, nExtended;
        
    nScan = MapVirtualKey(keyVK, 2);
    nExtended = 0;
    if (nScan == 0)
        nExtended = KEYEVENTF_EXTENDEDKEY;
    nScan = MapVirtualKey(keyVK, 0);
    
    if (compatible)
        nExtended = 0;
    
    if (!release)
        keybd_event((BYTE)keyVK, (BYTE)nScan, nExtended, 0);
    
    if (!hold)
        keybd_event((BYTE)keyVK, (BYTE)nScan, KEYEVENTF_KEYUP | nExtended, 0);
}


#if defined(__MINGW32__)
//	#define	MAPVK_VK_TO_VSC		0
//	#define	MAPVK_VSC_TO_VK   	1
	#ifndef MAPVK_VK_TO_CHAR
		#define	MAPVK_VK_TO_CHAR  	2
	#endif
//	#define	MAPVK_VSC_TO_VK_EX 	3
#endif
//#define	MAPVK_VK_TO_VSC_EX 	4


// This is less nice but more compatible for Notepad and MSWord for example
void PressKey(wchar key, bool hold = false, bool release = false) {
	if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
		HKL hKeyboardLayout = ::GetKeyboardLayout(0);
    	SHORT nVK = VkKeyScanExW(key, hKeyboardLayout);
    	UINT nScan = MapVirtualKeyExW(nVK, MAPVK_VK_TO_CHAR, hKeyboardLayout);
		if (!release) 
        	keybd_event((BYTE)nVK, (BYTE)nScan, 0, 0);
    	if (!hold) 
        	keybd_event((BYTE)nVK, (BYTE)nScan, KEYEVENTF_KEYUP, 0);
	} else {
		String numStr = FormatIntDec(key, 5, '0');
		PressKeyVK(VK_LMENU, true);
		PressKeyVK(VK_NUMPAD0 + numStr[0] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[1] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[2] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[3] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[4] - '0');
		PressKeyVK(VK_LMENU, false, true);
	}
}
/*
void PressKey(wchar key, bool hold = false, bool release = false)
{
	bool caps, num, scroll;
	if (IsLetter(key)) {
    	GetKeyLockStatus(caps, num, scroll);
    	if (caps) 
    		SetKeyLockStatus(false, num, scroll);	
    }
	HKL hKeyboardLayout = ::GetKeyboardLayout(0);
	if (hKeyboardLayout) {		// Last resource !!
		String numStr = FormatIntDec(key, 4, '0');
		PressKeyVK(VK_LMENU, true);
		PressKeyVK(VK_NUMPAD0 + numStr[0] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[1] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[2] - '0');
		PressKeyVK(VK_NUMPAD0 + numStr[3] - '0');
		PressKeyVK(VK_LMENU, false, true);
    	return;
	}
    UINT nScan = MapVirtualKeyExW(nVK, MAPVK_VK_TO_CHAR, hKeyboardLayout);
    long nExtended = 0;
    if (nScan == 0)
        nExtended = KEYEVENTF_EXTENDEDKEY;
    
    nScan = MapVirtualKeyExW(nVK, MAPVK_VK_TO_VSC, hKeyboardLayout);
    
    bool shift, ctrl, alt;
    
    shift = nVK & 0x100;
    ctrl  = nVK & 0x200;
    alt   = nVK & 0x400;    
    nVK   = nVK & 0xFF;
    
    if (!release) {
        if (shift)
            keybd_event (VK_SHIFT, 0, 0, 0);
        if (ctrl)
            keybd_event (VK_CONTROL, 0, 0, 0);
        if (alt)
            keybd_event (VK_MENU, 0, 0, 0);
    
        keybd_event ((BYTE)nVK, (BYTE)nScan, nExtended, 0);
    }
    if (!hold) {
        keybd_event ((BYTE)nVK, (BYTE)nScan, KEYEVENTF_KEYUP | nExtended, 0);
    
   		if (shift)
            keybd_event (VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
        if (ctrl)
            keybd_event (VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
        if (alt)
            keybd_event (VK_MENU, 0, KEYEVENTF_KEYUP, 0);
    }
    if (IsLetter(key) && caps) 
    	SetKeyLockStatus(true, num, scroll);	
}
*/

bool GetKeyLockStatus(bool &caps, bool &num, bool &scroll) {
    caps = GetKeyState(VK_CAPITAL);
    num = GetKeyState(VK_NUMLOCK);
    scroll = GetKeyState(VK_SCROLL);
    return true;
}

bool SetKeyLockStatus(bool caps, bool num, bool scroll) {
	bool capsnow, numnow, scrollnow;
	
	GetKeyLockStatus(capsnow, numnow, scrollnow);
	if (capsnow != caps)
		PressKeyVK(VK_CAPITAL);
	if (numnow != num)
		PressKeyVK(VK_NUMLOCK);
	if (scrollnow != scroll)
		PressKeyVK(VK_SCROLL);
	return true;
}

#endif 

#ifdef PLATFORM_POSIX

bool Mouse_GetPos(long &x, long &y) {
	SetSysInfoX11ErrorHandler();
	_XDisplay *dpy = XOpenDisplay (NULL);
	if (!dpy) {
		SetX11ErrorHandler();
		return false; 
	}   
	bool ret = false;
	Window root, child;
	Window r = DefaultRootWindow(dpy);
	int retx, rety;
	int wx, wy;
	unsigned int keys_buttons;
   	if (XQueryPointer(dpy, r, &root, &child, &retx, &rety, &wx, &wy, &keys_buttons)) {
   		x = wx;
   		y = wy;
   		ret = true;
	} else 
		x = y = -1;
		
	XCloseDisplay (dpy);
	SetX11ErrorHandler();
	
	return ret;
}

bool Mouse_SetPos(long x, long y, int64 windowId) {
	SetSysInfoX11ErrorHandler();
	_XDisplay *dpy = XOpenDisplay (NULL);
	if (!dpy) {
		SetX11ErrorHandler();
		return false;
	}   
    long left, top, right, bottom;
    Window r = DefaultRootWindow(dpy);
    if (windowId != 0) {
        Window_GetRect(windowId, left, top, right, bottom);
        x = x + left;
        y = y + top;
    }     
	XWarpPointer(dpy, None, r, 0, 0, 0, 0, x, y);
	XCloseDisplay (dpy);
	SetX11ErrorHandler();
	
	return true;
}

// libxtst-dev
#if !defined(flagNO_XTEST)

void Mouse_FakeClick(int button, int press) {
	_XDisplay *dpy = XOpenDisplay(NULL);
	XTestFakeButtonEvent(dpy, button, press, CurrentTime);
	XFlush(dpy);
	XCloseDisplay(dpy);
}

void Mouse_LeftDown() 	{Mouse_FakeClick(1, True);}
void Mouse_LeftUp() 	{Mouse_FakeClick(1, False);}
void Mouse_MiddleDown() {Mouse_FakeClick(2, True);}
void Mouse_MiddleUp() 	{Mouse_FakeClick(2, False);}
void Mouse_RightDown() 	{Mouse_FakeClick(3, True);}
void Mouse_RightUp() 	{Mouse_FakeClick(3, False);}

void PressKeyVK(int key, _XDisplay *dpy = NULL) {
	bool local = false;
	if (!dpy) {
		if (!(dpy = XOpenDisplay(NULL)))
			return;
		local = true;
	}
	XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, key), True, CurrentTime);
	XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, key), False, CurrentTime);
	if (local) {
		XFlush(dpy);
		XCloseDisplay(dpy);
	}
}

void PressKey(wchar key, _XDisplay *dpy = NULL) {
	bool local = false;
	if (!dpy) {
		if (!(dpy = XOpenDisplay(NULL)))
			return;
		local = true;
	}
	wchar k = key;
	if (key > 0x00ff)
    	key = key | 0x01000000;
 	
 	bool shift = false;
	KeyCode code = XKeysymToKeycode(dpy, key);
	if (code != 0) { 
		if (XkbKeycodeToKeysym(dpy, code, 0, 0) != key) {
			if (XkbKeycodeToKeysym(dpy, code, 1, 0) == key) 
				shift = true;
			else
				code = 0;
		}
	} else {  
		int firstKeycode, maxKeycode;
		int keysymsPerKeycode;
		
		XDisplayKeycodes(dpy, &firstKeycode, &maxKeycode);
		KeySym *keysyms = XGetKeyboardMapping(dpy, firstKeycode, maxKeycode - firstKeycode + 1, &keysymsPerKeycode);
      	int indx = (maxKeycode - firstKeycode - 1)*keysymsPerKeycode;
		keysyms[indx] = key;
      	XChangeKeyboardMapping(dpy, firstKeycode, keysymsPerKeycode, keysyms, maxKeycode-firstKeycode);
      	XSync(dpy, False);
      	code = maxKeycode-1;
      	if (XkbKeycodeToKeysym(dpy, code, 0, 0) != key) {
			if (XkbKeycodeToKeysym(dpy, code, 1, 0) == key) 
				shift = true;
		}
    }
	if (code != 0) {
		if (shift)
			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, XK_Shift_L), True, CurrentTime);	
		XTestFakeKeyEvent(dpy, code, True,  CurrentTime);
		XTestFakeKeyEvent(dpy, code, False, CurrentTime);
	 	if (shift)
			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, XK_Shift_L), False, CurrentTime);	
	} 	
	if (local) {
 		XFlush(dpy);
 		XCloseDisplay(dpy);
	}
}

#endif

bool GetKeyLockStatus0(bool &caps, bool &num, bool &scroll, _XDisplay *dpy) {
	int x, y, xx, yy;
	Window dm1, dm2;
	unsigned int sKbdState;
	
	if(!XQueryPointer(dpy, DefaultRootWindow(dpy), &dm1, &dm2, &x, &y, &xx, &yy, &sKbdState))
		return false;

	caps   = sKbdState & LockMask;
	num    = sKbdState & Mod2Mask;
	scroll = sKbdState & Mod5Mask;
	
	return true;
}

bool GetKeyLockStatus(bool &caps, bool &num, bool &scroll) {
	_XDisplay *dpy;
	if (!(dpy = XOpenDisplay(NULL)))
		return false;

	if (!GetKeyLockStatus0(caps, num, scroll, dpy)) {
		XCloseDisplay(dpy);
		return false;
	}
	XFlush(dpy);
	XCloseDisplay(dpy);
	return true;
}

#if !defined(flagNO_XTEST)

bool SetKeyLockStatus(bool caps, bool num, bool scroll) {
	_XDisplay *dpy;
	if (!(dpy = XOpenDisplay(NULL)))
		return false;
			
	bool oldcaps, oldnum, oldscroll; 
	if (!GetKeyLockStatus0(oldcaps, oldnum, oldscroll, dpy)) {
		XCloseDisplay(dpy);
		return false;
	}
	if (caps != oldcaps) 
		PressKeyVK(XK_Caps_Lock, dpy);
	if (num != oldnum) 
		PressKeyVK(XK_Num_Lock, dpy);
	if (scroll != oldscroll) 
		PressKeyVK(XK_Scroll_Lock, dpy);

	XFlush(dpy);
	XCloseDisplay(dpy);
	return true;
}

KeyCodes keyCodes[] = {
	"NUMPAD7", 	XK_KP_7,	 	"BACK", 	XK_BackSpace, 
	"NUMPAD8", 	XK_KP_8,		"TAB", 		XK_Tab,
	"NUMPAD9", 	XK_KP_9, 		"RETURN", 	XK_Return,
	"MULTIPLY", XK_KP_Multiply,	"SHIFT",	XK_Shift_Lock,
	"ADD",		XK_KP_Add, 	 	"CONTROL",	XK_Control_L,
	"SEPARATOR", XK_KP_Separator,"MENU", 	XK_Super_L,
	"SUBTRACT", XK_KP_Subtract,	"PAUSE", 	XK_Pause,
	"DECIMAL",	XK_KP_Decimal,	/*"CAPITAL", 	VK_CAPITAL,*/
	"DIVIDE",	XK_KP_Divide,	"ESCAPE",	XK_Escape,
	"F1", 		XK_F1, 			"SPACE", 	XK_KP_Space,
	"F2", 		XK_F2,	 		"END",		XK_End,
	"F3",		XK_F3, 			"HOME",		XK_Home,
	"F4",		XK_F4, 			"LEFT", 	XK_Left,
	"F5",		XK_F5,		 	"UP", 		XK_Up,
	"F6",		XK_F6,		 	"RIGHT",	XK_Right,
	"F7",		XK_F7,		 	"DOWN",		XK_Down,
	"F8",		XK_F8,		 	"PRINT",	XK_Sys_Req,
	"F9",		XK_F9,		 	/*"SNAPSHOT",	VK_SNAPSHOT,*/
	"F10",		XK_F10,		 	"INSERT",	XK_Insert,
	"F11",		XK_F11,		 	"DELETE",	XK_Delete,
	"F12",		XK_F12,		 	"LWIN",		XK_Meta_L,
	"NUMLOCK",	XK_Num_Lock, 	"RWIN",		XK_Meta_R,
	"SCROLL",	XK_Scroll_Lock,	"NUMPAD0",	XK_KP_0,
	"LSHIFT",	XK_Shift_L,	 	"NUMPAD1", 	XK_KP_1,
	"RSHIFT",	XK_Shift_R,	 	"NUMPAD2",	XK_KP_2,
	"LCONTROL",	XK_Control_L, 	"NUMPAD3",	XK_KP_3,
	"RCONTROL",	XK_Control_R, 	"NUMPAD4",	XK_KP_4,
	"LMENU",	XK_Super_L,		"NUMPAD5",	XK_KP_5,
	"RMENU",	XK_Super_R, 	"NUMPAD6",	XK_KP_6,
	"PGUP", 	XK_Page_Up, 	"PGDOWN", 	XK_Page_Down,
	"CAPSLOCK", XK_Caps_Lock, 	"BACKSPACE",XK_BackSpace,
	""
};
#endif

#endif

#if defined(PLATFORM_WIN32) || !defined(flagNO_XTEST)

void Mouse_LeftClick()
{
    Mouse_LeftDown(); 
    Mouse_LeftUp(); 
}
void Mouse_RightClick()
{
    Mouse_RightDown();
    Mouse_RightUp();
}
void Mouse_MiddleClick()
{
    Mouse_MiddleDown();
    Mouse_MiddleUp();
}
void Mouse_LeftDblClick()
{
	Mouse_LeftClick();
	Mouse_LeftClick();
}
void Mouse_MiddleDblClick()
{
	Mouse_MiddleClick();
	Mouse_MiddleClick();
}
void Mouse_RightDblClick()
{
	Mouse_RightClick();
	Mouse_RightClick();
}

int GetKeyCode(String key) {
	for (int i = 0; keyCodes[i].code != 0; ++i)
		if (keyCodes[i].key == key)
			return keyCodes[i].code;
	return 0;
}

void Keyb_SendKeys(String text, long finalDelay, long delayBetweenKeys)
{
	Array <wchar> virt;
	bool inKey = false;
	String key = "";
	WString wtext(text);
	for (int i = 0; i < wtext.GetCount(); ++i) {
		bool vk = false;
		Sleep(delayBetweenKeys);
		wchar c = wtext[i];
		if (c == '{')
			inKey = true;
		else if (c == '}') {
			if (key == "{") 
				c = '{';
			else {
				c = GetKeyCode(key);
				vk = true;
			}
			inKey = false;
			key = "";
		} else if (inKey == 1)
			key.Cat(c);
		else if (c == '\n') {
			c = GetKeyCode("RETURN");
			vk = true;
		}
 		if (inKey == false) {
			if (!vk) 
 				PressKey(c);
#if defined(PLATFORM_WIN32) || defined (PLATFORM_WIN64)
			else {
				PressKeyVK(c, true);
				virt.Add(c);
			}
#endif
		}
	}
#if defined(PLATFORM_WIN32) || defined (PLATFORM_WIN64)
	for (int i = 0; i < virt.GetCount(); ++i)
		PressKeyVK(virt[i], false, true);
#endif
	Sleep(finalDelay);
}

#endif

