/***************************************************************************
 *   Copyright (C) 2009 by Jeff Gibbons                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; version 2 of the License.               *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "lib/HelperMacX.h"
#include <map>

static pid_t keepassxPID;

#ifdef GLOBAL_AUTOTYPE
#include "lib/AutoTypeGlobalMacX.h"

uint HelperMacX::getShortcutModifierMask(const Shortcut& s){
	AutoTypeGlobalMacX* autoTypeGlobal = static_cast<AutoTypeGlobalMacX*>(autoType);

	uint mod = 0;
	if (s.ctrl)  mod |= autoTypeGlobal->maskCtrl();
	if (s.shift) mod |= autoTypeGlobal->maskShift();
	if (s.alt)   mod |= autoTypeGlobal->maskAlt();
	if (s.altgr) mod |= autoTypeGlobal->maskAltGr();
	if (s.win)   mod |= autoTypeGlobal->maskMeta();
	
	return mod;
}

Boolean HelperMacX::isFrontProcess(pid_t pid){
	Boolean result;
	ProcessSerialNumber pidPSN;
	ProcessSerialNumber frontPSN;
	OSStatus status = GetProcessForPID(pid, &pidPSN);
	if (noErr != status) {
		qWarning("HelperMacX::isFrontProcess: GetProcessForPID error for pid %d: %d", pid, (int)status);
		return false;
	}
	GetFrontProcess(&frontPSN);
	SameProcess(&pidPSN, &frontPSN, &result);
	return result;
}
#endif

pid_t HelperMacX::getKeepassxPID(){
	if (0 == keepassxPID) {
		ProcessSerialNumber processSerialNumber;
		GetCurrentProcess(&processSerialNumber);
		GetProcessPID(&processSerialNumber, &keepassxPID);
	}
	return keepassxPID;
}

Boolean HelperMacX::getTargetWindowInfo(pid_t *pidPtr, int *windowNumberPtr,
										char* windowName, int maxWindowNameSize){
	char windowNameBuffer[256];
	if (NULL == windowName) {
		windowName = windowNameBuffer;
		maxWindowNameSize = sizeof(windowNameBuffer);
	}
	// get info for on screen windows (excluding desktop elements) in top to bottom order
	CFArrayRef windowInfo = CGWindowListCopyWindowInfo(
		kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
	CFIndex windowCount = CFArrayGetCount(windowInfo);
	for (CFIndex i = 0; i < windowCount; i++){
		CFDictionaryRef window = (CFDictionaryRef)CFArrayGetValueAtIndex(windowInfo, i);
		// only want windows in layer 0
		CFNumberRef windowLayerRef = (CFNumberRef)CFDictionaryGetValue(window, kCGWindowLayer);
		int windowLayer = -1;
		CFNumberGetValue(windowLayerRef, kCFNumberIntType, &windowLayer);
		if (0 != windowLayer) continue;
		// get the pid owning this window
		CFNumberRef pidRef = (CFNumberRef)CFDictionaryGetValue(window, kCGWindowOwnerPID);
		pid_t pid = -1;
		CFNumberGetValue(pidRef, kCFNumberIntType, &pid);
		// skip KeePassX windows
		if (getKeepassxPID() == pid) continue;
		// get window name; continue if no name
		CFStringRef windowNameRef = (CFStringRef)CFDictionaryGetValue(window, kCGWindowName);
		if (!windowNameRef) continue;
		windowName[0] = 0;
		if (!CFStringGetCString(windowNameRef, windowName, maxWindowNameSize, kCFStringEncodingUTF8) ||
			  (0 == windowName[0]))
			continue;
		if (NULL != pidPtr) *pidPtr = pid;
		if (NULL != windowNumberPtr) {
			CFNumberRef windowNumberRef = (CFNumberRef)CFDictionaryGetValue(window, kCGWindowNumber);
			CFNumberGetValue(windowNumberRef, kCGWindowIDCFNumberType, windowNumberPtr);
		}
		CFRelease(windowInfo);
		return true;
	}
	CFRelease(windowInfo);
	return false;
}

OSType HelperMacX::getProcessSignature(pid_t pid){
	OSErr err;
	ProcessSerialNumber processSerialNumber;
	ProcessInfoRec processInfoRec;
	memset(&processInfoRec, 0, sizeof(processInfoRec));
	processInfoRec.processInfoLength = sizeof(processInfoRec);
	err = GetProcessForPID(pid, &processSerialNumber);
	if (noErr != err) {
		qWarning("HelperMacX::getProcessSignature: GetProcessForPID error for pid %d: %d", pid, err);
		return 0;
	}
	err = GetProcessInformation(&processSerialNumber, &processInfoRec);
	if (noErr != err) {
		qWarning("HelperMacX::getProcessSignature: GetProcessInformation error for pid %d: %d\n", pid, err);
		return 0;
	}
	return processInfoRec.processSignature;
}

static uint orderedModifiers[] = {
	0,
	(  shiftKey                                   ) >> 8,
	(controlKey                                   ) >> 8,
	( optionKey                                   ) >> 8,
	( cmdKey                                      ) >> 8,
	(  shiftKey | controlKey                      ) >> 8,
	(  shiftKey | optionKey                       ) >> 8,
	(  shiftKey | cmdKey                          ) >> 8,
	(controlKey | optionKey                       ) >> 8,
	(controlKey | cmdKey                          ) >> 8,
	( optionKey | cmdKey                          ) >> 8,
	(  shiftKey | controlKey | optionKey          ) >> 8,
	(  shiftKey | controlKey | cmdKey             ) >> 8,
	(  shiftKey | optionKey  | cmdKey             ) >> 8,
	(controlKey | optionKey  | cmdKey             ) >> 8,
	(  shiftKey | controlKey | optionKey | cmdKey ) >> 8
};

static std::map<uint,KeycodeWithMods> unicodeToKeycodeWithModsMap;

void HelperMacX::initUnicodeToKeycodeWithModsMap(){
	unicodeToKeycodeWithModsMap.clear();
	TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardInputSource();
	if (NULL == inputSourceRef) {
		qWarning("HelperMacX::initUnicodeToKeycodeWithModsMap: inputSourceRef is NULL");
		return;
	}
	CFDataRef unicodeKeyLayoutDataRef = (CFDataRef)TISGetInputSourceProperty(inputSourceRef,
		kTISPropertyUnicodeKeyLayoutData);
	if (NULL == unicodeKeyLayoutDataRef) {
		qWarning("HelperMacX::initUnicodeToKeycodeWithModsMap: unicodeKeyLayoutDataRef is NULL");
		return;
	}
	UCKeyboardLayout *unicodeKeyLayoutDataPtr = (UCKeyboardLayout*)CFDataGetBytePtr(unicodeKeyLayoutDataRef);

	UInt32 deadKeyState;
	UniChar unicodeString[8];
	UniCharCount len;
	for (int m = 0; m < 16; m++) {
		uint mods =  orderedModifiers[m];
		for (uint keycode = 0; keycode < 0x80; keycode++) {
			deadKeyState = 0;
			len = 0;
			OSStatus status = UCKeyTranslate(unicodeKeyLayoutDataPtr, keycode, kUCKeyActionDown,
											 mods, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask,
											 &deadKeyState, sizeof(unicodeString), &len, unicodeString);
			if (noErr != status) {
				qWarning("HelperMacX::initUnicodeToKeycodeWithModsMap: UCKeyTranslate error: %d keycode 0x%02X modifiers 0x%02X",
						 (int)status, keycode, mods);
				continue;
			}
			// store if only one char and not already in store
			if ((1 != len) || (0 < unicodeToKeycodeWithModsMap.count(unicodeString[0]))) continue;
			unicodeToKeycodeWithModsMap[unicodeString[0]] = (KeycodeWithMods){ keycode, mods };
		}
	}
}

KeySym HelperMacX::keycodeToKeysym(uint keycode){
	TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardInputSource();
	if (NULL == inputSourceRef) {
		qWarning("HelperMacX::keycodeToKeysym: inputSourceRef is NULL");
		return NoSymbol;
	}
	CFDataRef unicodeKeyLayoutDataRef = (CFDataRef)TISGetInputSourceProperty(inputSourceRef,
		kTISPropertyUnicodeKeyLayoutData);
	if (NULL == unicodeKeyLayoutDataRef) {
		CFRelease(inputSourceRef);
		qWarning("HelperMacX::keycodeToKeysym: unicodeKeyLayoutDataRef is NULL");
		return NoSymbol;
	}
	UCKeyboardLayout *unicodeKeyLayoutDataPtr = (UCKeyboardLayout*)CFDataGetBytePtr(unicodeKeyLayoutDataRef);
	UInt32 deadKeyState = 0;
	UniChar unicodeString[8];
	UniCharCount len = 0;
	OSStatus status = UCKeyTranslate(unicodeKeyLayoutDataPtr, keycode, kUCKeyActionDown,
									 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask,
									 &deadKeyState, sizeof(unicodeString), &len, unicodeString);
	CFRelease(inputSourceRef);
	if (noErr != status) {
		qWarning("HelperMacX::keycodeToKeysym: UCKeyTranslate error: %d", (int)status);
		return NoSymbol;
	}
	return 1 != len ? NoSymbol : unicodeString[0];
}

uint HelperMacX::keysymToKeycode(KeySym keysym){
	return keysymToKeycodeWithMods(keysym).keycode;
}

static const KeycodeWithMods NoKeycodeWithMods = (KeycodeWithMods){ NoKeycode, 0 };

KeycodeWithMods HelperMacX::keysymToKeycodeWithMods(KeySym keysym){
	return 0 == unicodeToKeycodeWithModsMap.count(keysym)
			? NoKeycodeWithMods : unicodeToKeycodeWithModsMap[keysym];
}

void HelperMacX::processToFront(pid_t pid){
	OSStatus status;
	ProcessSerialNumber processSerialNumber;
	status = GetProcessForPID(pid, &processSerialNumber);
	if (noErr != status) {
		qWarning("HelperMacX::processToFront: GetProcessForPID error for pid %d: %d", pid, (int)status);
		return;
	}
	status = SetFrontProcessWithOptions(&processSerialNumber, kSetFrontProcessFrontWindowOnly);
	if (noErr != status) {
		qWarning("HelperMacX::processToFront: SetFrontProcessWithOptions for pid %d: %d", pid, (int)status);
		return;
	}
}
