#include "ozConnection.h"

ozConnection* conn = NULL;


ozConnection::ozConnection(void)
{
	this->transactionId = -1;
	this->run = false;
	srand( (unsigned)time( NULL ) );
}

ozConnection::~ozConnection(void)
{
	conn = NULL;
}


int ozConnection::open(string host, int port)
{
	int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != NO_ERROR)
		return iResult;

	ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (ConnectSocket == INVALID_SOCKET) {
		cerr << "Error at socket(): " << WSAGetLastError() << endl;
		WSACleanup();
		return ConnectSocket;
	}

	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = inet_addr( host.c_str() );
	clientService.sin_port = htons( port );

	if ( connect( ConnectSocket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) {
		cerr << "Failed to connect." << endl;
		WSACleanup();
		return SOCKET_ERROR;
	}

	return 0;
}

int ozConnection::close()
{
    closesocket(ConnectSocket);
	WSACleanup();

	return 0; 
}
bool ozConnection::login(string username, string password)
{
	stringstream outbuffer;
	stringstream inbuffer;
	char recvbuf[BUFFLEN];		

	outbuffer << STX << "transactionid=" << nextTransactionId();
	outbuffer << "&action=getversion";
	outbuffer << "&responseformat=urlencoded" << ETX;
	sendPdu(outbuffer.str());

	int iResult = recv(ConnectSocket, recvbuf, BUFFLEN, 0);
	if(iResult <= 0)
	{
		cerr << "Connection error: no response." << endl;
		return false;

	}

	outbuffer.str("");

	outbuffer << STX << "transactionid=" << nextTransactionId();
	outbuffer << "&action=login&username=" << username;
	outbuffer << "&password=" << password;
	outbuffer << "&responseformat=urlencoded" << ETX;

	sendPdu(outbuffer.str());

	iResult = recv(ConnectSocket, recvbuf, BUFFLEN, 0);
	if(iResult <= 0)
	{
		cerr << "Connection error: no response." << endl;
		return false;
	}

	string s = recvbuf;
	bool ret = false;
	s = s.substr(0, iResult);
	int pos = s.find("statusmessage=active+account");
	if(pos > 0) ret = true;
	if(ret)
	{
		run = true;
		startReceiveThread();
	}
	return ret;
}

int ozConnection::sendPdu(string pdu)
{
	try {
	if(pdu.length() > 0)
	{
			int iResult = send( ConnectSocket, pdu.c_str(), pdu.length(), 0 );
			if(iResult != pdu.length()) {
				cerr << "send() failed." << endl;
				exit(-1);
			}
			if (iResult == SOCKET_ERROR) {
				cerr << "send() failed with error: " << WSAGetLastError() << endl;
				exit(SOCKET_ERROR);
			}
			return 0;
		}
		return -1;
	} catch(exception& ex) {
		cerr << "Exception@sendPdu(): " << ex.what() << endl;
	}
	return -2;
}

string ozConnection::randomString(int size, bool lowerCase)
{
	try {
		stringstream s;
	    char ch;
	    for (int i = 0; i < size; i++)
	    {
			if(lowerCase) ch = rand() % 26 + 97;
			else ch = rand() % 26 + 65;
			s << ch;
	    }
		return s.str();
	} catch(exception& ex) {
		cerr << "Exception@randomString(): " << ex.what() << endl;
	}
	return "";
}  

string ozConnection::makeMessageId() {
	return randomString(8, false);
}

int ozConnection::nextTransactionId()
{
	this->transactionId += 2;
	return transactionId;
}

string ozConnection::sendMessage(string receiver, string message,string sender, string messagetype, string serviceProvider)
{
	try {
        string messageId = makeMessageId();
        int transId = nextTransactionId();

		stringstream pdu;
        
        pdu << STX;
        pdu << "transactionid=" << transId;
        pdu << "&action=sendmessage";
        pdu << "&responseformat=urlencoded";
        pdu << "&originator=" << encode(sender);
        pdu << "&recipient=" << encode(receiver);
        pdu << "&messagetype=" << messagetype;
        pdu << "&messagedata=" << encode(message);
        pdu << "&messageid=" << encode(messageId);
        pdu << "&serviceprovider=" << encode(serviceProvider);
        pdu << ETX;

		msgQueue.push(pdu.str());
		return messageId;
	} catch(exception& ex) {
		cerr << "Exception@sendMessage(): " << ex.what() << endl;
	}
	return "";
}

string ozConnection::encode(string text)
{
	char *hex = "0123456789ABCDEF";
	stringstream s;

	for(unsigned int i = 0; i < text.length(); i++)
	{
		char c = (char)text.c_str()[i];
		if( ('a' <= c && c <= 'z')
			|| ('A' <= c && c <= 'Z')
			|| ('0' <= c && c <= '9') )
		{
			s << c;
		} else 
		{
			s << '%' << (char)hex[c >> 4] << (char)hex[c & 15];
		}
	}
	return s.str();

}

int ozConnection::keepAlive()
{
	stringstream keepAlivePdu;
	keepAlivePdu << STX << "transactionid=" << nextTransactionId() << "&action=keepalive&responseformat=urlencoded" << ETX;
	msgQueue.push(keepAlivePdu.str());
	return 0;
}

int ozConnection::startReceiveThread()
{
	DWORD dwThreadReceive, dwThreadKeepAlive;

	hThreadReceive   = CreateThread(NULL, 0, sreceiveThread,   (void*)this, 0, &dwThreadReceive);
	hThreadKeepAlive = CreateThread(NULL, 0, skeepAliveThread, (void*)this, 0, &dwThreadKeepAlive);

	return 0;
}

DWORD WINAPI ozConnection::skeepAliveThread(void* param)
{
	ozConnection* This = (ozConnection*) param;
	return This->keepAliveThread();
}

DWORD ozConnection::keepAliveThread(void)
{
	while(run)
	{
		Sleep(KEEPALIVETIMER);
		keepAlive();
	}
	return 0;
}

DWORD WINAPI ozConnection::sreceiveThread(void* param)
{
	ozConnection* This = (ozConnection*) param;
	return This->receiveThread();
}


DWORD ozConnection::receiveThread(void)
{
	try {
		stringstream tmp;
		stringstream inbuff;
		char recvbuf[BUFFLEN];		

		while(run)
		{
			fd_set Read, Write, Err;
			TIMEVAL Timeout;
			int TimeoutSec = 1; // timeout after 10 seconds

			FD_ZERO(&Read);			
			FD_ZERO(&Write);
			FD_ZERO(&Err);
			FD_SET(ConnectSocket, &Read);
			FD_SET(ConnectSocket, &Write);
			FD_SET(ConnectSocket, &Err);

			Timeout.tv_sec = TimeoutSec;
			Timeout.tv_usec = 0;
	
			int iResult = select(0,		//ignored
								&Read,	//read
								&Write,	//Write Check
								&Err,	//Error Check
								&Timeout);

			if(iResult <= 0)
			{
				cerr << "Connection error: " << iResult << endl;
				exit(iResult);
			}
			else
			{
				if(FD_ISSET(ConnectSocket, &Write))
				{
					if(!msgQueue.empty())
					{
						try {
							string msg(msgQueue.front());
							msgQueue.pop();
							sendPdu(msg);
							Sleep(10);
						} catch(exception& ex) {
							cerr << "Exception@receiveThread(Write): " << ex.what() << endl;
							exit(-1);

						}
					}
					else
					{
						Sleep(100);
					}
				}
				if(FD_ISSET(ConnectSocket, &Read))
				{

					iResult = recv(ConnectSocket, recvbuf, BUFFLEN, 0);	
					if(iResult > 0)
					{
						tmp.str(recvbuf);
						tmp.str(tmp.str().substr(0, iResult));
						inbuff << tmp.str();
					}
					else
					{
						cerr << "Connection lost." << endl;
						exit(1);
					}

					vector<string> v;
					SplitPdu(inbuff.str(), v);
				
					inbuff.str(v.at(v.size() - 1));

		
					for(unsigned int i = 0; i < v.size() - 1; i++)
					{
						vector<string> v2;
						map<string, string> report;

						SplitString(v.at(i), "&", v2);			

						for(unsigned int j = 0; j < v2.size(); j++)
						{
							vector<string> v3;
							SplitString(v2.at(j), "=", v3);
							if(v3.size() > 1) report[v3.at(0)] = v3.at(1);
						}

						if(report["action"].compare("sendmessage") == 0
							|| report["action"].compare("deliveryreport") == 0
							|| report["action"].compare("messagereceived") == 0)
						{
							stringstream ackPDU;	

							ackPDU << STX;
            				ackPDU << "transactionid=" << report["transactionid"];
            				ackPDU << "&action=" << report["action"];
            				ackPDU << "&acceptreport.statuscode=0";
            				ackPDU << "&acceptreport.statusmessage=acknowledged";
            				ackPDU << ETX;					
							msgQueue.push(ackPDU.str());
						}
					}
				}
				if(FD_ISSET(ConnectSocket, &Err))
				{
					cerr << "ERROR.\n";
					system("pause");
					return 1;
				}
			}
		}
	} catch(exception& ex) {
		cerr << "Exception@receiveThread(): " << ex.what() << endl;
	}
	return 0;
}

void ozConnection::SplitString(string str, string delim, vector<string> &results)
{
	try {
		int cutAt;
		while( (cutAt = str.find_first_of(delim)) != str.npos )
		{
			if(cutAt > 0)
			{
				results.push_back(str.substr(0,cutAt));
			}
			str = str.substr(cutAt+1);
		}
		if(str.length() > 0)
		{
			results.push_back(str);
		}
	} catch(exception& ex)
	{
		cerr << "Exception@SplitString(): " << ex.what() << endl;
		exit(0);
	}
}

void ozConnection::SplitPdu(string str, vector<string> &results)
{
	try {
		string delim(1, ETX);

		int cutAt;
		while( (cutAt = str.find_first_of(delim)) != str.npos )
		{
			if(cutAt > 0)
			{
				results.push_back(str.substr(1,cutAt));
			}
			str = str.substr(cutAt+1);
		}
		if(str.length() > 0)
		{
			results.push_back(str);
		}
	} catch(exception& ex)
	{
		cerr << "Exception@SplitPdu(): " << ex.what() << endl;
		exit(0);
	}
}

extern "C" __declspec(dllexport) ozApi* CreateSmsConnection()
{
	return new ozConnection();
}