/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2003 Chris Schoeneman
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 * 
 * This package 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.
 */

#include "CScreen.h"
#include "IPlatformScreen.h"
#include "IScreenReceiver.h"
#include "ISecondaryScreen.h"
#include "ProtocolTypes.h"
#include "CLock.h"
#include "CThread.h"
#include "CLog.h"

//
// CScreen
//

CScreen::CScreen(IPlatformScreen* platformScreen, IScreenReceiver* receiver) :
	m_screen(platformScreen),
	m_receiver(receiver),
	m_isPrimary(platformScreen->isPrimary()),
	m_enabled(false),
	m_entered(m_isPrimary),
	m_toggleKeys(0),
	m_screenSaverSync(true)
{
	// do nothing
}

CScreen::~CScreen()
{
	delete m_screen;
}

void
CScreen::open()
{
	CLock lock(&m_mutex);

	// open screen
	m_screen->open(this);

	// reset options
	resetOptions();

	LOG((CLOG_DEBUG "opened display"));
}

void
CScreen::close()
{
	CLock lock(&m_mutex);
	assert(!m_enabled);
	assert(m_entered == m_isPrimary);

	// close screen
	m_screen->close();

	LOG((CLOG_DEBUG "closed display"));
}

void
CScreen::enable()
{
	CLock lock(&m_mutex);
	assert(!m_enabled);

	m_screen->enable();
	if (m_isPrimary) {
		enablePrimary();
	}
	else {
		enableSecondary();
	}

	// note activation
	m_enabled = true;
}

void
CScreen::disable()
{
	CLock lock(&m_mutex);
	assert(m_enabled);

	m_screen->disable();
	if (m_isPrimary) {
		disablePrimary();
	}
	else {
		disableSecondary();
	}

	// note deactivation
	m_enabled = false;
}

void
CScreen::mainLoop()
{
	// change our priority
	CThread::getCurrentThread().setPriority(-14);

	// run event loop
	try {
		LOG((CLOG_DEBUG "entering event loop"));
		m_screen->mainLoop();
		LOG((CLOG_DEBUG "exiting event loop"));
	}
	catch (...) {
		LOG((CLOG_DEBUG "exiting event loop"));
		throw;
	}
}

void
CScreen::exitMainLoop()
{
	m_screen->exitMainLoop();
}

void
CScreen::enter()
{
	CLock lock(&m_mutex);
	assert(m_entered == false);
	LOG((CLOG_INFO "entering screen"));

	// now on screen
	m_entered = true;

	if (m_isPrimary) {
		enterPrimary();
	}
	else {
		enterSecondary();
	}
	m_screen->enter();
}

bool
CScreen::leave()
{
	CLock lock(&m_mutex);
	assert(m_entered == true);
	LOG((CLOG_INFO "leaving screen"));

	if (!m_screen->leave()) {
		return false;
	}
	if (m_isPrimary) {
		leavePrimary();
	}
	else {
		leaveSecondary();
	}

	// make sure our idea of clipboard ownership is correct
	m_screen->checkClipboards();

	// now not on screen
	m_entered = false;

	return true;
}

void
CScreen::reconfigure(UInt32 activeSides)
{
	assert(m_isPrimary);
	m_screen->reconfigure(activeSides);
}

void
CScreen::warpCursor(SInt32 x, SInt32 y)
{
	assert(m_isPrimary);
	m_screen->warpCursor(x, y);
}

void
CScreen::setClipboard(ClipboardID id, const IClipboard* clipboard)
{
	m_screen->setClipboard(id, clipboard);
}

void
CScreen::grabClipboard(ClipboardID id)
{
	m_screen->setClipboard(id, NULL);
}

void
CScreen::screensaver(bool activate)
{
	CLock lock(&m_mutex);

	if (!m_isPrimary) {
		// activate/deactivation screen saver iff synchronization enabled
		if (m_screenSaverSync) {
			m_screen->screensaver(activate);
		}
	}
}

void
CScreen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button)
{
	CLock lock(&m_mutex);
	assert(!m_isPrimary);

	// check for ctrl+alt+del emulation
	if (id == kKeyDelete &&
		(mask & (KeyModifierControl | KeyModifierAlt)) ==
				(KeyModifierControl | KeyModifierAlt)) {
		LOG((CLOG_DEBUG "emulating ctrl+alt+del press"));
		if (m_screen->fakeCtrlAltDel()) {
			return;
		}
	}

	// get the sequence of keys to simulate key press and the final
	// modifier state.
	Keystrokes keys;
	KeyButton key = m_screen->mapKey(keys, *this, id, mask, false);
	if (key == 0) {
		LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id));
		return;
	}
	if (keys.empty()) {
		// do nothing if there are no associated keys
		return;
	}

	// generate key events
	doKeystrokes(keys, 1);

	// note that key is down
	updateKeyState(button, key, true);
}

void
CScreen::keyRepeat(KeyID id,
				KeyModifierMask mask, SInt32 count, KeyButton button)
{
	CLock lock(&m_mutex);
	assert(!m_isPrimary);

	// if we haven't seen this button go down then ignore it
	ServerKeyMap::iterator index = m_serverKeyMap.find(button);
	if (index == m_serverKeyMap.end()) {
		return;
	}

	// get the sequence of keys to simulate key repeat and the final
	// modifier state.
	Keystrokes keys;
	KeyButton key = m_screen->mapKey(keys, *this, id, mask, true);
	if (key == 0) {
		LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id));
		return;
	}
	if (keys.empty()) {
		// do nothing if there are no associated keys
		return;
	}

	// if the keycode for the auto-repeat is not the same as for the
	// initial press then mark the initial key as released and the new
	// key as pressed.  this can happen when we auto-repeat after a
	// dead key.  for example, a dead accent followed by 'a' will
	// generate an 'a with accent' followed by a repeating 'a'.  the
	// keycodes for the two keysyms might be different.
	key &= 0xffu;
	if (key != index->second) {
		// replace key up with previous key id but leave key down
		// alone so it uses the new keycode and store that keycode
		// in the server key map.
		for (Keystrokes::iterator index2 = keys.begin();
								index2 != keys.end(); ++index2) {
			if ((index2->m_key & 0xffu) == key) {
				index2->m_key = index->second;
				break;
			}
		}

		// note that old key is now up
		m_keys[index->second]     &= ~kDown;
		m_fakeKeys[index->second] &= ~kDown;

		// map server key to new key
		index->second              = key;

		// note that new key is now down
		m_keys[index->second]     |= kDown;
		m_fakeKeys[index->second] |= kDown;
	}

	// generate key events
	doKeystrokes(keys, count);
}

void
CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button)
{
	CLock lock(&m_mutex);
	assert(!m_isPrimary);

	// if we haven't seen this button go down then ignore it
	ServerKeyMap::iterator index = m_serverKeyMap.find(button);
	if (index == m_serverKeyMap.end()) {
		return;
	}
	KeyButton key = index->second;

	// get the sequence of keys to simulate key release
	Keystrokes keys;
	Keystroke keystroke;
	keystroke.m_key    = key;
	keystroke.m_press  = false;
	keystroke.m_repeat = false;
	keys.push_back(keystroke);

	// generate key events
	doKeystrokes(keys, 1);

	// note that key is now up
	updateKeyState(button, key, false);
}

void
CScreen::mouseDown(ButtonID button)
{
	assert(!m_isPrimary);
	m_screen->fakeMouseButton(button, true);
}

void
CScreen::mouseUp(ButtonID button)
{
	assert(!m_isPrimary);
	m_screen->fakeMouseButton(button, false);
}

void
CScreen::mouseMove(SInt32 x, SInt32 y)
{
	assert(!m_isPrimary);
	m_screen->fakeMouseMove(x, y);
}

void
CScreen::mouseWheel(SInt32 delta)
{
	assert(!m_isPrimary);
	m_screen->fakeMouseWheel(delta);
}

void
CScreen::resetOptions()
{
	CLock lock(&m_mutex);

	// reset options
	m_numLockHalfDuplex  = false;
	m_capsLockHalfDuplex = false;

	// if screen saver synchronization was off then turn it on since
	// that's the default option state.
	if (!m_screenSaverSync) {
		m_screenSaverSync = true;
		if (!m_isPrimary) {
			m_screen->openScreensaver(false);
		}
	}

	// let screen handle its own options
	m_screen->resetOptions();
}

void
CScreen::setOptions(const COptionsList& options)
{
	CLock lock(&m_mutex);

	// update options
	bool oldScreenSaverSync = m_screenSaverSync;
	for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
		if (options[i] == kOptionScreenSaverSync) {
			m_screenSaverSync = (options[i + 1] != 0);
			LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off"));
		}
		else if (options[i] == kOptionHalfDuplexCapsLock) {
			m_capsLockHalfDuplex = (options[i + 1] != 0);
			LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off"));
		}
		else if (options[i] == kOptionHalfDuplexNumLock) {
			m_numLockHalfDuplex = (options[i + 1] != 0);
			LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off"));
		}
	}

	// update screen saver synchronization
	if (!m_isPrimary && oldScreenSaverSync != m_screenSaverSync) {
		if (m_screenSaverSync) {
			m_screen->openScreensaver(false);
		}
		else {
			m_screen->closeScreensaver();
		}
	}

	// let screen handle its own options
	m_screen->setOptions(options);
}

UInt32
CScreen::addOneShotTimer(double timeout)
{
	return m_screen->addOneShotTimer(timeout);
}

bool
CScreen::isOnScreen() const
{
	CLock lock(&m_mutex);
	return m_entered;
}

void
CScreen::getClipboard(ClipboardID id,
				IClipboard* clipboard) const
{
	m_screen->getClipboard(id, clipboard);
}

SInt32
CScreen::getJumpZoneSize() const
{
	if (!m_isPrimary) {
		return 0;
	}
	else {
		return m_screen->getJumpZoneSize();
	}
}

bool
CScreen::isLockedToScreen() const
{
	// check for pressed mouse buttons
	if (m_screen->isAnyMouseButtonDown()) {
		LOG((CLOG_DEBUG "locked by mouse button"));
		return true;
	}

	// we don't keep primary key state up to date so get the
	// current state.
	const_cast<CScreen*>(this)->updateKeys();

	// check for scroll lock toggled on
	if (isModifierActive(KeyModifierScrollLock)) {
		LOG((CLOG_DEBUG "locked by scroll lock"));
		return true;
	}

	// check for any pressed key
	KeyButton key = isAnyKeyDown();
	if (key != 0) {
		LOG((CLOG_DEBUG "locked by %s", m_screen->getKeyName(key)));
		return true;
	}

	// not locked
	return false;
}

void
CScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
	m_screen->getShape(x, y, w, h);
}

void
CScreen::getCursorPos(SInt32& x, SInt32& y) const
{
	m_screen->getCursorPos(x, y);
}

void
CScreen::updateKeys()
{
	CLock lock(&m_mutex);

	// clear key state
	memset(m_keys,     0, sizeof(m_keys));
	memset(m_fakeKeys, 0, sizeof(m_fakeKeys));
	m_maskToKeys.clear();
	m_keyToMask.clear();

	// let subclass set m_keys
	m_screen->updateKeys();

	// figure out active modifier mask
	m_mask = getModifierMask();
	LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask));
}

void
CScreen::releaseKeys()
{
	CLock lock(&m_mutex);

	// release keys that we've synthesized a press for and only those
	// keys.  we don't want to synthesize a release on a key the user
	// is still physically pressing.
	for (KeyButton i = 1; i < 256; ++i) {
		if ((m_fakeKeys[i] & kDown) != 0) {
			fakeKeyEvent(i, false, false);
			m_keys[i]     &= ~kDown;
			m_fakeKeys[i] &= ~kDown;
		}
	}
}

void
CScreen::setKeyDown(KeyButton key)
{
	CLock lock(&m_mutex);

	m_keys[key & 0xffu] |= kDown;
}

void
CScreen::setToggled(KeyModifierMask mask)
{
	CLock lock(&m_mutex);

	if (!isToggle(mask)) {
		return;
	}
	MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
	if (i == m_maskToKeys.end()) {
		return;
	}
	for (KeyButtons::const_iterator j = i->second.begin();
							j != i->second.end(); ++j) {
		m_keys[(*j) & 0xffu] |= kToggled;
	}
}

void
CScreen::addModifier(KeyModifierMask mask, KeyButtons& keys)
{
	CLock lock(&m_mutex);

	// the modifier must have associated keys
	if (keys.empty()) {
		return;
	}

	// the mask must not be zero
	assert(mask != 0);

	// the mask must have exactly one high bit
	assert((mask & (mask - 1)) == 0);

	// index mask by keycodes
	for (KeyButtons::iterator j = keys.begin(); j != keys.end(); ++j) {
		// key must be valid
		assert(((*j) & 0xffu) != 0);
		m_keyToMask[static_cast<KeyButton>((*j) & 0xffu)] = mask;
	}

	// index keys by mask
	m_maskToKeys[mask].swap(keys);
}

void
CScreen::setToggleState(KeyModifierMask mask)
{
	// toggle modifiers that don't match the desired state
	KeyModifierMask different = (m_mask ^ mask);
	if ((different & KeyModifierCapsLock)   != 0) {
		toggleKey(KeyModifierCapsLock);
	}
	if ((different & KeyModifierNumLock)    != 0) {
		toggleKey(KeyModifierNumLock);
	}
	if ((different & KeyModifierScrollLock) != 0) {
		toggleKey(KeyModifierScrollLock);
	}
}

KeyButton
CScreen::isAnyKeyDown() const
{
	CLock lock(&m_mutex);

	for (UInt32 i = 1; i <  256; ++i) {
		if ((m_keys[i] & kDown) != 0) {
			return static_cast<KeyButton>(i);
		}
	}
	return 0;
}

bool
CScreen::isKeyDown(KeyButton key) const
{
	CLock lock(&m_mutex);

	key &= 0xffu;
	return (key != 0 && ((m_keys[key] & kDown) != 0));
}

bool
CScreen::isToggle(KeyModifierMask mask) const
{
	static const KeyModifierMask s_toggleMask =
		KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock;
	return ((mask & s_toggleMask) != 0);
}

bool
CScreen::isHalfDuplex(KeyModifierMask mask) const
{
	CLock lock(&m_mutex);

	return ((mask == KeyModifierCapsLock && m_capsLockHalfDuplex) ||
			(mask == KeyModifierNumLock  && m_numLockHalfDuplex));
}

bool
CScreen::isModifierActive(KeyModifierMask mask) const
{
	CLock lock(&m_mutex);

	MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
	if (i == m_maskToKeys.end()) {
		return false;
	}

	KeyButtons::const_iterator j = i->second.begin();
	if (isToggle(mask)) {
		// modifier is a toggle
		if (isKeyToggled(*j)) {
			return true;
		}
	}
	else {
		// modifier is not a toggle
		for (; j != i->second.end(); ++j) {
			if (isKeyDown(*j)) {
				return true;
			}
		}
	}
	return false;
}

KeyModifierMask
CScreen::getActiveModifiers() const
{
	CLock lock(&m_mutex);
	if (m_isPrimary) {
		// we don't keep primary key state up to date so get the
		// current state.
		const_cast<CScreen*>(this)->updateKeys();
	}
	return m_mask;
}

bool
CScreen::mapModifier(Keystrokes& keys, Keystrokes& undo,
				KeyModifierMask mask, bool desireActive) const
{
	CLock lock(&m_mutex);

	// look up modifier
	MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
	if (i == m_maskToKeys.end()) {
		return false;
	}

	// ignore if already in desired state
	if (isModifierActive(mask) == desireActive) {
		return true;
	}

	// initialize keystroke
	Keystroke keystroke;
	keystroke.m_repeat = false;

	// handle toggles
	if (isToggle(mask)) {
		keystroke.m_key   = i->second.front();
		keystroke.m_press = true;
		keys.push_back(keystroke);
		keystroke.m_press = false;
		keys.push_back(keystroke);
		keystroke.m_press = false;
		undo.push_back(keystroke);
		keystroke.m_press = true;
		undo.push_back(keystroke);
	}

	else if (desireActive) {
		// press
		keystroke.m_key   = i->second.front();
		keystroke.m_press = true;
		keys.push_back(keystroke);
		keystroke.m_press = false;
		undo.push_back(keystroke);
	}

	else {
		// releasing a modifier is quite different from pressing one.
		// when we release a modifier we have to release every keycode that
		// is assigned to the modifier since the modifier is active if any
		// one of them is down.  when we press a modifier we just have to
		// press one of those keycodes.
		for (KeyButtons::const_iterator j = i->second.begin();
								j != i->second.end(); ++j) {
			if (isKeyDown(*j)) {
				keystroke.m_key   = *j;
				keystroke.m_press = false;
				keys.push_back(keystroke);
				keystroke.m_press = true;
				undo.push_back(keystroke);
			}
		}
	}

	return true;
}

KeyModifierMask
CScreen::getMaskForKey(KeyButton key) const
{
	CLock lock(&m_mutex);
	KeyToMask::const_iterator i = m_keyToMask.find(key);
	if (i == m_keyToMask.end()) {
		return 0;
	}
	else {
		return i->second;
	}
}

void
CScreen::enablePrimary()
{
	// get notified of screen saver activation/deactivation
	m_screen->openScreensaver(true);

	// collect and send screen info
	CClientInfo info;
	m_screen->getShape(info.m_x, info.m_y, info.m_w, info.m_h);
	m_screen->getCursorPos(info.m_mx, info.m_my);
	info.m_zoneSize = getJumpZoneSize();
	m_receiver->onInfoChanged(info);
}

void
CScreen::enableSecondary()
{
	// assume primary has all clipboards
	for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
		grabClipboard(id);
	}

	// disable the screen saver if synchronization is enabled
	if (m_screenSaverSync) {
		m_screen->openScreensaver(false);
	}
}

void
CScreen::disablePrimary()
{
	// done with screen saver
	m_screen->closeScreensaver();
}

void
CScreen::disableSecondary()
{
	// done with screen saver
	m_screen->closeScreensaver();
}

void
CScreen::enterPrimary()
{
	// do nothing
}

void
CScreen::enterSecondary()
{
	// update our keyboard state to reflect the local state
	updateKeys();

	// remember toggle key state.  we'll restore this when we leave.
	m_toggleKeys = m_mask;
}

void
CScreen::leavePrimary()
{
	// do nothing
}

void
CScreen::leaveSecondary()
{
	// release any keys we think are still down
	releaseKeys();

	// restore toggle key state
	setToggleState(m_toggleKeys);
}

KeyModifierMask
CScreen::getModifierMask() const
{
	KeyModifierMask mask = 0;
	if (isModifierActive(KeyModifierShift)) {
		mask |= KeyModifierShift;
	}
	if (isModifierActive(KeyModifierControl)) {
		mask |= KeyModifierControl;
	}
	if (isModifierActive(KeyModifierAlt)) {
		mask |= KeyModifierAlt;
	}
	if (isModifierActive(KeyModifierMeta)) {
		mask |= KeyModifierMeta;
	}
	if (isModifierActive(KeyModifierSuper)) {
		mask |= KeyModifierSuper;
	}
	if (isModifierActive(KeyModifierModeSwitch)) {
		mask |= KeyModifierModeSwitch;
	}
	if (isModifierActive(KeyModifierNumLock)) {
		mask |= KeyModifierNumLock;
	}
	if (isModifierActive(KeyModifierCapsLock)) {
		mask |= KeyModifierCapsLock;
	}
	if (isModifierActive(KeyModifierScrollLock)) {
		mask |= KeyModifierScrollLock;
	}
	return mask;
}

void
CScreen::doKeystrokes(const Keystrokes& keys, SInt32 count)
{
	// do nothing if no keys or no repeats
	if (count < 1 || keys.empty()) {
		return;
	}

	// generate key events
	LOG((CLOG_DEBUG2 "keystrokes:"));
	for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) {
		if (k->m_repeat) {
			// repeat from here up to but not including the next key
			// with m_repeat == false count times.
			Keystrokes::const_iterator start = k;
			for (; count > 0; --count) {
				// send repeating events
				for (k = start; k != keys.end() && k->m_repeat; ++k) {
					fakeKeyEvent(k->m_key, k->m_press, true);
				}
			}

			// note -- k is now on the first non-repeat key after the
			// repeat keys, exactly where we'd like to continue from.
		}
		else {
			// send event
			fakeKeyEvent(k->m_key, k->m_press, false);

			// next key
			++k;
		}
	}
}

void
CScreen::fakeKeyEvent(KeyButton key, bool press, bool repeat) const
{
	// half-duplex keys are special.  we ignore releases and convert
	// a press when the toggle is active to a release.
	KeyModifierMask mask = getMaskForKey(key);
	if (isHalfDuplex(mask)) {
		if (repeat || !press) {
			return;
		}
		if (isModifierActive(mask)) {
			press = false;
		}
	}

	// send key event
	LOG((CLOG_DEBUG2 "  %d %s%s", key, press ? "down" : "up", repeat ? " repeat" : ""));
	m_screen->fakeKeyEvent(key, press);
}

void
CScreen::updateKeyState(KeyButton button, KeyButton key, bool press)
{
	// ignore bogus keys
	key &= 0xffu;
	if (button == 0 || key == 0) {
		return;
	}

	// update shadow state.  shadow state doesn't change on auto-repeat.
	if (press) {
		// key is now down
		m_serverKeyMap[button] = key;
		m_keys[key]           |= kDown;
		m_fakeKeys[key]       |= kDown;
	}
	else {
		// key is now up
		m_serverKeyMap.erase(button);
		m_keys[key]     &= ~kDown;
		m_fakeKeys[key] &= ~kDown;
	}
	KeyModifierMask mask = getMaskForKey(key);
	if (mask != 0) {
		// key is a modifier
		if (isToggle(mask)) {
			// key is a toggle modifier
			if (press) {
				m_keys[key] ^= kToggled;
				m_mask      ^= mask;

				// if key is half duplex then don't report it as down
				if (isHalfDuplex(mask)) {
					m_keys[key]     &= ~kDown;
					m_fakeKeys[key] &= ~kDown;
				}
			}
		}
		else {
			// key is a normal modifier
			if (press) {
				m_mask |= mask;
			}
			else if (!isModifierActive(mask)) {
				// no key for modifier is down anymore
				m_mask &= ~mask;
			}
		}
		LOG((CLOG_DEBUG2 "new mask: 0x%04x", m_mask));
	}
}

void
CScreen::toggleKey(KeyModifierMask mask)
{
	// get the system key ID for this toggle key ID
	MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
	if (i == m_maskToKeys.end()) {
		return;
	}
	KeyButton key = i->second.front();

	// toggle the key
	fakeKeyEvent(key, true, false);
	fakeKeyEvent(key, false, false);

	// toggle shadow state
	m_mask      ^= mask;
	key         &= 0xffu;
	m_keys[key] ^= kToggled;
}

bool
CScreen::isKeyToggled(KeyButton key) const
{
	key &= 0xffu;
	return (key != 0 && ((m_keys[key] & kToggled) != 0));
}