/*
 * kbiff.cpp
 * Copyright (C) 1999 Kurt Granroth <granroth@kde.org>
 *
 * This file contains the implementation of the main KBiff
 * widget
 *
 * $Id: kbiff.cpp,v 1.41 2000/05/04 00:49:16 rikkus Exp $
 */
#include "kbiff.h"
#include <qmovie.h>
#include <qpopupmenu.h>
#include <qtooltip.h>

#include <kconfig.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kprocess.h>
#include <kwm.h>

#include "Trace.h"
#include "setupdlg.h"
#include "notify.h"
#include "status.h"

#include <unistd.h>

#include <dcopclient.h>

KBiff::KBiff(DCOPClient *client, QWidget *parent)
	: QLabel(parent),
      DCOPObjectProxy(client),
	  statusTimer(0),
	  status(0),
	  statusChanged(true)
{
TRACEINIT("KBiff::KBiff()");
	setAutoResize(true);
	setMargin(0);
	setAlignment(AlignLeft | AlignTop);

	// Init the audio server.  serverStatus return 0 when it is ok
//	hasAudio = (audioServer.serverStatus() == 0) ? true : false;

	// enable the session management stuff
	connect(kapp, SIGNAL(saveYourself()), this, SLOT(saveYourself()));

	// nuke the list stuff when removed
	monitorList.setAutoDelete(true);
	notifyList.setAutoDelete(true);
	statusList.setAutoDelete(true);

    // register with DCOP
    registerMe(client);

	reset();
}

KBiff::~KBiff()
{
TRACEINIT("KBiff::~KBiff()");
	monitorList.clear();
	notifyList.clear();
	statusList.clear();

    // we no longer want to be registered
    DCOPClient *client = kapp->dcopClient();
    QString proxy(QString("kbiff-%1").arg(getpid()));
    if (client->isApplicationRegistered(proxy.data()) == true)
    {
        QByteArray params;
        QDataStream ds(params, IO_WriteOnly);
        ds << proxy;
        client->send("kbiff", "kbiff", "proxyDeregister(QString)", params);
    }
    client->detach();
}

void KBiff::processSetup(const KBiffSetup* setup, bool run)
{
TRACEINIT("KBiff::processSetup()");
	// General settings
	isSecure    = setup->getSecure();
	profile     = setup->getProfile();
	mailClient  = setup->getMailClient();
	sessions    = setup->getSessionManagement();
	skipcheck   = setup->getCheckStartup();
	noMailIcon  = setup->getNoMailIcon();
	newMailIcon = setup->getNewMailIcon();
	oldMailIcon = setup->getOldMailIcon();
	noConnIcon  = setup->getNoConnIcon();

	// New mail
	systemBeep     = setup->getSystemBeep();
	runCommand     = setup->getRunCommand();
	runCommandPath = setup->getRunCommandPath();
	playSound      = setup->getPlaySound();
	playSoundPath  = setup->getPlaySoundPath();
	notify         = setup->getNotify();
	dostatus       = setup->getStatus();

	// if we aren't going the status route, we should at least
	// provide a tooltip!
	if (dostatus == false)
		QToolTip::add(this, profile);
	else
		QToolTip::remove(this);

	// set all the new mailboxes
	setMailboxList(setup->getMailboxList(), setup->getPoll());

	// change the dock state if necessary
TRACEF("docked = %d", docked);
TRACEF("setup->getDock() = %d", setup->getDock());
	if (docked != setup->getDock())
		dock();

	if (run && !skipcheck)
		start();
    skipcheck = false;

	delete setup;
}

void KBiff::setMailboxList(const QList<KBiffURL>& mailbox_list, unsigned int poll)
{
TRACEINIT("KBiff::setMailboxList");
	QList<KBiffURL> tmp_list = mailbox_list;

	myMUTEX = true;
	if (isRunning())
		stop();
	monitorList.clear();
	
	KBiffURL *url;
	for (url = tmp_list.first(); url != 0; url = tmp_list.next())
	{
		TRACEF("Now adding %s", url->url().data());
		KBiffMonitor *monitor = new KBiffMonitor();
		monitor->setMailbox(*url);
		monitor->setPollInterval(poll);
		connect(monitor, SIGNAL(signal_newMail(const int, const QString&)),
		        this, SLOT(haveNewMail(const int, const QString&)));
		connect(monitor, SIGNAL(signal_currentStatus(const int, const QString&, const KBiffMailState)),
		        this, SLOT(currentStatus(const int, const QString&, const KBiffMailState)));
		connect(monitor, SIGNAL(signal_noMail()), this, SLOT(displayPixmap()));
		connect(monitor, SIGNAL(signal_oldMail()), this, SLOT(displayPixmap()));
		connect(monitor, SIGNAL(signal_noConn()), this, SLOT(displayPixmap()));
		monitorList.append(monitor);
	}
	myMUTEX = false;
}

const bool KBiff::isDocked() const
{
	return docked;
}

void KBiff::readSessionConfig()
{
TRACEINIT("KBiff::readSesionConfig()");
	KConfig *config = kapp->sessionConfig();

	config->setGroup("KBiff");

	profile = config->readEntry("Profile", "Inbox");
	docked = config->readBoolEntry("IsDocked", false);
	bool run = config->readBoolEntry("IsRunning", true);

	KBiffSetup *setup_dlg = new KBiffSetup(profile);
	connect(setup_dlg, SIGNAL(signalPlaySound(const QString&)),
	        this, SLOT(slotPlaySound(const QString&)));

	processSetup(setup_dlg, run);
}

///////////////////////////////////////////////////////////////////////////
// Protected Virtuals
///////////////////////////////////////////////////////////////////////////
void KBiff::mousePressEvent(QMouseEvent *event)
{
TRACEINIT("KBiff::mousePressEvent()");
	// regardless of which button, get rid of the status box
	if (status)
	{
		status->hide();
		delete status;
		status = 0;
	}

	// also, ditch the timer
	if (statusTimer)
	{
		statusTimer->stop();
		delete statusTimer;
		statusTimer = 0;
	}

	// check if this is a right click
	if(event->button() == RightButton)
	{
		// popup the context menu
		popupMenu();
	}
	else
	{
		// execute the command
		slotLaunchMailClient();

		readPop3MailNow();
	}
}

void KBiff::enterEvent(QEvent *e)
{
TRACEINIT("KBiff::enterEvent()");
	QLabel::enterEvent(e);

	// return now if the user doesn't want this feature.
	// *sniff*.. the ingrate.. I worked so hard on this, too... *sob*
	if (dostatus == false)
		return;

	// don't do anything if we already have a timer
	if (statusTimer)
		return;

	// popup the status in one second
	statusTimer = new QTimer(this);
	connect(statusTimer, SIGNAL(timeout()), this, SLOT(popupStatus()));

	TRACE("Starting status timer for 1 second...");
	statusTimer->start(1000, true);
}

void KBiff::leaveEvent(QEvent *e)
{
TRACEINIT("KBiff::leaveEvent()");
	QLabel::leaveEvent(e);

	// stop the timer if it is going
	if (statusTimer)
	{
		statusTimer->stop();
		delete statusTimer;
		statusTimer = 0;
	}

	// get rid of the status box if it is activated
	if (status)
	{
		status->hide();
		delete status;
		status = 0;
	}
}

void KBiff::popupStatus()
{
TRACEINIT("KBiff::popupStatus()");
	if (status)
		delete status;

	// if we don't get rid of the timer, then the very next
	// time we float over the icon, the status box will
	// *not* be activated!
	if (statusTimer)
	{
		statusTimer->stop();
		delete statusTimer;
		statusTimer = 0;
	}

	if (statusChanged)
	{
		statusList.clear();
		KBiffMonitor *monitor;
		for(monitor = monitorList.first(); monitor; monitor = monitorList.next())
		{
			TRACEF("%s: %d\n", (const char*)monitor->getMailbox(), monitor->newMessages());
			statusList.append(new KBiffStatusItem(monitor->getMailbox(), monitor->newMessages()));
		}
		statusChanged = false;
	}

	status = new KBiffStatus(profile, statusList);
	status->popup(QCursor::pos());
}

bool KBiff::isGIF8x(const QString& file_name)
{
TRACEINIT("KBiff::isGIF8x()");

	/* The first test checks if we can open the file */
	QFile gif8x(file_name);
	if (gif8x.open(IO_ReadOnly) == false)
		return false;

	/**
	 * The GIF89 format specifies that the first five bytes of
	 * the file shall have the characters 'G' 'I' 'F' '8' '9'.
	 * The GIF87a format specifies that the first six bytes
	 * shall read 'G' 'I' 'F' '8' '7' 'a'.  Knowing that, we
	 * shall read in the first six bytes and test away.
	 */
	char header[6];
	int bytes_read = gif8x.readBlock(header, 6);

	/* Close the file just to be nice */
	gif8x.close();

	/* If we read less than 6 bytes, then its definitely not GIF8x */
	if (bytes_read < 6)
		return false;

	/* Now test for the magical GIF8(9|7a) */
	if (header[0] == 'G' &&
		 header[1] == 'I' &&
		 header[2] == 'F' &&
		 header[3] == '8' &&
		 header[4] == '9' ||
		(header[4] == '7' && header[5] == 'a'))
	{
		/* Success! */
		TRACE("Is GIF8x!");
		return true;
	}

	/* Apparently not GIF8(9|7a) */
	return false;
}

///////////////////////////////////////////////////////////////////////////
// Protected Slots
///////////////////////////////////////////////////////////////////////////
void KBiff::saveYourself()
{
TRACEINIT("KBiff::saveYourself()");
	if (sessions)
	{
		KConfig *config = kapp->sessionConfig();
		config->setGroup("KBiff");

		config->writeEntry("Profile", profile);
		config->writeEntry("IsDocked", docked);
		config->writeEntry("IsRunning", isRunning());

		config->sync();

		TRACEF("Saving session profile: %s", profile.data());
	}
}

void KBiff::invokeHelp()
{
	kapp->invokeHTMLHelp("kbiff/kbiff.html", "");
}

void KBiff::displayPixmap()
{
TRACEINIT("KBiff::displayPixmap()");
	if (myMUTEX)
		return;
	TRACE("After MUTEX");

	// we will try to deduce the pixmap (or gif) name now.  it will
	// vary depending on the dock and mail state
	QString pixmap_name;
	bool has_new = false, has_old = false, has_no = true, has_noconn = false;
	KBiffMonitor *monitor;
	for (monitor = monitorList.first();
	     monitor != 0 && has_new == false;
		  monitor = monitorList.next())
	{
		TRACE("Checking mailbox");
		switch (monitor->getMailState())
		{
			case NoMail:
				has_no = true;
				break;
			case NewMail:
				has_new = true;
				break;
			case OldMail:
				has_old = true;
				break;
			case NoConn:
				has_noconn = true;
				break;
			default:
				has_no = true;
				break;
		}
	}
	TRACE("Done checking mailboxes");

	if (has_new)
		pixmap_name = newMailIcon;
	else if (has_old)
		pixmap_name = oldMailIcon;
	else if (has_noconn)
		pixmap_name = noConnIcon;
	else
		pixmap_name = noMailIcon;

    if (docked)
        pixmap_name.prepend("mini-");
    QString filename = KGlobal::iconLoader()->iconPath( pixmap_name, KIcon::User );
    QFileInfo file(filename);

	TRACEF("Displaying %s", file.absFilePath().data());
	// at this point, we have the file to display.  so display it
	if (isGIF8x(file.absFilePath()))
		setMovie(QMovie(file.absFilePath()));
	else
		setPixmap(QPixmap(file.absFilePath()));
	adjustSize();
}

void KBiff::currentStatus(const int num, const QString& the_mailbox, const KBiffMailState the_state)
{
TRACEINIT("KBiff::currentStatus()");
TRACEF("num = %d mailbox = %s state = %d", num, (const char*)the_mailbox, the_state);
	statusChanged = true;
	// iterate through all saved notify dialogs to see if "our" one is
	// currently being displayed
	KBiffNotify *notify;
	for (notify = notifyList.first();
	     notify != 0;
		  notify = notifyList.next())
	{
		// if this one is not visible, delete it from the list.  the only
		// way it will again become visible is if the haveNewMail slot
		// gets triggered
		if (notify->isVisible() == false)
		{
			notifyList.remove();
		}
		else
		{
			TRACEF("notify->mailbox = %s", (const char*)notify->getMailbox());
			// if this box is visible (active), we see if it is the one
			// we are looking for
			if (notify->getMailbox() == the_mailbox)
			{
				// yep.  now, if there is new mail, we set the new number in
				// the dialog.  if it is any other state, we remove this
				// dialog from the list
				switch (the_state)
				{
				case NewMail:
					notify->setNew(num);
					break;
				case OldMail:
				case NoMail:
				case NoConn:
				default:
					notifyList.remove();
					break;
				}
			}
		}
	}
}

void KBiff::haveNewMail(const int num, const QString& the_mailbox)
{
TRACEINIT("KBiff::haveNewMail()");
	displayPixmap();

	// beep if we are allowed to
	if (systemBeep)
	{
		TRACE("Beep!");
		kapp->beep();
	}

	// run a command if we have to
	if (runCommand)
	{
		// make sure the command exists
		if (!runCommandPath.isEmpty())
		{
			TRACEF("Running %s", runCommandPath.data());
			executeCommand(runCommandPath);
		}
	}

	// play a sound if we have to
	if (playSound)
		slotPlaySound(playSoundPath);

	// notify if we must
	if (notify)
	{
		TRACEF("Notifying %d in %s", num, the_mailbox.data());
		KBiffNotify *notify_dlg = new KBiffNotify(num, the_mailbox);
		connect(notify_dlg, SIGNAL(signalLaunchMailClient()),
				this, SLOT(slotLaunchMailClient()));
		notifyList.append(notify_dlg);
		notify_dlg->show();
	}
}

void KBiff::dock()
{
TRACEINIT("KBiff::dock()");
	// destroy the old window
	if (this->isVisible())
	{
		this->hide();
		this->destroy(true, true);
		this->create(0, true, false);
		kapp->setMainWidget(this);

		// we don't want a "real" top widget if we are _going_ to
		// be docked.
		if (docked)
			kapp->setTopWidget(this);
		else
			kapp->setTopWidget(new QWidget);
	}

	if (docked == false)
	{
		docked = true;

		// enable docking
		KWM::setDockWindow(this->winId());
	}
	else
		docked = false;

	// (un)dock it!
	this->show();
	QTimer::singleShot(1000, this, SLOT(displayPixmap()));
}

void KBiff::setup()
{
TRACEINIT("KBiff::setup()");
	KBiffSetup* setup_dlg = new KBiffSetup(profile);

	if (setup_dlg->exec())
		processSetup(setup_dlg, true);
	else
		delete setup_dlg;
}

void KBiff::checkMailNow()
{
TRACEINIT("KBiff::checkMailNow()");
	KBiffMonitor *monitor;
	for (monitor = monitorList.first();
	     monitor != 0;
		  monitor = monitorList.next())
	{
		monitor->checkMailNow();
	}
}

void KBiff::readMailNow()
{
TRACEINIT("KBiff::readMailNow()");
	KBiffMonitor *monitor;
	for (monitor = monitorList.first();
	     monitor != 0;
		  monitor = monitorList.next())
	{
		monitor->setMailboxIsRead();
	}
}

void KBiff::readPop3MailNow()
{
TRACEINIT("KBiff::readPop3MailNow()");
	KBiffMonitor *monitor;
	for (monitor = monitorList.first();
	     monitor != 0;
		  monitor = monitorList.next())
	{
		if (!strcmp(monitor->getProtocol(), "pop3"))
			monitor->setMailboxIsRead();
	}
}

void KBiff::stop()
{
TRACEINIT("KBiff::stop()");
	KBiffMonitor *monitor;
	for (monitor = monitorList.first();
	     monitor != 0;
		  monitor = monitorList.next())
	{
		monitor->stop();
	}
}

void KBiff::start()
{
TRACEINIT("KBiff::start()");
	myMUTEX = true;
	KBiffMonitor *monitor;
	for (unsigned int i = 0; i < monitorList.count(); i++)
	{
		TRACE("Starting a monitor");
		monitor = monitorList.at(i);
		monitor->start();
	}
	myMUTEX = false;
	displayPixmap();
}

///////////////////////////////////////////////////////////////////////////
// Protected Functions
///////////////////////////////////////////////////////////////////////////
void KBiff::popupMenu()
{
TRACEINIT("KBiff::popupMenu()");
	QPopupMenu *popup = new QPopupMenu(0, "popup");

	// if secure, disable everything but exit
	if (isSecure == false)
	{
		if (docked)
			popup->insertItem(i18n("&UnDock"), this, SLOT(dock()));
		else
			popup->insertItem(i18n("&Dock"), this, SLOT(dock()));
		popup->insertItem(i18n("&Setup..."), this, SLOT(setup()));
		popup->insertSeparator();
		popup->insertItem(i18n("&Help..."), this, SLOT(invokeHelp()));
		popup->insertSeparator();

		int check_id;
		check_id = popup->insertItem(i18n("&Check Mail Now"), this, SLOT(checkMailNow()));
		int read_id;
		read_id = popup->insertItem(i18n("&Read Mail Now"), this, SLOT(readMailNow()));

		if (isRunning())
		{
			popup->setItemEnabled(check_id, true);
			popup->setItemEnabled(read_id, true);
			popup->insertItem(i18n("&Stop"), this, SLOT(stop()));
		}
		else
		{
			popup->setItemEnabled(check_id, false);
			popup->setItemEnabled(read_id, false);
			popup->insertItem(i18n("&Start"), this, SLOT(start()));
		}
		popup->insertSeparator();
	}

	popup->insertItem(i18n("E&xit"), kapp, SLOT(quit()));

	popup->popup(QCursor::pos());
}

void KBiff::reset()
{
TRACEINIT("KBiff::reset()");
	// reset all the member variables
	systemBeep     = true;
	runCommand     = false;
	runCommandPath = "";
	playSound      = false;
	playSoundPath  = "";
	notify         = true;
	dostatus       = true;

	noMailIcon  = "nomail";
	newMailIcon = "newmail";
	oldMailIcon = "oldmail";
	noConnIcon  = "noconn";

	docked    = false;
	isSecure  = false;

	mailClient  = "xmutt";

	myMUTEX = false;
}

bool KBiff::isRunning()
{
TRACEINIT("KBiff::isRunning()");
	bool is_running = false;
	KBiffMonitor *monitor;
	for (monitor = monitorList.first();
	     monitor != 0;
		  monitor = monitorList.next())
	{
		if (monitor->isRunning())
		{
			is_running = true;
			break;
		}
	}
	return is_running;
}

void KBiff::executeCommand(const QString& command)
{
TRACEINIT("KBiff::executeCommand()");
	/**
	 * The KProcess object expects the first param to be the
	 * command and every param after that to be the command's params.
	 * As a result, though, if you pass it a string with spaces in it,
	 * then it takes the first token of the string as the command
	 * and ignores the rest of it!  We need to pass all tokens from
	 * the command string on as individual parameters to get around
	 * this.
	 */
	KProcess process;
	int index, beg;
	char param[60];
	const char* pom=(const char*)command;
	index=0;
	while(pom[index])
	{
		// simplyfies whitespaces
		while(pom[index] && (pom[index]==' ' || (pom[index]>=9 && pom[index]<=13)))
			index++;

		beg=0;
		if(pom[index]=='"')
		{
			index++;
			while(pom[index] && pom[index]!='"')
			{
				if(pom[index]==92 && pom[index+1])   // '\'
					index++;
				if(beg<59)
				{
					param[beg]=pom[index];
					beg++;
				}
				index++;
			}  
			index++;
		}	
		else
		{
			while(pom[index] && pom[index]>' ')
			{
				if(pom[index]==92 && pom[index+1])   // '\'
					index++;
				if(beg<59)
				{
					param[beg]=pom[index];
					beg++;
				}
				index++;
			}  
		}	
		param[beg]=0;  
		if(beg)
			process << param;  
	}

	process.start(KProcess::DontCare);
}

void KBiff::slotLaunchMailClient()
{
TRACEINIT("KBiff::slotLaunchMailClient()");
	if (!mailClient.isEmpty())
		executeCommand(mailClient);
}

void KBiff::slotPlaySound(const QString& play_sound)
{
TRACEINIT("KBiff::slotPlaySound()");
	if (hasAudio == false)
		return;

	// make sure something is specified
	if (play_sound)
	{
		TRACEF("Playing sound: %s", play_sound.ascii());
//		audioServer.play(play_sound);
//		audioServer.sync();
	}
}

bool KBiff::process(const QCString&, const QCString& function,
                    const QByteArray& data, QCString& replyType,
                    QByteArray &replyData)
{
    QDataStream args(data, IO_ReadOnly);
    QDataStream reply(replyData, IO_WriteOnly);
    QString proxy;
    if (function == "proxyRegister(QString)")
    {
        args >> proxy;
        proxyList.append(proxy);
        replyType = "void";
        return true;
    }

    else if (function == "proxyDeregister(QString)")
    {
        args >> proxy;
        proxyList.remove(proxy);
        replyType = "void";
        return true;
    }

    else if (function == "hasMailbox(QString)")
    {
        QString mailbox;
        args >> mailbox;

        reply << (bool) findMailbox(mailbox, proxy);
        replyType = "bool";
        return true;
    }

    else if (function == "mailCount(QString)")
    {
        reply << -1;
        replyType = "int";
        return true;
    }

    else if (function == "newMailCount(QString)")
    {
        QString mailbox;
        args >> mailbox;

        reply << newMailCount(mailbox);
        replyType = "int";
        return true;
    }


    return false;
}

int KBiff::newMailCount(const QString& url)
{
    int newmail = -1;

    QString proxy;
    if (findMailbox(url, proxy) == true)
    {
        if (proxy != QString::null)
        {
            QByteArray data;
            QDataStream ds(data, IO_WriteOnly);
            ds << url;

            QByteArray reply_data;
            QCString reply_type;
            QDataStream reply(reply_data, IO_ReadOnly);

            DCOPClient *dcc = kapp->dcopClient();
            if (dcc->call(proxy.data(), "kbiff",
                          "newMailCount(QString)", data, reply_type,
                          reply_data) == true)
            {
                reply >> newmail;
            }
        }
        else
        {
            KBiffMonitor *monitor;
            for(monitor = monitorList.first(); monitor;
                monitor = monitorList.next())
            {
                if (monitor->getMailbox() == url)
                {
                    newmail = monitor->newMessages();
                    break;
                }
            }
        }
    }

    return newmail;
}

bool KBiff::findMailbox(const QString& url, QString& proxy)
{
    bool has_mailbox = false;
    KBiffMonitor *monitor;
    for(monitor = monitorList.first(); monitor; monitor = monitorList.next())
    {
        if (monitor->getMailbox() == url)
        {
            has_mailbox = true;
            break;
        }
    }
    if (has_mailbox == false)
    {
        QByteArray data, replyData;
        QCString replyType;
        QDataStream ds(data, IO_WriteOnly);
        ds << url;
        // okay, now try to iterate through our proxies
        QStringList::Iterator it = proxyList.begin();
        for ( ; it != proxyList.end(); it++)
        {
            DCOPClient *dcc = kapp->dcopClient();
            if (dcc->call(QCString((*it).data()), "kbiff",
                        "hasMailbox(QString)", data, replyType,
                        replyData) == true)
            {
                has_mailbox = true;
                proxy = *it;
                break;
            }
        }
    }

    return has_mailbox;
}

void KBiff::registerMe(DCOPClient *client)
{
    // we need to attach our client before doing anything
    client->attach();

    // if we aren't registered yet, then we will do so.. and be
    // responsible for all *other* kbiff requests, too!
    if (client->isApplicationRegistered("kbiff") == false)
        client->registerAs("kbiff");
    else
    {
        // okay, there is a running kbiff already.  we will let it
        // know that we are active and let it feed us requests
        QString proxy(QString("kbiff-%1").arg(getpid()));
        QByteArray params, reply;
        QCString reply_type;
        QDataStream ds(params, IO_WriteOnly);
        ds << proxy;
        client->send("kbiff", "kbiff", "proxyRegister(QString)", params);
        client->registerAs(QCString(proxy.data()));
    }
}

