/* Yo Emacs, this -*- C++ -*-

  Copyright (C) 1999-2001 Jens Hoefkens
  jens@hoefkens.com

  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.

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

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  $Id: kbgfibs.cpp 384584 2005-01-31 15:35:48Z coles $

*/

/*

  TODO: popup dialog for accept/reject and join ??
        clear the chat history?
	game over, clear the caption?
	need show saved
	need buddy list
	need wait for player,...

*/

#include "kbgfibs.h"
#include "kbgfibs.moc"

#include <kapplication.h>
#include <kconfig.h>
#include <qtimer.h>
#include <qcheckbox.h>
#include <qlayout.h>
#include <qstring.h>
#include <qsocket.h>
#include <qpopupmenu.h>
#include <qgroupbox.h>
#include <qbuttongroup.h>
#include <qradiobutton.h>
#include <kmainwindow.h>
#include <klineeditdlg.h>
#include <kmessagebox.h>
#include <qdatetime.h>
#include <qwhatsthis.h>
#include <kaudioplayer.h>
#include <kstandarddirs.h>
#include <qvbox.h>
#include <kiconloader.h>
#include <ktabctl.h>
#include <kpassdlg.h>
#include <qcstring.h>
#include <knotifyclient.h>
#include <kaction.h>


#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <iostream>
#include "kbgboard.h"
#include "kbgstatus.h"

#include "clip.h"
#include "version.h"


void KBgEngineFIBS::start()
{
	// FIXME: open the child windows here and not in the constructor
}

// == configuration handling ===================================================

/*
 * Restore settings and ask children to do the same
 */
void KBgEngineFIBS::readConfig()
{
	KConfig *config = kapp->config();
	config->setGroup("fibs engine");

	// history variables
	lastAway = config->readEntry("away_hist", "");

	// various options
	showMsg  = config->readBoolEntry("pers_msg", false);
	whoisInvite = config->readBoolEntry("whois_invite", false);

	// connection information
	infoFIBS[FIBSHost] = config->readEntry("server", "fibs.com");
	infoFIBS[FIBSPort] = config->readEntry("port", "4321");
	infoFIBS[FIBSUser] = config->readEntry("user", "");
	infoFIBS[FIBSPswd] = config->readEntry("password", "");

	// automatic messages
	useAutoMsg[MsgBeg] = config->readBoolEntry("auto-beg", false);
	useAutoMsg[MsgLos] = config->readBoolEntry("auto-los", false);
	useAutoMsg[MsgWin] = config->readBoolEntry("auto-win", false);

	autoMsg[MsgBeg] = config->readEntry("msg-beg", "");
	autoMsg[MsgLos] = config->readEntry("msg-los", "");
	autoMsg[MsgWin] = config->readEntry("msg-win", "");

	// ask the children to read their config options
	playerlist->readConfig();
	chatWindow->readConfig();
}

/*
 * Save the engine specific settings and tell all clients
 */
void KBgEngineFIBS::saveConfig()
{
	KConfig *config = kapp->config();
	config->setGroup("fibs engine");

	// history variables
	config->writeEntry("away_hist", lastAway);

	// various options
	config->writeEntry("pers_msg", showMsg);
	config->writeEntry("whois_invite", whoisInvite);

	// connection information
	config->writeEntry("server",   infoFIBS[FIBSHost]);
	config->writeEntry("port",     infoFIBS[FIBSPort]);
	config->writeEntry("user",     infoFIBS[FIBSUser]);
	config->writeEntry("password", infoFIBS[FIBSPswd]);

	// automatic messages
	config->writeEntry("auto-beg", useAutoMsg[MsgBeg]);
	config->writeEntry("auto-los", useAutoMsg[MsgLos]);
	config->writeEntry("auto-win", useAutoMsg[MsgWin]);

	config->writeEntry("msg-beg", autoMsg[MsgBeg]);
	config->writeEntry("msg-los", autoMsg[MsgLos]);
	config->writeEntry("msg-win", autoMsg[MsgWin]);

	// ask the children to read their config options
	playerlist->saveConfig();
	chatWindow->saveConfig();
}

void KBgEngineFIBS::setupDefault()
{

	cbp->setChecked(false);
	cbi->setChecked(false);

	lec[FIBSHost]->setText("fibs.com");
	lec[FIBSPort]->setText("4321");

	lec[FIBSUser]->clear();
	lec[FIBSPswd]->clear();


	chatWindow->setupDefault();
	playerlist->setupDefault();
}

void KBgEngineFIBS::setupCancel()
{
	chatWindow->setupCancel();
	playerlist->setupCancel();
}

/*
 * Called when the setup dialog is positively closed
 */
void KBgEngineFIBS::setupOk()
{
	// various options
	showMsg = cbp->isChecked();
	whoisInvite = cbi->isChecked();

	// connection information
	for (int i = 0; i < NumFIBS; i++)
		infoFIBS[i] = lec[i]->text();

	// automatic messages
	for (int i = 0; i < NumMsg; i++) {
		useAutoMsg[i] = cbm[i]->isChecked();
		autoMsg[i] = lem[i]->text();
	}

	chatWindow->setupOk();
	playerlist->setupOk();

	// save settings
	saveConfig();
}

/*
 * Puts the FIBS specific setup into the dialog nb
 */
void KBgEngineFIBS::getSetupPages(KDialogBase *nb)
{
	/*
	 * Main Widget
	 */
	QVBox *vbp = nb->addVBoxPage(i18n("FIBS Engine"), i18n("Here you can configure the FIBS backgammon engine"),
				     kapp->iconLoader()->loadIcon(PROG_NAME "_engine", KIcon::Desktop));

	/*
	 * Get a multi page work space
	 */
	KTabCtl *tc = new KTabCtl(vbp, "fibs tabs");

	/*
	 * FIBS, local options
	 */
	QWidget *w = new QWidget(tc);
	QGridLayout *gl = new QGridLayout(w, 3, 1, nb->spacingHint());

	/*
	 * Group boxes
	 */
	QGroupBox *gbo = new QGroupBox(i18n("Options"), w);
	QGroupBox *gbm = new QGroupBox(i18n("Automatic Messages"), w);

	gl->addWidget(gbo, 0, 0);
	gl->addWidget(gbm, 1, 0);

	/*
	 * Options
	 */
	cbp = new QCheckBox(i18n("Show copy of personal messages in main window"), gbo);
	cbi = new QCheckBox(i18n("Automatically request player info on invitation"), gbo);

	QWhatsThis::add(cbp, i18n("Usually, all messages sent directly to you by other players "
				  "are displayed only in the chat window. Check this box if you "
				  "would like to get a copy of these messages in the main window."));
	QWhatsThis::add(cbi, i18n("Check this box if you would like to receive information on "
				  "players that invite you to games."));

	cbp->setChecked(showMsg);
	cbi->setChecked(whoisInvite);

	gl = new QGridLayout(gbo, 2, 1, 20);
	gl->addWidget(cbp, 0, 0);
	gl->addWidget(cbi, 1, 0);

	/*
	 * Automatic messages
	 */
	gl = new QGridLayout(gbm, NumMsg, 2, 20);

	cbm[MsgBeg] = new QCheckBox(i18n("Start match:"), gbm);
	cbm[MsgWin] = new QCheckBox(i18n("Win match:"), gbm);
	cbm[MsgLos] = new QCheckBox(i18n("Lose match:"), gbm);

	QWhatsThis::add(cbm[MsgBeg], i18n("If you want to send a standard greeting to your "
					  "opponent whenever you start a new match, check "
					  "this box and write the message into the entry "
					  "field."));
	QWhatsThis::add(cbm[MsgWin], i18n("If you want to send a standard message to your "
					  "opponent whenever you won a match, check this box "
					  "and write the message into the entry field."));
	QWhatsThis::add(cbm[MsgLos], i18n("If you want to send a standard message to your "
					  "opponent whenever you lost a match, check this box "
					  "and write the message into the entry field."));

	for (int i = 0; i < NumMsg; i++) {
		lem[i] = new QLineEdit(autoMsg[i], gbm);
		gl->addWidget(cbm[i], i, 0);
		gl->addWidget(lem[i], i, 1);
		connect(cbm[i], SIGNAL(toggled(bool)), lem[i], SLOT(setEnabled(bool)));
		cbm[i]->setChecked(useAutoMsg[i]);
		lem[i]->setEnabled(useAutoMsg[i]);
		QWhatsThis::add(lem[i], QWhatsThis::textFor(cbm[i]));
	}

	/*
	 * Put the page into the notebook
	 */
	gl->activate();
	tc->addTab(w, i18n("&Local"));


	/*
	 * FIBS, connection setup
	 */
	w = new QWidget(tc);
	gl = new QGridLayout(w, 3, 1, nb->spacingHint());

	QGroupBox *gbc = new QGroupBox(i18n("Server"), w);
	QGroupBox *gbk = new QGroupBox(i18n("Other"), w);

	gl->addWidget(gbc, 0, 0);
	gl->addWidget(gbk, 1, 0);

	/*
	 * Server box
	 */
	gl = new QGridLayout(gbc, 4, 2, 20);

	QLabel *lbc[NumFIBS];

	lbc[FIBSHost] = new QLabel(i18n("Server name:"), gbc);
	lbc[FIBSPort] = new QLabel(i18n("Server port:"), gbc);
	lbc[FIBSUser] = new QLabel(i18n("User name:"), gbc);
	lbc[FIBSPswd] = new QLabel(i18n("Password:"), gbc);

	for (int i = 0; i < NumFIBS; i++) {
		lec[i] = new QLineEdit(infoFIBS[i], gbc);
		gl->addWidget(lbc[i], i, 0);
		gl->addWidget(lec[i], i, 1);
	}
	lec[FIBSPswd]->setEchoMode(QLineEdit::Password);

	QWhatsThis::add(lec[FIBSHost], i18n("Enter here the host name of FIBS. With almost "
					    "absolute certainty this should be \"fibs.com\". "
					    "If you leave this blank, you will be asked again "
					    "at connection time."));
	QWhatsThis::add(lec[FIBSPort], i18n("Enter here the port number of FIBS. With almost "
					    "absolute certainty this should be \"4321\". "
					    "If you leave this blank, you will be asked again "
					    "at connection time."));
	QWhatsThis::add(lec[FIBSUser], i18n("Enter your login on FIBS here. If you do not have a "
					    "login yet, you should first create an account using "
					    "the corresponding menu entry. If you leave this blank, "
					    "you will be asked again at connection time."));
	QWhatsThis::add(lec[FIBSPswd], i18n("Enter your password on FIBS here. If you do not have a "
					    "login yet, you should first create an account using "
					    "the corresponding menu entry. If you leave this blank, "
					    "you will be asked again at connection time. The password "
					    "will not be visible."));

	/*
	 * Connection keepalive
	 */
	cbk = new QCheckBox(i18n("Keep connections alive"), gbk);

	QWhatsThis::add(cbk, i18n("Usually, FIBS drops the connection after one hour of inactivity. When "
				  "you check this box, %1 will try to keep the connection alive, even "
				  "if you are not actually playing or chatting. Use this with caution "
				  "if you do not have flat-rate Internet access.").arg(PROG_NAME));

	cbk->setChecked(keepalive);

	gl = new QGridLayout(gbk, 1, 1, nb->spacingHint());
	gl->addWidget(cbk, 0, 0);

	/*
	 * Done with the page, put it in
	 */
	gl->activate();
	tc->addTab(w, i18n("&Connection"));

	/*
	 * Ask children for settings
	 */
	chatWindow->getSetupPages(tc, nb->spacingHint());
	playerlist->getSetupPages(tc, nb->spacingHint());

	/*
	 * TODO: future extensions
	 */
	w = new QWidget(tc);
	tc->addTab(w, i18n("&Buddy List"));
}


// == functions related to the invitation menu =================================

/*
 * Remove a player from the invitation list in the join menu
 */
void KBgEngineFIBS::cancelJoin(const QString &info)
{
	QRegExp patt = QRegExp("^" + info + " ");

	for (int i = 0; i <= numJoin; i++) {
		if (actJoin[i]->text().contains(patt)) {
			// move all entries starting at i+1 up by one...
			for (int j = i; j < numJoin; j++)
				actJoin[j]->setText(actJoin[j+1]->text());
			actJoin[numJoin--]->unplug(joinMenu);
			break;
		}
	}
}

/*
 * Parse the information in info for the purposes of the invitation
 * submenu
 */
void KBgEngineFIBS::changeJoin(const QString &info)
{
	char name_p[100], name_o[100];
	float rate;
	int expi;

	/*
	 * Extract the name of the player, her opponent, rating and experience.
	 * It is okay to use latin1(), since the string is coming from FIBS.
	 */
	sscanf(info.latin1(), "%99s %99s %*s %*s %*s %f %i %*s %*s %*s %*s %*s",
	       name_p, name_o, &rate, &expi);

	QString name = name_p;
	QString oppo = name_o;

	QString rate_s; rate_s.setNum(rate);
	QString expi_s; expi_s.setNum(expi);

	QRegExp patt = QRegExp("^" + name + " ");

	/*
	 * We have essentially two lists of names to check against: the ones
	 * that have invited us and are not yet in the menu and the ones that
	 * are already in the menu.
	 */

	if (numJoin > -1 && oppo != "-")
		cancelJoin(name);

	for (QStringList::Iterator it = invitations.begin(); it != invitations.end(); ++it) {

		if ((*it).contains(patt)) {

			QString text, menu;

			if ((*it).contains(QRegExp(" r$"))) {
				menu = i18n("R means resume", "%1 (R)").arg(name);
				text = i18n("%1 (experience %2, rating %3) wants to resume a saved match with you. "
					    "If you want to play, use the corresponding menu entry to join (or type "
					    "'join %4').").arg(name).arg(expi_s).arg(rate_s).arg(name);
				KNotifyClient::event("invitation", i18n("%1 wants to resume a saved match with you").
						     arg(name));
			} else if ((*it).contains(QRegExp(" u$"))) {
				menu = i18n("U means unlimited", "%1 (U)").arg(name);
				text = i18n("%1 (experience %2, rating %3) wants to play an unlimited match with you. "
					    "If you want to play, use the corresponding menu entry to join (or type "
					    "'join %4').").arg(name).arg(expi_s).arg(rate_s).arg(name);
				KNotifyClient::event("invitation", i18n("%1 has invited you to an unlimited match").
						     arg(name));
			} else {
				QString len = (*it).right((*it).length() - name.length() - 1);
				menu = i18n("If the format of the (U) and (R) strings is changed, it should also be changed here",
					    "%1 (%2)").arg(name).arg(len);
				text = i18n("%1 (experience %2, rating %3) wants to play a %4 point match with you. "
					    "If you want to play, use the corresponding menu entry to join (or type "
					    "'join %5').").arg(name).arg(expi_s).arg(rate_s).arg(len).arg(name);
				KNotifyClient::event("invitation", i18n("%1 has invited you for a %2 point match").
						     arg(name).arg(len));
			}
			emit serverString("rawwho " + name); // this avoids a race
			if (whoisInvite) {
				emit serverString("whois " + name);
				emit infoText("<font color=\"red\">" + text + "</font>");
			} else
				emit infoText("<font color=\"red\">" + text + "</font><br>");

			for (int i = 0; i <=numJoin; i++)
				actJoin[i]->unplug(joinMenu);

			if (++numJoin > 7) numJoin = 7;

			for (int i = numJoin; i > 0; i--)
				actJoin[i]->setText(actJoin[i-1]->text());

			actJoin[0]->setText(menu);

			for (int i = 0; i <= numJoin; i++)
				actJoin[i]->plug(joinMenu);

			invitations.remove(it);
			break;
		}
	}

	/*
	 * If there are entries in the menu, enable it
	 */
	menu->setItemEnabled(joinMenuID, numJoin > -1);
}


// == various slots and functions ==============================================

/*
 * Keep the connection alive.
 */
void KBgEngineFIBS::keepAlive()
{
	emit serverString("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

/*
 * Several bookkeeping operations that have to be done at the
 * end of every game. Some of these may or may not be necessary
 * at a particular time, but they don't hurt either.
 */
void KBgEngineFIBS::endGame()
{
	playing = false;

	emit serverString("rawwho " + infoFIBS[FIBSUser]);

	actConti->setEnabled(false);
	actLeave->setEnabled(false);

	actAccept->setEnabled(false);
	actReject->setEnabled(false);

	emit allowCommand(Load, false);
	emit allowCommand(Undo, false);
	emit allowCommand(Done, false);
	emit allowCommand(Cube, false);
	emit allowCommand(Roll, false);
}

/*
 * Toggle visibility of the player list
 */
void KBgEngineFIBS::showList()
{
	playerlist->isVisible() ? playerlist->hide() : playerlist->show();
}

/*
 * Toggle visibility of the chat window
 */
void KBgEngineFIBS::showChat()
{
	chatWindow->isVisible() ? chatWindow->hide() : chatWindow->show();
}

/*
 * Process the last move coming from the board
 */
void KBgEngineFIBS::handleMove(QString *s)
{
	lastMove = *s;
	QString t = lastMove.left(1);
	int moves = t.toInt();

	emit allowCommand(Done, moves == toMove);
	emit allowCommand(Undo, moves > 0);

	/*
	 * Allow undo and possibly start the commit timer
	 */
	redoPossible &= ((moves < toMove) && (undoCounter > 0));
	emit allowCommand(Redo, redoPossible);
	if (moves == toMove && cl >= 0) {
		emit allowMoving(false);
		ct->start(cl, true);
	}
}

/*
 * Done with the move
 */
void KBgEngineFIBS::done()
{
	// prevent the timer from expiring again
	ct->stop();

	// no more moves
	emit allowMoving(false);

	// no more commands until it's our turn
	emit allowCommand(Load, false);
	emit allowCommand(Undo, false);
	emit allowCommand(Done, false);
	emit allowCommand(Cube, false);
	emit allowCommand(Roll, false);

	// Transform the string to FIBS cormat
	lastMove.replace(0, 2, "move ");
	lastMove.replace(pat[PlsChar], "-");

	// sent it to the server
	emit serverString(lastMove);
}

/*
 * Undo the last move
 */
void KBgEngineFIBS::undo()
{
	ct->stop();

	redoPossible = true;
	++undoCounter;

	emit allowMoving(true);
	emit allowCommand(Done, false);
	emit allowCommand(Redo, true);
	emit undoMove();
}

/*
 * Redo the last undone move
 */
void KBgEngineFIBS::redo()
{
	--undoCounter;
	emit redoMove();
}

/*
 * Double the cube - coming from the board
 */
void KBgEngineFIBS::doubleCube(const int w)
{
	if (playing && w == US) cube();
}

/*
 * Roll the dice - coming from the board
 */
void KBgEngineFIBS::rollDice(const int w)
{
	if (playing && w == US) roll();
}

/*
 * This engine passes all commands unmodified to the server
 */
void KBgEngineFIBS::handleCommand(QString const &cmd)
{
	emit serverString(cmd);
}

/*
 * If we have a connection, we don't quit right away
 */
bool KBgEngineFIBS::queryClose()
{
	if (connection->state() == QSocket::Idle)
		return true;

	switch (KMessageBox::warningYesNoCancel((QWidget *)parent(),i18n("Still connected. Log out first?"))) {
	case KMessageBox::Yes :
		disconnectFIBS();
		return true;
	case KMessageBox::No :
		return true;
	default: // cancel
		return false;
	}
}

/*
 * If we have a connection, we don't quit right away
 */
bool KBgEngineFIBS::queryExit()
{
	if( kapp->sessionSaving())
	    return true;
	if (connection->state() != QSocket::Idle)
		disconnectFIBS();
	return true;
}

/*
 * This displays a copy of personal messages in the main window.
 * Normally, these only get displayed in the chat window.
 */
void KBgEngineFIBS::personalMessage(const QString &msg)
{
	if (showMsg)
		emit infoText(msg);
}


// == slots and functions for FIBS commands ====================================

/*
 * Accept the offer
 */
void KBgEngineFIBS::accept()
{
	actAccept->setEnabled(false);
	actReject->setEnabled(false);

	emit serverString("accept");
}

/*
 * Reject the offer
 */
void KBgEngineFIBS::reject()
{
	actAccept->setEnabled(false);
	actReject->setEnabled(false);

	emit serverString("reject");
}

/*
 * Continue a multi game match
 */
void KBgEngineFIBS::match_conti()
{
	actConti->setEnabled(false);
	actLeave->setEnabled(false);

	emit serverString("join");
}

/*
 * Leave a multi game match
 */
void KBgEngineFIBS::match_leave()
{
	actConti->setEnabled(false);
	actLeave->setEnabled(false);

	emit serverString("leave");
}

/*
 * Go away from the server for a little while. Offer the last know away
 * message as a default to the user.
 */
void KBgEngineFIBS::away()
{
	bool ret;
	QString msg = KLineEditDlg::getText(i18n("Please type the message that should be displayed to other\n"
						 "users while you are away."),
					    lastAway, &ret, (QWidget *)parent());
	if (ret) {
		lastAway = msg;
		emit serverString("away " + msg);
		actAway->setEnabled(false);
	}
}

/*
 * Toggle being ready for games
 */
void KBgEngineFIBS::toggle_ready()
{
	emit serverString("toggle ready");
}

/*
 * Toggle the use of greedy bearoffs
 */
void KBgEngineFIBS::toggle_greedy()
{
	emit serverString("toggle greedy");
}

/*
 * Toggle whether we will be asked to double/roll or not
 */
void KBgEngineFIBS::toggle_double()
{
	emit serverString("toggle double");
}

/*
 * Toggle whether we want to see details on rating computations
 */
void KBgEngineFIBS::toggle_ratings()
{
	emit serverString("toggle ratings");
}

/*
 * Come back after being away.
 */
void KBgEngineFIBS::back()
{
	emit serverString("back");
}

/*
 * Double the cube
 */
void KBgEngineFIBS::cube()
{
	emit serverString("double");
}

/*
 * Roll the dice
 */
void KBgEngineFIBS::roll()
{
	emit serverString("roll");
}

/*
 * Reload the board
 */
void KBgEngineFIBS::load()
{
	emit serverString("board");
}

/*
 * Handle the menu short cuts for joining. This is not as pretty as it
 * could or should be, but it works and is easy to understand.
 */
void KBgEngineFIBS::join(const QString &msg)
{
	emit serverString("join " + msg.left(msg.find('(')));
}
void KBgEngineFIBS::join_0() { join(actJoin[0]->text()); }
void KBgEngineFIBS::join_1() { join(actJoin[1]->text()); }
void KBgEngineFIBS::join_2() { join(actJoin[2]->text()); }
void KBgEngineFIBS::join_3() { join(actJoin[3]->text()); }
void KBgEngineFIBS::join_4() { join(actJoin[4]->text()); }
void KBgEngineFIBS::join_5() { join(actJoin[5]->text()); }
void KBgEngineFIBS::join_6() { join(actJoin[6]->text()); }
void KBgEngineFIBS::join_7() { join(actJoin[7]->text()); }


// == invitation handling ======================================================

/*
 * Show the invitation dialog and set the name to player
 */
void KBgEngineFIBS::inviteDialog()
{
	fibsRequestInvitation("");
}

/*
 * Show the invitation dialog and set the name to player
 */
void KBgEngineFIBS::fibsRequestInvitation(const QString &player)
{
	if (!invitationDlg) {
		QString p = player;
		invitationDlg = new KBgInvite("invite");
		connect(invitationDlg, SIGNAL(inviteCommand(const QString &)), this, SLOT(handleCommand(const QString &)));
		connect(invitationDlg, SIGNAL(dialogDone()), this, SLOT(invitationDone()));
	}
	invitationDlg->setPlayer(player);
	invitationDlg->show();
}

/*
 * Finish off the invitation dialog
 */
void KBgEngineFIBS::invitationDone()
{
	delete invitationDlg;
	invitationDlg = 0;
}


// == connection handling ======================================================

/*
 * Establish a connection to the server and log in if the parameter login
 * is true.
 */
void KBgEngineFIBS::connectFIBS()
{
	/*
	 * Make sure the connection parameter are properly set.
	 */
	if (!queryConnection(false))
		return;

	conAction->setEnabled(false);
	newAction->setEnabled(false);
	disAction->setEnabled(false);

	/*
	 * Connect
	 */
	emit infoText(i18n("Looking up %1").arg(infoFIBS[FIBSHost]));
	connection->connectToHost(infoFIBS[FIBSHost], infoFIBS[FIBSPort].toUShort());

	return;
}

/*
 * Hostname has been resolved.
 */
void KBgEngineFIBS::hostFound()
{
	emit infoText(i18n("Connecting to %1").arg(infoFIBS[FIBSHost]));
}

/*
 * An error has occurred. Reset and inform the user.
 */
void KBgEngineFIBS::connError(int f)
{
	switch (f) {
	case QSocket::ErrConnectionRefused:
		emit infoText(i18n("Error, connection has been refused"));
		break;
	case QSocket::ErrHostNotFound:
		emit infoText(i18n("Error, nonexistent host or name server down."));
		break;
	case QSocket::ErrSocketRead:
		emit infoText(i18n("Error, reading data from socket"));
		break;
	}
	connectionClosed();
	return;
}

void KBgEngineFIBS::readData()
{
	QString line;
	while(connection->canReadLine()) {
		line = connection->readLine();
		if (line.length() > 2) {
			line.truncate(line.length()-2);
			handleServerData(line);
		}
	}
}

/*
 * Transmit the string s to the server
 */
void KBgEngineFIBS::sendData(const QString &s)
{
	connection->writeBlock((s+"\r\n").latin1(),2+s.length());
}

/*
 * Connection has been established. Log in and update the menus & actions.
 */
void KBgEngineFIBS::connected()
{
	conAction->setEnabled(false);
	newAction->setEnabled(false);
	disAction->setEnabled(true);

	menu->setItemEnabled( cmdMenuID, true);
	menu->setItemEnabled(respMenuID, true);
	menu->setItemEnabled(optsMenuID, true);

	/*
	 * Initialize the rx state machine
	 */
	rxStatus = RxConnect;
	rxCollect = "";

	/*
	 * Depending on whether somebody else wants to handle the login or not
	 */
	if (login) {

		/*
		 * Make sure the player list is empty when the whole list comes
		 * right after login
		 */
		playerlist->clear();

		/*
		 * Login, using the autologin feature of FIBS, before we even receive anything.
		 */
		QString entry;
		entry.setNum(CLIP_VERSION);
		emit serverString(QString("login ") + PROG_NAME + "-" + PROG_VERSION + " " + entry + " "
				  + infoFIBS[FIBSUser] + " " + infoFIBS[FIBSPswd]);

	} else {

		emit serverString("guest");
		login = true;

	}

	/*
	 * Some visual feedback and done
	 */
	emit infoText(i18n("Connected") + "<br>");
}

/*
 * Create a new account on FIBS. Obviously, this will also create
 * a connection. The actual login is handled in the message parsing
 * state machine.
 */
void KBgEngineFIBS::newAccount()
{
	if (!queryConnection(true))
		return;

	rxStatus = RxNewLogin;
	rxCollect = "";
	login = false;
	connectFIBS();
}

/*
 * Send a disconnection request to the server. The server will disconnect
 * and we will receive a connectionClosed() signal.
 */
void KBgEngineFIBS::disconnectFIBS()
{
	// send two lines in case we are stuck in the login phase
	emit serverString("quit");
	emit serverString("quit");
}

/*
 * Connection to the server is closed for some (unknown) reason. Delete
 * the connection object and get the actions into a proper state.
 */
void KBgEngineFIBS::connectionClosed()
{
	/*
	 * Read remaining input
	 */
	readData();

	/*
	 * Flush whatever is left in the rxBuffer and send a note
	 */
	emit infoText(rxCollect + "<br><hr>");
	emit infoText(i18n("Disconnected.") + "<br>");

	conAction->setEnabled(true);
	newAction->setEnabled(true);
	disAction->setEnabled(false);

	menu->setItemEnabled(joinMenuID, false);
	menu->setItemEnabled( cmdMenuID, false);
	menu->setItemEnabled(respMenuID, false);
	menu->setItemEnabled(optsMenuID, false);
}

/*
 * To establish a connection, we need to query the server name, the port
 * number, the login and the password.
 */
bool KBgEngineFIBS::queryConnection(const bool newlogin)
{
	QString text, msg;
	bool first, ret = true;

	/*
	 * query the connection parameter
	 */
	if (newlogin || infoFIBS[FIBSHost].isEmpty()) {

		msg = KLineEditDlg::getText(i18n("Enter the name of the server you want to connect to.\n"
						 "This should almost always be \"fibs.com\"."),
					    infoFIBS[FIBSHost], &ret, (QWidget *)parent());

		if (ret)
			infoFIBS[FIBSHost] = msg;
		else
			return false;

	}
	if (newlogin || infoFIBS[FIBSPort].isEmpty()) {

		msg = KLineEditDlg::getText(i18n("Enter the port number on the server. "
						 "It should almost always be \"4321\"."),
					    infoFIBS[FIBSPort], &ret, (QWidget *)parent());

		if (ret)
			infoFIBS[FIBSPort] = msg;
		else
			return false;
	}
	if (newlogin || infoFIBS[FIBSUser].isEmpty()) {

		if (newlogin)

			text = i18n("Enter the login you would like to use on the server %1. The login may not\n"
				    "contain spaces or colons. If the login you choose is not available, you'll later be\n"
				    "given the opportunity to pick another one.\n\n").arg(infoFIBS[FIBSHost]);

		else

			text = i18n("Enter your login on the server %1. If you don't have a login, you\n"
				    "should create one using the corresponding menu option.\n\n").arg(infoFIBS[FIBSHost]);


		first = true;
		do {
			msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret,
						     (QWidget *)parent())).stripWhiteSpace();
			if (first) {
				text += i18n("The login may not contain spaces or colons!");
				first = false;
			}

		} while (ret && (msg.isEmpty() || msg.contains(' ') || msg.contains(':')));

		if (ret)
			infoFIBS[FIBSUser] = msg;
		else
			return false;
	}
	if (newlogin || infoFIBS[FIBSPswd].isEmpty()) {

		if (newlogin)

			text = i18n("Enter the password you would like to use with the login %1\n"
				    "on the server %2. It may not contain colons.\n\n").
				arg(infoFIBS[FIBSUser]).arg(infoFIBS[FIBSHost]);

		else

			text = i18n("Enter the password for the login %1 on the server %2.\n\n").
				arg(infoFIBS[FIBSUser]).arg(infoFIBS[FIBSHost]);

		first = true;
		do {
			QCString password;
			if (newlogin)
				ret = (KPasswordDialog::getNewPassword(password, text) == KPasswordDialog::Accepted);
			else
				ret = (KPasswordDialog::getPassword(password, text) == KPasswordDialog::Accepted);

			password.stripWhiteSpace();
			msg = password;

			if (first) {
				text += i18n("The password may not contain colons or spaces!");
				first = false;
			}

		} while (ret && (msg.isEmpty() || msg.contains(' ') || msg.contains(':')));

		if (ret)
			infoFIBS[FIBSPswd] = msg;
		else
			return false;
	}

	/*
	 * Made it here, all parameters acquired
	 */
	return true;
}


// == message parsing ==========================================================

/*
 * Pattern setup - rather long and boring
 */
void KBgEngineFIBS::initPattern()
{
	QString pattern;

	/*
	 * Initialize the search pattern array
	 */
	pat[Welcome] = QRegExp(pattern.sprintf("^%d ", CLIP_WELCOME));
	pat[OwnInfo] = QRegExp(pattern.sprintf("^%d ", CLIP_OWN_INFO));
	pat[WhoInfo] = QRegExp(pattern.sprintf("^%d ", CLIP_WHO_INFO));
	pat[WhoEnde] = QRegExp(pattern.sprintf("^%d$", CLIP_WHO_END));
	pat[MotdBeg] = QRegExp(pattern.sprintf("^%d" , CLIP_MOTD_BEGIN));
	pat[MotdEnd] = QRegExp(pattern.sprintf("^%d" , CLIP_MOTD_END));
	pat[MsgPers] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE));
	pat[MsgDeli] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE_DELIVERED));
	pat[MsgSave] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE_SAVED));
	pat[ChatSay] = QRegExp(pattern.sprintf("^%d ", CLIP_SAYS));
	pat[ChatSht] = QRegExp(pattern.sprintf("^%d ", CLIP_SHOUTS));
	pat[ChatWis] = QRegExp(pattern.sprintf("^%d ", CLIP_WHISPERS));
	pat[ChatKib] = QRegExp(pattern.sprintf("^%d ", CLIP_KIBITZES));
	pat[SelfSay] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_SAY));
	pat[SelfSht] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_SHOUT));
	pat[SelfWis] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_WHISPER));
	pat[SelfKib] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_KIBITZ));
	pat[UserLin] = QRegExp(pattern.sprintf("^%d ", CLIP_LOGIN));
	pat[UserLot] = QRegExp(pattern.sprintf("^%d ", CLIP_LOGOUT));

	pat[NoLogin] = QRegExp("\\*\\* Unknown command: 'login'");
	pat[BegRate] = QRegExp("^rating calculation:$");
	pat[EndRate] = QRegExp("^change for ");
	pat[HTML_lt] = QRegExp("<");
	pat[HTML_gt] = QRegExp(">");
	pat[BoardSY] = QRegExp("^Value of 'boardstyle' set to 3");
	pat[BoardSN] = QRegExp("^Value of 'boardstyle' set to [^3]");
	pat[WhoisBG] = QRegExp("^Information about ");
	pat[WhoisE1] = QRegExp("^  No email address\\.$");
	pat[WhoisE2] = QRegExp("^  Email address: ");
	pat[SelfSlf] = QRegExp("^You say to yourself:");
	pat[Goodbye] = QRegExp("^                             Goodbye\\.");
	pat[GameSav] = QRegExp("The game was saved\\.$");
	pat[RawBord] = QRegExp("^board:");
	pat[YouTurn] = QRegExp("^It's your turn\\. Please roll or double");
	pat[PlsMove] = QRegExp("^Please move [1-6]+ pie");
	pat[EndWtch] = QRegExp("^You stop watching ");
	pat[BegWtch] = QRegExp("^You're now watching ");
	pat[BegGame] = QRegExp("^Starting a new game with ");
	pat[Reload1] = QRegExp("^You are now playing with ");
	pat[Reload2] = QRegExp(" has joined you. Your running match was loaded\\.$");
	pat[OneWave] = QRegExp(" waves goodbye.$");
	pat[TwoWave] = QRegExp(" waves goodbye again.$");
	pat[YouWave] = QRegExp("^You wave goodbye.$");
	pat[GameBG1] = QRegExp("start a [0-9]+ point match");
	pat[GameBG2] = QRegExp("start an unlimited match");
	pat[GameRE1] = QRegExp("are resuming their [0-9]+-point match");
	pat[GameRE2] = QRegExp("are resuming their unlimited match");
	pat[GameEnd] = QRegExp("point match against");
	pat[TabChar] = QRegExp("\\t");
	pat[PlsChar] = QRegExp("\\+");
	pat[Invite0] = QRegExp(" wants to play a [0-9]+ point match with you\\.$");
	pat[Invite1] = QRegExp("^.+ wants to play a ");
	pat[Invite2] = QRegExp(" wants to resume a saved match with you\\.$");
	pat[Invite3] = QRegExp(" wants to play an unlimited match with you\\.$");
	pat[TypJoin] = QRegExp("^Type 'join ");
	pat[OneName] = QRegExp("^ONE USERNAME PER PERSON ONLY!!!");
	pat[YouAway] = QRegExp("^You're away. Please type 'back'");
	pat[YouBack] = QRegExp("^Welcome back\\.$");
	pat[YouMove] = QRegExp("^It's your turn to move\\.");
	pat[YouRoll] = QRegExp("^It's your turn to roll or double\\.");
	pat[TwoStar] = QRegExp("^\\*\\* ");
	pat[OthrNam] = QRegExp("^\\*\\* Please use another name\\. ");
	pat[BoxHori] = QRegExp("^ *\\+-*\\+ *$");
	pat[BoxVer1] = QRegExp("^ *\\|");
	pat[BoxVer2] = QRegExp("\\| *$");
	pat[YourNam] = QRegExp("Your name will be ");
	pat[GivePwd] = QRegExp("Please give your password:");
	pat[RetypeP] = QRegExp("Please retype your password:");
	pat[HelpTxt] = QRegExp("^NAME$");
	pat[MatchB1] = QRegExp(" has joined you for a [0-9]+ point match\\.$");
	pat[MatchB2] = QRegExp(" has joined you for an unlimited match\\.$");
	pat[EndLose] = QRegExp(" wins the [0-9]+ point match [0-9]+-[0-9]+");
	pat[EndVict] = QRegExp(" win the [0-9]+ point match [0-9]+-[0-9]+");
	pat[RejAcpt] = QRegExp("Type 'accept' or 'reject'\\.$");
	pat[YouAcpt] = QRegExp("^You accept the double\\. The cube shows [0-9]+\\.");

	pat[KeepAlv] = QRegExp("^\\*\\* Unknown command: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'");
	pat[RatingY] = QRegExp("You'll see how the rating changes are calculated\\.$");
	pat[RatingN] = QRegExp("You won't see how the rating changes are calculated\\.$");

	// FIXME same problem as in previous line
	// mpgnu accepts the double.5 arthur_tn - gnu 1 0 1243.32 365 6 983722411  adsl-61-168-141.bna.bellsouth.net - -

	// FIXME: <PLAYER> can't move. -- needs board reload...

        /*

        opponent matchlength score (your points first)
        **gnu 1 0 - 0
        *blah 1 0 - 0
        kraut 1 0 - 0

        logged in and ready **
        logged in *
        otherwise " "

        */

	pat[ConLeav] = QRegExp("^Type 'join' if you want to play the next game, type 'leave' if you don't\\.$");
	pat[GreedyY] = QRegExp("^\\*\\* Will use automatic greedy bearoffs\\.");
	pat[GreedyN] = QRegExp("^\\*\\* Won't use automatic greedy bearoffs\\.");
	pat[BegBlnd] = QRegExp("^\\*\\* You blind ");
	pat[EndBlnd] = QRegExp("^\\*\\* You unblind ");
        pat[MatchB3] = QRegExp("^\\*\\* You are now playing a [0-9]+ point match with ");
        pat[MatchB4] = QRegExp("^\\*\\* You are now playing an unlimited match with ");
	pat[RejCont] = QRegExp("^You reject\\. The game continues\\.");
	pat[AcptWin] = QRegExp("^You accept and win ");
	pat[YouGive] = QRegExp("^You give up\\.");
	pat[DoubleY] = QRegExp("^\\*\\* You will be asked if you want to double\\.");
	pat[DoubleN] = QRegExp("^\\*\\* You won't be asked if you want to double\\.");
}

/*
 * Parse an incoming line and notify all interested parties - first match
 * decides.
 */
void KBgEngineFIBS::handleServerData(QString &line)
{
	QString rawline = line; // contains the line before it is HTML'fied

	/*
	 * Fix-up any HTML-like tags in the line
	 */
	line.replace(pat[HTML_lt], "&lt;");
	line.replace(pat[HTML_gt], "&gt;");

	/*
	 * FIBS sometimes sends tabs, where it should send 8 spaces...
	 */
	line.replace(pat[TabChar], "        ");

	switch (rxStatus) {

	case RxConnect:
		handleMessageConnect(line, rawline);
		break;

	case RxMotd:
		handleMessageMotd(line);
		return;

	case RxWhois:
		handleMessageWhois(line);
		break;

	case RxRating:
		handleMessageRating(line);
		break;

	case RxNewLogin:
		handleMessageNewLogin(line);
		break;

	case RxIgnore:
		/*
		 * Ignore _ALL_ incoming strings - this is needed during the
		 * login phase, when the message box is open.
		 */
		break;

	case RxGoodbye:
		/*
		 * Receive the logout sequence. The string will be flushed by the
		 * disconnectFIBS() callback
		 */
		rxCollect += QString("<font color=\"blue\"><pre>") + line + "</pre></font><br>";
		break;

	case RxNormal:
		handleMessageNormal(line, rawline);
		break;

	default:
		/*
		 * This is a serious problem - latin1() is fine since the line comes from FIBS.
		 */
		std::cerr << "PROBLEM in KBgEngineFIBS::handleServerData: " << line.latin1() << std::endl;
	}
}

/*
 * Handle messages during the RxWhois state
 */
void KBgEngineFIBS::handleMessageWhois(const QString &line)
{
	rxCollect += "<br>&nbsp;&nbsp;&nbsp;&nbsp;" + line;
	if (line.contains(pat[WhoisE1]) || line.contains(pat[WhoisE2])) {
		rxStatus = RxNormal;
		emit infoText("<font color=\"darkgreen\">" + rxCollect + "<br></font>");
	}
}

/*
 * Handle messages during the RxRating state
 */
void KBgEngineFIBS::handleMessageRating(const QString &line)
{
	rxCollect += "<br>" + line;
	if (line.contains(pat[EndRate]) && ++rxCount == 2) {
		emit infoText("<font color=\"blue\">" + rxCollect + "<br></font>");
		rxStatus = RxNormal;
	}
}

/*
 * Handle messages during the RxMotd state
 */
void KBgEngineFIBS::handleMessageMotd(const QString &line)
{
	if (line.contains(pat[MotdEnd])) {
		rxStatus = RxNormal;
		emit infoText("<font color=\"blue\"><pre>" + rxCollect + "</pre></font>");
		/*
		 * just to be on the safe side, we set the value of boardstyle.
		 * we do it here, since this is reasonably late in the login
		 * procedure
		 */
		emit serverString("set boardstyle 3");
	} else {
		QString tline = line;
		tline.replace(pat[BoxHori], "<br><hr>");
		tline.replace(pat[BoxVer1], "");
		tline.replace(pat[BoxVer2], "");
		rxCollect += "<br>" + tline;
	}
}

/*
 * Handle messages during the RxConnect state
 */
void KBgEngineFIBS::handleMessageConnect(const QString &line, const QString &rawline)
{
	/*
	 * Two possibilities: either we are logged in or we sent bad password/login
	 */
	if (line.contains("login:")) {
		/*
		 * This can only happen if the password/login is wrong.
		 */
		if (rxCollect.isEmpty()) {
			rxStatus = RxIgnore;
			int ret = KMessageBox::warningContinueCancel
				((QWidget *)parent(), i18n("There was a problem with "
							   "your login and password. "
							   "You can reenter\n"
							   "your login and password and "
							   "try to reconnect."),
				 i18n("Wrong Login/Password"),
				 i18n("Reconnect"));
			if (ret == KMessageBox::Continue) {
				infoFIBS[FIBSUser] = "";
				infoFIBS[FIBSPswd] = "";
				login = true;
				connectFIBS(); // will reset the rxStatus
			} else {
				rxStatus = RxConnect;
				emit serverString("");
				emit serverString("");
			}
			return;
		}
		emit infoText("<hr><pre>" + rxCollect + "</pre><br>");
		rxCollect = "";
		return;
	}

	/*
	 * Ok, we are logged in! Now receive personal information. These
	 * are completely useless but what the heck.
	 */
	if (line.contains(pat[Welcome])) {
		char p[3][256];
		time_t tmp;
		// Using latin1() is okay, since the string comes from FIBS.
		int words = sscanf (line.latin1(), "%255s%255s%li%255s", p[0], p[1], &tmp, p[2]);
		if (words >= 4) {
			QDateTime d; d.setTime_t(tmp);
			QString text = i18n("%1, last logged in from %2 at %3.").arg(p[1]).arg(p[2]).arg(d.toString());
			emit infoText("<hr><br>" + text);
			playerlist->setName(p[1]);
		}
		return;
	}

	/*
	 * Initial parsing of user options and making sure that settings needed
	 * by us are at the correct value. We use and ignore values according
	 * to the following list:
	 *
	 *     p[ 0]   - CLIP_OWN_INFO
	 *     p[ 1]   - name        -- IGNORE
	 *   OptAllowPip
	 *     n[ 0]   - autoboard   -- IGNORE
	 *   OptAutoDouble
	 *   OptAutoMove
	 *     n[ 1]   - away        -- IGNORE
	 *     n[ 2]   - bell        -- IGNORE
	 *   OptCrawford
	 *     n[ 3]   - double      -- IGNORE
	 *     n[ 4]   - expierience -- IGNORE
	 *   OptGreedy
	 *     n[ 6]   - moreboards  -- IGNORE and set to YES
	 *   OptMoves
	 *     n[ 8]   - notify      -- IGNORE and set to YES
	 *     rating  - rating      -- IGNORE
	 *   OptRatings
	 *   OptReady
	 *     n[10]   - redoubles   -- IGNORE
	 *     n[11]   - report      -- IGNORE and set to YES
	 *   OptSilent
	 *     p[3]    - timezone
	 *
	 */
	if (line.contains(pat[OwnInfo])) {

		rxStatus = RxNormal;

		int fibsOptions[NumFIBSOpt];

		char p[3][256];
		int n[12];
		double rating;

		// Using latin1() is okay, since the string comes from FIBS.
		int words = sscanf (line.latin1(), "%255s%255s%i%i%i%i%i%i%i%i%i%i%i%i%i%lf%i%i%i%i%i%255s",
				    p[0], p[1],
				    &fibsOptions[OptAllowPip],
				    &n[0],
				    &fibsOptions[OptDouble],
				    &fibsOptions[OptAutoMove], // equivalent to OptDouble, can be ignored
				    &n[1], &n[2],
				    &fibsOptions[OptCrawford],
				    &n[3], &n[4],
				    &fibsOptions[OptGreedy],
				    &n[6],
				    &fibsOptions[OptMoves],
				    &n[8],
				    &rating,
				    &fibsOptions[OptRatings],
				    &fibsOptions[OptReady],
				    &n[10], &n[11],
				    &fibsOptions[OptSilent],
				    p[2]);

		if (words >= 22 && n[6] != 1) {
			/*
			 * need to get boards after new dice have arrived
			 */
			emit infoText("<font color=\"red\">" + i18n("The moreboards toggle has been set.") + "</font>");
			emit serverString("toggle moreboards");
		}
		if (words >= 22 && n[8] != 1) {
			/*
			 * need to know who logs out
			 */
			emit infoText("<font color=\"red\">" + i18n("The notify toggle has been set.") + "</font>");
			emit serverString("toggle notify");
		}
		if (words >= 22 && n[11] != 1) {
			/*
			 * want to know who starts playing games
			 */
			emit infoText("<font color=\"red\">" + i18n("The report toggle has been set.") + "</font>");
			emit serverString("toggle report");
		}

		/*
		 * Set the correct toggles in the options menu
		 */
		fibsOpt[OptReady]->setChecked(fibsOptions[OptReady]);
		fibsOpt[OptDouble]->setChecked(!fibsOptions[OptDouble]);
		fibsOpt[OptRatings]->setChecked(fibsOptions[OptRatings]);

		return;
	}

	/*
	 * The beginning of a new login procedure starts starts here
	 */
	if (line.contains(pat[OneName])) {
		rxStatus = RxNewLogin;
		emit infoText(QString("<font color=\"red\">") + rxCollect + "</font>");
		rxCollect = "";
		QString tmp = rawline;
		handleServerData(tmp);
		return;
	}

	/*
	 * Still in the middle of the login sequence, still collecting information
	 */
	rxCollect += "<br>" + line;
}

/*
 * Handle messages during the RxNewLogin state
 */
void KBgEngineFIBS::handleMessageNewLogin(const QString &line)
{
	/*
	 * Request the new login
	 */
	if (line.contains(pat[OneName])) {
		emit serverString(QString("name ") + infoFIBS[FIBSUser]);
		return;
	}
	/*
	 * Ooops, user name already exists
	 */
	if (line.contains(pat[OthrNam])) {
		QString text = i18n("The selected login is alreay in use! Please select another one.");
		bool ret, first = true;
		QString msg;

		do {
			msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret,
						     (QWidget *)parent())).stripWhiteSpace();
			if (first) {
				text += i18n("\n\nThe login may not contain spaces or colons!");
				first = false;
			}
		} while (msg.contains(' ') || msg.contains(':'));

		if (ret) {
			infoFIBS[FIBSUser] =  msg;
			emit serverString("name " + msg);
		} else
			emit serverString("bye");

		return;
	}
	/*
	 * first time we send the password
	 */
	if (line.contains(pat[YourNam])) {
		emit serverString(infoFIBS[FIBSPswd]);
		return;
	}
	/*
	 * second time we send the password
	 */
	if (line.contains(pat[GivePwd])) {
		emit serverString(infoFIBS[FIBSPswd]);
		return;
	}
	/*
	 * at this point we are done creating the account
	 */
	if (line.contains(pat[RetypeP])) {

		QString text = i18n("Your account has been created. Your new login is <u>%1</u>. To fully activate "
				    "this account, I will now close the connection. Once you reconnect, you can start "
				    "playing backgammon on FIBS.").arg(infoFIBS[FIBSUser]);
		emit infoText("<br><hr><font color=\"blue\">" + text + "</font><br><hr>");
		emit serverString("bye");
		rxStatus = RxNormal;
		rxCollect = "";
		return;
	}
	return;
}

/*
 * Handle all normal messages - during the RxNormal state
 */
void KBgEngineFIBS::handleMessageNormal(QString &line, QString &rawline)
{

    // - ignored ----------------------------------------------------------------------

    /*
     * For now, the waves are ignored. They should probably go into
     * the chat window -- but only optional
     */
    if (line.contains(pat[OneWave]) || line.contains(pat[TwoWave]) || line.contains(pat[YouWave])) {

        return;
    }

    /*
     * These messages used to go into the games window. If KBackgammon
     * ever gets a games window, they should be in there. For now, they
     * are ignored.
     */
    else if (line.contains(pat[GameBG1]) || line.contains(pat[GameBG2]) || line.contains(pat[GameRE1]) ||
             line.contains(pat[GameRE2]) || line.contains(pat[GameEnd])) {

        return;
    }

    /*
     * Artefact caused by the login test procedure utilized.
     */
    else if (line.contains(pat[NoLogin])) {

        return;
    }

    /*
     * Connection keep-alive response
     */
    else if (line.contains(pat[KeepAlv])) {

        return;
    }

    // --------------------------------------------------------------------------------

    /*
     * Chat and personal messages - note that the chat window sends these messages
     * back to us so we can display them if the user wants that.
     */
    else if (line.contains(pat[ChatSay]) || line.contains(pat[ChatSht]) || line.contains(pat[ChatWis]) ||
             line.contains(pat[ChatKib]) || line.contains(pat[SelfSay]) || line.contains(pat[SelfSht]) ||
             line.contains(pat[SelfWis]) || line.contains(pat[SelfKib]) || line.contains(pat[SelfSlf]) ||
             line.contains(pat[MsgPers]) || line.contains(pat[MsgDeli]) || line.contains(pat[MsgSave])) {

        emit chatMessage(line);
        return;
    }

    // --------------------------------------------------------------------------------

    /*
     * Beginning of games. In all these cases we are playing and not watching.
     */
    else if (line.contains(pat[MatchB1]) || line.contains(pat[MatchB2])) {

        if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty())
            emit serverString("kibitz " + autoMsg[MsgBeg]);
    }
    else if (line.contains(pat[MatchB3]) || line.contains(pat[MatchB4])) {

        if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty())
            emit serverString("kibitz " + autoMsg[MsgBeg]);
        line = "<font color=\"red\">" + line + "</font>";
    }

    // --------------------------------------------------------------------------------

	/*
	 * The help should be handled separately. A fairly complete implementation of a
	 * help parsing can be found in KFibs.
	 */
	else if (line.contains(pat[HelpTxt])) {

		// do nothing
	}

	// --------------------------------------------------------------------------------

	/*
	 * Simple cases without the need for many comments...
	 */
	else if (line.contains(pat[RawBord])) {

		/*
		 * Save the board string and create a new game state
		 */
		KBgStatus *st = new KBgStatus(currBoard = rawline);

		/*
		 * Save important state data and stop the timeout
		 */
		ct->stop();
		undoCounter = 0;

		pname[US  ] = st->player(US);
		pname[THEM] = st->player(THEM);

		playing = (QString("You") == pname[US]);

		toMove = st->moves();

		/*
		 * Update the caption string
		 */
		if (st->turn() < 0)
			caption = i18n("%1 (%2) vs. %3 (%4) - game over").arg(pname[US]).
				arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM));
		else if (st->length() < 0)
			caption = i18n("%1 (%2) vs. %3 (%4) - unlimited match").arg(pname[US]).
				arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM));
		else
			caption = i18n("%1 (%2) vs. %3 (%4) - %5 point match").arg(pname[US]).
				arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)).
				arg(st->length());

		emit statText(caption);

		/*
		 * Emit information and drop the state object
		 */
		emit allowMoving(playing && (st->turn() == US));
		emit newState(*st);

		delete st;

		/*
		 * Set the actions correctly
		 */
		emit allowCommand(Load, true );
		emit allowCommand(Undo, false);
		emit allowCommand(Redo, false);
		emit allowCommand(Done, false);

		return;
	}
	else if (line.contains(pat[PlsMove]) || line.contains(pat[YouMove])) {

		KNotifyClient::event("move", i18n("Please make your move"));

	}

	// --------------------------------------------------------------------------------

	/*
	 * Being away and coming back
	 */
	else if (line.contains(pat[YouAway])) {

		emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, true);
		actBack->setEnabled(true);
		line += "<br><pre>  </pre>" + i18n("(or use the corresponding menu entry to join the match)");
	}
	else if (line.contains(pat[YouBack])) {

		emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, false);
		actBack->setEnabled(false);
		actAway->setEnabled(true);
	}

	// --------------------------------------------------------------------------------

	/*
	 * Catch the response of the user responding to double or resign
	 */
	else if (line.contains(pat[YouGive]) || line.contains(pat[RejCont]) || line.contains(pat[AcptWin])) {

		actAccept->setEnabled(false);
		actReject->setEnabled(false);
	}

	// --------------------------------------------------------------------------------

	/*
	 * Catch the responses to newly set toggles
	 */
	else if (line.contains(pat[GreedyY]) || line.contains(pat[GreedyN])) {

		fibsOpt[OptGreedy]->setChecked(line.contains(pat[GreedyY]));
		line = "<font color=\"red\">" + line + "</font>";
	}
	else if (line.contains(pat[DoubleY]) || line.contains(pat[DoubleN])) {

		fibsOpt[OptDouble]->setChecked(line.contains(pat[DoubleY]));
		line = "<font color=\"red\">" + line + "</font>";
	}

	else if (line.contains(pat[RatingY]) || line.contains(pat[RatingN])) {

		fibsOpt[OptRatings]->setChecked(line.contains(pat[RatingY]));
		line = "<font color=\"red\">" + line + "</font>";
	}

	// --------------------------------------------------------------------------------

	/*
	 * It's our turn to roll or double
	 */
	else if (line.contains(pat[YouTurn]) || line.contains(pat[YouRoll])) {

		emit allowCommand(Cube, playing);
		emit allowCommand(Roll, playing);

		emit statText(caption); // force a pip count recomputation by the board

		KNotifyClient::event("roll or double", i18n("It's your turn to roll the dice or double the cube"));
	}

	// --------------------------------------------------------------------------------

	/*
	 * Got an invitation for a match
	 */
	else if (line.contains(pat[Invite0]) || line.contains(pat[Invite2]) || line.contains(pat[Invite3])) {

		rxCollect = rawline.left(rawline.find(' '));
		emit serverString("rawwho " + rxCollect);

		if (line.contains(pat[Invite0])) {
			rawline.replace(pat[Invite1], "");
			rawline = rxCollect + " "+ rawline.left(rawline.find(' '));
		} else if (line.contains(pat[Invite2])) {
			rawline = rxCollect + " r";
		} else if (line.contains(pat[Invite3])) {
			invitations += rxCollect + " u";
		}
		invitations += rawline;
		return; // will be printed once the rawwho is received
	}

	// - rx status changes ------------------------------------------------------------

	else if (line.contains(pat[WhoisBG])) {
		rxStatus = RxWhois;
		rxCollect = QString("<br><u>") + line + "</u>";
		return;
	}
	else if (line.contains(pat[MotdBeg])) {
		rxStatus = RxMotd;
		rxCollect = "";
		return;
	}
	else if (line.contains(pat[BegRate])) {
		rxStatus = RxRating;
		rxCount = 0;
		rxCollect = "<br>" + line;
		return;
	}
	else if (line.contains(pat[Goodbye])) {
		rxStatus = RxGoodbye;
		rxCollect = "<br><hr><br>";
		handleServerData(rawline); // danger: recursion!
		return;
	}

	// --------------------------------------------------------------------------------
	// --------------------------------------------------------------------------------
	// --------------------------------------------------------------------------------
	// --------------------------------------------------------------------------------

	/*
	 * Continue a mutli game match? We have to either leave or continue
	 */
	else if (line.contains(pat[ConLeav])) {
		actConti->setEnabled(true);
		actLeave->setEnabled(true);
		line.append("<br><pre>  </pre>" + i18n("(or use the corresponding menu "
						       "entry to leave or continue the match)"));
	}
	/*
	 * Beginning and end of user updates
	 */
	else if (line.contains(pat[WhoInfo])) {
		rawline.replace(pat[WhoInfo], "");
		if (rawline.contains(QRegExp("^" + infoFIBS[FIBSUser] + " "))) {
			int ready;
			// Using latin1() is fine, since the string is coming from FIBS.
			sscanf(rawline.latin1(), "%*s %*s %*s %i %*s %*s %*s %*s %*s %*s %*s %*s", &ready);
			fibsOpt[OptReady]->setChecked(ready);
		}
		emit fibsWhoInfo(rawline);
		return;
	}
	else if (line.contains(pat[WhoEnde])) {
		emit fibsWhoEnd();
		return;
	}
	/*
	 * This message is ignored. The instruction is given elsewhere (and slightly
	 * delayed in the flow of time).
	 */
	if (line.contains(pat[TypJoin])) {
		return;
	}
	/*
	 * Watching other players
	 */
	else if (line.contains(pat[BegWtch])) {
		emit allowCommand(Load, true);
		rawline.replace(pat[BegWtch], "");
		rawline.truncate(rawline.length()-1);
		emit fibsStartNewGame(rawline);
		load();
	}
	else if (line.contains(pat[EndWtch])) {
		emit gameOver();
	}
	/*
	 * Blinding of players, the actual blind is handled by
	 * the player list
	 */
	else if (line.contains(pat[BegBlnd])) {
		rawline.replace(pat[BegBlnd], "");
		rawline.truncate(rawline.length()-1);
		emit changePlayerStatus(rawline, KFibsPlayerList::Blind, true);
		line = "<font color=\"red\">" + line + "</font>";
	}
	else if (line.contains(pat[EndBlnd])) {
		rawline.replace(pat[EndBlnd], "");
		rawline.truncate(rawline.length()-1);
		emit changePlayerStatus(rawline, KFibsPlayerList::Blind, false);
		line = "<font color=\"red\">" + line + "</font>";
	}
	/*
	 * Starting or reloading games or matches
	 */
	else if (line.contains(pat[BegGame])) {
		rawline.replace(pat[BegGame], "");
		rawline.truncate(rawline.length()-1);
		emit fibsStartNewGame(rawline);
		fibsOpt[OptDouble]->setChecked(true);
		fibsOpt[OptGreedy]->setChecked(false);
		actConti->setEnabled(false);
		actLeave->setEnabled(false);
	}
	else if (line.contains(pat[Reload1])) {
		rawline.replace(pat[Reload1], "");
		rawline = rawline.left(rawline.find(' '));
		rawline.truncate(rawline.length()-1);
		emit fibsStartNewGame(rawline);
		fibsOpt[OptDouble]->setChecked(true);
		fibsOpt[OptGreedy]->setChecked(false);
		actConti->setEnabled(false);
		actLeave->setEnabled(false);
		load();
	}
	else if (line.contains(pat[Reload2])) {
		rawline.replace(pat[Reload2], "");
		emit fibsStartNewGame(rawline);
		fibsOpt[OptDouble]->setChecked(true);
		fibsOpt[OptGreedy]->setChecked(false);
		actConti->setEnabled(false);
		actLeave->setEnabled(false);
		load();
	}
	/*
	 * Opponent offered resignation or the cube. We have to accept
	 * or reject the offer.
	 */
	else if (line.contains(pat[RejAcpt])) {
		actAccept->setEnabled(true);
		actReject->setEnabled(true);
		line += "<br><pre>  </pre>" + i18n("(or use the corresponding menu "
						   "entry to accept or reject the offer)");
	}
	/*
	 * This is strange: FIBS seems to not send a newline at the
	 * end of this pattern. So we work around that.
	 */
	else if (line.contains(pat[YouAcpt])) {
		actAccept->setEnabled(false);
		actReject->setEnabled(false);
		rawline.replace(pat[YouAcpt], "");
		line.truncate(line.length()-rawline.length());
		if (!rawline.stripWhiteSpace().isEmpty()) {
			handleServerData(rawline);
		}
	}
	/*
	 * Ending of games
	 */
	else if (line.contains(pat[EndLose])) {
		if (playing) {
			KNotifyClient::event("game over l", i18n("Sorry, you lost the game."));
			if (useAutoMsg[MsgLos] && !autoMsg[MsgLos].stripWhiteSpace().isEmpty())
				emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgLos]);
		}
		emit gameOver();
	}
	else if (line.contains(pat[EndVict])) {
		if (playing) {
			KNotifyClient::event("game over w", i18n("Congratulations, you won the game!"));
			if (useAutoMsg[MsgWin] && !autoMsg[MsgWin].stripWhiteSpace().isEmpty())
				emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgWin]);
		}
		emit gameOver();
	}
	else if (line.contains(pat[GameSav])) {
		emit gameOver();
	}
	/*
	 * User logs out. This has to be signalled to the player
	 * list. Get the true user names by working on the rawline.
	 */
	else if (line.contains(pat[UserLot])) {
		rawline.replace(pat[UserLot], "");
		emit fibsLogout(rawline.left(rawline.find(' ')));
		return;
	}
	/*
	 * Emit the name of the newly logged in user.
	 */
	else if (line.contains(pat[UserLin])) {
		rawline.replace(pat[UserLin], "");
		emit fibsLogin(rawline.left(rawline.find(' ')));
		return;
	}
	/*
	 * Special attention has to be paid to the proper setting of
	 * the 'boardstyle' variable, since we will not be able to display
	 * the board properly without it.
	 */
	else if (line.contains(pat[BoardSY])) {
		// ignored
		return;
	}
	else if (line.contains(pat[BoardSN])) {
		emit serverString("set boardstyle 3");
		emit infoText(QString("<font color=\"red\"><br>")
			      + i18n("You should never set the 'boardstyle' variable "
				     "by hand! It is vital for proper functioning of "
				     "this program that it remains set to 3. It has "
				     "been reset for you.")
			      + "<br></font>");
		return;
	}
	/*
	 * This is the final fall through: if the line started with ** and
	 * hasn't been processed, make it red, since it is a server resp.
	 * to something we just did.
	 */
	else if (line.contains(pat[TwoStar])) {
		line = "<font color=\"red\">" + line + "</font>";
	}

	// --------------------------------------------------------------------------------

	/*
	 * Print whatever part of the line made it here
	 */
	emit infoText(line);
}

// EOF


// == constructor, destructor and setup ========================================

/*
 * Constructor
 */
KBgEngineFIBS::KBgEngineFIBS(QWidget *parent, QString *name, QPopupMenu *pmenu)
	: KBgEngine(parent, name, pmenu)
{
	/*
	 * No connection, not playing, ready for login
	 */
	connection = new QSocket(parent, "fibs connection");
	playing = false;
	login = true;

	connect(connection, SIGNAL(hostFound()),            this, SLOT(hostFound()));
	connect(connection, SIGNAL(connected()),            this, SLOT(connected()));
	connect(connection, SIGNAL(error(int)),             this, SLOT(connError(int)));
	connect(connection, SIGNAL(connectionClosed()),     this, SLOT(connectionClosed()));
	connect(connection, SIGNAL(delayedCloseFinished()), this, SLOT(connectionClosed()));
	connect(connection, SIGNAL(readyRead()),            this, SLOT(readData()));

	connect(this, SIGNAL(serverString(const QString &)), this, SLOT(sendData(const QString &)));

	/*
	 * No invitation dialog
	 */
	invitationDlg = 0;

	connect(this, SIGNAL(fibsWhoInfo(const QString &)), this, SLOT(changeJoin(const QString &)));
	connect(this, SIGNAL(fibsLogout (const QString &)), this, SLOT(cancelJoin(const QString &)));
	connect(this, SIGNAL(gameOver()), this, SLOT(endGame()));

	/*
	 * Creating, initializing and connecting the player list
	 */
	playerlist = new KFibsPlayerList(0, "fibs player list");

	connect(this, SIGNAL(fibsWhoInfo(const QString &)), playerlist, SLOT(changePlayer(const QString &)));
	connect(this, SIGNAL(fibsLogout (const QString &)), playerlist, SLOT(deletePlayer(const QString &)));
	connect(this, SIGNAL(fibsWhoEnd()),           playerlist, SLOT(stopUpdate()));
	connect(this, SIGNAL(fibsConnectionClosed()), playerlist, SLOT(stopUpdate()));
	connect(this, SIGNAL(changePlayerStatus(const QString &, int, bool)),
		playerlist, SLOT(changePlayerStatus(const QString &, int, bool)));
	connect(playerlist, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &)));
	connect(playerlist, SIGNAL(fibsInvite(const QString &)), this, SLOT(fibsRequestInvitation(const QString &)));

	/*
	 * Create, initialize and connect the chat window
	 */
	chatWindow = new KBgChat(0, "chat window");

	connect(this, SIGNAL(chatMessage(const QString &)), chatWindow, SLOT(handleData(const QString &)));
	connect(this, SIGNAL(fibsStartNewGame(const QString &)), chatWindow, SLOT(startGame(const QString &)));
	connect(this, SIGNAL(gameOver()), chatWindow, SLOT(endGame()));
	connect(this, SIGNAL(fibsLogout (const QString &)), chatWindow, SLOT(deletePlayer(const QString &)));
	connect(chatWindow, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &)));
	connect(chatWindow, SIGNAL(fibsRequestInvitation(const QString &)), this, SLOT(fibsRequestInvitation(const QString &)));
	connect(chatWindow, SIGNAL(personalMessage(const QString &)), this, SLOT(personalMessage(const QString &)));
	connect(playerlist, SIGNAL(fibsTalk(const QString &)), chatWindow, SLOT(fibsTalk(const QString &)));

	/*
	 * Creating, initializing and connecting the menu
	 * ----------------------------------------------
	 */
	respMenu = new QPopupMenu();
	joinMenu = new QPopupMenu();
	cmdMenu  = new QPopupMenu();
	optsMenu = new QPopupMenu();

	/*
	 * Initialize the FIBS submenu - this is also put in the play menu
	 */
	conAction  = new KAction(i18n("&Connect"),    0, this, SLOT(   connectFIBS()), this);
	newAction  = new KAction(i18n("New Account"), 0, this, SLOT(    newAccount()), this);
	disAction  = new KAction(i18n("&Disconnect"), 0, this, SLOT(disconnectFIBS()), this);

	conAction->setEnabled(true ); conAction->plug(menu);
	disAction->setEnabled(false); disAction->plug(menu);
	newAction->setEnabled(true ); newAction->plug(menu);

	menu->insertSeparator();

	(invAction  = new KAction(i18n("&Invite..."), 0, this, SLOT(inviteDialog()), this))->plug(menu);

	/*
	 * Create and fill the response menu. This is for all these: type this or
	 * that messages from FIBS.
	 */
	cmdMenuID = menu->insertItem(i18n("&Commands"), cmdMenu); {

		(actAway  = new KAction(i18n("Away"), 0, this, SLOT(away()), this))->plug(cmdMenu);
		(actBack  = new KAction(i18n("Back"), 0, this, SLOT(back()), this))->plug(cmdMenu);

		actAway->setEnabled(true);
		actBack->setEnabled(false);
	}

	/*
	 * Create the server side options. This is preliminary and needs more work.
	 * The available options are skewed, since they refelect the needs of the
	 * author. Contact jens@hoefkens.com if your favorite option is missing.
	 */
	optsMenuID = menu->insertItem(i18n("&Options"), optsMenu); {

		for (int i = 0; i < NumFIBSOpt; i++)
			fibsOpt[i] = 0;

		fibsOpt[OptReady]   = new KToggleAction(i18n("Ready to Play"),
							0, this, SLOT(toggle_ready()),   this);
		fibsOpt[OptRatings] = new KToggleAction(i18n("Show Rating Computations"),
							0, this, SLOT(toggle_ratings()), this);
		fibsOpt[OptRatings]->setCheckedState(i18n("Hide Rating Computations"));
		fibsOpt[OptGreedy]  = new KToggleAction(i18n("Greedy Bearoffs"),
							0, this, SLOT(toggle_greedy()),  this);
		fibsOpt[OptDouble]  = new KToggleAction(i18n("Ask for Doubles"),
							0, this, SLOT(toggle_double()),  this);

		for (int i = 0; i < NumFIBSOpt; i++)
			if (fibsOpt[i])
				fibsOpt[i]->plug(optsMenu);

	}

	/*
	 * Create and fill the response menu. This is for all these: type this or
	 * that messages from FIBS.
	 */
	respMenuID = menu->insertItem(i18n("&Response"), respMenu); {

		(actAccept  = new KAction(i18n("Accept"), 0, this, SLOT(accept()), this))->plug(respMenu);
		(actReject  = new KAction(i18n("Reject"), 0, this, SLOT(reject()), this))->plug(respMenu);

		actAccept->setEnabled(false);
		actReject->setEnabled(false);

		respMenu->insertSeparator();

		(actConti  = new KAction(i18n("Join"),  0, this, SLOT(match_conti()), this))->plug(respMenu);
		(actLeave  = new KAction(i18n("Leave"), 0, this, SLOT(match_leave()), this))->plug(respMenu);

		actConti->setEnabled(false);
		actLeave->setEnabled(false);
	}

	/*
	 * Create the join menu and do not fill it (this happens at first
	 * action setup.
	 */
	joinMenuID = menu->insertItem(i18n("&Join"), joinMenu); {
		numJoin = -1;

		actJoin[0] = new KAction("", 0, this, SLOT(join_0()), this);
		actJoin[1] = new KAction("", 0, this, SLOT(join_1()), this);
		actJoin[2] = new KAction("", 0, this, SLOT(join_2()), this);
		actJoin[3] = new KAction("", 0, this, SLOT(join_3()), this);
		actJoin[4] = new KAction("", 0, this, SLOT(join_4()), this);
		actJoin[5] = new KAction("", 0, this, SLOT(join_5()), this);
		actJoin[6] = new KAction("", 0, this, SLOT(join_6()), this);
		actJoin[7] = new KAction("", 0, this, SLOT(join_7()), this);
	}

	menu->setItemEnabled(joinMenuID, false);
	menu->setItemEnabled( cmdMenuID, false);
	menu->setItemEnabled(respMenuID, false);
	menu->setItemEnabled(optsMenuID, false);

	/*
	 * Continue with the FIBS menu
	 */
	menu->insertSeparator();

	(listAct = new KToggleAction(i18n("&Player List"), 0, this, SLOT(showList()), this))->plug(menu);
	(chatAct = new KToggleAction(i18n("&Chat"),        0, this, SLOT(showChat()), this))->plug(menu);

	connect(playerlist, SIGNAL(windowVisible(bool)), listAct, SLOT(setChecked(bool)));
	connect(chatWindow, SIGNAL(windowVisible(bool)), chatAct, SLOT(setChecked(bool)));

	/*
	 * Create message IDs. This sets up a lot of regular expressions.
	 */
	initPattern();

	/*
	 * Restore old settings
	 */
	readConfig();

	// FIXME: open the child windows in start() and not here

	/*
	 * Update the menu actions
	 */
	listAct->setChecked(playerlist->isVisible());
	chatAct->setChecked(chatWindow->isVisible());

	/*
	 * Initialize the keepalive timer FIXME: make this a setting
	 */
	keepalive = true;

	// FIXME: move the start to connect...

	keepaliveTimer = new QTimer(this);
	connect(keepaliveTimer, SIGNAL(timeout()), this, SLOT(keepAlive()));
	keepaliveTimer->start(1200000);
}

/*
 * Destructor deletes child objects if necessary
 */
KBgEngineFIBS::~KBgEngineFIBS()
{
	delete joinMenu;
	delete respMenu;
	delete cmdMenu;
	delete optsMenu;

	delete connection;
	delete invitationDlg;

	delete playerlist;
	delete chatWindow;
}


