/*
    kirc.cpp - IRC Client

    Copyright (c) 2005      by Tommi Rantala <tommi.rantala@cs.helsinki.fi>
    Copyright (c) 2003-2004 by Michel Hermier <michel.hermier@wanadoo.fr>
    Copyright (c) 2002      by Nick Betcher <nbetcher@kde.org>

    Kopete    (c) 2002-2005 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 <tqtextcodec.h>
#include <tqtimer.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 TQString::replace statement many times.
 * This gets rid of trailing \r\n, \r, \n, and \n\r characters.
 */
const TQRegExp Engine::m_RemoveLinefeeds( TQString::tqfromLatin1("[\\r\\n]*$") );

Engine::Engine(TQObject *tqparent, const char *name)
	: TQObject(tqparent, TQString::tqfromLatin1("[KIRC::Engine]%1").tqarg(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(TQString());

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

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

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

	defaultCodec = TQTextCodec::codecForMib(106); // UTF8 mib is 106
	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 );

			connect(m_sock, TQT_SIGNAL(certificateAccepted()), TQT_SLOT(slotConnected()));
			connect(m_sock, TQT_SIGNAL(certificateRejected()), TQT_SLOT(slotConnectionClosed()));
			connect(m_sock, TQT_SIGNAL(sslFailure()),          TQT_SLOT(slotConnectionClosed()));
		}
		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 );

			connect(m_sock, TQT_SIGNAL(connectionSuccess()),   TQT_SLOT(slotConnected()));
			connect(m_sock, TQT_SIGNAL(connectionFailed(int)), TQT_SLOT(error(int)));
		}

		connect(m_sock, TQT_SIGNAL(closed(int)), TQT_SLOT(slotConnectionClosed()));
		connect(m_sock, TQT_SIGNAL(readyRead()), TQT_SLOT(slotReadyRead()));
	}
}

void Engine::settqStatus(Engine::tqStatus status)
{
	kdDebug(14120) << k_funcinfo << status << endl;

	if (m_status == status)
		return;

//	Engine::tqStatus oldtqStatus = 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();
		settqStatus(Idle);
		break;
	case AuthentifyingFailed:
		settqStatus(Closing);
		break;
	case Timeout:
		settqStatus(Closing);
		break;
	case Disconnected:
		settqStatus(Closing);
		break;
	}
}

void Engine::connectToServer(const TQString &host, TQ_UINT16 port, const TQString &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. tqStatus:  " << m_sock->socketStatus() << endl;

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

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

void Engine::slotConnectionClosed()
{
	settqStatus(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
		settqStatus(Disconnected);
	}
}

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

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

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

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

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

bool Engine::_bind(TQDict<KIRC::MessageRedirector> &dict,
		TQString command, TQObject *object, const char *member,
		int minArgs, int maxArgs, const TQString &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 TQString &command, TQObject *object, const char *member,
	int minArgs, int maxArgs, const TQString &helpMessage)
{
	return _bind(m_commands, command, object, member,
		minArgs, maxArgs, helpMessage);
}

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

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

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

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

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

void Engine::writeMessage(const TQString &command, const TQStringList &args, const TQString &suffix, const TQTextCodec *codec)
{
	Message::writeMessage(this, codec ? codec : defaultCodec, command, args, suffix );
}

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

void Engine::writeCtcpMessage(const TQString &command, const TQString &to, const TQString &suffix,
		const TQString &ctcpCommand, const TQStringList &ctcpArgs, const TQString &ctcpSuffix, bool )
{
	TQString 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[ TQString::number(msg.command().toInt()) ];
			else
				mr = m_commands[ msg.command() ];

			if (mr)
			{
				TQStringList 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);
		}

		TQTimer::singleShot( 0, this, TQT_SLOT( slotReadyRead() ) );
	}

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

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

	TQTextCodec *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 TQDict<MessageRedirector> &map, Message &msg)
{
	if(msg.hasCtcpMessage() && msg.ctcpMessage().isValid())
	{
		Message &ctcpMsg = msg.ctcpMessage();

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

			if (errors.isEmpty())
				return true;

			kdDebug(14120) << "Method error for line:" << ctcpMsg.raw() << endl;
			writeCtcpErrorMessage(msg.prefix(), msg.ctcpRaw(),
				TQString::tqfromLatin1("%1 internal error(s)").tqarg(errors.size()));
		}
		else
		{
			kdDebug(14120) << "Unknow IRC/CTCP command for line:" << ctcpMsg.raw() << endl;
			// Don't send error message on unknown CTCP command
			// None of the client send it, and it makes the client as infected by virus for IRC network scanners
			// 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;
}

EntityPtr Engine::getEntity(const TQString &name)
{
	Entity *entity = 0;

	#pragma warning Do the searching code here.

	if (!entity)
	{
		entity = new Entity(name);
		m_entities.append(entity);
	}

	connect(entity, TQT_SIGNAL(destroyed(KIRC::Entity *)), TQT_SLOT(destroyed(KIRC::Entity *)));
	return EntityPtr(entity);
}

void Engine::destroyed(KIRC::Entity *entity)
{
	m_entities.remove(entity);
}

void Engine::ignoreMessage(KIRC::Message &/*msg*/)
{
}

void Engine::emitSuffix(KIRC::Message &msg)
{
	emit receivedMessage(InfoMessage, m_server, m_server, msg.suffix());
}

#include "kircengine.moc"

// vim: set noet ts=4 sts=4 sw=4: