/***************************************************************************
 *   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 "evasocket.h"
#include "qmdcodec.h" 
#include <stdlib.h>
#include <qsocketdevice.h> 
#include <qsocketnotifier.h>
#include <qmutex.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

EvaSocket::EvaSocket(const  QHostAddress &host, const short port, const Type type)
{
	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); 

	socketNotifier = new QSocketNotifier(connectSocket->socket(),
						QSocketNotifier::Read,0,"SocketNotifier");
	connectionStatus = Init;
	QObject::connect(socketNotifier,SIGNAL(activated(int)),SLOT(receiving()));
}

EvaSocket::~EvaSocket()
{
	delete connectSocket;
	delete socketNotifier;
}

void EvaSocket::setHost(const QHostAddress &address, const short port)
{
	server = address;
	serverPort = port;  
	connectionStatus = None;
	if(connectSocket->isValid()){
		QObject::disconnect(socketNotifier,0,0,0);
		delete socketNotifier;
		delete connectSocket;
		if(connectionType == UDP)
			connectSocket = new QSocketDevice(QSocketDevice::Datagram); 
		else
			connectSocket = new QSocketDevice(QSocketDevice::Stream); 
				
		socketNotifier = new QSocketNotifier(connectSocket->socket(),
							QSocketNotifier::Read,0,"SocketNotifier");
		QObject::connect(socketNotifier,SIGNAL(activated(int)),SLOT(receiving()));
	}
	connectionStatus = Init;
}

void EvaSocket::closeConnection()
{
	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;
		}
	}
	connectionStatus = Ready;
	emit isReady();
}

bool EvaSocket::write(const char *buf, const int len)
{
	if(connectionStatus != Ready) 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);
	
	mutex.unlock();
	//printf("send:%s\n", buf);
	if(len != BytesSent) return false;
	return true;
}

bool EvaSocket::read(char *buf, int len)
{
	if(receivedLength != len) return false;
	if(connectionStatus != Ready) return false;
//         printf("receivedLength: %d\n", receivedLength);
//         for(int i=0; i<receivedLength; i++){
//             if(!(i%8)) printf("\n%4d: ", i);
//             printf("%4x ",(uint8_t)(receivedBuffer[i]) );
//         }
// 	printf("\n");
	memcpy(buf, receivedBuffer, receivedLength);
	return true;
}

void EvaSocket::receiving()
{
	int ByteCount = 0;
	ByteCount = connectSocket->bytesAvailable(); 
	if(ByteCount==0) return;
	
		
	if(receivedBuffer!=NULL) free(receivedBuffer);
	
	receivedBuffer =  (char *)malloc((ByteCount+1)*sizeof(char));   
	
	receivedLength = connectSocket->readBlock(receivedBuffer,ByteCount*2);
	//printf("len: %d, rec:%s\n",receivedLength, receivedBuffer);
	
	if(receivedLength == ByteCount)
		emit receivedData(receivedLength);
	else
		emit exceptionEvent(BytesReadWrong);
}

/* =========================================================== */
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()
{
	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;
	printf("rec:\n%s\n",readBuffer);
	QString replyBuffer(readBuffer);
	if(replyBuffer.startsWith("HTTP/1.",false)){
		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);
}

