/***************************************************************************
 *   Copyright (C) 2004 by yunfan                                          *
 *   yunfan_zg@163.com                                                     *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
 
#include "evaconnecter.h"
#include "evaqtutil.h"
#include "evahtmlparser.h"
#include <qtimer.h> 
#include <qmutex.h>
#include <arpa/inet.h>
#include <stdlib.h>

#define POOL_CHECK_INTERVAL  30000

EvaConnecter::EvaConnecter(EvaNetwork *network)
{
	memset(buffer, 0, 65535);
	packetLength = 0;
	bufLength = 0;
	connecter = network;
	connectionReady = false;
	isMonitorRunning = false;
	isClientSetup = false;
	QObject::connect(connecter, SIGNAL(isReady()), this, SLOT(isReadySlot()));
	QObject::connect(connecter, SIGNAL(dataComming(int)), this, SLOT(dataCommingSlot(int)));
	QObject::connect(connecter, SIGNAL(exceptionEvent(int)), this, SIGNAL(networkException(int)));
	
        outPool.setAutoDelete(true);
        inPool.setAutoDelete(false);
	
	timer = new QTimer(this);
	QObject::connect(timer, SIGNAL(timeout()), this, SLOT(packetMonitor()));
	timer->start(POOL_CHECK_INTERVAL, false); // every 30  second check two pools
}

EvaConnecter::~EvaConnecter()
{
	delete connecter;
	if(timer->isActive())
		timer->stop();
	delete timer;
}

void EvaConnecter::append(OutPacket *out)
{
	sendOut(out);   // force to send
	if(out->needAck())
		outPool.append(out);
	else
		delete out;
}

InPacket *EvaConnecter::getInPacket()
{
	if(inPool.count() <= 0 ){ 
		emit packetException(-1);
		return NULL;
	}
	return inPool.take();
}

void EvaConnecter::redirectTo(const int ip, const short port)
{	
	inPool.clear();
	outPool.clear();
	if(connecter->connectionType()!= EvaNetwork::HTTP_Proxy){
		connectionReady = false;
		connecter->setServer(QHostAddress(ip), port==-1?connecter->getHostPort():port);
	}else{
		connecter->setServer(connecter->getHostAddress(), connecter->getHostPort());
		connecter->setDestinationServer(QHostAddress(ip).toString(), 443); // always 443 for http proxy
	      }
	connect();
}

void EvaConnecter::connect()
{
	memset(buffer, 0, 65535);
	packetLength = 0;
	bufLength = 0;
	connecter->connect();
}

void EvaConnecter::stop()
{
	if(timer->isActive())
		timer->stop();
	inPool.clear();
	outPool.clear();
}

void EvaConnecter::sendOut( OutPacket *out)
{
	if(timer->isActive())
		timer->stop();
	if(!connectionReady) return;
	unsigned char *buf = (unsigned char *)malloc(MAX_PACKET_SIZE * sizeof(unsigned char));
	int len;
	
	out->fill(buf, &len);
	connecter->write((char *)buf, len);
	free(buf);
	if(!timer->isActive())
		timer->start(POOL_CHECK_INTERVAL, false);
}

void EvaConnecter::removePacket(const int hashCode)
{
	QMutex mutex;
	mutex.lock();
	for( uint i = 0; i < outPool.count(); i++){
		if(outPool.at(i)->hashCode() == hashCode){
			outPool.remove(i);
			--i;
		}
	}
	mutex.unlock();
}

void EvaConnecter::removeOutRequests(const short cmd)
{
	while(isMonitorRunning);
	for( uint i = 0; i < outPool.count(); i++){
		if(outPool.at(i)->getCommand() == cmd){
			outPool.remove(i);
			--i;
		}
	}
}

void EvaConnecter::isReadySlot()
{
	connectionReady = true;
	emit isReady();
}

void EvaConnecter::dataCommingSlot(int len)
{
	char *rawData = new char[len+1];
	if(!connecter->read(rawData, len)){
		fprintf(stderr, "--Eva Connecter: Bytes read wrong!\n");
		return;
	}
	if(connecter->connectionType() != EvaNetwork::UDP){
		memcpy(buffer+bufLength, rawData, len);
		bufLength += len;
		delete rawData;
		unsigned short tmp;
		memcpy(&tmp, buffer, 2);
		packetLength = ntohs(tmp);
		while(bufLength >= packetLength){
			rawData = new char[packetLength];
			memcpy(rawData, buffer, packetLength);
			memcpy(buffer, buffer + packetLength, bufLength - packetLength);
			len = packetLength;
			bufLength -=packetLength;
			processPacket(rawData, len);
			delete rawData;
			if(!bufLength)	break;
			memcpy(&tmp, buffer, 2);
			packetLength = ntohs(tmp);
		}
		
		
	}else{
		processPacket(rawData, len);
		delete rawData;
	}
}

void EvaConnecter::processPacket( char * data, int len )
{
	InPacket *packet = new InPacket((unsigned char *)data, len);
	if(!packet->getLength()){
		printf("Bad packet, ignore it\n");
		delete packet;
		return;
	}
        removePacket(packet->hashCode());
	
	if(packet->getCommand() == QQ_CMD_KEEP_ALIVE)
		removeOutRequests(QQ_CMD_KEEP_ALIVE);
		
	if(packet->getCommand() == QQ_CMD_GET_FRIEND_ONLINE)
		removeOutRequests(QQ_CMD_GET_FRIEND_ONLINE);
		
        inPool.append(packet);
	if(isClientSetup || (!isClientSetup && packet->getCommand()!= QQ_CMD_RECV_IM && 
				packet->getCommand()!= QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS))
		emit newPacket();
}

void EvaConnecter::packetMonitor()
{
	isMonitorRunning = true;
	for ( uint i=0;  i < outPool.count(); i++ ){
		if(outPool.at(i)->needResend()){
			sendOut(outPool.at(i));
		}else{
			short cmd = outPool.at(i)->getCommand();
			removePacket(outPool.at(i)->hashCode());
			isMonitorRunning = false;
			emit packetException(cmd);
			return;
		}
	}
	if(isClientSetup && inPool.count()>0){
		emit newPacket();
	}
	isMonitorRunning = false;
}

void EvaConnecter::clientReady( )
{
	isClientSetup = true;
	while(inPool.count())
		emit newPacket();
}






