/* Copyright (C) 2009 TightVNC Team
 * Copyright (C) 2009 Red Hat, Inc.
 * Copyright 2013-2015 Pierre Ossman for Cendio AB
 *
 * This 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; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include "xorg-version.h"

#include <stdio.h>

#include <X11/keysym.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "xkbsrv.h"
#include "xkbstr.h"
#include "eventstr.h"
#include "scrnintstr.h"
#include "mi.h"

#include "Input.h"

#ifndef KEYBOARD_OR_FLOAT
#define KEYBOARD_OR_FLOAT MASTER_KEYBOARD
#endif

extern DeviceIntPtr vncKeyboardDev;

static void vncXkbProcessDeviceEvent(int screenNum,
                                     InternalEvent *event,
                                     DeviceIntPtr dev);

/* Stolen from libX11 */
static Bool
XkbTranslateKeyCode(register XkbDescPtr xkb, KeyCode key,
                    register unsigned int mods, unsigned int *mods_rtrn,
                    KeySym *keysym_rtrn)
{
	XkbKeyTypeRec *type;
	int col,nKeyGroups;
	unsigned preserve,effectiveGroup;
	KeySym *syms;

	if (mods_rtrn!=NULL)
		*mods_rtrn = 0;

	nKeyGroups= XkbKeyNumGroups(xkb,key);
	if ((!XkbKeycodeInRange(xkb,key))||(nKeyGroups==0)) {
		if (keysym_rtrn!=NULL)
			*keysym_rtrn = NoSymbol;
		return False;
	}

	syms = XkbKeySymsPtr(xkb,key);

	/* find the offset of the effective group */
	col = 0;
	effectiveGroup= XkbGroupForCoreState(mods);
	if ( effectiveGroup>=nKeyGroups ) {
		unsigned groupInfo= XkbKeyGroupInfo(xkb,key);
		switch (XkbOutOfRangeGroupAction(groupInfo)) {
		default:
			effectiveGroup %= nKeyGroups;
			break;
		case XkbClampIntoRange:
			effectiveGroup = nKeyGroups-1;
			break;
		case XkbRedirectIntoRange:
			effectiveGroup = XkbOutOfRangeGroupNumber(groupInfo);
			if (effectiveGroup>=nKeyGroups)
				effectiveGroup= 0;
			break;
		}
	}
	col= effectiveGroup*XkbKeyGroupsWidth(xkb,key);
	type = XkbKeyKeyType(xkb,key,effectiveGroup);

	preserve= 0;
	if (type->map) { /* find the column (shift level) within the group */
		register int i;
		register XkbKTMapEntryPtr entry;
		for (i=0,entry=type->map;i<type->map_count;i++,entry++) {
			if ((entry->active)&&((mods&type->mods.mask)==entry->mods.mask)) {
				col+= entry->level;
				if (type->preserve)
					preserve= type->preserve[i].mask;
				break;
			}
		}
	}

	if (keysym_rtrn!=NULL)
		*keysym_rtrn= syms[col];
	if (mods_rtrn)
		*mods_rtrn= type->mods.mask&(~preserve);

	return (syms[col]!=NoSymbol);
}

static XkbAction *XkbKeyActionPtr(XkbDescPtr xkb, KeyCode key, unsigned int mods)
{
	XkbKeyTypeRec *type;
	int col,nKeyGroups;
	unsigned effectiveGroup;
	XkbAction *acts;

	if (!XkbKeyHasActions(xkb, key))
		return NULL;

	nKeyGroups= XkbKeyNumGroups(xkb,key);
	if ((!XkbKeycodeInRange(xkb,key))||(nKeyGroups==0))
		return NULL;

	acts = XkbKeyActionsPtr(xkb,key);

	/* find the offset of the effective group */
	col = 0;
	effectiveGroup= XkbGroupForCoreState(mods);
	if ( effectiveGroup>=nKeyGroups ) {
		unsigned groupInfo= XkbKeyGroupInfo(xkb,key);
		switch (XkbOutOfRangeGroupAction(groupInfo)) {
		default:
			effectiveGroup %= nKeyGroups;
			break;
		case XkbClampIntoRange:
			effectiveGroup = nKeyGroups-1;
			break;
		case XkbRedirectIntoRange:
			effectiveGroup = XkbOutOfRangeGroupNumber(groupInfo);
			if (effectiveGroup>=nKeyGroups)
				effectiveGroup= 0;
			break;
		}
	}
	col= effectiveGroup*XkbKeyGroupsWidth(xkb,key);
	type = XkbKeyKeyType(xkb,key,effectiveGroup);

	if (type->map) { /* find the column (shift level) within the group */
		register int i;
		register XkbKTMapEntryPtr entry;
		for (i=0,entry=type->map;i<type->map_count;i++,entry++) {
			if ((entry->active)&&((mods&type->mods.mask)==entry->mods.mask)) {
				col+= entry->level;
				break;
			}
		}
	}

	return &acts[col];
}

static unsigned XkbKeyEffectiveGroup(XkbDescPtr xkb, KeyCode key, unsigned int mods)
{
	int nKeyGroups;
	unsigned effectiveGroup;

	nKeyGroups= XkbKeyNumGroups(xkb,key);
	if ((!XkbKeycodeInRange(xkb,key))||(nKeyGroups==0))
		return 0;

	effectiveGroup= XkbGroupForCoreState(mods);
	if ( effectiveGroup>=nKeyGroups ) {
		unsigned groupInfo= XkbKeyGroupInfo(xkb,key);
		switch (XkbOutOfRangeGroupAction(groupInfo)) {
		default:
			effectiveGroup %= nKeyGroups;
			break;
		case XkbClampIntoRange:
			effectiveGroup = nKeyGroups-1;
			break;
		case XkbRedirectIntoRange:
			effectiveGroup = XkbOutOfRangeGroupNumber(groupInfo);
			if (effectiveGroup>=nKeyGroups)
				effectiveGroup= 0;
			break;
		}
	}

	return effectiveGroup;
}

void vncPrepareInputDevices(void)
{
	/*
	 * Not ideal since these callbacks do not stack, but it's the only
	 * decent way we can reliably catch events for both the slave and
	 * master device.
	 */
	mieqSetHandler(ET_KeyPress, vncXkbProcessDeviceEvent);
	mieqSetHandler(ET_KeyRelease, vncXkbProcessDeviceEvent);
}

unsigned vncGetKeyboardState(void)
{
	DeviceIntPtr master;

	master = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT);
	return XkbStateFieldFromRec(&master->key->xkbInfo->state);
}

unsigned vncGetLevelThreeMask(void)
{
	unsigned state;
	KeyCode keycode;
	XkbDescPtr xkb;
	XkbAction *act;

	/* Group state is still important */
	state = vncGetKeyboardState();
	state &= ~0xff;

	keycode = vncKeysymToKeycode(XK_ISO_Level3_Shift, state, NULL);
	if (keycode == 0) {
		keycode = vncKeysymToKeycode(XK_Mode_switch, state, NULL);
		if (keycode == 0)
			return 0;
	}

	xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc;

	act = XkbKeyActionPtr(xkb, keycode, state);
	if (act == NULL)
		return 0;
	if (act->type != XkbSA_SetMods)
		return 0;

	if (act->mods.flags & XkbSA_UseModMapMods)
		return xkb->map->modmap[keycode];
	else
		return act->mods.mask;
}

KeyCode vncPressShift(void)
{
	unsigned state;

	XkbDescPtr xkb;
	unsigned int key;

	state = vncGetKeyboardState();
	if (state & ShiftMask)
		return 0;

	xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc;
	for (key = xkb->min_key_code; key <= xkb->max_key_code; key++) {
		XkbAction *act;
		unsigned char mask;

		act = XkbKeyActionPtr(xkb, key, state);
		if (act == NULL)
			continue;

		if (act->type != XkbSA_SetMods)
			continue;

		if (act->mods.flags & XkbSA_UseModMapMods)
			mask = xkb->map->modmap[key];
		else
			mask = act->mods.mask;

		if ((mask & ShiftMask) == ShiftMask)
			return key;
	}

	return 0;
}

size_t vncReleaseShift(KeyCode *keys, size_t maxKeys)
{
	size_t count;

	unsigned state;

	DeviceIntPtr master;
	XkbDescPtr xkb;
	unsigned int key;

	state = vncGetKeyboardState();
	if (!(state & ShiftMask))
		return 0;

	count = 0;

	master = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT);
	xkb = master->key->xkbInfo->desc;
	for (key = xkb->min_key_code; key <= xkb->max_key_code; key++) {
		XkbAction *act;
		unsigned char mask;

		if (!key_is_down(master, key, KEY_PROCESSED))
			continue;

		act = XkbKeyActionPtr(xkb, key, state);
		if (act == NULL)
			continue;

		if (act->type != XkbSA_SetMods)
			continue;

		if (act->mods.flags & XkbSA_UseModMapMods)
			mask = xkb->map->modmap[key];
		else
			mask = act->mods.mask;

		if (!(mask & ShiftMask))
			continue;

		if (count >= maxKeys)
			return 0;

		keys[count++] = key;
	}

	return count;
}

KeyCode vncPressLevelThree(void)
{
	unsigned state, mask;

	KeyCode keycode;
	XkbDescPtr xkb;
	XkbAction *act;

	mask = vncGetLevelThreeMask();
	if (mask == 0)
		return 0;

	state = vncGetKeyboardState();
	if (state & mask)
		return 0;

	keycode = vncKeysymToKeycode(XK_ISO_Level3_Shift, state, NULL);
	if (keycode == 0) {
		keycode = vncKeysymToKeycode(XK_Mode_switch, state, NULL);
		if (keycode == 0)
			return 0;
	}

	xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc;

	act = XkbKeyActionPtr(xkb, keycode, state);
	if (act == NULL)
		return 0;
	if (act->type != XkbSA_SetMods)
		return 0;

	return keycode;
}

size_t vncReleaseLevelThree(KeyCode *keys, size_t maxKeys)
{
	size_t count;

	unsigned state, mask;

	DeviceIntPtr master;
	XkbDescPtr xkb;
	unsigned int key;

	mask = vncGetLevelThreeMask();
	if (mask == 0)
		return 0;

	state = vncGetKeyboardState();
	if (!(state & mask))
		return 0;

	count = 0;

	master = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT);
	xkb = master->key->xkbInfo->desc;
	for (key = xkb->min_key_code; key <= xkb->max_key_code; key++) {
		XkbAction *act;
		unsigned char key_mask;

		if (!key_is_down(master, key, KEY_PROCESSED))
			continue;

		act = XkbKeyActionPtr(xkb, key, state);
		if (act == NULL)
			continue;

		if (act->type != XkbSA_SetMods)
			continue;

		if (act->mods.flags & XkbSA_UseModMapMods)
			key_mask = xkb->map->modmap[key];
		else
			key_mask = act->mods.mask;

		if (!(key_mask & mask))
			continue;

		if (count >= maxKeys)
			return 0;

		keys[count++] = key;
	}

	return count;
}

KeyCode vncKeysymToKeycode(KeySym keysym, unsigned state, unsigned *new_state)
{
	XkbDescPtr xkb;
	unsigned int key;
	KeySym ks;
	unsigned level_three_mask;

	if (new_state != NULL)
		*new_state = state;

	xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc;
	for (key = xkb->min_key_code; key <= xkb->max_key_code; key++) {
		unsigned int state_out;
		KeySym dummy;

		XkbTranslateKeyCode(xkb, key, state, &state_out, &ks);
		if (ks == NoSymbol)
			continue;

		/*
		 * Despite every known piece of documentation on
		 * XkbTranslateKeyCode() stating that mods_rtrn returns
		 * the unconsumed modifiers, in reality it always
		 * returns the _potentially consumed_ modifiers.
		 */
		state_out = state & ~state_out;
		if (state_out & LockMask)
			XkbConvertCase(ks, &dummy, &ks);

		if (ks == keysym)
			return key;
	}

	if (new_state == NULL)
		return 0;

	*new_state = (state & ~ShiftMask) |
	             ((state & ShiftMask) ? 0 : ShiftMask);
	key = vncKeysymToKeycode(keysym, *new_state, NULL);
	if (key != 0)
		return key;

	level_three_mask = vncGetLevelThreeMask();
	if (level_three_mask == 0)
		return 0;

	*new_state = (state & ~level_three_mask) | 
	             ((state & level_three_mask) ? 0 : level_three_mask);
	key = vncKeysymToKeycode(keysym, *new_state, NULL);
	if (key != 0)
		return key;

	*new_state = (state & ~(ShiftMask | level_three_mask)) | 
	             ((state & ShiftMask) ? 0 : ShiftMask) |
	             ((state & level_three_mask) ? 0 : level_three_mask);
	key = vncKeysymToKeycode(keysym, *new_state, NULL);
	if (key != 0)
		return key;

	return 0;
}

int vncIsLockModifier(KeyCode keycode, unsigned state)
{
	XkbDescPtr xkb;
	XkbAction *act;

	xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc;

	act = XkbKeyActionPtr(xkb, keycode, state);
	if (act == NULL)
		return 0;

	if (act->type != XkbSA_LockMods)
		return 0;

	return 1;
}

int vncIsAffectedByNumLock(KeyCode keycode)
{
	unsigned state;

	KeyCode numlock_keycode;
	unsigned numlock_mask;

	XkbDescPtr xkb;
	XkbAction *act;

	unsigned group;
	XkbKeyTypeRec *type;

	/* Group state is still important */
	state = vncGetKeyboardState();
	state &= ~0xff;

	/*
	 * Not sure if hunting for a virtual modifier called "NumLock",
	 * or following the keysym Num_Lock is the best approach. We
	 * try the latter.
	 */
	numlock_keycode = vncKeysymToKeycode(XK_Num_Lock, state, NULL);
	if (numlock_keycode == 0)
		return 0;

	xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc;

	act = XkbKeyActionPtr(xkb, numlock_keycode, state);
	if (act == NULL)
		return 0;
	if (act->type != XkbSA_LockMods)
		return 0;

	if (act->mods.flags & XkbSA_UseModMapMods)
		numlock_mask = xkb->map->modmap[keycode];
	else
		numlock_mask = act->mods.mask;

	group = XkbKeyEffectiveGroup(xkb, keycode, state);
	type = XkbKeyKeyType(xkb, keycode, group);
	if ((type->mods.mask & numlock_mask) == 0)
		return 0;

	return 1;
}

KeyCode vncAddKeysym(KeySym keysym, unsigned state)
{
	DeviceIntPtr master;
	XkbDescPtr xkb;
	unsigned int key;

	XkbEventCauseRec cause;
	XkbChangesRec changes;

	int types[1];
	KeySym *syms;
	KeySym upper, lower;

	master = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT);
	xkb = master->key->xkbInfo->desc;
	for (key = xkb->max_key_code; key >= xkb->min_key_code; key--) {
		if (XkbKeyNumGroups(xkb, key) == 0)
			break;
	}

	if (key < xkb->min_key_code)
		return 0;

	memset(&changes, 0, sizeof(changes));
	memset(&cause, 0, sizeof(cause));

	XkbSetCauseUnknown(&cause);

	/*
	 * Tools like xkbcomp get confused if there isn't a name
	 * assigned to the keycode we're trying to use.
	 */
	if (xkb->names && xkb->names->keys &&
	    (xkb->names->keys[key].name[0] == '\0')) {
		xkb->names->keys[key].name[0] = 'I';
		xkb->names->keys[key].name[1] = '0' + (key / 100) % 10;
		xkb->names->keys[key].name[2] = '0' + (key /  10) % 10;
		xkb->names->keys[key].name[3] = '0' + (key /   1) % 10;

		changes.names.changed |= XkbKeyNamesMask;
		changes.names.first_key = key;
		changes.names.num_keys = 1;
	}

	/* FIXME: Verify that ONE_LEVEL/ALPHABETIC isn't screwed up */

	/*
	 * For keysyms that are affected by Lock, we are better off
	 * using ALPHABETIC rather than ONE_LEVEL as the latter
	 * generally cannot produce lower case when Lock is active.
	 */
	XkbConvertCase(keysym, &lower, &upper);
	if (upper == lower)
		types[XkbGroup1Index] = XkbOneLevelIndex;
	else
		types[XkbGroup1Index] = XkbAlphabeticIndex;

	XkbChangeTypesOfKey(xkb, key, 1, XkbGroup1Mask, types, &changes.map);

	syms = XkbKeySymsPtr(xkb,key);
	if (upper == lower)
		syms[0] = keysym;
	else {
		syms[0] = lower;
		syms[1] = upper;
	}

	changes.map.changed |= XkbKeySymsMask;
	changes.map.first_key_sym = key;
	changes.map.num_key_syms = 1;

	XkbSendNotification(master, &changes, &cause);

	return key;
}

static void vncXkbProcessDeviceEvent(int screenNum,
                                     InternalEvent *event,
                                     DeviceIntPtr dev)
{
	unsigned int backupctrls;
	XkbControlsPtr ctrls;

	if (event->device_event.sourceid != vncKeyboardDev->id) {
		dev->public.processInputProc(event, dev);
		return;
	}

	/*
	 * We need to bypass AccessX since it is timing sensitive and
	 * the network can cause fake event delays.
	 */
	ctrls = dev->key->xkbInfo->desc->ctrls;
	backupctrls = ctrls->enabled_ctrls;
	ctrls->enabled_ctrls &= ~XkbAllFilteredEventsMask;

	/*
	 * This flag needs to be set for key repeats to be properly
	 * respected.
	 */
	if ((event->device_event.type == ET_KeyPress) &&
	    key_is_down(dev, event->device_event.detail.key, KEY_PROCESSED))
		event->device_event.key_repeat = TRUE;

	dev->public.processInputProc(event, dev);

	ctrls->enabled_ctrls = backupctrls;
}
