/*
	oscarclientstream.cpp - Kopete Oscar Protocol
	
	Copyright (c) 2004 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
	
	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 "oscarclientstream.h"

#include <tqguardedptr.h> 
#include <tqobject.h>
#include <tqptrqueue.h>
#include <tqtimer.h>

#include <kdebug.h>

#include "bytestream.h"
#include "connection.h"
#include "connector.h"
#include "coreprotocol.h"
#include "rateclassmanager.h"
#include "transfer.h"

#define LIBOSCAR_DEBUG 0

void cs_dump( const TQByteArray &bytes );

enum {
	Idle,
	Connecting,
	Active,
	Closing
};

enum {
	ClientMode,
	ServerMode
};

class ClientStream::Private
{
public:
	Private()
	{
		conn = 0;
		bs = 0;
		connection = 0;
		
		username = TQString();
		password = TQString();
		server = TQString();
		haveLocalAddr = false;
		doBinding = true;

		reset();
	}
	void reset()
	{
		state = Idle;
		notify = 0;
		newTransfers = false;
	}
	
	TQString username;
	TQString password;
	TQString server;
	bool doAuth; //send the initial login sequences to get the cookie
	bool haveLocalAddr;
	TQHostAddress localAddr;
	TQ_UINT16 localPort;
	bool doBinding;

	Connector *conn;
	ByteStream *bs;
	CoreProtocol client;
	Connection* connection;

	TQString defRealm;

	int mode;
	int state;
	int notify;
	bool newTransfers;
	
	int errCond;
	TQString errText;

	TQPtrQueue<Transfer> in;

	TQTimer noopTimer; // used to send icq keepalive
	int noop_time;
};

ClientStream::ClientStream(Connector *conn, TQObject *parent)
:Stream(parent)
{
	//tqDebug("CLIENTSTREAM::ClientStream");

	d = new Private;
	d->mode = ClientMode;
	d->conn = conn;
	connect( d->conn, TQ_SIGNAL(connected()), TQ_SLOT(cr_connected()) );
	connect( d->conn, TQ_SIGNAL(error()), TQ_SLOT(cr_error()) );
	connect( &d->client, TQ_SIGNAL( outgoingData( const TQByteArray& ) ), TQ_SLOT ( cp_outgoingData( const TQByteArray & ) ) );
	connect( &d->client, TQ_SIGNAL( incomingData() ), TQ_SLOT ( cp_incomingData() ) );

	d->noop_time = 0;
	connect(&d->noopTimer, TQ_SIGNAL(timeout()), TQ_SLOT(doNoop()));
}

ClientStream::~ClientStream()
{
	reset();
	delete d;
}

void ClientStream::reset(bool all)
{
	d->reset();
	d->noopTimer.stop();

	// client
	if(d->mode == ClientMode)
	{
		// reset connector
		if ( d->bs )
		{
			d->bs->close();
			d->bs = 0;
		}
		if ( d->conn )
			d->conn->done();

		// reset state machine
		d->client.reset();
	}
	if(all)
		d->in.clear();
}

void ClientStream::connectToServer(const TQString& server, bool auth)
{
	reset(true);
	d->state = Connecting;
	d->doAuth = auth;
	d->server = server;

	d->conn->connectToServer( d->server );
}

void ClientStream::continueAfterWarning()
{
/* unneeded?
	if(d->state == WaitVersion) {
		d->state = Connecting;
		processNext();
	}
	else if(d->state == WaitTLS) {
		d->state = Connecting;
		processNext();
	}
*/
}

void ClientStream::accept()
{

}

bool ClientStream::isActive() const
{
	return (d->state != Idle);
}

bool ClientStream::isAuthenticated() const
{
	return (d->state == Active);
}

void ClientStream::setNoopTime(int mills)
{
	d->noop_time = mills;

	if(d->noop_time == 0) {
		d->noopTimer.stop();
		return;
	}
	
	if( d->state != Active )
		return;
	
	d->noopTimer.start( d->noop_time );
}

void ClientStream::setLocalAddr(const TQHostAddress &addr, TQ_UINT16 port)
{
	d->haveLocalAddr = true;
	d->localAddr = addr;
	d->localPort = port;
}

int ClientStream::errorCondition() const
{
	return d->errCond;
}

TQString ClientStream::errorText() const
{
	return d->errText;
}

void ClientStream::close()
{
	if(d->state == Active) {
		d->state = Closing;
//		d->client.shutdown();
		processNext();
	}
	else if(d->state != Idle && d->state != Closing) {
		reset();
	}
}

void ClientStream::setConnection( Connection *c )
{
	d->connection = c;
}

Connection* ClientStream::connection() const
{
	return d->connection;
}


bool ClientStream::transfersAvailable() const
{
	return ( !d->in.isEmpty() );
}

Transfer* ClientStream::read()
{
	if(d->in.isEmpty())
		return 0; //first from queue...
	else 
		return d->in.dequeue();
}

void ClientStream::write( Transfer *request )
{
	d->client.outgoingTransfer( request );
}
	
void cs_dump( const TQByteArray &bytes )
{
#if 0
	tqDebug( "contains: %i bytes ", bytes.count() );
	uint count = 0;
	while ( count < bytes.count() )
	{
		int dword = 0;
		for ( int i = 0; i < 8; ++i )
		{
			if ( count + i < bytes.count() )
				printf( "%02x ", bytes[ count + i ] );
			else
				printf( "   " );
			if ( i == 3 )
				printf( " " );
		}
		printf(" | ");
		dword = 0;
		for ( int i = 0; i < 8; ++i )
		{
			if ( count + i < bytes.count() )
			{
				int j = bytes [ count + i ];
				if ( j >= 0x20 && j <= 0x7e ) 
					printf( "%2c ", j );
				else
					printf( "%2c ", '.' );
			}
			else
				printf( "   " );
			if ( i == 3 )
				printf( " " );
		}
		printf( "\n" );
		count += 8;
	}
	printf( "\n" );
#endif
	Q_UNUSED( bytes );
}

void ClientStream::cp_outgoingData( const TQByteArray& outgoingBytes )
{
	// take formatted bytes from CoreProtocol and put them on the wire
	d->bs->write( outgoingBytes );
}

void ClientStream::cp_incomingData()
{
	Transfer * incoming = d->client.incomingTransfer();
	if ( incoming )
	{
		d->in.enqueue( incoming );
		d->newTransfers = true;
		doReadyRead();
	}
	else
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << 
			"client signalled incomingData but none was available, state is: " <<
			d->client.state() << endl;
}

void ClientStream::cr_connected()
{
	d->bs = d->conn->stream();
	connect(d->bs, TQ_SIGNAL(connectionClosed()), TQ_SLOT(bs_connectionClosed()));
	connect(d->bs, TQ_SIGNAL(delayedCloseFinished()), TQ_SLOT(bs_delayedCloseFinished()));
	connect(d->bs, TQ_SIGNAL(readyRead()), TQ_SLOT(bs_readyRead()));
	connect(d->bs, TQ_SIGNAL(bytesWritten(int)), TQ_SLOT(bs_bytesWritten(int)));
	connect(d->bs, TQ_SIGNAL(error(int)), TQ_SLOT(bs_error(int)));

	d->state = Active;
	if ( d->noop_time )
		d->noopTimer.start( d->noop_time );

	TQByteArray spare = d->bs->read();

	TQGuardedPtr<TQObject> self = this;
	emit connected();
	if(!self)
		return;
}

void ClientStream::cr_error()
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << endl;
	reset();
	emit error(ErrConnection);
}

void ClientStream::bs_connectionClosed()	
{
	reset();
	emit connectionClosed();
}

void ClientStream::bs_delayedCloseFinished()
{
	// we don't care about this (we track all important data ourself)
}

void ClientStream::bs_error(int)
{
	// TODO
}

void ClientStream::bs_readyRead()
{
	TQByteArray a;
	//tqDebug( "size of storage for incoming data is %i bytes.", a.size() );
	a = d->bs->read();

#if LIBOSCAR_DEBUG
	TQCString cs(a.data(), a.size()+1);
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "recv: " << a.size() << "bytes" << endl;
	cs_dump( a );
#endif

	d->client.addIncomingData(a);
}

void ClientStream::bs_bytesWritten(int bytes)
{
#if LIBOSCAR_DEBUG
 	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << bytes << " bytes written" << endl;
	Q_UNUSED( bytes );
#else
	Q_UNUSED( bytes );
#endif
}

void ClientStream::srvProcessNext()
{
}

void ClientStream::doReadyRead()
{
	emit readyRead();
}

void ClientStream::processNext()
{
	if( !d->in.isEmpty() ) 
	{
		TQTimer::singleShot(0, this, TQ_SLOT(doReadyRead()));
	}
}

bool ClientStream::handleNeed()
{
	return false;
}

void ClientStream::doNoop()
{
	if ( d->state != Active )
		return;
	
	FLAP f = { 0x05, d->connection->flapSequence(), 0 };
	Buffer* b = new Buffer(); //deleted in Transfer destructor
	Transfer* t = new FlapTransfer( f, b ); //deleted after being sent
	write( t );
}

void ClientStream::handleError()
{
}

#include "oscarclientstream.moc"
