/*
    kirc.cpp - IRC Client

    Copyright (c) 2003-2004 by Michel Hermier <michel.hermier@wanadoo.fr>
    Copyright (c) 2002      by Nick Betcher <nbetcher@kde.org>

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

    *************************************************************************
    *                                                                       *
    * 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.                                   *
    *                                                                       *
    *************************************************************************
*/

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

#include "kircengine.h"
#include "ksslsocket.h"

#include <kconfig.h>
#include <kdebug.h>
#include <kextsock.h>
#include <klocale.h>
#include <kstandarddirs.h>

#include <qtextcodec.h>
#include <qtimer.h>

//Needed for getuid / getpwuid
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

#include <kopetemessage.h>

#ifndef KIRC_SSL_SUPPORT
#define KIRC_SSL_SUPPORT
#endif

using namespace KIRC;

// FIXME: Remove slotConnected() and error(int errCode) while going to KNetwork namespace

/* Please note that the regular expression "[\\r\\n]*$" is used in a QString::replace statement many times.
 * This gets rid of trailing \r\n, \r, \n, and \n\r characters.
 */
const QRegExp Engine::m_RemoveLinefeeds( QString::fromLatin1("[\\r\\n]*$") );

Engine::Engine(QObject *parent, const char *name)
	: QObject(parent, QString::fromLatin1("[KIRC::Engine]%1").arg(name).latin1()),
	  m_status(Idle),
	  m_FailedNickOnLogin(false),
	  m_useSSL(false),
	  m_commands(101, false),
//	  m_numericCommands(101),
	  m_ctcpQueries(17, false),
	  m_ctcpReplies(17, false),
	  codecs(577,false)
{
	setUserName(QString::null);

	m_commands.setAutoDelete(true);
	m_ctcpQueries.setAutoDelete(true);
	m_ctcpReplies.setAutoDelete(true);

	bindCommands();
	bindNumericReplies();
	bindCtcp();

	m_VersionString = QString::fromLatin1("Anonymous client using the KIRC engine.");
	m_UserString = QString::fromLatin1("Response not supplied by user.");
	m_SourceString = QString::fromLatin1("Unknown client, known source.");

	defaultCodec = QTextCodec::codecForMib(4);
	kdDebug(14120) << "Setting default engine codec, " << defaultCodec->name() << endl;

	m_sock = 0L;
}

Engine::~Engine()
{
	kdDebug(14120) << k_funcinfo << m_Host << endl;
	quit("KIRC Deleted", true);
	if( m_sock )
		delete m_sock;
}

void Engine::setUseSSL( bool useSSL )
{
	kdDebug(14120) << k_funcinfo << useSSL << endl;

	if( !m_sock || useSSL != m_useSSL )
	{
		if( m_sock )
			delete m_sock;

		m_useSSL = useSSL;


		if( m_useSSL )
		{
		#ifdef KIRC_SSL_SUPPORT
			m_sock = new KSSLSocket;
			m_sock->setSocketFlags( KExtendedSocket::inetSocket );
		}
		else
		#else
			kdWarning(14120) << "You tried to use SSL, but this version of Kopete was"
				" not compiled with IRC SSL support. A normal IRC connection will be attempted." << endl;
		}
		#endif
		{
			m_sock = new KExtendedSocket;
			m_sock->setSocketFlags( KExtendedSocket::inputBufferedSocket | KExtendedSocket::inetSocket );
		}

		QObject::connect(m_sock, SIGNAL(closed(int)),
				 this, SLOT(slotConnectionClosed()));
		QObject::connect(m_sock, SIGNAL(readyRead()),
				 this, SLOT(slotReadyRead()));
		QObject::connect(m_sock, SIGNAL(connectionSuccess()),
				 this, SLOT(slotConnected()));
		QObject::connect(m_sock, SIGNAL(connectionFailed(int)),
				 this, SLOT(error(int)));
	}
}

void Engine::setStatus(Engine::Status status)
{
	kdDebug(14120) << k_funcinfo << status << endl;

	if (m_status == status)
		return;

//	Engine::Status oldStatus = m_status;
	m_status = status;
	emit statusChanged(status);

	switch (m_status)
	{
	case Idle:
		// Do nothing.
		break;
	case Connecting:
		// Do nothing.
		break;
	case Authentifying:
		m_sock->enableRead(true);

		// If password is given for this server, send it now, and don't expect a reply
		if (!(password()).isEmpty())
			pass(password());

		user(m_Username, 0, m_realName);
		nick(m_Nickname);

		break;
	case Connected:
		// Do nothing.
		break;
	case Closing:
		m_sock->close();
		m_sock->reset();
		setStatus(Idle);
		break;
	case AuthentifyingFailed:
		setStatus(Closing);
		break;
	case Timeout:
		setStatus(Closing);
		break;
	case Disconnected:
		setStatus(Closing);
		break;
	}
}

void Engine::connectToServer(const QString &host, Q_UINT16 port, const QString &nickname, bool useSSL )
{
	setUseSSL(useSSL);

	m_Nickname = nickname;
	m_Host = host;
	m_Port = port;

	kdDebug(14120) << "Trying to connect to server " << m_Host << ":" << m_Port << endl;
	kdDebug(14120) << "Sock status: " << m_sock->socketStatus() << endl;

	if( !m_sock->setAddress(m_Host, m_Port) )
		kdDebug(14120) << k_funcinfo << "setAddress failed. Status:  " << m_sock->socketStatus() << endl;

	if( m_sock->startAsyncConnect() == 0 )
	{
		kdDebug(14120) << k_funcinfo << "Success!. Status: " << m_sock->socketStatus() << endl;
		setStatus(Connecting);
	}
	else
	{
		kdDebug(14120) << k_funcinfo << "Failed. Status: " << m_sock->socketStatus() << endl;
		setStatus(Disconnected);
	}
}

void Engine::slotConnected()
{
	setStatus(Authentifying);
}

void Engine::slotConnectionClosed()
{
    setStatus(Disconnected);
}

void Engine::error(int errCode)
{
	kdDebug(14120) << k_funcinfo << "Socket error: " << errCode << endl;
	if (m_sock->socketStatus () != KExtendedSocket::connecting)
	{
		// Connection in progress.. This is a signal fired wrong
		setStatus(Disconnected);
	}
}

void Engine::setVersionString(const QString &newString)
{
	m_VersionString = newString;
	m_VersionString.remove(m_RemoveLinefeeds);
}

void Engine::setUserString(const QString &newString)
{
	m_UserString = newString;
	m_UserString.remove(m_RemoveLinefeeds);
}

void Engine::setSourceString(const QString &newString)
{
	m_SourceString = newString;
	m_SourceString.remove(m_RemoveLinefeeds);
}

void Engine::setUserName(const QString &newName)
{
	if(newName.isEmpty())
		m_Username = QString::fromLatin1(getpwuid(getuid())->pw_name);
	else
		m_Username = newName;
	m_Username.remove(m_RemoveLinefeeds);
}

void Engine::setRealName(const QString &newName)
{
	if(newName.isEmpty())
		m_realName = QString::fromLatin1(getpwuid(getuid())->pw_gecos);
	else
		m_realName = newName;
	m_realName.remove(m_RemoveLinefeeds);
}

bool Engine::_bind(QDict<KIRC::MessageRedirector> &dict,
		QString command, QObject *object, const char *member,
		int minArgs, int maxArgs, const QString &helpMessage)
{
//	FIXME: Force upper case.
//	FIXME: Force number format.

	MessageRedirector *mr = dict[command];

	if (!mr)
	{
		mr = new MessageRedirector(this, minArgs, maxArgs, helpMessage);
		dict.replace(command, mr);
	}

	return mr->connect(object, member);
}

bool Engine::bind(const QString &command, QObject *object, const char *member,
	int minArgs, int maxArgs, const QString &helpMessage)
{
	return _bind(m_commands, command, object, member,
		minArgs, maxArgs, helpMessage);
}

bool Engine::bind(int id, QObject *object, const char *member,
		int minArgs, int maxArgs, const QString &helpMessage)
{
	return _bind(m_commands, QString::number(id), object, member,
		     minArgs, maxArgs, helpMessage);
}

bool Engine::bindCtcpQuery(const QString &command, QObject *object, const char *member,
	int minArgs, int maxArgs, const QString &helpMessage)
{
	return _bind(m_ctcpQueries, command, object, member,
		minArgs, maxArgs, helpMessage);
}

bool Engine::bindCtcpReply(const QString &command, QObject *object, const char *member,
	int minArgs, int maxArgs, const QString &helpMessage)
{
	return _bind(m_ctcpReplies, command, object, member,
		minArgs, maxArgs, helpMessage);
}

/* Message will be send as passed.
 */
void Engine::writeRawMessage(const QString &rawMsg)
{
	Message::writeRawMessage(this, defaultCodec, rawMsg);
}

/* Message will be quoted before beeing send.
 */
void Engine::writeMessage(const QString &msg, const QTextCodec *codec)
{
	Message::writeMessage(this, codec ? codec : defaultCodec, msg);
}

void Engine::writeMessage(const QString &command, const QStringList &args, const QString &suffix, const QTextCodec *codec)
{
	Message::writeMessage(this, codec ? codec : defaultCodec, command, args, suffix );
}

void Engine::writeCtcpMessage(const QString &command, const QString &to, const QString &ctcpMessage)
{
	Message::writeCtcpMessage(this, defaultCodec, command, to, ctcpMessage);
}

void Engine::writeCtcpMessage(const QString &command, const QString &to, const QString &suffix,
		const QString &ctcpCommand, const QStringList &ctcpArgs, const QString &ctcpSuffix, bool )
{
	QString nick =  Entity::userNick(to);

	Message::writeCtcpMessage(this, codecForNick( nick ), command, nick, suffix,
		ctcpCommand, ctcpArgs, ctcpSuffix );
}

void Engine::slotReadyRead()
{
	// This condition is buggy when the peer server
	// close the socket unexpectedly
	bool parseSuccess;

	if (m_sock->socketStatus() == KExtendedSocket::connected && m_sock->canReadLine())
	{
		Message msg = Message::parse(this, defaultCodec, &parseSuccess);
		if (parseSuccess)
		{
			emit receivedMessage(msg);

			KIRC::MessageRedirector *mr;
			if (msg.isNumeric())
//				mr = m_numericCommands[ msg.command().toInt() ];
				// we do this conversion because some dummy servers sends 1 instead of 001
				// numbers are stored as "1" instead of "001" to make convertion faster (no 0 pading).
				mr = m_commands[ QString::number(msg.command().toInt()) ];
			else
				mr = m_commands[ msg.command() ];

			if (mr)
			{
				QStringList errors = mr->operator()(msg);

				if (!errors.isEmpty())
				{
					kdDebug(14120) << "Method error for line:" << msg.raw() << endl;
					emit internalError(MethodFailed, msg);
				}
			}
			else if (msg.isNumeric())
			{
				kdWarning(14120) << "Unknown IRC numeric reply for line:" << msg.raw() << endl;
				emit incomingUnknown(msg.raw());
			}
			else
			{
				kdWarning(14120) << "Unknown IRC command for line:" << msg.raw() << endl;
				emit internalError(UnknownCommand, msg);
			}
		}
		else
		{
			emit incomingUnknown(msg.raw());
			emit internalError(ParsingFailed, msg);
		}

		QTimer::singleShot( 0, this, SLOT( slotReadyRead() ) );
	}

	if(m_sock->socketStatus() != KExtendedSocket::connected)
		error();
}

const QTextCodec *Engine::codecForNick( const QString &nick ) const
{
	if( nick.isEmpty() )
		return defaultCodec;

	QTextCodec *codec = codecs[ nick ];
	kdDebug(14120) << nick << " has codec " << codec << endl;

	if( !codec )
		return defaultCodec;
	else
		return codec;
}

void Engine::showInfoDialog()
{
	if( m_useSSL )
	{
		static_cast<KSSLSocket*>( m_sock )->showInfoDialog();
	}
}

/*
 * The ctcp commands seems to follow the same message behaviours has normal IRC command.
 * (Only missing the \n\r final characters)
 * So applying the same parsing rules to the messages.
 */
bool Engine::invokeCtcpCommandOfMessage(const QDict<MessageRedirector> &map, Message &msg)
{
	if(msg.hasCtcpMessage() && msg.ctcpMessage().isValid())
	{
		Message &ctcpMsg = msg.ctcpMessage();

		MessageRedirector *mr = map[ctcpMsg.command()];
		if (mr)
		{
			QStringList errors = mr->operator()(msg);

			if (errors.isEmpty())
				return true;

			kdDebug(14120) << "Method error for line:" << ctcpMsg.raw();
			writeCtcpErrorMessage(msg.prefix(), msg.ctcpRaw(),
				QString::fromLatin1("%1 internal error(s)").arg(errors.size()));
		}
		else
		{
			kdDebug(14120) << "Unknow IRC/CTCP command for line:" << ctcpMsg.raw();
			writeCtcpErrorMessage(msg.prefix(), msg.ctcpRaw(), "Unknown CTCP command");

			emit incomingUnknownCtcp(msg.ctcpRaw());
		}
	}
	else
	{
		kdDebug(14120) << "Message do not embed a CTCP message:" << msg.raw();
	}
	return false;
}

#include "kircengine.moc"
