/* This file is part of the KDE libraries
   Copyright (C) 2005 Olivier Goffart <ogoffart @ kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "knotification.h"

#include <kdebug.h>
#include <kapplication.h>
#include <knotifyclient.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kiconloader.h>
#include <kconfig.h>
#include <kpassivepopup.h>
#include <kactivelabel.h>
#include <kprocess.h>
#include <kdialog.h>
#include <kmacroexpander.h>
#include <kwin.h>


#include <tqvbox.h>
#include <dcopclient.h>
#include <tqcstring.h>
#include <tqguardedptr.h>
#include <tqstylesheet.h>
#include <tqlabel.h>
#include <tqtimer.h>
#include <tqtabwidget.h>



//TODO,  make the KNotification aware of the systemtray.
#include "kopeteuiglobal.h"
static WId checkWinId( const TQString &/*appName*/, WId senderWinId )
{
	if(senderWinId==0)
		senderWinId=Kopete::UI::Global::sysTrayWId();

	return senderWinId;
}


struct KNotification::Private
{
	TQWidget *widget;
	TQString text;
	TQStringList actions;
	int level;
};

KNotification::KNotification(TQObject *parent) :
		TQObject(parent) , d(new Private)
{
	m_linkClicked = false;
}

KNotification::~KNotification() 
{
	delete d;
}


void KNotification::notifyByExecute(const TQString &command, const TQString& event,
							  const TQString& fromApp, const TQString& text,
							  int winId, int eventId)
{
	if (!command.isEmpty())
	{
	// kdDebug() << "executing command '" << command << "'" << endl;
		TQMap<TQChar,TQString> subst;
		subst.insert( 'e', event );
		subst.insert( 'a', fromApp );
		subst.insert( 's', text );
		subst.insert( 'w', TQString::number( winId ));
		subst.insert( 'i', TQString::number( eventId ));
		TQString execLine = KMacroExpander::expandMacrosShellQuote( command, subst );
		if ( execLine.isEmpty() )
			execLine = command; // fallback

		KProcess p;
		p.setUseShell(true);
		p << execLine;
		p.start(KProcess::DontCare);
//		return true;
	}
	//return false;
}


void KNotification::notifyByMessagebox()
{
		// ignore empty messages
	if ( d->text.isEmpty() )
		return;

	TQString action=d->actions[0];
	WId winId=d->widget ? d->widget->topLevelWidget()->winId()  : 0;

	if( action.isEmpty())
	{
	// display message box for specified event level
		switch( d->level )
		{
			default:
			case KNotifyClient::Notification:
				KMessageBox::informationWId( winId, d->text, i18n( "Notification" ) );
				break;
			case KNotifyClient::Warning:
				KMessageBox::sorryWId( winId, d->text, i18n( "Warning" ) );
				break;
			case KNotifyClient::Error:
				KMessageBox::errorWId( winId, d->text, i18n( "Error" ) );
				break;
			case KNotifyClient::Catastrophe:
				KMessageBox::errorWId( winId, d->text, i18n( "Fatal" ) );
				break;
		}
	}
	else
	{ //we may show the specific action button
		int result=0;
		TQGuardedPtr<KNotification> _this=this; //this can be deleted
		switch( d->level )
		{
			default:
			case KNotifyClient::Notification:
				result = KMessageBox::questionYesNo(d->widget, d->text, i18n( "Notification" ), action, KStdGuiItem::cancel(), TQString::null, false );
				break;
			case KNotifyClient::Warning:
				result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Warning" ), action, KStdGuiItem::cancel(), TQString::null, false );
				break;
			case KNotifyClient::Error:
				result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Error" ), action, KStdGuiItem::cancel(), TQString::null, false );
				break;
			case KNotifyClient::Catastrophe:
				result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Fatal" ), action, KStdGuiItem::cancel(), TQString::null, false );
				break;
		}
		if(result==KMessageBox::Yes && _this)
		{
			activate(0);
		}
	}
}



void KNotification::notifyByPassivePopup(const TQPixmap &pix )
{
	TQString appName = TQString::fromAscii( KNotifyClient::instance()->instanceName() );
	KIconLoader iconLoader( appName );
	KConfig eventsFile( TQString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data");
	KConfigGroup config( &eventsFile, "!Global!" );
	TQString iconName = config.readEntry( "IconName", appName );
	TQPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small );
	TQString title = config.readEntry( "Comment", appName );
    //KPassivePopup::message(title, text, icon, senderWinId);

	WId winId=d->widget ? d->widget->topLevelWidget()->winId()  : 0;
	
	KPassivePopup *pop = new KPassivePopup( checkWinId(appName, winId) );
	TQObject::connect(this, TQT_SIGNAL(closed()), pop, TQT_SLOT(deleteLater()));

	TQVBox *vb = pop->standardView( title, pix.isNull() ? d->text: TQString::null , icon );
	TQVBox *vb2=vb;

	if(!pix.isNull())
	{
		TQHBox *hb = new TQHBox(vb);
		hb->setSpacing(KDialog::spacingHint());
		TQLabel *pil=new TQLabel(hb);
		pil->setPixmap(pix);
		pil->setScaledContents(true);
		if(pix.height() > 80 && pix.height() > pix.width() )
		{
			pil->setMaximumHeight(80);
			pil->setMaximumWidth(80*pix.width()/pix.height());
		}
		else if(pix.width() > 80 && pix.height() <= pix.width())
		{
			pil->setMaximumWidth(80);
			pil->setMaximumHeight(80*pix.height()/pix.width());
		}
		vb=new TQVBox(hb);
		TQLabel *msg = new TQLabel( d->text, vb, "msg_label" );
		msg->setAlignment( AlignLeft );
	}


	if ( !d->actions.isEmpty() )
	{
		TQString linkCode=TQString::fromLatin1("<p align=\"right\">");
		int i=0;
		for ( TQStringList::ConstIterator it = d->actions.begin() ; it != d->actions.end(); ++it )
		{
			i++;
			linkCode+=TQString::fromLatin1("&nbsp;<a href=\"%1\">%2</a> ").arg( TQString::number(i) , TQStyleSheet::escape(*it)  );
		}
		linkCode+=TQString::fromLatin1("</p>");
		KActiveLabel *link = new KActiveLabel(linkCode , vb );
		//link->setAlignment( AlignRight );
		TQObject::disconnect(link, TQT_SIGNAL(linkClicked(const TQString &)), link, TQT_SLOT(openLink(const TQString &)));
		TQObject::connect(link, TQT_SIGNAL(linkClicked(const TQString &)), this, TQT_SLOT(slotPopupLinkClicked(const TQString &)));
		TQObject::connect(link, TQT_SIGNAL(linkClicked(const TQString &)), pop, TQT_SLOT(hide()));
	}

	pop->setAutoDelete( true );
	//pop->setTimeout(-1);

	pop->setView( vb2 );
	pop->show();

}

void KNotification::slotPopupLinkClicked(const TQString &adr)
{
	m_linkClicked = true;
	unsigned int action=adr.toUInt();
	if(action==0)
		return;

	activate(action);

	// since we've hidden the message (KNotification::notifyByPassivePopup(const TQPixmap &pix ))
	// we must now schedule overselves for deletion
	close();
}

void KNotification::activate(unsigned int action)
{
	if(action==0)
		emit activated();

	emit activated(action);
	deleteLater();
}


void KNotification::close()
{
	// if the user hasn't clicked the link, and if we got here, it means the dialog closed
	// and we were ignored
	if (!m_linkClicked)
	{
		emit ignored();
	}

	emit closed();
	deleteLater();
}


void KNotification::raiseWidget()
{
	if(!d->widget)
		return;
	
	raiseWidget(d->widget);
}


void KNotification::raiseWidget(TQWidget *w)
{
	//TODO  this funciton is far from finished.
	if(w->isTopLevel())
	{
		w->raise();
		KWin::activateWindow( w->winId() );
	}
	else
	{
		TQWidget *pw=w->parentWidget();
		raiseWidget(pw);

		if( TQTabWidget *tab_widget=dynamic_cast<TQTabWidget*>(pw))
		{
			tab_widget->showPage(w);
		}
	}
}





KNotification *KNotification::event( const TQString& message , const TQString& text,
			const TQPixmap& pixmap, TQWidget *widget,
			const TQStringList &actions, unsigned int flags)
{
	/* NOTE:  this function still use the KNotifyClient,
	 *        in the future (KDE4) all the function of the knotifyclient will be moved there.
	 *  Some code here is derived from the old KNotify deamon
	 */

	int level=KNotifyClient::Default;
	TQString sound;
	TQString file;
	TQString commandline;

	// get config file
	KConfig eventsFile( TQString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data");
	eventsFile.setGroup(message);

	KConfig configFile( TQString::fromAscii( KNotifyClient::instance()->instanceName()+".eventsrc" ), true, false);
	configFile.setGroup(message);

	int present=KNotifyClient::getPresentation(message);
	if(present==-1)
		present=KNotifyClient::getDefaultPresentation(message);
	if(present==-1)
		present=0;

	// get sound file name
	if( present & KNotifyClient::Sound ) {
		TQString theSound = configFile.readPathEntry( "soundfile" );
		if ( theSound.isEmpty() )
			theSound = eventsFile.readPathEntry( "default_sound" );
		if ( !theSound.isEmpty() )
			sound = theSound;
	}

	// get log file name
	if( present & KNotifyClient::Logfile ) {
		TQString theFile = configFile.readPathEntry( "logfile" );
		if ( theFile.isEmpty() )
			theFile = eventsFile.readPathEntry( "default_logfile" );
		if ( !theFile.isEmpty() )
			file = theFile;
	}

	// get default event level
	if( present & KNotifyClient::Messagebox )
		level = eventsFile.readNumEntry( "level", 0 );

	// get command line
	if (present & KNotifyClient::Execute ) {
		commandline = configFile.readPathEntry( "commandline" );
		if ( commandline.isEmpty() )
			commandline = eventsFile.readPathEntry( "default_commandline" );
	}

	return userEvent( text, pixmap, widget, actions,  present , level, sound, file, commandline, flags );
}

KNotification *KNotification::userEvent( const TQString& text, const TQPixmap& pixmap, TQWidget *widget,
				TQStringList actions,int present, int level, const TQString &sound, const TQString &file,
				const TQString &commandline, unsigned int flags)
{

	/* NOTE:  this function still use the KNotifyClient,
	 *        in the futur (KDE4) all the function of the knotifyclient will be moved there.
	 *  Some code of this function fome from the old KNotify deamon
	 */

	
	KNotification *notify=new KNotification(widget);
	notify->d->widget=widget;
	notify->d->text=text;
	notify->d->actions=actions;
	notify->d->level=level;
	WId winId=widget ? widget->topLevelWidget()->winId()  : 0;
	
	
	//we will catch some event that will not be fired by the old deamon
	

	//we remove presentation that has been already be played, and we fire the event in the old way
	
	
	KNotifyClient::userEvent(winId,text,present & ~( KNotifyClient::PassivePopup|KNotifyClient::Messagebox|KNotifyClient::Execute),level,sound,file);


	if ( present & KNotifyClient::PassivePopup )
	{
		notify->notifyByPassivePopup( pixmap );
	}
	if ( present & KNotifyClient::Messagebox )
	{
		TQTimer::singleShot(0,notify,TQT_SLOT(notifyByMessagebox()));
	}
	else  //not a message box  (because closing the event when a message box is there is suicide)
		if(flags & CloseOnTimeout)
	{
		TQTimer::singleShot(6*1000, notify, TQT_SLOT(close()));
	}
	if ( present & KNotifyClient::Execute )
	{
		TQString appname = TQString::fromAscii( KNotifyClient::instance()->instanceName() );
		notify->notifyByExecute(commandline, TQString::null,appname,text, winId, 0 );
	}

	return notify;
	
}



/* This code is there before i find a great way to perform context-dependent notifications
 * in a way independent of kopete.
 *   i'm in fact still using the Will's old code.
 */


#include "kopeteeventpresentation.h"
#include "kopetegroup.h"
#include "kopetenotifydataobject.h"
#include "kopetenotifyevent.h"
#include "kopetemetacontact.h"
#include "kopeteuiglobal.h"
#include <tqimage.h>


static KNotification *performCustomNotifications( TQWidget *widget, Kopete::MetaContact * mc, const TQString &message, bool& suppress)
{
	KNotification *n=0L;
	//kdDebug( 14010 ) << k_funcinfo << endl;
	if ( suppress )
		return n;
	
	// Anything, including the MC itself, may set suppress and prevent further notifications
	
	/* This is a really ugly piece of logic now.  The idea is to check for notifications
	* first on the metacontact, then on each of its groups, until something suppresses
	* any further notifications.
	* So on the first run round this loop, dataObj points to the metacontact, and on subsequent
	* iterations it points to one of the contact's groups.  The metacontact pointer is maintained
	* so that if a group has a chat notification set for this event, we can call execute() on the MC.
	*/

	bool checkingMetaContact = true;
	Kopete::NotifyDataObject * dataObj = mc;
	do {
		TQString sound;
		TQString text;
		
		if ( dataObj )
		{
			Kopete::NotifyEvent *evt = dataObj->notifyEvent( message );
			if ( evt )
			{
				suppress = evt->suppressCommon();
				int present = 0;
				// sound
				Kopete::EventPresentation *pres = evt->presentation( Kopete::EventPresentation::Sound );
				if ( pres && pres->enabled() )
				{
					present = present | KNotifyClient::Sound;
					sound = pres->content();
					evt->firePresentation( Kopete::EventPresentation::Sound );
				}
				// message
				if ( ( pres = evt->presentation( Kopete::EventPresentation::Message ) )
								   && pres->enabled() )
				{
					present = present | KNotifyClient::PassivePopup;
					text = pres->content();
					evt->firePresentation( Kopete::EventPresentation::Message );
				}
				// chat
				if ( ( pres = evt->presentation( Kopete::EventPresentation::Chat ) )
								   && pres->enabled() )
				{
					mc->execute();
					evt->firePresentation( Kopete::EventPresentation::Chat );
				}
				// fire the event
				n=KNotification::userEvent( text, mc->photo(), widget, TQStringList() , present, 0, sound, TQString::null, TQString::null , KNotification::CloseOnTimeout);
			}
		}

		if ( mc )
		{
			if ( checkingMetaContact )
			{
				// only executed on first iteration
				checkingMetaContact = false;
				dataObj = mc->groups().first();
			}
			else
				dataObj = mc->groups().next();
		}
	}
	while ( dataObj && !suppress );
	return n;
}




KNotification *KNotification::event( Kopete::MetaContact *mc, const TQString& message ,
			const TQString& text, const TQPixmap& pixmap, TQWidget *widget,
			const TQStringList &actions, unsigned int flags)
{
	if (message.isEmpty()) return 0;
    
	bool suppress = false;
	KNotification *n=performCustomNotifications( widget, mc, message, suppress);
		 
	if ( suppress )
	{
		//kdDebug( 14000 ) << "suppressing common notifications" << endl;
		return n; // custom notifications don't create a single unique id
	}
	else
	{
		//kdDebug( 14000 ) << "carrying out common notifications" << endl;
		return event(  message, text, pixmap, widget , actions, flags);
	}
}





#include "knotification.moc"