/*
    Kopete Oscar Protocol
    coreprotocol.h- the core Oscar protocol

    Copyright (c) 2004 by Matt Rogers <mattr@kde.org>

    Based on code Copyright (c) 2004 SuSE Linux AG http://www.suse.com

    Based on Iris, Copyright (C) 2003  Justin Karneges
    url_escape_string from Pidgin src/protocols/novell/nmconn.c
    Copyright (c) 2004 Novell, Inc. All Rights Reserved

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

    *************************************************************************
    *                                                                       *
    * This library is free software; you can redistribute it and/or         *
    * modify it under the terms of the GNU Lesser General Public            *
    * License as published by the Free Software Foundation; either          *
    * version 2 of the License, or (at your option) any later version.      *
    *                                                                       *
    *************************************************************************
*/

#include "coreprotocol.h"

#include <tqdatastream.h>
#include <tqdatetime.h>
#include <tqtextstream.h>

#include <kdebug.h>
#include <ctype.h>

#include "oscartypes.h"
#include "transfer.h"
#include "flapprotocol.h"
#include "snacprotocol.h"

static TQString toString( const TQByteArray& buffer )
{
	// line format:
	//00 03 00 0b 00 00 90 b8 f5 9f 09 31 31 33 37 38   |;tJ�..........|

	int i = 0;
	TQString output = "\n";
	TQString hex, ascii;

	TQByteArray::ConstIterator it;
	for ( it = buffer.begin(); it != buffer.end(); ++it )
	{
		i++;

		unsigned char c = static_cast<unsigned char>(*it);

		if ( c < 0x10 )
			hex.append("0");
		hex.append(TQString("%1 ").arg(c, 0, 16));

		ascii.append(isprint(c) ? c : '.');

		if (i == 16)
		{
			output += hex + "   |" + ascii + "|\n";
			i=0;
			hex=TQString();
			ascii=TQString();
		}
	}

	if(!hex.isEmpty())
		output += hex.leftJustify(48, ' ') + "   |" + ascii.leftJustify(16, ' ') + '|';
	output.append('\n');

	return output;
}


using namespace Oscar;

CoreProtocol::CoreProtocol() : TQObject()
{
	m_snacProtocol = new SnacProtocol( this, "snacprotocol" );
	m_flapProtocol = new FlapProtocol( this, "flapprotocol" );
}

CoreProtocol::~CoreProtocol() 
{
}

int CoreProtocol::state()
{
	return m_state;
}

void CoreProtocol::addIncomingData( const TQByteArray & incomingBytes )
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Received " << incomingBytes.count() << " bytes. " << endl;
	// store locally
	int oldsize = m_in.size();
	m_in.resize( oldsize + incomingBytes.size() );
	memcpy( m_in.data() + oldsize, incomingBytes.data(), incomingBytes.size() );
	m_state = Available;
	
	// convert every event in the chunk to a Transfer, signalling it back to the clientstream
	int parsedBytes = 0;
	int transferCount = 0;
	// while there is data left in the input buffer, and we are able to parse something out of it
	while ( m_in.size() && ( parsedBytes = wireToTransfer( m_in ) ) )
	{
		transferCount++;
		//kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "parsed transfer #" << transferCount << " in chunk" << endl;
		int size =  m_in.size();
		if ( parsedBytes < size )
		{
			//kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "more data in chunk!" << endl;
			// copy the unparsed bytes into a new qbytearray and replace m_in with that
			TQByteArray remainder( size - parsedBytes );
			memcpy( remainder.data(), m_in.data() + parsedBytes, remainder.size() );
			m_in = remainder;
		}
		else
			m_in.truncate( 0 );
	}
	
	if ( m_state == NeedMore )
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "message was incomplete, waiting for more..." << endl;
	
	if ( m_snacProtocol->state() == OutOfSync || m_flapProtocol->state() == OutOfSync )
	{	
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "protocol thinks it's out of sync. "
			<< "discarding the rest of the buffer and hoping the server regains sync soon..." << endl;
		m_in.truncate( 0 );
	}
// 	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "done processing chunk" << endl;
}

Transfer* CoreProtocol::incomingTransfer()
{	
	if ( m_state == Available )
	{
		m_state = NoData;
		return m_inTransfer;
		m_inTransfer = 0;
	}
	else
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "we shouldn't be here!" << kdBacktrace() << endl;
		return 0;
	}
}

void cp_dump( const TQByteArray &bytes )
{
#ifdef OSCAR_COREPROTOCOL_DEBUG
	kdDebug(OSCAR_RAW_DEBUG) << "contains: " << bytes.count() << " bytes" << endl;
	for ( uint i = 0; i < bytes.count(); ++i )
	{
		printf( "%02x ", bytes[ i ] );
	}
	printf( "\n" );
#else
	Q_UNUSED( bytes );
#endif
}

void CoreProtocol::outgoingTransfer( Transfer* outgoing )
{
	//kdDebug(OSCAR_RAW_DEBUG) << "CoreProtocol::outgoingTransfer()" << endl;
	// Convert the outgoing data into wire format
	// pretty leet, eh?
	emit outgoingData( outgoing->toWire() );
	delete outgoing;
	
	return;
}

int CoreProtocol::wireToTransfer( const TQByteArray& wire )
{
	// processing incoming data and reassembling it into transfers
	// may be an event or a response
	
	BYTE flapStart, flapChannel = 0;
	WORD flapLength = 0;
	WORD s1, s2 = 0;
	uint bytesParsed = 0;
	
	//kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Current packet" << toString(wire) << endl;
	if ( wire.size() < 6 ) //check for valid flap length
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo 
				<< "packet not long enough! couldn't parse FLAP!" << endl;
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "packet size is " << wire.size() << endl;
		m_state = NeedMore;
		return bytesParsed;
	}	
	
	TQDataStream din( wire, IO_ReadOnly );
	
	// look at first four bytes and decide what to do with the chunk
	if ( okToProceed( din ) )
	{
		din >> flapStart;
		TQByteArray packet;
		packet.duplicate( wire );
		if ( flapStart == 0x2A )
		{
			din >> flapChannel;
			din >> flapLength; //discard the first one it's not really the flap length
			din >> flapLength;
			if ( wire.size() < flapLength )
			{
				kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo 
					<< "Not enough bytes to make a correct transfer. Have " << wire.size()
					<< " bytes. need " << flapLength + 6 << " bytes" << endl;
				m_state = NeedMore;
				return bytesParsed;
			}
			
			if ( flapChannel != 2 )
			{
				Transfer *t = m_flapProtocol->parse( packet, bytesParsed );
				if ( t )
				{
					m_inTransfer = t;
					m_state = Available;
					emit incomingData();
				}
				else
					bytesParsed = 0;
			}
			
			if ( flapChannel == 2 )
			{
				din >> s1;
				din >> s2;
				
				Transfer * t = m_snacProtocol->parse( packet, bytesParsed );
				if ( t )
				{
					m_inTransfer = t;
					m_state = Available;
					emit incomingData();
				}
				else
				{
					bytesParsed = 0;
					m_state = NeedMore;
				}
			}
		}
		else 
		{ //unknown wire format
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "unknown wire format detected!" << endl;
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "start byte is " << flapStart << endl;
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Packet is " << endl << toString( wire ) << endl;
		}
		
	}
	return bytesParsed;
}

void CoreProtocol::reset()
{
	m_in.resize( 0 );
}

void CoreProtocol::slotOutgoingData( const TQCString &out )
{
	kdDebug(OSCAR_RAW_DEBUG) << out.data() << endl;
}

bool CoreProtocol::okToProceed( const TQDataStream &din )
{
	if ( din.atEnd() )
	{
		m_state = NeedMore;
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Server message ended prematurely!" << endl;
		return false;
	}
	else
		return true;
}

#include "coreprotocol.moc"
