/***************************************************************************
 *   Copyright (C) 2004-2005 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 "evasocket.h"
#include "qmdcodec.h" 
#include <stdlib.h>
#include <qsocketdevice.h> 
#include <qsocketnotifier.h>
#include <qmutex.h>
#include <qtimer.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>


/*!
	\class EvaSocket evasocket.h
	\brief The EvaSocket class provides a UDP or TCP connection
	
	It provides a very simple non-blocking socket connection.
	socketWriteNotifier is only used once to notify that the connection is ready
 	to write, after isReady() emited, socketWriteNotifier is disabled unless 
	setHost() is called.
 */
EvaSocket::EvaSocket(const  QHostAddress &host, const short port, const Type type)
	: socketReadNotifier(NULL), socketWriteNotifier(NULL)
{
	connectionStatus = None;
	receivedLength = 0;
	receivedBuffer = NULL;
	connectionType = type;
        server = host;
	serverPort = port;
	if(connectionType == UDP){
		connectSocket = new QSocketDevice(QSocketDevice::Datagram); 
	}else{
		connectSocket = new QSocketDevice(QSocketDevice::Stream);
		connectSocket->setBlocking(false);
        	socketWriteNotifier =  new QSocketNotifier(connectSocket->socket(),
							QSocketNotifier::Write,0,"writeNotifier");
		QObject::connect(socketWriteNotifier,SIGNAL(activated(int)),SLOT(slotWriteReady(int)));
		socketWriteNotifier->setEnabled(false);
	}
	socketReadNotifier = new QSocketNotifier(connectSocket->socket(),
						QSocketNotifier::Read,0,"readNotifier");
	connectionStatus = Init;
	QObject::connect(socketReadNotifier,SIGNAL(activated(int)),SLOT(slotReceiveReady(int)));
	socketReadNotifier->setEnabled(false);
}

EvaSocket::~EvaSocket()
{
	delete connectSocket;
	if(socketReadNotifier) {
		socketReadNotifier->setEnabled(false);
		delete socketReadNotifier;
	}
	if(socketWriteNotifier) {
		socketWriteNotifier->setEnabled(false);
		delete socketWriteNotifier;
	}
}

void EvaSocket::setHost(const QHostAddress &address, const short port)
{
	server = address;
	serverPort = port;  
	connectionStatus = None;
	if(connectSocket->isValid()){
		delete connectSocket;
		if(socketReadNotifier) {
			socketReadNotifier->setEnabled(false);
			delete socketReadNotifier;
		}
		if(socketWriteNotifier) {
			socketWriteNotifier->setEnabled(false);
			delete socketWriteNotifier;
		}
		if(connectionType == UDP){
			connectSocket = new QSocketDevice(QSocketDevice::Datagram); 
		}else{
			connectSocket = new QSocketDevice(QSocketDevice::Stream); 
			connectSocket->setBlocking(false);
			socketWriteNotifier =  new QSocketNotifier(connectSocket->socket(),
								QSocketNotifier::Write,0,"writeNotifier");
			QObject::connect(socketWriteNotifier,SIGNAL(activated(int)),SLOT(slotWriteReady(int)));
			socketWriteNotifier->setEnabled(false);
		}
		socketReadNotifier = new QSocketNotifier(connectSocket->socket(),
							QSocketNotifier::Read,0,"SocketNotifier");
		QObject::connect(socketReadNotifier,SIGNAL(activated(int)),SLOT(slotReceiveReady(int)));
		if(connectionType == TCP)
			socketReadNotifier->setEnabled(false);
	}
	connectionStatus = Init;
}

void EvaSocket::closeConnection()
{
	if(connectSocket->isOpen()) connectSocket->close();
	connectionStatus = None;
	receivedLength = 0;
}

void EvaSocket::startConnecting()
{
	if(connectionStatus != Init) {
		emit exceptionEvent(connectionStatus);
		return;
	}
	connectionStatus = Connecting;
	if(connectionType == TCP){
		if(!connectSocket->connect(server, serverPort)){
			fprintf(stderr,"connecting server failed\nError type: ");
			connectionStatus = Failed;
			switch(connectSocket->error()){
				case QSocketDevice::NoError:
					fprintf(stderr,"NoError\n");
					break;
				case QSocketDevice::AlreadyBound:
					fprintf(stderr,"AlreadyBound\n");
					break; 
				case QSocketDevice::Inaccessible:
					fprintf(stderr,"Inaccessible\n");
					break;
				case QSocketDevice::NoResources:
					fprintf(stderr,"NoResources\n");
					break;
				case QSocketDevice::InternalError:
					fprintf(stderr,"InternalError\n");
					break;
				case QSocketDevice::Impossible:
					fprintf(stderr,"Impossible\n");
					break;
				case QSocketDevice::NoFiles:
					fprintf(stderr,"NoFiles\n");
					break;
				case QSocketDevice::ConnectionRefused:
					fprintf(stderr,"ConnectionRefused\n");
					break;
				case QSocketDevice::NetworkFailure:
					fprintf(stderr,"NetworkFailure\n");
					break;
				case QSocketDevice::UnknownError:
					fprintf(stderr,"UnknownError\n");
					break;
				default:
					printf("not listed error\n");
			}
			emit exceptionEvent(connectionStatus);
			return;
		}
	}
	if(socketReadNotifier) socketReadNotifier->setEnabled(true);
	if(connectionType == TCP && socketWriteNotifier) {
		socketWriteNotifier->setEnabled(true);
	}else{
		connectionStatus = Ready;
		emit isReady();
	}
}

bool EvaSocket::write(const char *buf, const int len)
{
	if(connectionStatus != Ready || !buf ) return false;
	if(!connectSocket->isValid()){
		if(connectionType == TCP && socketReadNotifier && socketWriteNotifier){
			socketReadNotifier->setEnabled(false);
			socketWriteNotifier->setEnabled(false);
		}
		emit exceptionEvent(Failed);
		return false;
	}
	QMutex mutex;
	mutex.lock();
	int BytesSent = 0;
	if(connectionType == UDP)
		BytesSent =connectSocket->writeBlock(buf, len, server, serverPort); 
	else{
		BytesSent =connectSocket->writeBlock(buf, len);
		if(socketWriteNotifier)
			socketWriteNotifier->setEnabled(false);
	}	
	mutex.unlock();
	if(len != BytesSent){
		printf("EvaSocket::write -- error code: %d\n", connectSocket->error());
		return false;
	}
	return true;
}

bool EvaSocket::read(char *buf, int len)
{
	if(connectionStatus != Ready || receivedLength != len || !buf ) 
		return false;
	memcpy(buf, receivedBuffer, receivedLength);
	if(socketReadNotifier) socketReadNotifier->setEnabled(true);
	return true;
}

void EvaSocket::slotWriteReady(int /*socket */)
{
	connectionStatus = Ready;
	emit isReady();
}

void EvaSocket::slotReceiveReady(int /*socket*/)
{
	if( (socketReadNotifier->type() != QSocketNotifier::Read) || (!connectSocket->isValid()) ){
		socketReadNotifier->setEnabled(false);
		printf("EvaSocket::slotReceiveReady -- socket not valid or notifier not set to Read \n");
		emit exceptionEvent(Failed);
		return;
	}
		
	int ByteCount = 0;
	ByteCount = connectSocket->bytesAvailable();
	
	if(receivedBuffer!=NULL) delete receivedBuffer;
	receivedBuffer = new char[ByteCount * 2];  
	
	receivedLength = connectSocket->readBlock(receivedBuffer,ByteCount*2);
	if(!receivedLength){
		printf("EvaSocket::slotReceiveReady -- connection closed due to ZERO byte\n");
		socketReadNotifier->setEnabled(false);
		emit exceptionEvent(Failed);
		return;
	}
	
	if(receivedLength == -1){
		printf("EvaSocket::slotReceiveReady -- readBlock return -1\n");
		emit exceptionEvent(Failed);
		return;
	}
	if(socketReadNotifier) socketReadNotifier->setEnabled(false);
	emit receivedData(receivedLength);
	if(receivedLength != ByteCount)
		printf("EvaSocket::slotReceiveReady -- bytesAvailable() might not be accurate.\n");
}


/* =========================================================== */
EvaHttpProxy::EvaHttpProxy(const QHostAddress &proxyHost, const short proxyPort, const QString username, const QString password)
	: EvaSocket(proxyHost, proxyPort, EvaSocket::TCP),
	status(Proxy_None),
	destinationAddress(""),
	base64AuthParam(""),
	readBuffer(NULL)
{
	if(username!=QString::null && password!= QString::null){
		setAuthParameter(username, password);
	}
	QObject::connect(this, SIGNAL(isReady()), SLOT(tcpReady()));
	QObject::connect(this, SIGNAL(receivedData(int)), SLOT(parseData(int)));
	QObject::connect(this, SIGNAL(exceptionEvent(int)), SIGNAL(socketException(int)));
}

void EvaHttpProxy::setDestinationServer(const QString &server, const int port) // server could be IP or URL
{
	destinationAddress = server + ':' + QString::number(port);// qq http proxy server port: 443
	status = Proxy_None;
}

void EvaHttpProxy::setAuthParameter(const QString &username, const QString &password)
{
	QCString para = (username + ':' + password).local8Bit();
	base64AuthParam = QCodecs::base64Encode(para);
	status = Proxy_None;
}

bool EvaHttpProxy::doInitConnecting()
{
	if(getStatus() != EvaSocket::Ready) return false;
	if(destinationAddress == "") return false;
	sentBuffer = "CONNECT " + destinationAddress.local8Bit() + " HTTP/1.1\r\n" +
		"Accept: */*\r\n" + "Content-Type: text/html\r\nProxy-Connection: Keep-Alive\r\n" +
		"Content-length: 0\r\n\r\n";
	status = Proxy_Connecting;
	return write(sentBuffer.data(), sentBuffer.length());
}

bool EvaHttpProxy::doAuthConnecting()
{
	if(getStatus() != EvaSocket::Ready) return false;
	if(destinationAddress == "") return false;
	if(base64AuthParam == "") return false;
	
	sentBuffer = "CONNECT " + destinationAddress.local8Bit() + " HTTP/1.1\r\n" +
		"Proxy-Authorization: Basic " + base64AuthParam + "\r\n" +
		"Accept: */*\r\nContent-Type: text/html\r\nProxy-Connection: Keep-Alive\r\n" +
		"Content-length: 0\r\n\r\n";
	status = Proxy_Connecting;
	return write(sentBuffer.data(), sentBuffer.length());
}

void EvaHttpProxy::tcpReady()
{
	printf("EvaHttpProxy::tcpReady -- TCP connection ready\n");
	if(destinationAddress == "") {
		emit proxyEvent(Proxy_TCP_Ready);
		return;
	}
	
	if(base64AuthParam != "")
		doAuthConnecting();
	else 
		doInitConnecting();
}

void EvaHttpProxy::parseData(int len)
{	
	if(readBuffer!=NULL) free(readBuffer);
	readBuffer = (char *)malloc((len+1) * sizeof(char));
	if(!read(readBuffer, len)){ 
		emit proxyEvent(Proxy_Read_Error);
		return;
	}
	readBuffer[len]=0x00;
	QString replyBuffer(readBuffer);
	if(replyBuffer.startsWith("HTTP/1.")){ // this is for RedHat 9, the old Qt dosen't support QString::startsWith(const QString &str, bool cs) const
		int replyCode = replyBuffer.mid(9, 3).toInt();
		fprintf(stderr, "Proxy Server Reply Code: %d\n",replyCode);
		switch(replyCode){
		case 200:
			status = Proxy_Ready;
			emit proxyEvent(Proxy_Ready);
			break;
		case 407:
			status = Proxy_Need_Auth;
			emit proxyEvent(Proxy_Need_Auth);
			break;
		case 501:  // "Not Support"
		case 502:  // "Proxy Error"
		default:
			status = Proxy_Error;
			emit proxyEvent(Proxy_Error);
			break;
		}
		return;
	}
	if(status == Proxy_Ready)
		dataArrived(len);
}

