/***************************************************************************
 *                                                                         *
 *   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; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/




#include <qapplication.h>
#include <qmap.h>
#include <qmessagebox.h>
#include <qstringlist.h>
#include <qthread.h>
#include <qvaluelist.h>

#include "config_file.h"
#include "debug.h"
#include "kadu.h"

#include "activate.h"
#include "chat_manager.h"
#include "misc.h"
#include "pending_msgs.h"

#include "globalhotkeys.h"

#include <X11/Xlib.h>




GlobalHotkeys *globalHotkeys;




extern "C" int globalhotkeys_init()
{
	kdebugf();
	globalHotkeys = new GlobalHotkeys();
	MainConfigurationWindow::registerUiFile( dataPath("kadu/modules/configuration/globalhotkeys.ui"), globalHotkeys );
	kdebugf2();
	return 0;
}


extern "C" void globalhotkeys_close()
{
	kdebugf();
	MainConfigurationWindow::unregisterUiFile( dataPath("kadu/modules/configuration/globalhotkeys.ui"), globalHotkeys );
	delete globalHotkeys;
	globalHotkeys = NULL;
	kdebugf2();
}




Hotkey::Hotkey( bool _shift, bool _control, bool _alt, bool _altgr, bool _super, int _keycode, QString _comment )
{
	shift   = _shift;
	control = _control;
	alt     = _alt;
	altgr   = _altgr;
	super   = _super;
	keycode = _keycode;
	comment = _comment;
}


Hotkey::Hotkey( QString hotkeystring )
{
	// remove "white" characters from begining and end of the string
	hotkeystring = hotkeystring.stripWhiteSpace();
	// initial values
	shift   = false;
	control = false;
	alt     = false;
	altgr   = false;
	super   = false;
	keycode = 0;
	// return if the string is empty
	if( hotkeystring.isEmpty() ) return;
	// split hotkeystring into parts using "+" as the separator
	QStringList parts = QStringList::split( QString("+"), hotkeystring, true );
	// set Shift
	if( parts.contains( "Shift" ) > 0 )
		shift = true;
	// set Control
	if( parts.contains( "Control" ) > 0 )
		control = true;
	// set Alt
	if( parts.contains( "Alt" ) > 0 )
		alt = true;
	// set AltGr
	if( parts.contains( "AltGr" ) > 0 )
		altgr = true;
	// set Super
	if( parts.contains( "Super" ) > 0 )
		super = true;
	bool ok;
	keycode = parts.last().toInt( &ok );
	if( ! ok )  // if the last part of hotkey string is not an integer
	{
		KeySym keysym = XStringToKeysym( parts.last().ascii() );
		if( keysym != NoSymbol )
		{
			keycode = XKeysymToKeycode( qt_xdisplay(), keysym );
		}
		else
		{
			QMessageBox::warning(
					kadu,
					qApp->translate( "@default", "Kadu - Global hotkeys" ),
					QString( qApp->translate( "@default", "Could not understand hotkey" ) ) + QString( " \"" ) + hotkeystring + QString( "\"." )
				);
		}
	}
	comment = hotkeystring;
}


bool Hotkey::equals( Hotkey *h )
{
	if( shift   != h->shift   ) return false;
	if( control != h->control ) return false;
	if( alt     != h->alt     ) return false;
	if( altgr   != h->altgr   ) return false;
	if( super   != h->super   ) return false;
	if( keycode != h->keycode ) return false;
	return true;
}




void HotkeyThread::setDisplay( Display *_display )
{
	display = _display;
}


void HotkeyThread::run()
{
	XEvent event;
	bool mod_shift;
	bool mod_control;
	bool mod_alt;
	bool mod_altgr;
	bool mod_super;
	for(;;)
	{
		// get next event (if the events queue is empty, XNextEvent will block and wait for an event to occur)
		XNextEvent( display, &event );
		// is it KeyPress event?
		if( event.type == KeyPress )
		{
			// read modifiers state from current event
			mod_shift   = ( ( event.xkey.state & GLOBALHOTKEYS_X11SHIFTMASK   ) != 0 );
			mod_control = ( ( event.xkey.state & GLOBALHOTKEYS_X11CONTROLMASK ) != 0 );
			mod_alt     = ( ( event.xkey.state & GLOBALHOTKEYS_X11ALTMASK     ) != 0 );
			mod_altgr   = ( ( event.xkey.state & GLOBALHOTKEYS_X11ALTGRMASK   ) != 0 );
			mod_super   = ( ( event.xkey.state & GLOBALHOTKEYS_X11SUPERMASK   ) != 0 );
			// check hotkeys and execute related commands
			if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["ShowKadusMainWindow"] ) )
			{
				if( ! kadu->isVisible() )
				{
					// thread: lock application
					qApp->lock();
					// show and activate Kadu's main window
					kadu->show();
					kadu->raise();
					activateWindow( kadu->winId() );
					// thread: unlock application
					qApp->unlock();
				}
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["HideKadusMainWindow"] ) )
			{
				if( kadu->isVisible() )
				{
					// thread: lock application
					qApp->lock();
					// hide Kadu's main window
					kadu->hide();
					// thread: unlock application
					qApp->unlock();
				}
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["ShowHideKadusMainWindow"] ) )
			{
				if( ! kadu->isVisible() )
				{
					// thread: lock application
					qApp->lock();
					// show and activate Kadu's main window
					kadu->show();
					kadu->raise();
					activateWindow( kadu->winId() );
					// thread: unlock application
					qApp->unlock();
				}
				else
				{
					// thread: lock application
					qApp->lock();
					// hide Kadu's main window
					kadu->hide();
					// thread: unlock application
					qApp->unlock();
				}
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["OpenIncomingChatWindow"] ) )
			{
				// thread: lock application
				qApp->lock();
				if( pending.count() > 0 )
				{
					// open window for pending message(s)
					chat_manager->openPendingMsgs( true );
					// activate it
					QWidget *win = chat_manager->chats()[ chat_manager->chats().count() - 1 ];  // last created chat widget
					while( win->parent() )
						win = static_cast<QWidget *>(win->parent());
					win->show();
					win->raise();
					activateWindow( win->winId() );
				}
				else
				{
					// show window with new unread message(s)
					CONST_FOREACH( chat, chat_manager->chats() )
					{
						if( (*chat)->newMessagesCount() > 0 )
						{
							// reopen the chat (needed when Tabs module is active)
							chat_manager->openChatWidget( gadu, (*chat)->users()->toUserListElements() );
							// activate the window
							QWidget *win = (*chat);
							while( win->parent() )
								win = static_cast<QWidget *>(win->parent());
							win->show();
							win->raise();
							activateWindow( win->winId() );
							// done - only one window
							break;
						}
					}
				}
				// thread: unlock application
				qApp->unlock();
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["OpenAllIncomingChatWindows"] ) )
			{
				// thread: lock application
				qApp->lock();
				// open all windows for pending message(s)
				while( pending.count() > 0 )
				{
					// open the window
					chat_manager->openPendingMsgs( true );
					// activate it
					QWidget *win = chat_manager->chats()[ chat_manager->chats().count() - 1 ];  // last created chat widget
					while( win->parent() )
						win = static_cast<QWidget *>(win->parent());
					win->show();
					win->raise();
					activateWindow( win->winId() );
				}
				// show all windows with new unread message(s)
				CONST_FOREACH( chat, chat_manager->chats() )
				{
					if( (*chat)->newMessagesCount() > 0 )
					{
						// reopen the chat (needed when Tabs module is active)
						chat_manager->openChatWidget( gadu, (*chat)->users()->toUserListElements() );
						// activate the window
						QWidget *win = (*chat);
						while( win->parent() )
							win = static_cast<QWidget *>(win->parent());
						win->show();
						win->raise();
						activateWindow( win->winId() );
					}
				}
				// thread: unlock application
				qApp->unlock();
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["MinimizeOpenedChatWindows"] ) )
			{
				// thread: lock application
				qApp->lock();
				CONST_FOREACH( chat, chat_manager->chats() )
				{
					// minimize the window
					QWidget *win = (*chat);
					while( win->parent() )
						win = static_cast<QWidget *>(win->parent());
					if( ! win->isMinimized() )
						win->showMinimized();
				}
				// thread: unlock application
				qApp->unlock();
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["RestoreMinimizedChatWindows"] ) )
			{
				// thread: lock application
				qApp->lock();
				CONST_FOREACH( chat, chat_manager->chats() )
				{
					// restore the window
					QWidget *win = (*chat);
					while( win->parent() )
						win = static_cast<QWidget *>(win->parent());
					if( win->isMinimized() )
						win->showNormal();
				}
				// thread: unlock application
				qApp->unlock();
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["CloseAllChatWindows"] ) )
			{
				// thread: lock application
				qApp->lock();
				// list of windows to close (needed by Tabs module - we cannot close the same window multiple times!)
				QValueList<QWidget*> wins;
				CONST_FOREACH( chat, chat_manager->chats() )
				{
					// add the window to the list
					QWidget *win = (*chat);
					while( win->parent() )
						win = static_cast<QWidget *>(win->parent());
					if( wins.contains( win ) == 0 )  // if this window is not on the list yet
						wins.push_back( win );
				}
				// close the windows from the list
				for( QValueList<QWidget*>::iterator I = wins.begin(); I != wins.end(); I++ )
				{
					(*I)->close();
				}
				// thread: unlock application
				qApp->unlock();
			}
			else if( ( new Hotkey( mod_shift, mod_control, mod_alt, mod_altgr, mod_super, event.xkey.keycode ) )->equals( hotkeys["OpenChatWith"] ) )
			{
				// thread: lock application
				qApp->lock();
				// open chat with ...
				QWidget *openchatwith = new OpenChatWith( kadu , "open_chat_with" );
				openchatwith->move(
						( qApp->desktop()->width()  - openchatwith->width()  ) / 2,
						( qApp->desktop()->height() - openchatwith->height() ) / 2
					);
				openchatwith->show();
				openchatwith->raise();
				activateWindow( openchatwith->winId() );
				// thread: unlock application
				qApp->unlock();
			}

		}
	}
}


void HotkeyThread::setHotkeys( QMap<QString,Hotkey*> _hotkeys )
{
	hotkeys = _hotkeys;
}




GlobalHotkeys::GlobalHotkeys() : QObject( NULL, "globalhotkeys" )
{
	// configuration handling
	createDefaultConfiguration();
	// read the configuration and force its usage
	display = NULL;
	configurationUpdated();
}


GlobalHotkeys::~GlobalHotkeys()
{
	if( hotkeyThread.running() )
	{
		hotkeyThread.terminate();
		hotkeyThread.wait();
	}
	if( display != NULL )
	{
		XCloseDisplay( display );
	}
}


void GlobalHotkeys::mainConfigurationWindowCreated( MainConfigurationWindow *mainConfigurationWindow )
{
}


void GlobalHotkeys::createDefaultConfiguration()
{
	config_file.addVariable( "GlobalHotkeys", "ShowKadusMainWindow"        , "" );
	config_file.addVariable( "GlobalHotkeys", "HideKadusMainWindow"        , "" );
	config_file.addVariable( "GlobalHotkeys", "ShowHideKadusMainWindow"    , "" );
	config_file.addVariable( "GlobalHotkeys", "OpenIncomingChatWindow"     , "" );
	config_file.addVariable( "GlobalHotkeys", "OpenAllIncomingChatWindows" , "" );
	config_file.addVariable( "GlobalHotkeys", "MinimizeOpenedChatWindows"  , "" );
	config_file.addVariable( "GlobalHotkeys", "RestoreMinimizedChatWindows", "" );
	config_file.addVariable( "GlobalHotkeys", "CloseAllChatWindows"        , "" );
	config_file.addVariable( "GlobalHotkeys", "OpenChatWith"               , "" );

}


void GlobalHotkeys::configurationUpdated()
{
	// update configuration data
	showKadusMainWindow         = config_file.readEntry( "GlobalHotkeys", "ShowKadusMainWindow"         );
	hideKadusMainWindow         = config_file.readEntry( "GlobalHotkeys", "HideKadusMainWindow"         );
	showHideKadusMainWindow     = config_file.readEntry( "GlobalHotkeys", "ShowHideKadusMainWindow"     );
	openIncomingChatWindow      = config_file.readEntry( "GlobalHotkeys", "OpenIncomingChatWindow"      );
	openAllIncomingChatWindows  = config_file.readEntry( "GlobalHotkeys", "OpenAllIncomingChatWindows"  );
	minimizeOpenedChatWindows   = config_file.readEntry( "GlobalHotkeys", "MinimizeOpenedChatWindows"   );
	restoreMinimizedChatWindows = config_file.readEntry( "GlobalHotkeys", "RestoreMinimizedChatWindows" );
	closeAllChatWindows         = config_file.readEntry( "GlobalHotkeys", "CloseAllChatWindows"         );
	openChatWith                = config_file.readEntry( "GlobalHotkeys", "OpenChatWith"                );

	// stop hotkeys thread
	if( hotkeyThread.running() )
	{
		hotkeyThread.terminate();
		hotkeyThread.wait();
	}
	// new X11 display
	if( display != NULL )
	{
		XCloseDisplay( display );
	}
	display = XOpenDisplay( 0 );
	hotkeyThread.setDisplay( display );
	// update and grab hotkeys
	QMap<QString,Hotkey*> hotkeys;
	hotkeys.insert( "ShowKadusMainWindow"        , new Hotkey( showKadusMainWindow         ) );
	hotkeys.insert( "HideKadusMainWindow"        , new Hotkey( hideKadusMainWindow         ) );
	hotkeys.insert( "ShowHideKadusMainWindow"    , new Hotkey( showHideKadusMainWindow     ) );
	hotkeys.insert( "OpenIncomingChatWindow"     , new Hotkey( openIncomingChatWindow      ) );
	hotkeys.insert( "OpenAllIncomingChatWindows" , new Hotkey( openAllIncomingChatWindows  ) );
	hotkeys.insert( "MinimizeOpenedChatWindows"  , new Hotkey( minimizeOpenedChatWindows   ) );
	hotkeys.insert( "RestoreMinimizedChatWindows", new Hotkey( restoreMinimizedChatWindows ) );
	hotkeys.insert( "CloseAllChatWindows"        , new Hotkey( closeAllChatWindows         ) );
	hotkeys.insert( "OpenChatWith"               , new Hotkey( openChatWith                ) );

	hotkeyThread.setHotkeys( hotkeys );
	grabHotkeys( hotkeys );
	// start hotkeys thread
	hotkeyThread.start();
}


int GlobalHotkeys::grabHotkeys( QMap<QString,Hotkey*> hotkeys )
{
	uint modifiers;
	for( QMap<QString,Hotkey*>::Iterator I = hotkeys.begin(); I != hotkeys.end(); I++ )
	{
		if( (*I)->keycode != 0 )
		{
			modifiers = 0;
			modifiers |= ( (*I)->shift   ? GLOBALHOTKEYS_X11SHIFTMASK   : 0 );
			modifiers |= ( (*I)->control ? GLOBALHOTKEYS_X11CONTROLMASK : 0 );
			modifiers |= ( (*I)->alt     ? GLOBALHOTKEYS_X11ALTMASK     : 0 );
			modifiers |= ( (*I)->altgr   ? GLOBALHOTKEYS_X11ALTGRMASK   : 0 );
			modifiers |= ( (*I)->super   ? GLOBALHOTKEYS_X11SUPERMASK   : 0 );
			// pure hotkey
			XGrabKey(
				display, (*I)->keycode, modifiers,
				DefaultRootWindow( display ), False, GrabModeAsync, GrabModeAsync
				);
			// hotkey with CapsLock
			XGrabKey(
				display, (*I)->keycode, modifiers | GLOBALHOTKEYS_X11CAPSLOCKMASK,
				DefaultRootWindow( display ), False, GrabModeAsync, GrabModeAsync
				);
			// hotkey with NumLock
			XGrabKey(
				display, (*I)->keycode, modifiers | GLOBALHOTKEYS_X11NUMLOCKMASK,
				DefaultRootWindow( display ), False, GrabModeAsync, GrabModeAsync
				);
			// hotkey with CapsLock and NumLock
			XGrabKey(
				display, (*I)->keycode, modifiers | GLOBALHOTKEYS_X11CAPSLOCKMASK | GLOBALHOTKEYS_X11NUMLOCKMASK,
				DefaultRootWindow( display ), False, GrabModeAsync, GrabModeAsync
				);
		}
	}
	return 0; /// only if success!
}
