#include "MyComponent.h"
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/asio.hpp>

NS_IMPL_ISUPPORTS1(MyComponent, IMyComponent)

using boost::asio::ip::tcp;

std::string ENCRYPT_COMMAND = " --quiet --no-tty --batch --yes --always-trust -a -e -r ";
std::string DECRYPT_COMMAND = " --quiet --no-tty --batch --yes -a";
std::string GPGPath = "";
char ServerID[] = "bas.server@server";
long UserNonce, ServerNonce;

boost::asio::io_service *io_service = 0;
tcp::socket *Socket = 0;

MyComponent::MyComponent()
{
  /* member initializers and constructor code */
	srand((unsigned)time(0));
}

MyComponent::~MyComponent()
{
  /* destructor code */
}

/* void SetPGP (in string path); */
NS_IMETHODIMP MyComponent::SetPGP(const char *path)
{
	GPGPath.assign(path);
    return NS_OK;
}

/* void SetNonce (in long nonce1, in long nonce2); */
NS_IMETHODIMP MyComponent::SetNonce(PRInt32 nonce1, PRInt32 nonce2)
{
	UserNonce = nonce1;
	ServerNonce = nonce2;
    return NS_OK;
}

int RunCommand(const char *CommandLine, const char *Input, char *Output){
	SECURITY_ATTRIBUTES sattr;
	sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
	sattr.lpSecurityDescriptor = 0;
	sattr.bInheritHandle = TRUE;
	HANDLE hOutRead, hOutWrite;
	CreatePipe(&hOutRead, &hOutWrite, &sattr, 0);
	SetHandleInformation(hOutRead, HANDLE_FLAG_INHERIT, 0);
	HANDLE hInRead, hInWrite;
	CreatePipe(&hInRead, &hInWrite, &sattr, 0);
	SetHandleInformation(hInWrite, HANDLE_FLAG_INHERIT, 0);
	PROCESS_INFORMATION ProcessInfo = {NULL,NULL,0,0};
	STARTUPINFO StartupInfo;
	memset(&StartupInfo, 0, sizeof(STARTUPINFO));
	StartupInfo.cb = sizeof(STARTUPINFO);
	StartupInfo.hStdError = GetStdHandle(STD_OUTPUT_HANDLE);
	StartupInfo.hStdOutput = hOutWrite;
	StartupInfo.hStdInput = hInRead;
	StartupInfo.dwFlags = STARTF_USESTDHANDLES;
	CreateProcess(NULL, (LPSTR)CommandLine, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &StartupInfo, &ProcessInfo);	
	DWORD writtenbytes = 0;
	WriteFile(hInWrite, Input, strlen(Input), &writtenbytes, NULL);
	CloseHandle(hInWrite);
	DWORD retval = WaitForSingleObject(ProcessInfo.hProcess, 5000);
	DWORD retcode;
	GetExitCodeProcess(ProcessInfo.hProcess, &retcode);
	if (retcode==0){
		DWORD readbytes = 0;
		CloseHandle(hOutWrite);
		ReadFile(hOutRead, Output, 65535, &readbytes, NULL);
		Output[readbytes] = '\0';
	}
	return retcode;
}

void EncryptText(const char *Text, char *Output)
{
//	memcpy(Output, Text, strlen(Text)+1);
//	return;
	std::string str;
	str.append(GPGPath);
	str.append(ENCRYPT_COMMAND);
	str.append(ServerID);
	if (RunCommand(str.c_str(), Text, Output)!=0)
		throw std::exception("Encrypt failed!");
}

void DecryptText(const char *Text, char *Output)
{
//	memcpy(Output, Text, strlen(Text)+1);
//	return;
	std::string str;
	str.append(GPGPath);
	str.append(DECRYPT_COMMAND);
	if (RunCommand(str.c_str(), Text, Output)!=0)
		throw std::exception("Decrypt failed!");
}

/* void Connect (in string address, in string port); */
NS_IMETHODIMP MyComponent::Connect(const char *address, const char *port)
{
	try
    {
		io_service = new boost::asio::io_service();
		tcp::resolver resolver(*io_service);
		tcp::resolver::query query(tcp::v4(), address, port);
		tcp::resolver::iterator iterator = resolver.resolve(query);

		Socket = new tcp::socket(*io_service);
		Socket->connect(*iterator);
		UserNonce = (rand() << 15) + rand();
	}
	catch (std::exception& e)
	{ 
		delete Socket;
		Socket = 0;
		return NS_ERROR_FAILURE; 
	}
	return NS_OK;
}

/* boolean Connected (); */
NS_IMETHODIMP MyComponent::Connected(PRBool *_retval)
{
	*_retval = (Socket != 0);
    return NS_OK;
}

/* void Disconnect (); */
NS_IMETHODIMP MyComponent::Disconnect()
{
	delete Socket;
	Socket = 0;
    return NS_OK;
}

/* void Auth (in string email); */
NS_IMETHODIMP MyComponent::Auth(const char *email)
{
	try
	{
		char Nonce[20];
		std::string str;
		str.append("AUTH ");
		str.append(email);
		str.append(" ");
		itoa(UserNonce, Nonce, 10);
		str.append(Nonce);
		char estr[65535];
		EncryptText(str.c_str(), estr);
		strcat(estr, "$\r\n");
		boost::asio::write(*Socket, boost::asio::buffer(estr, strlen(estr)));
	}
	catch (std::exception& e)
	{ 
	}
	return NS_OK;
}

/* void Login (in string email); */
NS_IMETHODIMP MyComponent::Login(const char *email)
{
	try
	{
		UserNonce++;
		ServerNonce++;
		char Nonce[20];
		std::string str;
		str.append("LOGIN ");
		str.append(email);
		str.append(" ");
		itoa(UserNonce, Nonce, 10);
		str.append(Nonce);
		str.append(" ");
		itoa(ServerNonce, Nonce, 10);
		str.append(Nonce);
		char estr[65535];
		EncryptText(str.c_str(), estr);
		strcat(estr, "$\r\n");
		boost::asio::write(*Socket, boost::asio::buffer(estr, strlen(estr)));
	}
	catch (std::exception& e)
	{ 
	}
	return NS_OK;
}

/* void RequestCredits (); */
NS_IMETHODIMP MyComponent::RequestCredits()
{
	try
	{
		UserNonce++;
		ServerNonce++;
		char Nonce[20];
		std::string str;
		str.append("CREDITS");
		str.append(" ");
		itoa(UserNonce, Nonce, 10);
		str.append(Nonce);
		str.append(" ");
		itoa(ServerNonce, Nonce, 10);
		str.append(Nonce);
		char estr[65535];
		EncryptText(str.c_str(), estr);
		strcat(estr, "$\r\n");
		boost::asio::write(*Socket, boost::asio::buffer(estr, strlen(estr)));
	}
	catch (std::exception& e)
	{ 
	}
	return NS_OK;
}

/* void EnoughCredits (in string to); */
NS_IMETHODIMP MyComponent::EnoughCredits(const char *to)
{
	try
	{
		UserNonce++;
		ServerNonce++;
		char Nonce[20];
		std::string str;
		str.append("ENOUGHCREDITS ");
		str.append(to);
		str.append(" ");
		itoa(UserNonce, Nonce, 10);
		str.append(Nonce);
		str.append(" ");
		itoa(ServerNonce, Nonce, 10);
		str.append(Nonce);
		char estr[65535];
		EncryptText(str.c_str(), estr);
		strcat(estr, "$\r\n");
		boost::asio::write(*Socket, boost::asio::buffer(estr, strlen(estr)));
	}
	catch (std::exception& e)
	{ 
	}
	return NS_OK;
}

/* void CheckStamp (in string from, in string stamp); */
NS_IMETHODIMP MyComponent::CheckStamp(const char *from, const char *stamp)
{
	try
	{
		UserNonce++;
		ServerNonce++;
		char Nonce[20];
		std::string str;
		str.append("CHECK ");
		str.append(from);
		str.append(" ");
		str.append(stamp);
		str.append(" ");
		itoa(UserNonce, Nonce, 10);
		str.append(Nonce);
		str.append(" ");
		itoa(ServerNonce, Nonce, 10);
		str.append(Nonce);
		char estr[65535];
		EncryptText(str.c_str(), estr);
		strcat(estr, "$\r\n");
		boost::asio::write(*Socket, boost::asio::buffer(estr, strlen(estr)));
	}
	catch (std::exception& e)
	{ 
	}
    return NS_OK;
}

/* void SendStamp (in string to, in string stamp); */
NS_IMETHODIMP MyComponent::SendStamp(const char *to, const char *stamp)
{
	try
	{
		UserNonce++;
		ServerNonce++;
		char Nonce[20];
		std::string str;
		str.append("SEND ");
		str.append(to);
		str.append(" ");
		str.append(stamp);
		str.append(" ");
		itoa(UserNonce, Nonce, 10);
		str.append(Nonce);
		str.append(" ");
		itoa(ServerNonce, Nonce, 10);
		str.append(Nonce);
		char estr[65535];
		EncryptText(str.c_str(), estr);
		strcat(estr, "$\r\n");
		boost::asio::write(*Socket, boost::asio::buffer(estr, strlen(estr)));
	}
	catch (std::exception& e)
	{ 
	}
    return NS_OK;
}

/* string Decrypt (in string text); */
NS_IMETHODIMP MyComponent::Decrypt(const char *text, char **_retval)
{
	try
	{
		char str[65535];
		DecryptText(text, str);
		char *output = new char[strlen(str)+1];
		memcpy(output, str, strlen(str)+1);
		*_retval = output;
	}
	catch(std::exception& e)
	{
		*_retval = new char[1];
		(*_retval)[0] = '\0';
	}
    return NS_OK;
}

/* string Read (in long time); */
NS_IMETHODIMP MyComponent::Read(char **_retval)
{
	*_retval = 0;
	int count = 0;
	std::size_t bytes_readable = 0;
	boost::asio::socket_base::bytes_readable command(true);
	Socket->io_control(command);
	bytes_readable = command.get();
	if (bytes_readable>0){
		if(bytes_readable>2048){
			return NS_ERROR_FAILURE;
		}
		char *reply = new char[2048];
		memset(reply, 0, 512);
		size_t reply_length = boost::asio::read(*Socket, boost::asio::buffer(reply, bytes_readable));
		*_retval = reply;
	} else {
		*_retval = new char[1];
		(*_retval)[0] = '\0';
	}
    return NS_OK;
}
