/*
    msnsocket.cpp - Base class for the sockets used in MSN

    Copyright (c) 2002-2003 by Martijn Klingens       <klingens@kde.org>
    Copyright (c) 2002-2005 by Olivier Goffart        <ogoffart at kde.org>
    Copyright (c) 2005		by Gregg Edghill 		  <gregg.edghill@gmail.com>

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

    Portions of this code are taken from KMerlin,
              (c) 2001      by Olaf Lueg              <olueg@olsd.de>

    *************************************************************************
    *                                                                       *
    * 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 "msnsocket.h"
//#include "msnprotocol.h"

#include <qregexp.h>
#include <qtimer.h>

#include <kdebug.h>
#include <kconfig.h>
#include <kbufferedsocket.h>
#include <kserversocket.h>
#include <kresolver.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kurl.h>

#include "kopeteuiglobal.h"

using namespace KNetwork;

class MimeMessage
{
	public:
		MimeMessage(const QString &msg) : message(msg) {}

		QString getValue(const QString &key)
		{
			QRegExp rx(key+": ([^\r\n]+)");
			rx.search(message);
			return rx.cap(1);
		}
	private:
		QString message;
};

MSNSocket::MSNSocket(QObject* parent)  : QObject (parent)
{
	m_onlineStatus = Disconnected;
	m_socket = 0L;
	m_useHttp = false;
	m_timer  = 0L;
}

MSNSocket::~MSNSocket()
{
	//if ( m_onlineStatus != Disconnected )
	//	disconnect();
	delete m_timer;
	m_timer = 0L;
	doneDisconnect();
	if ( m_socket )
		m_socket->deleteLater();
}

void MSNSocket::connect( const QString &server, uint port )
{
	if ( m_onlineStatus == Connected || m_onlineStatus == Connecting )
	{
		kdWarning( 14140 ) << k_funcinfo << "Already connected or connecting! Not connecting again." << endl;
		return;
	}

	if( m_onlineStatus == Disconnecting )
	{
		// Cleanup first.
		// FIXME: More generic!!!
		kdWarning( 14140 ) << k_funcinfo << "We're still disconnecting! Deleting socket the hard way first." << endl;
		delete m_socket;
	}

	setOnlineStatus( Connecting );
	m_id = 0;
	//m_lastId = 0;
	m_waitBlockSize = 0;
	m_buffer = Buffer( 0 );

	//m_sendQueue.clear();

	m_server = server;
	m_port = port;

	if(!m_useHttp)
		m_socket = new KBufferedSocket( server, QString::number(port) );
	else {
		m_socket = new KBufferedSocket( m_gateway, "80" );
	}

	m_socket->enableRead( true );

	// enableWrite eats the CPU, and we only need it when the queue is
	// non-empty, so disable it until we have actual data in the queue
	m_socket->enableWrite( false );

	QObject::connect( m_socket, SIGNAL( readyRead() ),             this, SLOT( slotDataReceived() ) );
	QObject::connect( m_socket, SIGNAL( readyWrite() ),            this, SLOT( slotReadyWrite() ) );
	QObject::connect( m_socket, SIGNAL( hostFound() ),	       this, SLOT( slotHostFound() ) );
	QObject::connect( m_socket, SIGNAL( connected( const KResolverEntry&) ), this, SLOT( slotConnectionSuccess() ) );
	QObject::connect( m_socket, SIGNAL( gotError( int ) ),         this, SLOT( slotSocketError( int ) ) );
	QObject::connect( m_socket, SIGNAL( closed( ) ),               this, SLOT( slotSocketClosed( ) ) );

	if(m_useHttp)
	{
		if(m_timer == 0L)
		{
			m_timer = new QTimer(this, "Http poll timer");
			// Connect the slot HttpPoll with the timer timeout signal.
			QObject::connect(m_timer, SIGNAL(timeout()), this, SLOT(slotHttpPoll()));
		}
	}

	aboutToConnect();

	// start the asynchronous connection
	m_socket->connect();
}

void MSNSocket::disconnect()
{
	if(m_useHttp)
		if(m_timer->isActive()) {
			// If the timer is still active, stop the timer.
			m_timer->stop();
		}

	if ( m_socket )
		m_socket->closeNow();
	else
		slotSocketClosed();
}

void MSNSocket::aboutToConnect()
{
	/* Empty default implementation */
}

void MSNSocket::doneConnect()
{
	setOnlineStatus( Connected );
}

void MSNSocket::doneDisconnect()
{
	setOnlineStatus( Disconnected );
}

void MSNSocket::setOnlineStatus( MSNSocket::OnlineStatus status )
{
	if ( m_onlineStatus == status )
		return;

	m_onlineStatus = status;
	emit onlineStatusChanged( status );
}

void MSNSocket::slotSocketError( int error )
{
	kdWarning( 14140 ) << k_funcinfo << "Error: " << error << " (" << m_socket->errorString() << ")" << endl;

	if(!KSocketBase::isFatalError(error))
		return;
		//we only care about fatal error

	QString errormsg = i18n( "There was an error while connecting to the MSN server.\nError message:\n" );
	if ( error == KSocketBase::LookupFailure )
		errormsg += i18n( "Unable to lookup %1" ).arg( m_socket->peerResolver().nodeName() );
	else
		errormsg +=  m_socket->errorString() ;

	//delete m_socket;
	m_socket->deleteLater();
	m_socket = 0L;

	setOnlineStatus( Disconnected );
	emit connectionFailed();
	//like if the socket is closed
	emit socketClosed();

	emit errorMessage( ErrorConnectionError, errormsg );
}

void MSNSocket::slotDataReceived()
{
	int avail = m_socket->bytesAvailable();
	if ( avail < 0 )
	{
		// error!
		kdWarning( 14140 ) << k_funcinfo << "bytesAvailable() returned " << avail
		 	<< ". This should not happen!" << endl
			<< "Are we disconnected? Backtrace:" << endl << kdBacktrace() << endl;
		return;
	}

	// incoming data, plus an extra char where we pretend a NUL is so the conversion
	// to QCString doesn't go over the end of the allocated memory.
	char *buffer = new char[ avail + 1 ];
	int ret = m_socket->readBlock( buffer, avail );

	if ( ret < 0 )
	{
		kdWarning( 14140 ) << k_funcinfo << "readBlock() returned " << ret << "!" <<endl;
	}
	else if ( ret == 0 )
	{
		kdWarning( 14140 ) << k_funcinfo << "readBlock() returned no data!" <<endl;
	}
	else
	{
		if ( avail )
		{
			if ( ret != avail)
			{
				kdWarning( 14140 ) << k_funcinfo << avail << " bytes were reported available, "
					<< "but readBlock() returned only " << ret << " bytes! Proceeding anyway." << endl;
			}
		}
		else
		{
			kdDebug( 14140 ) << k_funcinfo << "Read " << ret << " bytes into 4kb block." << endl;
		}


		QString rawData;

		if(m_useHttp)
		{
			bool error = false;
			QByteArray bytes;

			// Check if all data has arrived.
			rawData = QString(QCString(buffer, avail + 1));
			bool headers = (rawData.find(QRegExp("HTTP/\\d\\.\\d (\\d+) ([^\r\n]+)")) != -1);

			if(headers)
			{
				// The http header packet arrived.
				int endOfHeaders = rawData.find("\r\n\r\n");
				if((endOfHeaders + 4) == avail)
				{
					// Only the response headers data is included.
					QRegExp re("Content-Length: ([^\r\n]+)");
					if(re.search(rawData) != -1)
					{
						bool valid;
						int l = re.cap(1).toInt(&valid);
						if(valid && l > 0)
						{
							// The packet contains the headers but does not contain the content data;
							// buffer the data received and read again.
							m_buffer.add(buffer, avail);

							delete[] buffer;
							// Update how much data remains.
							m_remaining = l;
							return;
						}
					}
				}
			}
			else
			{
				// Write the received data to the buffer.
				m_buffer.add(buffer, avail);

				m_remaining -= avail;
				if(m_remaining != 0)
				{
					// We have not received all the content data, read again.
					delete[] buffer;
					return;
				}

				// At this point, we have all the bytes returned from the web request.
				bytes = m_buffer.take(m_buffer.size());
			}
			
			if(bytes.size() == 0) 
			{
				// The response headers and the content came in one packet.
				bytes.assign(buffer, avail);
			}


			// Create the web response object from the response bytes.
			WebResponse response(bytes);

			if(response.getStatusCode() == 100) {
				return;
			}

			if(response.getStatusCode() == 200)
			{
				// If we received a valid response, read the required headers.
				// Retrieve the X-MSN-Messenger header.
				QString header = response.getHeaders()->getValue("X-MSN-Messenger");

				QStringList parts = QStringList::split(";", header.replace(" ", ""));
				if(!header.isNull() && (parts.count() >= 2))
				{
					if(parts[0].find("SessionID", 0) != -1)
					{
						// Assign the session id.
						m_sessionId = parts[0].section("=", 1, 1);
					}else
						error = true;

					if(parts[1].find("GW-IP", 0) != -1)
					{
						// Assign the gateway IP address.
						m_gwip = parts[1].section("=", 1, 1);
					}else
						error = true;


					if(parts.count() > 2)
						if((parts[2].find("Session", 0) != -1) && (parts[2].section("=", 1, 1) == "close"))
						{
							// The http session has been closed by the server, disconnect.
							kdDebug(14140) << k_funcinfo << "Session closed." << endl;
							m_bCanPoll = false;
							disconnect();
							return;
						}
				}else
					error = true;

				// Retrieve the content length header.
				header = response.getHeaders()->getValue("Content-Length");

				if(!header.isNull())
				{
					bool valid;
					int length = header.toInt(&valid);
					if(valid && (length == 0))
					{
						// If the response content length is zero, there is nothing to do.
						m_pending  = false;
						return;
					}

					if(valid && (length > 0))
					{
						// Otherwise, if the content length is greater than zero, get the web response stream.
						QDataStream *stream = response.getResponseStream();
						buffer = new char[length];
						// Read the web response content.
						stream->readRawBytes(buffer, length);
						ret = length;
					}else
						error = true;
				}else
					error = true;
			}else
				error = true;

			if(error)
			{
				kdDebug(14140) << k_funcinfo << "Http error: " << response.getStatusCode() << " "
					<< response.getStatusDescription() << endl;

				// If we encountered an error, disconnect and return.
				m_bCanPoll = false;
				// Disconnect from the service.
				disconnect();
				return;
			}
		}

		// Simple check to avoid dumping the binary data from the icons and emoticons to kdDebug:
		// all MSN commands start with one or more uppercase characters.
		// For now just check the first three chars, let's see how accurate it is.
		// Additionally, if we receive an MSN-P2P packet, strip off anything after the P2P header.
		rawData = QString( QCString( buffer, ((!m_useHttp)? avail : ret) + 1 ) ).stripWhiteSpace().replace(
			QRegExp( "(P2P-Dest:.[a-zA-Z@.]*).*" ), "\\1\n\n(Stripped binary data)" );

		bool isBinary = false;
		for ( uint i = 0; i < 3 ; ++i )
		{
			if ( (rawData[ i ] < 'A' || rawData[ i ] > 'Z') && (rawData[ i ] < '0' || rawData[ i ] > '9')  )
				isBinary = true;
		}

		if ( isBinary )
			kdDebug( 14141 ) << k_funcinfo << "(Stripped binary data)" << endl;
		else
			kdDebug( 14141 ) << k_funcinfo << rawData << endl;

		// fill the buffer with the received data
		m_buffer.add( buffer, ret );

		slotReadLine();

		if(m_useHttp) {
			// Set data pending to false.
			m_pending  = false;
		}
	}

	// Cleanup.
	delete[] buffer;
}

void MSNSocket::slotReadLine()
{
	// We have data, first check if it's meant for a block read, otherwise
	// parse the first line (which will recursively parse the other lines)
	if ( !pollReadBlock() )
	{
		if ( m_buffer.size() >= 3 && ( m_buffer.data()[ 0 ] == '\0' || m_buffer.data()[ 0 ]== '\1' ) )
		{
			bytesReceived( m_buffer.take( 3 ) );
			QTimer::singleShot( 0, this, SLOT( slotReadLine() ) );
			return;
		}

		int index = -1;
		for ( uint x = 0; m_buffer.size() > x + 1; ++x )
		{
			if ( ( m_buffer[ x ] == '\r' ) && ( m_buffer[ x + 1 ] == '\n' ) )
			{
				index = x;
				break;
			}
		}

		if ( index != -1 )
		{
			QString command = QString::fromUtf8( m_buffer.take( index + 2 ), index );
			command.replace( "\r\n", "" );
			//kdDebug( 14141 ) << k_funcinfo << command << endl;

			// Don't block the GUI while parsing data, only do a single line!
			// (Done before parseLine() to prevent a potential crash)
			QTimer::singleShot( 0, this, SLOT( slotReadLine() ) );

			parseLine( command );
			// WARNING: At this point 'this' can be deleted (when disconnecting)
		}
	}
}

void MSNSocket::readBlock( uint len )
{
	if ( m_waitBlockSize )
	{
		kdWarning( 14140 ) << k_funcinfo << "Cannot wait for data block: still waiting for other block of size "
			<< m_waitBlockSize << "! Data will not be returned." << endl;
		return;
	}

	m_waitBlockSize = len;

	//kdDebug( 14140 ) << k_funcinfo << "Preparing for block read of size " << len << endl;

	// Try to return the data now, if available. Otherwise slotDataReady
	// will do this whenever all data is there.
	pollReadBlock();
}

bool MSNSocket::pollReadBlock()
{
	if ( !m_waitBlockSize )
	{
		return false;
	}
	else if ( m_buffer.size() < m_waitBlockSize )
	{
		kdDebug( 14140 ) << k_funcinfo << "Waiting for data. Received: " << m_buffer.size() << ", required: " << m_waitBlockSize << endl;
		return true;
	}

	QByteArray block = m_buffer.take( m_waitBlockSize );

	//kdDebug( 14140 ) << k_funcinfo << "Successfully read block of size " << m_waitBlockSize << endl;

	m_waitBlockSize = 0;
	emit blockRead( block);

	return false;
}

void MSNSocket::parseLine( const QString &str )
{
	QString cmd  = str.section( ' ', 0, 0 );
	QString data = str.section( ' ', 2 ).replace( "\r\n" , "" );

	bool isNum;
	uint id = str.section( ' ', 1, 1 ).toUInt( &isNum );

	// In some rare cases, like the 'NLN' / 'FLN' commands no id at all
	// is sent. Here it's actually a real parameter...
	if ( !isNum )
		data = str.section( ' ', 1, 1 ) + " " + data;

	//if ( isNum && id )
	//	m_lastId = id;

	//kdDebug( 14140 ) << k_funcinfo << "Parsing command " << cmd << " (ID " << id << "): '" << data << "'" << endl;

	data.replace( "\r\n", "" );
	bool isError;
	uint errorCode = cmd.toUInt( &isError );
	if ( isError )
		handleError( errorCode, id );
	else
		parseCommand( cmd, id, data );
}

void MSNSocket::handleError( uint code, uint /* id */ )
{
	kdDebug(14140) << k_funcinfo << endl;
	QString msg;
	ErrorType type = ErrorServerError;
	switch ( code )
	{
/*
		// We cant show message for error we don't know what they are or not related to the correct socket
		//  Theses following messages are not so instructive
	case 205:
		msg = i18n ( "An invalid username has been specified.\nPlease correct it, and try to reconnect.\n" );
		break;
	case 201:
		msg = i18n ( "Fully Qualified domain name missing.\n" );
		break;
	case 207:
		msg = i18n ( "You are already logged in!\n" );
		break;
	case 208:
		msg = i18n ( "You specified an invalid username.\nPlease correct it, and try to reconnect.\n");
		break;
	case 209:
		msg = i18n ( "Your nickname is invalid. Please check it, correct it,\nand try to reconnect.\n" );
		break;
	case 210:
		msg = i18n ( "Your list has reached its maximum capacity.\nNo more contacts can be added, unless you remove some first.\n" );
		break;
	case 216:
		 msg = i18n ( "This user is not in your contact list.\n " );
		break;
	case 300:
		msg = i18n ( "Some required fields are missing. Please fill them in and try again.\n" );
		break;
	case 302:
		msg = i18n ( "You are not logged in.\n" );
		break;
*/
	case 500:
		msg = i18n ( "An internal server error occurred. Please try again later." );
		type = MSNSocket::ErrorCannotConnect;
		break;
	case 502:
		msg = i18n ( "It is no longer possible to perform this operation. The MSN server does not allow it anymore." );
		type = MSNSocket::ErrorServerError;
		break;
	case 600:
	case 910:
	case 912:
	case 921:
	case 922:
		msg = i18n ( "The MSN server is busy. Please try again later." );
		type = MSNSocket::ErrorConnectionError;
		break;
	case 601:
	case 604:
	case 605:
	case 914:
	case 915:
	case 916:
	case 917:
		msg = i18n ( "The server is not available at the moment. Please try again later." );
		type = MSNSocket::ErrorCannotConnect;
		break;
	// Server error
	default:
		// FIXME: if the error causes a disconnect, it will crash, but we can't disconnect every time
		msg = i18n( "Unhandled MSN error code %1 \n"
			"Please fill a bug report with a detailed description and if possible the last console debug output." ).arg( code );
			// "See http://www.hypothetic.org/docs/msn/basics.php for a description of all error codes."
		break;
	}

	if ( !msg.isEmpty() )
		emit errorMessage( type, msg );

	return;
}

int MSNSocket::sendCommand( const QString &cmd, const QString &args, bool addId, const QByteArray &body, bool binary )
{
	if ( !m_socket )
	{
		kdWarning( 14140 ) << k_funcinfo << "m_socket == NULL!" << endl;
		return -1;
	}

	QCString data = cmd.utf8();
	if ( addId )
		data += " " + QString::number( m_id ).utf8();

	if ( !args.isEmpty() )
		data += " " + args.utf8();

	// Add length in bytes, not characters
	if ( !body.isEmpty() )
		data += " " + QString::number( body.size() - (binary ? 0 : 1 ) ).utf8();

	data += "\r\n";


	// the command will be sent in slotReadyWrite
	QByteArray bytes;
	const uint length = data.length();
	bytes.duplicate(data.data(), length);
	if(!body.isEmpty())
	{
		uint l = body.size() - (binary ? 0 : 1);
		bytes.resize(length + l);
		for(uint i=0; i < l; i++)
			bytes[length + i] = body[i];
	}

	// Add the request to the queue.
	m_sendQueue.append(bytes);
	m_socket->enableWrite(true);

	if ( addId )
	{
		++m_id;
		return m_id - 1;
	}

	return 0;
}

void MSNSocket::slotReadyWrite()
{
	if ( !m_sendQueue.isEmpty() )
	{
		// If the command queue is not empty, retrieve the first command.
		QValueList<QByteArray>::Iterator it = m_sendQueue.begin();

		if(m_useHttp)
		{
			// If web response data is not pending, send the http request.
			if(!m_pending)
			{
				m_pending = true;
				// Temporarily disable http polling.
				m_bCanPoll = false;
				// Set the host to the msn gateway by default.
				QString host = m_gateway;
				QString query; // Web request query string.

				if(m_bIsFirstInTransaction)
				{
					query.append("Action=open&Server=");
					query.append(m_type);

					query += "&IP=" + m_server;

					m_bIsFirstInTransaction = false;
				}
				else
				{
					// If this is not the first request sent in the transaction,
					// only add the session Id.
					host = m_gwip;
					query += "SessionID=" + m_sessionId;
				}

				// Create the web request headers.
				QString s = makeHttpRequestString(host, query, (*it).size());

				uint length = s.length();
				// Create the web request bytes.
				QByteArray bytes(length + (*it).size());

				// Copy the request headers into the request bytes.
				for(uint i=0; i < length; i++)
					bytes[i] = s.ascii()[i];
				// Copy the request body into the request bytes.
				for(uint i=0; i < (*it).size(); i++)
					bytes[length + i] = (*it)[i];

				kdDebug( 14141 ) << k_funcinfo << "Sending http command: " << QString(*it).stripWhiteSpace() << endl;

				// Write the request bytes to the socket.
				m_socket->writeBlock(bytes.data(), bytes.size());

				// Remove the request from the request queue.
				m_sendQueue.remove(it);

				if(m_sendQueue.isEmpty())
				{
					// Disable sending requests.
					m_socket->enableWrite(false);
					// If the request queue is empty, poll the server.
					m_bCanPoll = true;
				}
			}
		}
		else
		{
			// Otherwise, send the command normally.

			// Simple check to avoid dumping the binary data from the icons and emoticons to kdDebug:
			// When sending an MSN-P2P packet, strip off anything after the P2P header.
			QString debugData = QString( *it ).stripWhiteSpace().replace(
				QRegExp( "(P2P-Dest:.[a-zA-Z@.]*).*" ), "\\1\n\n(Stripped binary data)" );
			kdDebug( 14141 ) << k_funcinfo << "Sending command: " << debugData << endl;

			m_socket->writeBlock( *it, ( *it ).size() );
			m_sendQueue.remove( it );

			// If the queue is empty agalin stop waiting for readyWrite signals
			// because of the CPU usage
			if ( m_sendQueue.isEmpty() )
				m_socket->enableWrite( false );
		}
	}
	else
	{
		m_socket->enableWrite( false );

		if(m_useHttp)
		{
			// If the request queue is empty, poll the server.
			m_bCanPoll = true;
		}
	}
}

QString MSNSocket::escape( const QString &str )
{
	//return ( KURL::encode_string( str, 106 ) );
	//It's not needed to encode everything. The official msn client only encode spaces and %
	//If we encode more, the size can be longer than excepted.

	int old_length= str.length();
	QChar *new_segment = new QChar[ old_length * 3 + 1 ];
	int new_length = 0;

	for	( int i = 0; i < old_length; i++ )
	{
		unsigned short character = str[i].unicode();

		if( character <= 32 || character == '%' )
		{
			new_segment[ new_length++ ] = '%';

			unsigned int c = character / 16;
			c += (c > 9) ? ('A' - 10) : '0';
			new_segment[ new_length++ ] = c;

			c = character % 16;
			c += (c > 9) ? ('A' - 10) : '0';
			new_segment[ new_length++ ] = c;
		}
		else
			new_segment[ new_length++ ] = str[i];
	}

	QString result = QString(new_segment, new_length);
	delete [] new_segment;
	return result;

}

QString MSNSocket::unescape( const QString &str )
{
	QString str2 = KURL::decode_string( str, 106 );
	//remove msn+ colors code
	str2 = str2.replace( QRegExp("[\\x1-\\x8]"), "" ); // old msn+ colors
	// added by kaoul <erwin.kwolek at gmail.com>
	str2 = str2.replace( QRegExp("\\xB7[&@\'#0]"),""); // dot ...
	str2 = str2.replace( QRegExp("\\xB7\\$,?\\d{1,2}"),""); // dot dollar (comma)? 0-99

	return str2;
}

void MSNSocket::slotConnectionSuccess()
{
	if(m_useHttp)
	{
		// If we are connected, set the data pending flag to false,
		// and disable http polling.
		m_pending  = false;
		m_bCanPoll = false;
		// If we are connected, start the timer.
		m_timer->start(2000, false);
	}

	//kdDebug( 14140 ) << k_funcinfo << endl;
	doneConnect();
}

void MSNSocket::slotHostFound()
{
        // nothing to do
}

void MSNSocket::slotSocketClosed()
{
    kdDebug( 14140 ) << k_funcinfo << "Socket closed. " << endl;

	if ( !m_socket ||  m_onlineStatus == Disconnected )
	{
		kdDebug( 14140 ) << k_funcinfo << "Socket already deleted or already disconnected" << endl;
		return;
	}

	doneDisconnect();

	m_buffer = Buffer( 0 );
	//delete m_socket;
	m_socket->deleteLater();
	m_socket = 0L;

	emit socketClosed();
}

void MSNSocket::slotHttpPoll()
{
	if(m_pending || !m_bCanPoll){
		// If data is pending or poll has been temporary disabled, return.
		return;
	}

	// Create the http request headers.
	const QCString headers = makeHttpRequestString(m_gwip, "Action=poll&SessionID=" + m_sessionId, 0).utf8();
	m_socket->writeBlock(headers, headers.length());
	// Wait for the response.
	m_pending = true;
	m_socket->enableWrite(true);
}

// Used in MSNFileTransferSocket
// FIXME: Why is this here if it's only used for file transfer? - Martijn
void MSNSocket::bytesReceived( const QByteArray & /* data */ )
{
	kdWarning( 14140 ) << k_funcinfo << "Unknown bytes were received" << endl;
}

void MSNSocket::sendBytes( const QByteArray &data )
{
	if ( !m_socket )
	{
		kdWarning( 14140 ) << k_funcinfo << "Not yet connected" << endl;
		return;
	}

	m_socket->writeBlock( data, data.size() );
	m_socket->enableWrite( true );
}

bool MSNSocket::setUseHttpMethod( bool useHttp )
{
	if( m_useHttp == useHttp )
		return true;

	if( useHttp ) {
		QString s = QString( this->className() ).lower();
		if( s == "msnnotifysocket" )
			m_type = "NS";
		else if( s == "msnswitchboardsocket" )
			m_type = "SB";
		else
			m_type = QString::null;

		if( m_type.isNull() )
			return false;

		m_bCanPoll = false;
		m_bIsFirstInTransaction = true;
		m_pending = false;
		m_remaining = 0;
		m_gateway = "gateway.messenger.hotmail.com";
	}

	if ( m_onlineStatus != Disconnected )
		disconnect();

	m_useHttp = useHttp;

	return true;
}

bool MSNSocket::useHttpMethod() const
{
	return m_useHttp;
}

bool MSNSocket::accept( KServerSocket *server )
{
	if ( m_socket )
	{
		kdWarning( 14140 ) << k_funcinfo << "Socket already exists!" << endl;
		return false;
	}

	m_socket = static_cast<KBufferedSocket*>(server->accept());

	if ( !m_socket )
	{
//		kdWarning( 14140 ) << k_funcinfo << "Socket not created.  Error nb" << server->error() << " : " << server->errorString() << endl;
		return false;
	}

	kdDebug( 14140 ) << k_funcinfo << "incoming connection accepted" << endl;

	setOnlineStatus( Connecting );

	m_id = 0;
	//m_lastId = 0;
	m_waitBlockSize = 0;

	m_socket->setBlocking( false );
	m_socket->enableRead( true );
	m_socket->enableWrite( true );

	QObject::connect( m_socket, SIGNAL( readyRead() ),             this, SLOT( slotDataReceived() ) );
	QObject::connect( m_socket, SIGNAL( readyWrite() ),            this, SLOT( slotReadyWrite() ) );
	QObject::connect( m_socket, SIGNAL( closed() ),                this, SLOT( slotSocketClosed() ) );
	QObject::connect( m_socket, SIGNAL( gotError( int ) ),         this, SLOT( slotSocketError( int ) ) );

	doneConnect();
	return true;
}

QString MSNSocket::getLocalIP()
{
	if ( !m_socket )
		return QString::null;

	const KSocketAddress address = m_socket->localAddress();

	QString ip = address.nodeName();

	kdDebug( 14140 ) << k_funcinfo << "IP: " << ip  <<endl;
	//delete address;
	return ip;
}

MSNSocket::Buffer::Buffer( unsigned int sz )
: QByteArray( sz )
{
}

MSNSocket::Buffer::~Buffer()
{
}

void MSNSocket::Buffer::add( char *str, unsigned int sz )
{
	char *b = new char[ size() + sz ];
	for ( uint f = 0; f < size(); f++ )
		b[ f ] = data()[ f ];
	for ( uint f = 0; f < sz; f++ )
		b[ size() + f ] = str[ f ];

	duplicate( b, size() + sz );
	delete[] b;
}

QByteArray MSNSocket::Buffer::take( unsigned blockSize )
{
	if ( size() < blockSize )
	{
		kdWarning( 14140 ) << k_funcinfo << "Buffer size " << size() << " < asked size " << blockSize << "!" << endl;
		return QByteArray();
	}

	QByteArray rep( blockSize );
	for( uint i = 0; i < blockSize; i++ )
		rep[ i ] = data()[ i ];

	char *str = new char[ size() - blockSize ];
	for ( uint i = 0; i < size() - blockSize; i++ )
		str[ i ] = data()[ blockSize + i ];
	duplicate( str, size() - blockSize );
	delete[] str;

	return rep;
}

QString MSNSocket::makeHttpRequestString(const QString& host, const QString& query, uint contentLength)
{
	QString s(
		"POST http://" + host + "/gateway/gateway.dll?" + query + " HTTP/1.1\r\n" +
		"Accept: */*\r\n" +
		"Accept-Language: en-us\r\n" +
		"User-Agent: MSMSGS\r\n" +
		"Host: " + host + "\r\n" +
		"Proxy-Connection: Keep-Alive\r\n" +
		"Connection: Keep-Alive\r\n" +
		"Pragma: no-cache\r\n" +
		"Content-Type: application/x-msn-messenger\r\n" +
		"Content-Length: " + QString::number(contentLength) + "\r\n" +
		"\r\n");
	return s;
}

MSNSocket::WebResponse::WebResponse(const QByteArray& bytes)
{
	m_statusCode = 0;
	m_stream     = 0;

	int     headerEnd;
  	QString header;
	QString data(QCString(bytes, bytes.size() + 1));

	// Parse the HTTP status header
	QRegExp re("HTTP/\\d\\.\\d (\\d+) ([^\r\n]+)");
	headerEnd  = data.find("\r\n");
	header     = data.left( (headerEnd == -1) ? 20 : headerEnd );

	re.search(header);
	m_statusCode = re.cap(1).toInt();
	m_statusDescription = re.cap(2);

	// Remove the web response status header.
	data = data.mid(headerEnd + 2, (data.find("\r\n\r\n") + 2) - (headerEnd + 2));
	// Create a MimeMessage, removing the HTTP status header
	m_headers = new MimeMessage(data);

	// Retrieve the contentlength header.
	header = m_headers->getValue("Content-Length");
	if(!header.isNull())
	{
		bool valid;
		int length = header.toInt(&valid);
		if(valid && length > 0)
		{
			// If the content length is valid, and not zero,
			// copy the web response content bytes.
			int offset = bytes.size() - length;

			QByteArray content(length);
			for(int i=0; i < length; i++)
				content[i] = bytes[offset + i];
			// Create the web response stream from the response content bytes.
			m_stream = new QDataStream(content, IO_ReadOnly);
		}
	}
}

MSNSocket::WebResponse::~WebResponse()
{
	delete m_headers;
	m_headers = 0;
	delete m_stream;
	m_stream = 0;
}

MimeMessage* MSNSocket::WebResponse::getHeaders()
{
	return m_headers;
}

QDataStream* MSNSocket::WebResponse::getResponseStream()
{
	return m_stream;
}

int MSNSocket::WebResponse::getStatusCode()
{
	return m_statusCode;
}

QString MSNSocket::WebResponse::getStatusDescription()
{
	return m_statusDescription;
}


#include "msnsocket.moc"

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