/*
    kopeteaway.cpp  -  Kopete Away

    Copyright (c) 2002      by Hendrik vom Lehn       <hvl@linux-4-ever.de>
    Copyright (c) 2003      Olivier Goffart           <ogoffart @ kde.org>

    Kopete    (c) 2002-2003 by the Kopete developers  <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * This library is free software; you can redistribute it and/or         *
    * modify it under the terms of the GNU Lesser General Public            *
    * License as published by the Free Software Foundation; either          *
    * version 2 of the License, or (at your option) any later version.      *
    *                                                                       *
    *************************************************************************
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "kopeteaway.h"

#include "kopeteaccountmanager.h"
#include "kopeteaccount.h"
#include "kopeteonlinestatus.h"
#include "kopeteonlinestatusmanager.h"
#include "kopetecontact.h"
#include "kopeteprefs.h"

#include <tdeconfig.h>
#include <tqtimer.h>
#include <kapplication.h>
#include <dcopref.h>

#include <klocale.h>
#include <kglobal.h>
#include <kdebug.h>
#include <ksettings/dispatcher.h>

#ifdef TQ_WS_X11
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
// The following include is to make --enable-final work
#include <X11/Xutil.h>

#ifdef HAVE_XSCREENSAVER
#define HasScreenSaver
#include <X11/extensions/scrnsaver.h>
#endif
#endif // TQ_WS_X11

// As this is an untested X extension we better leave it off
#undef HAVE_XIDLE
#undef HasXidle


struct KopeteAwayPrivate
{
	TQString awayMessage;
	TQString autoAwayMessage;
	bool useAutoAwayMessage;
	bool globalAway;
	TQStringList awayMessageList;
	TQTime idleTime;
	TQTimer *timer;
	bool autoaway;
	bool goAvailable;
	int awayTimeout;
	bool useAutoAway;
	TQPtrList<Kopete::Account> autoAwayAccounts;

	int mouse_x;
	int mouse_y;
	unsigned int mouse_mask;
#ifdef TQ_WS_X11
	Window    root;               /* root window the pointer is on */
	Screen*   screen;             /* screen the pointer is on      */

	Time xIdleTime;
#endif
	bool useXidle;
	bool useMit;
};

Kopete::Away *Kopete::Away::instance = 0L;

Kopete::Away::Away() : TQObject( kapp , "Kopete::Away")
{
	int dummy = 0;
	dummy = dummy; // shut up

	d = new KopeteAwayPrivate;

	// Set up the away messages
	d->awayMessage = TQString();
	d->autoAwayMessage = TQString();
	d->useAutoAwayMessage = false;
	d->globalAway = false;
	d->autoaway = false;
	d->useAutoAway = true;

	// Empty the list
	d->awayMessageList.clear();

	// set the XAutoLock info
#ifdef TQ_WS_X11
	Display *dsp = tqt_xdisplay();
#endif
	d->mouse_x = d->mouse_y=0;
	d->mouse_mask = 0;
#ifdef TQ_WS_X11
	d->root = DefaultRootWindow (dsp);
	d->screen = ScreenOfDisplay (dsp, DefaultScreen (dsp));
#endif
	d->useXidle = false;
	d->useMit = false;
#ifdef HasXidle
	d->useXidle = XidleQueryExtension(tqt_xdisplay(), &dummy, &dummy);
#endif
#ifdef HasScreenSaver
	if(!d->useXidle)
		d->useMit = XScreenSaverQueryExtension(tqt_xdisplay(), &dummy, &dummy);
#endif
#ifdef TQ_WS_X11
	d->xIdleTime = 0;
#endif
	kdDebug(14010) << k_funcinfo << "Idle detection methods:" << endl;
	kdDebug(14010) << k_funcinfo << "\tKScreensaverIface::isBlanked()" << endl;
#ifdef TQ_WS_X11
	kdDebug(14010) << k_funcinfo << "\tX11 XQueryPointer()" << endl;
#endif
	if (d->useXidle)
	{
		kdDebug(14010) << k_funcinfo << "\tX11 Xidle extension" << endl;
	}
	if (d->useMit)
	{
		kdDebug(14010) << k_funcinfo << "\tX11 MIT Screensaver extension" << endl;
	}


	load();
	KSettings::Dispatcher::self()->registerInstance( TDEGlobal::instance(), this, TQT_SLOT( load() ) );
	// Set up the config object
	TDEConfig *config = TDEGlobal::config();
	/* Load the saved away messages */
	config->setGroup("Away Messages");

	// Away Messages
	if(config->hasKey("Messages"))
	{
		d->awayMessageList = config->readListEntry("Messages");
	}
	else if(config->hasKey("Titles"))  // Old config format
	{
		TQStringList titles = config->readListEntry("Titles");  // Get the titles
		for(TQStringList::iterator i = titles.begin(); i != titles.end(); ++i)
		{
			d->awayMessageList.append( config->readEntry(*i) ); // And add it to the list
		}

		/* Save this list to disk */
		save();
	}
	else
	{
		d->awayMessageList.append( i18n( "Sorry, I am busy right now" ) );
		d->awayMessageList.append( i18n( "I am gone right now, but I will be back later" ) );

		/* Save this list to disk */
		save();
	}

	// Auto away message
	if(config->hasKey("AutoAwayMessage"))
	{
		d->autoAwayMessage = config->readEntry("AutoAwayMessage");
	}
	else
	{
		d->autoAwayMessage = i18n( "I am gone right now, but I will be back later" );
		
		// Save the default auto away message to disk
		save();
	}
	
	// init the timer
	d->timer = new TQTimer(this, "AwayTimer");
	connect(d->timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotTimerTimeout()));
	d->timer->start(4000);

	//init the time and other
	setActive();
}

Kopete::Away::~Away()
{
	if(this == instance)
		instance = 0L;	
	delete d;
}

TQString Kopete::Away::message()
{
	return getInstance()->d->awayMessage;
}

TQString Kopete::Away::autoAwayMessage()
{
	return getInstance()->d->autoAwayMessage;
}

void Kopete::Away::setGlobalAwayMessage(const TQString &message)
{
	if( !message.isEmpty() )
	{
		kdDebug(14010) << k_funcinfo <<
			"Setting global away message: " << message << endl;
		d->awayMessage = message;
	}
}

void Kopete::Away::setAutoAwayMessage(const TQString &message)
{
	if( !message.isEmpty() )
	{
		kdDebug(14010) << k_funcinfo <<
			"Setting auto away message: " << message << endl;
		d->autoAwayMessage = message;
		
		// Save the new auto away message to disk
		save();
	}
}

Kopete::Away *Kopete::Away::getInstance()
{
	if (!instance)
		instance = new Kopete::Away;
	return instance;
}

bool Kopete::Away::globalAway()
{
	return getInstance()->d->globalAway;
}

void Kopete::Away::setGlobalAway(bool status)
{
	getInstance()->d->globalAway = status;
}

void Kopete::Away::save()
{
	TDEConfig *config = TDEGlobal::config();
	/* Set the away message settings in the Away Messages config group */
	config->setGroup("Away Messages");
	config->writeEntry("Messages", d->awayMessageList);
	config->writeEntry("AutoAwayMessage",  d->autoAwayMessage);
	config->sync();

	emit( messagesChanged() );
}

void Kopete::Away::load()
{
	TDEConfig *config = TDEGlobal::config();
	config->setGroup("AutoAway");
	d->awayTimeout=config->readNumEntry("Timeout", 600);
	d->goAvailable=config->readBoolEntry("GoAvailable", true);
	d->useAutoAway=config->readBoolEntry("UseAutoAway", true);
	d->useAutoAwayMessage=config->readBoolEntry("UseAutoAwayMessage", false);
}

TQStringList Kopete::Away::getMessages()
{
	return d->awayMessageList;
}

TQString Kopete::Away::getMessage( uint messageNumber )
{
	TQStringList::iterator it = d->awayMessageList.at( messageNumber );
	if( it != d->awayMessageList.end() )
	{
		TQString str = *it;
		d->awayMessageList.prepend( str );
		d->awayMessageList.remove( it );
		save();
		return str;
	}
	else
	{
		return TQString();
	}
}

void Kopete::Away::addMessage(const TQString &message)
{
	d->awayMessageList.prepend( message );
	if( (int)d->awayMessageList.count() > KopetePrefs::prefs()->rememberedMessages() )
		d->awayMessageList.pop_back();
	save();
}

long int Kopete::Away::idleTime()
{
	//FIXME: the time is reset to zero if more than 24 hours are elapsed
	// we can imagine someone who leave his PC for several weeks
	return (d->idleTime.elapsed() / 1000);
}

void Kopete::Away::slotTimerTimeout()
{
	// Time to check whether we're active or autoaway.  We basically have two
	// bits of info to go on - KDE's screensaver status
	// (KScreenSaverIface::isBlanked()) and the X11 activity detection.
	//
	// Note that isBlanked() is a slight of a misnomer.  It returns true if we're:
	//  - using a non-locking screensaver, which is running, or
	//  - using a locking screensaver which is still locked, regardless of
	//    whether the user is trying to unlock it right now
	// Either way, it's only worth checking for activity if the screensaver
	// isn't blanked/locked, because activity while blanked is impossible and
	// activity while locked never matters (if there is any, it's probably just
	// the cleaner wiping the keyboard :).
	
	
	/* we should be able to respond to KDesktop queries to avoid a deadlock, so we allow the event loop to be called */
	static bool rentrency_protection=false;
	if(rentrency_protection)
		return;
	rentrency_protection=true;
	DCOPRef screenSaver("kdesktop", "KScreensaverIface");
	DCOPReply isBlanked = screenSaver.callExt("isBlanked" ,  DCOPRef::UseEventLoop, 10);
	rentrency_protection=false;
	if(!instance) //this may have been deleted in the event loop
		return;
	if (!(isBlanked.isValid() && isBlanked.type == "bool" && ((bool)isBlanked)))
	{
		// DCOP failed, or returned something odd, or the screensaver is
		// inactive, so check for activity the X11 way.  It's only worth
		// checking for autoaway if there's no activity, and because
		// Screensaver blanking/locking implies autoAway activation (see
		// KopeteIface::KopeteIface()), only worth checking autoAway when the
		// screensaver isn't running.
		if (isActivity())
		{
			setActive();
		}
		else if (!d->autoaway && d->useAutoAway && idleTime() > d->awayTimeout)
		{
			setAutoAway();
		}
	}
}

bool Kopete::Away::isActivity()
{
	// Copyright (c) 1999 Martin R. Jones <mjones@kde.org>
	//
	// KDE screensaver engine
	//
	// This module is a heavily modified xautolock.
	// In fact as of KDE 2.0 this code is practically unrecognisable as xautolock.

	bool activity = false;

#ifdef TQ_WS_X11
	Display *dsp = tqt_xdisplay();
	Window           dummy_w;
	int              dummy_c;
	unsigned int     mask;               /* modifier mask                 */
	int              root_x;
	int              root_y;

	/*
	*  Find out whether the pointer has moved. Using XQueryPointer for this
	*  is gross, but it also is the only way never to mess up propagation
	*  of pointer events.
	*
	*  Remark : Unlike XNextEvent(), XPending () doesn't notice if the
	*           connection to the server is lost. For this reason, earlier
	*           versions of xautolock periodically called XNoOp (). But
	*           why not let XQueryPointer () do the job for us, since
	*           we now call that periodically anyway?
	*/
	if (!XQueryPointer (dsp, d->root, &(d->root), &dummy_w, &root_x, &root_y,
			&dummy_c, &dummy_c, &mask))
	{
		/*
		*  Pointer has moved to another screen, so let's find out which one.
		*/
		for (int i = 0; i < ScreenCount(dsp); i++)
		{
			if (d->root == RootWindow(dsp, i))
			{
				d->screen = ScreenOfDisplay (dsp, i);
				break;
			}
		}
	}

	// =================================================================================

	Time xIdleTime = 0; // millisecs since last input event

	#ifdef HasXidle
	if (d->useXidle)
	{
		XGetIdleTime(dsp, &xIdleTime);
	}
	else
	#endif /* HasXIdle */

	{
	#ifdef HasScreenSaver
		if(d->useMit)
		{
			static XScreenSaverInfo* mitInfo = 0;
			if (!mitInfo) mitInfo = XScreenSaverAllocInfo();
			XScreenSaverQueryInfo (dsp, d->root, mitInfo);
			xIdleTime = mitInfo->idle;
		}
	#endif /* HasScreenSaver */
	}

	// =================================================================================

	// Only check idle time if we have some way of measuring it, otherwise if
	// we've neither Mit nor Xidle it'll still be zero and we'll always appear active.
	// FIXME: what problem does the 2000ms fudge solve?
	if (root_x != d->mouse_x || root_y != d->mouse_y || mask != d->mouse_mask
		|| ((d->useXidle || d->useMit) && xIdleTime < d->xIdleTime + 2000))
	{
		// -1 => just gone autoaway, ignore apparent activity this time round
		// anything else => genuine activity
		// See setAutoAway().
		if (d->mouse_x != -1)
		{
			activity = true;
		}
		d->mouse_x = root_x;
		d->mouse_y = root_y;
		d->mouse_mask = mask;
		d->xIdleTime = xIdleTime;
	}
#endif // TQ_WS_X11
	// =================================================================================

	return activity;
}

void Kopete::Away::setActive()
{
//	kdDebug(14010) << k_funcinfo << "Found activity on desktop, resetting away timer" << endl;
	d->idleTime.start();

	if(d->autoaway)
	{
		d->autoaway = false;
		emit activity();
		if (d->goAvailable)
		{
			d->autoAwayAccounts.setAutoDelete(false);
			for(Kopete::Account *i=d->autoAwayAccounts.first() ; i; i=d->autoAwayAccounts.current() )
			{
				if(i->isConnected() && i->isAway())
				{
					i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() ,
										Kopete::OnlineStatusManager::Online ) );
				}

				// remove() makes the next entry in the list the current one,
				// that's why we use current() above
				d->autoAwayAccounts.remove();
			}
		}
	}
}

void Kopete::Away::setAutoAway()
{
	// A value of -1 in mouse_x indicates to checkActivity() that next time it
	// fires it should ignore any apparent idle/mouse/keyboard changes.
	// I think the point of this is that if you manually start the screensaver
	// then there'll unavoidably be some residual mouse/keyboard activity
	// that should be ignored.
	d->mouse_x = -1;

//	kdDebug(14010) << k_funcinfo << "Going AutoAway!" << endl;
	d->autoaway = true;

	// Set all accounts that are not away already to away.
	// We remember them so later we only set the accounts to
	// available that we set to away (and not the user).
	TQPtrList<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts();
	for(Kopete::Account *i=accounts.first() ; i; i=accounts.next()  )
	{
		if(i->myself()->onlineStatus().status() == Kopete::OnlineStatus::Online)
		{
			d->autoAwayAccounts.append(i);
			
			if(d->useAutoAwayMessage)
			{
			// Display a specific away message
			i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() ,
				Kopete::OnlineStatusManager::Idle ) ,
								getInstance()->d->autoAwayMessage);
			}
			else
			{
			// Display the last away message used
			i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() ,
				Kopete::OnlineStatusManager::Idle ) ,
								getInstance()->d->awayMessage);
			}
		}
	}
}

#include "kopeteaway.moc"
// vim: set et ts=4 sts=4 sw=4: