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

#include "x11_docking.h"

#include <qapplication.h>
#include <qtooltip.h>
#include <qobject.h>
#include <qtimer.h>

#include "../docking/docking.h"
#include "debug.h"
#include "config_file.h"
#include "kadu.h"
// #include "config_dialog.h"
#include "chat_manager.h"
#include "chat_widget.h"

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

/**
 * @ingroup x11_docking
 * @{
 */
// this is experimental option
//#define ENABLE_HIDING

extern Time qt_x_time;

static XErrorHandler old_handler = 0;
static int dock_xerror = 0;

static int dock_xerrhandler(Display* dpy, XErrorEvent* err)
{
	dock_xerror = err->error_code;
	return old_handler(dpy, err);
}

static void trap_errors()
{
	dock_xerror = 0;
	old_handler = XSetErrorHandler(dock_xerrhandler);
}

static bool untrap_errors()
{
	XSetErrorHandler(old_handler);
	return (dock_xerror == 0);
}

static bool send_message(
	Display* dpy, /* display */
	Window w,     /* sender (tray icon window) */
	long message, /* message opcode */
	long data1,   /* message data 1 */
	long data2,   /* message data 2 */
	long data3    /* message data 3 */
)
{
	XEvent ev;

	memset(&ev, 0, sizeof(ev));
	ev.xclient.type = ClientMessage;
	ev.xclient.window = w;
	ev.xclient.message_type = XInternAtom (dpy, "_NET_SYSTEM_TRAY_OPCODE", False );
	ev.xclient.format = 32;
	ev.xclient.data.l[0] = CurrentTime;
	ev.xclient.data.l[1] = message;
	ev.xclient.data.l[2] = data1;
	ev.xclient.data.l[3] = data2;
	ev.xclient.data.l[4] = data3;

	trap_errors();
	XSendEvent(dpy, w, False, NoEventMask, &ev);
	XSync(dpy, False);
	return untrap_errors();
}

#define SYSTEM_TRAY_REQUEST_DOCK    0
#define SYSTEM_TRAY_BEGIN_MESSAGE   1
#define SYSTEM_TRAY_CANCEL_MESSAGE  2

extern "C" int x11_docking_init()
{
	tray_restarter = new TrayRestarter();
	x11_tray_icon = new X11TrayIcon(NULL, "x11_tray_icon");
#ifdef ENABLE_HIDING
// 	ConfigDialog::addCheckBox("General", "grid-expert", QT_TRANSLATE_NOOP("@default", "Remove from taskbar (experimental)"), "HideTaskbar", false, 0, 0, Expert);
#endif
	return 0;
}

extern "C" void x11_docking_close()
{
	delete tray_restarter;
	delete x11_tray_icon;
	x11_tray_icon = NULL;
}


X11TrayIcon::X11TrayIcon(QWidget *parent, const char *name)
	: QLabel(parent, name, WMouseNoMask | WRepaintNoErase | WType_TopLevel | WStyle_Customize | WStyle_NoBorder | WStyle_StaysOnTop), timer()
{
	kdebugf();

	setBackgroundMode(X11ParentRelative);
	QPixmap pix = docking_manager->defaultPixmap();
	setMinimumSize(pix.size());
	QLabel::setPixmap(pix);
	resize(pix.size());
	setMouseTracking(true);
	setAlignment(Qt::AlignCenter);
	update();

	// avoid "hourglass effect" in KDE
	QWidget *w = new QWidget();
	w->setGeometry(-100,-100,10,10);
	w->show();
	w->hide();
	delete w;

	connect(docking_manager, SIGNAL(trayPixmapChanged(const QPixmap&, const QString &)), this, SLOT(setTrayPixmap(const QPixmap&, const QString &)));
	connect(docking_manager, SIGNAL(trayTooltipChanged(const QString&)), this, SLOT(setTrayTooltip(const QString&)));
	connect(docking_manager, SIGNAL(searchingForTrayPosition(QPoint&)), this, SLOT(findTrayPosition(QPoint&)));
	connect(docking_manager, SIGNAL(trayMovieChanged(const QMovie &)), this, SLOT(setTrayMovie(const QMovie &)));
	connect(chat_manager, SIGNAL(chatWidgetCreated(ChatWidget *)), this, SLOT(chatCreatedSlot(ChatWidget *)));
	connect(&timer, SIGNAL(timeout()), this, SLOT(tryToDock()));
	connect(&undockTimer, SIGNAL(timeout()), this, SLOT(undockAndTryToDock()));

	tryToDock();

#ifdef ENABLE_HIDING
	// disable Kadu icon on taskbar
	disableTaskbar();
	connect(kadu, SIGNAL(shown()), this, SLOT(disableTaskbar()));
//	connect(kadu, SIGNAL(minimized()), kadu, SLOT(hide()));
#endif

	kdebugf2();
}

void X11TrayIcon::tryToDockLater(int tm)
{
	timer.start(tm, true);
}

void X11TrayIcon::tryToDock()
{
	kdebugf();

	Display *dsp = x11Display();
	WId win = winId();

	XClassHint classhint;
	classhint.res_name  = (char*)"kadudock";
	classhint.res_class = (char*)"Kadu";
	XSetClassHint(dsp, win, &classhint);

	// 1. way
	// System Tray Protocol Specification
	// works on KDE 3.1 i GNOME 2.x
	Screen *screen = XDefaultScreenOfDisplay(dsp);
	int screen_id = XScreenNumberOfScreen(screen);
	char buf[32];
	snprintf(buf, sizeof(buf), "_NET_SYSTEM_TRAY_S%d", screen_id);
	Atom selection_atom = XInternAtom(dsp, buf, false);
	XGrabServer(dsp);
	Window manager_window = XGetSelectionOwner(dsp, selection_atom);
	if (manager_window != None)
		XSelectInput(dsp, manager_window, StructureNotifyMask);
	XUngrabServer(dsp);
	XFlush(dsp);
	if (manager_window != None)
		send_message(dsp, manager_window, SYSTEM_TRAY_REQUEST_DOCK, win, 0, 0);
	else
		kdebugm(KDEBUG_WARNING, "no manager_window!\n");

	// 2. way
	// works on KDE 3.0.x and probably older
	// and GNOME 1.x
	int r;
	int data = 1;
	r = XInternAtom(dsp, "KWM_DOCKWINDOW", false);
	XChangeProperty(dsp, win, r, r, 32, 0, (uchar *)&data, 1);
	r = XInternAtom(dsp, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", false);
	XChangeProperty(dsp, win, r, XA_WINDOW, 32, 0, (uchar *)&data, 1);

	if (manager_window != None)
	{
		docking_manager->setDocked(true);
//		show();
		// against stupid gnome notification area!
		QTimer::singleShot(500, this, SLOT(show()));

		// against 2 icons one above other (with a >10 pixel shift)
		QTimer::singleShot(600, this, SLOT(repaint()));
		QTimer::singleShot(1000, this, SLOT(repaint()));
	}
	else
	{
		// try again in 3 seconds
		tryToDockLater(3000);
	}

	kdebugf2();
}

void X11TrayIcon::chatCreatedSlot(ChatWidget *chat)
{
	kdebugf();

	Display *dsp = x11Display();
	WId win = chat->winId();

	XClassHint classhint;
	classhint.res_name  = (char*)"kadu-chat";
	classhint.res_class = (char*)"Kadu";
	XSetClassHint(dsp, win, &classhint);
	kdebugf2();
}

void X11TrayIcon::disableTaskbar()
{
#ifdef ENABLE_HIDING
	if (config_file.readBoolEntry("General", "HideTaskbar"))
		enableTaskbar(false);
#endif
}

void X11TrayIcon::enableTaskbar(bool enable)
{
#ifdef ENABLE_HIDING
	static Display *dsp = x11Display();
	static XEvent e;
	static bool set=false;

	Screen *screen = XDefaultScreenOfDisplay(dsp);
	int screen_id = XScreenNumberOfScreen(screen);
	WId rootWindow=QApplication::desktop()->screen(screen_id)->winId();

	if (!set)
	{
		memset(&e, 0, sizeof(e));
		e.xclient.type=ClientMessage;
		e.xclient.message_type=XInternAtom(dsp, "_NET_WM_STATE", False);
//		e.xclient.display=dsp;
		e.xclient.window=kadu->winId();
		e.xclient.format=32;
		e.xclient.data.l[1]=XInternAtom(dsp, "_NET_WM_STATE_SKIP_TASKBAR", False);
		set=true;
	}
	e.xclient.data.l[0] = (!enable);

	trap_errors();
	XSendEvent(dsp, rootWindow, False, (SubstructureRedirectMask|SubstructureNotifyMask), &e);
	XSync(dsp, False);
	untrap_errors();
#endif
}

X11TrayIcon::~X11TrayIcon()
{
	kdebugf();
#ifdef ENABLE_HIDING
//	disconnect(kadu, SIGNAL(minimized()), kadu, SLOT(hide()));
	disconnect(kadu, SIGNAL(shown()), this, SLOT(disableTaskbar()));
	enableTaskbar();
#endif
	disconnect(docking_manager, SIGNAL(trayMovieChanged(const QMovie &)), this, SLOT(setTrayMovie(const QMovie &)));
	disconnect(docking_manager, SIGNAL(trayPixmapChanged(const QPixmap&, const QString &)), this, SLOT(setTrayPixmap(const QPixmap&, const QString &)));
	disconnect(docking_manager, SIGNAL(trayTooltipChanged(const QString&)), this, SLOT(setTrayTooltip(const QString&)));
	disconnect(docking_manager, SIGNAL(searchingForTrayPosition(QPoint&)), this, SLOT(findTrayPosition(QPoint&)));
	disconnect(chat_manager, SIGNAL(chatWidgetCreated(ChatWidget *)), this, SLOT(chatCreatedSlot(ChatWidget *)));

	docking_manager->setDocked(false);

	kdebugf2();
}

void X11TrayIcon::findTrayPosition(QPoint& pos)
{
	pos = mapToGlobal(QPoint(0,0));
}

void X11TrayIcon::setTrayPixmap(const QPixmap& pixmap, const QString &/*iconName*/)
{
	QLabel::setPixmap(pixmap);
	repaint();
}

void X11TrayIcon::setTrayMovie(const QMovie &movie)
{
	QLabel::setMovie(movie);
	repaint();
}

void X11TrayIcon::setTrayTooltip(const QString& tooltip)
{
	QToolTip::add(this,tooltip);
}

void X11TrayIcon::enterEvent(QEvent* e)
{
	if (!qApp->focusWidget())
	{
		XEvent ev;
		memset(&ev, 0, sizeof(ev));
		ev.xfocus.display = qt_xdisplay();
		ev.xfocus.type = FocusIn;
		ev.xfocus.window = winId();
		ev.xfocus.mode = NotifyNormal;
		ev.xfocus.detail = NotifyAncestor;
		Time time = qt_x_time;
		qt_x_time = 1;
		qApp->x11ProcessEvent( &ev );
		qt_x_time = time;
	}
	QWidget::enterEvent(e);
}

void X11TrayIcon::mousePressEvent(QMouseEvent * e)
{
	docking_manager->trayMousePressEvent(e);
}

bool X11TrayIcon::x11Event(XEvent *e)
{
//	kdebugmf(KDEBUG_FUNCTION_START, "%d\n", e->type);
	// when notification area/tray/kicker/... crashes, X server sends us an event (because we requested it by XSelectInput)
	if (e->type == ReparentNotify)
	{
		Window rootWin = RootWindow(x11Display(), 0);
		kdebugm(KDEBUG_INFO, "type: %d, event: %ld, window: %ld, parent: %ld, root: %ld\n", e->type, e->xreparent.event, e->xreparent.window, e->xreparent.parent, rootWin);
		if (rootWin == e->xreparent.parent)
			// try not to show main window on session logout when kicker/etc... closes before kadu
			undockAndTryToDockLater(1000);
	}
	else if (e->type == DestroyNotify) // happens only on xfce :/
	{
		kdebugm(KDEBUG_WARNING, "wooops, window destroyed\n");
		QTimer::singleShot(1000, tray_restarter, SLOT(restart()));
	}
	return false;
}

void X11TrayIcon::undockAndTryToDockLater(int tm)
{
	kdebugf();
	undockTimer.start(tm, true);
	kdebugf2();
}

void X11TrayIcon::undockAndTryToDock()
{
	kdebugf();
	docking_manager->setDocked(false);
	tryToDockLater(500);
	kdebugf2();
}

void TrayRestarter::restart()
{
	kdebugf();
	delete x11_tray_icon;
	x11_tray_icon = new X11TrayIcon(NULL, "x11_tray_icon");
	kdebugf2();
}
TrayRestarter *tray_restarter = NULL;

X11TrayIcon* x11_tray_icon = NULL;

/** @} */

