/*
    kirc_ctcp.h - IRC Client

    Copyright (c) 2003      by Michel Hermier <michel.hermier@wanadoo.fr>
    Copyright (c) 2002      by Nick Betcher <nbetcher@kde.org>
    Copyright (c) 2003      by Jason Keirstead <jason@keirstead.org>

    Kopete    (c) 2002-2003 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.                                   *
    *                                                                       *
    *************************************************************************
*/

#include "config.h"

#include "kircengine.h"
#include "kirctransferhandler.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <kextsock.h>

#include <tqfileinfo.h>
#include <tqregexp.h>

using namespace KIRC;

void Engine::bindCtcp()
{
	bindCtcpQuery("ACTION",		this, TQ_SLOT(CtcpQuery_action(KIRC::Message &)),
		-1,	-1);
	bindCtcpQuery("CLIENTINFO",	this, TQ_SLOT(CtcpQuery_clientinfo(KIRC::Message &)),
		-1,	1);
	bindCtcpQuery("DCC",		this, TQ_SLOT(CtcpQuery_dcc(KIRC::Message &)),
		4,	5);
	bindCtcpQuery("FINGER",		this, TQ_SLOT(CtcpQuery_finger(KIRC::Message &)),
		-1,	0);
	bindCtcpQuery("PING",		this, TQ_SLOT(CtcpQuery_ping(KIRC::Message &)),
		1,	1);
	bindCtcpQuery("SOURCE",		this, TQ_SLOT(CtcpQuery_source(KIRC::Message &)),
		-1,	0);
	bindCtcpQuery("TIME",		this, TQ_SLOT(CtcpQuery_time(KIRC::Message &)),
		-1,	0);
	bindCtcpQuery("USERINFO",	this, TQ_SLOT(CtcpQuery_userinfo(KIRC::Message &)),
		-1,	0);
	bindCtcpQuery("VERSION",	this, TQ_SLOT(CtcpQuery_version(KIRC::Message &)),
		-1,	0);

	bindCtcpReply("ERRMSG",		this, TQ_SLOT(CtcpReply_errmsg(KIRC::Message &)),
		1,	-1);
	bindCtcpReply("PING",		this, TQ_SLOT(CtcpReply_ping(KIRC::Message &)),
		1,	1,	"");
	bindCtcpReply("VERSION",	this, TQ_SLOT(CtcpReply_version(KIRC::Message &)),
		-1,	-1,	"");
}

// Normal order for a ctcp command:
// CtcpRequest_*
// CtcpQuery_*
// CtcpReply_* (if any)

/* Generic ctcp commnd for the /ctcp trigger */
void Engine::CtcpRequestCommand(const TQString &contact, const TQString &command)
{
	if(m_status == Connected)
	{
		writeCtcpQueryMessage(contact, TQString(), command);
//		emit ctcpCommandMessage( contact, command );
	}
}

void Engine::CtcpRequest_action(const TQString &contact, const TQString &message)
{
	if(m_status == Connected)
	{
		writeCtcpQueryMessage(contact, TQString(), "ACTION", message);

		if( Entity::isChannel(contact) )
			emit incomingAction(Kopete::Message::unescape(contact), Kopete::Message::unescape(m_Nickname), message);
		else
			emit incomingPrivAction(Kopete::Message::unescape(m_Nickname), Kopete::Message::unescape(contact), message);
	}
}

void Engine::CtcpQuery_action(Message &msg)
{
	TQString target = msg.arg(0);
	if (target[0] == '#' || target[0] == '!' || target[0] == '&')
		emit incomingAction(target, msg.nickFromPrefix(), msg.ctcpMessage().ctcpRaw());
	else
		emit incomingPrivAction(msg.nickFromPrefix(), Kopete::Message::unescape(target), msg.ctcpMessage().ctcpRaw());
}

/*
NO REPLY EXIST FOR THE CTCP ACTION COMMAND !
bool Engine::CtcpReply_action(Message &msg)
{
}
*/

//	FIXME: the API can now answer to help commands.
void Engine::CtcpQuery_clientinfo(Message &msg)
{
	TQString clientinfo = customCtcpMap[ TQString::fromLatin1("clientinfo") ];

	if (clientinfo.isNull())
		clientinfo = TQString::fromLatin1("The following commands are supported, but "
			"without sub-command help: VERSION, CLIENTINFO, USERINFO, TIME, SOURCE, PING,"
			"ACTION.");

	writeCtcpReplyMessage(	msg.nickFromPrefix(), TQString(),
				msg.ctcpMessage().command(), TQString(), clientinfo);
}

void Engine::CtcpRequest_dcc(const TQString &nickname, const TQString &fileName, uint port, Transfer::Type type)
{
	if(	m_status != Connected ||
		m_sock->localAddress() == 0 ||
		m_sock->localAddress()->nodeName().isNull())
		return;

	switch(type)
	{
		case Transfer::Chat:
		{
			writeCtcpQueryMessage(nickname, TQString(),
				TQString::fromLatin1("DCC"),
				TQStringList(TQString::fromLatin1("CHAT")) << TQString::fromLatin1("chat") <<
					m_sock->localAddress()->nodeName() << TQString::number(port)
			);
			break;
		}

		case Transfer::FileOutgoing:
		{
			TQFileInfo file(fileName);
			TQString noWhiteSpace = file.fileName();
			if (noWhiteSpace.contains(' ') > 0)
				noWhiteSpace.replace(TQRegExp("\\s+"), "_");

			TransferServer *server = TransferHandler::self()->createServer(this, nickname, type, fileName, file.size());

			TQString ip = m_sock->localAddress()->nodeName();
			TQString ipNumber = TQString::number( ntohl( inet_addr( ip.latin1() ) ) );

			kdDebug(14120) << "Starting DCC file outgoing transfer." << endl;

			writeCtcpQueryMessage(nickname, TQString(),
				TQString::fromLatin1("DCC"),
				TQStringList(TQString::fromLatin1("SEND")) << noWhiteSpace << ipNumber <<
					TQString::number(server->port()) << TQString::number(file.size())
			);
			break;
		}

		case Transfer::FileIncoming:
		case Transfer::Unknown:
		default:
			break;
	}
}

void Engine::CtcpQuery_dcc(Message &msg)
{
	Message &ctcpMsg = msg.ctcpMessage();
	TQString dccCommand = ctcpMsg.arg(0).upper();

	if (dccCommand == TQString::fromLatin1("CHAT"))
	{
//		if(ctcpMsg.argsSize()!=4) return false;

		/* DCC CHAT type longip port
		 *
		 *  type   = Either Chat or Talk, but almost always Chat these days
		 *  longip = 32-bit Internet address of originator's machine
		 *  port   = Port on which the originator is waitng for a DCC chat
		 */
		bool okayHost, okayPort;
		// should ctctMsg.arg(1) be tested?
		TQHostAddress address(ctcpMsg.arg(2).toUInt(&okayHost));
		unsigned int port = ctcpMsg.arg(3).toUInt(&okayPort);
		if (okayHost && okayPort)
		{
			kdDebug(14120) << "Starting DCC chat window." << endl;
			TransferHandler::self()->createClient(
				this, msg.nickFromPrefix(),
				address, port,
				Transfer::Chat );
		}
	}
	else if (dccCommand == TQString::fromLatin1("SEND"))
	{
//		if(ctcpMsg.argsSize()!=5) return false;

		/* DCC SEND (filename) (longip) (port) (filesize)
		 *
		 *  filename = Name of file being sent
		 *  longip   = 32-bit Internet address of originator's machine
		 *  port     = Port on which the originator is waiitng for a DCC chat
		 *  filesize = Size of file being sent
		 */
		bool okayHost, okayPort, okaySize;
//		TQFileInfo realfile(msg.arg(1));
		TQHostAddress address(ctcpMsg.arg(2).toUInt(&okayHost));
		unsigned int port = ctcpMsg.arg(3).toUInt(&okayPort);
		unsigned int size = ctcpMsg.arg(4).toUInt(&okaySize);
		if (okayHost && okayPort && okaySize)
		{
			kdDebug(14120) << "Starting DCC send file transfert for file:" << ctcpMsg.arg(1) << endl;
			TransferHandler::self()->createClient(
				this, msg.nickFromPrefix(),
				address, port,
				Transfer::FileIncoming,
				ctcpMsg.arg(1), size );
		}
	}
//	else
//		((MessageRedirector *)sender())->error("Unknow dcc command");
}

/*
NO REPLY EXIST FOR THE CTCP DCC COMMAND !
bool Engine::CtcpReply_dcc(Message &msg)
{
}
*/

void Engine::CtcpReply_errmsg(Message &)
{
	// should emit one signal
}

void Engine::CtcpQuery_finger( Message &)
{
	// To be implemented
}

void Engine::CtcpRequest_ping(const TQString &target)
{
	kdDebug(14120) << k_funcinfo << endl;

	timeval time;
	if (gettimeofday(&time, 0) == 0)
	{
		TQString timeReply;

		if( Entity::isChannel(target) )
			timeReply = TQString::fromLatin1("%1.%2").arg(time.tv_sec).arg(time.tv_usec);
		else
		 	timeReply = TQString::number( time.tv_sec );

		writeCtcpQueryMessage(	target, TQString(), "PING", timeReply);
	}
//	else
//		((MessageRedirector *)sender())->error("failed to get current time");
}

void Engine::CtcpQuery_ping(Message &msg)
{
	writeCtcpReplyMessage(	msg.nickFromPrefix(), TQString(),
				msg.ctcpMessage().command(), msg.ctcpMessage().arg(0));
}

void Engine::CtcpReply_ping(Message &msg)
{
	timeval time;
	if (gettimeofday(&time, 0) == 0)
	{
		// FIXME: the time code is wrong for usec
		TQString timeReply = TQString::fromLatin1("%1.%2").arg(time.tv_sec).arg(time.tv_usec);
		double newTime = timeReply.toDouble();
		double oldTime = msg.suffix().section(' ',0, 0).toDouble();
		double difference = newTime - oldTime;
		TQString diffString;

		if (difference < 1)
		{
			diffString = TQString::number(difference);
			diffString.remove((diffString.find('.') -1), 2);
			diffString.truncate(3);
			diffString.append("milliseconds");
		}
		else
		{
			diffString = TQString::number(difference);
			TQString seconds = diffString.section('.', 0, 0);
			TQString millSec = diffString.section('.', 1, 1);
			millSec.remove(millSec.find('.'), 1);
			millSec.truncate(3);
			diffString = TQString::fromLatin1("%1 seconds, %2 milliseconds").arg(seconds).arg(millSec);
		}

		emit incomingCtcpReply(TQString::fromLatin1("PING"), msg.nickFromPrefix(), diffString);
	}
//	else
//		((MessageRedirector *)sender())->error("failed to get current time");
}

void Engine::CtcpQuery_source(Message &msg)
{
	writeCtcpReplyMessage(msg.nickFromPrefix(), TQString(),
			      msg.ctcpMessage().command(), m_SourceString);
}

void Engine::CtcpQuery_time(Message &msg)
{
	writeCtcpReplyMessage(msg.nickFromPrefix(), TQString(),
			      msg.ctcpMessage().command(), TQDateTime::currentDateTime().toString(),
			      TQString(), false);
}

void Engine::CtcpQuery_userinfo(Message &msg)
{
	TQString userinfo = customCtcpMap[ TQString::fromLatin1("userinfo") ];

	if (userinfo.isNull())
		userinfo = m_UserString;

	writeCtcpReplyMessage(msg.nickFromPrefix(), TQString(),
			      msg.ctcpMessage().command(), TQString(), userinfo);
}

void Engine::CtcpRequest_version(const TQString &target)
{
	writeCtcpQueryMessage(target, TQString(), "VERSION");
}

void Engine::CtcpQuery_version(Message &msg)
{
	TQString response = customCtcpMap[ TQString::fromLatin1("version") ];
	kdDebug(14120) << "Version check: " << response << endl;

	if (response.isNull())
		response = m_VersionString;

	writeCtcpReplyMessage(msg.nickFromPrefix(),
		msg.ctcpMessage().command() + " " + response);
}

void Engine::CtcpReply_version(Message &msg)
{
	emit incomingCtcpReply(msg.ctcpMessage().command(), msg.nickFromPrefix(), msg.ctcpMessage().ctcpRaw());
}
