#include <iomanip>
#include <iostream>
#include <cstring>

#include "XmlParser.h"
#include "hmac_sha2.h"
#include "paylidpaylidwsdlBindingProxy.h"  //  get proxy
#include "paylid.nsmap"  // obtain the generated XML namespace mapping table for the InterSwitch service

#define BUFSIZE 1024

using namespace std;
using namespace paylid;

#ifdef TEST
enum { FLOG_NONE,FLOG_FATAL,FLOG_CRITICAL,FLOG_WARN,FLOG_COMMAND,FLOG_MAX };
#define logfmt test_log
#define PREFIX "FAKE:"
class FakeLog {
	public:
		void SetFileName(const char *str) {
			cout << str << endl;
		}
};
static FakeLog *plog = new FakeLog();
#else
//#include "general_types.h"
#include "clog.h"
#warning "Including log..."
#endif

void test_log( int level, const char * format, ... ) {

	va_list ap;
	va_start( ap, format );

	vfprintf( stdout, format, ap );
	fprintf( stdout, "\n" );

	fflush( stdout );

	va_end( ap );
}

// Helper to convert hex digit to integer
char dtox(int d) {
	if (d>=0 && d<=9) return '0'+d;
	if (d>=10 && d<=16) return 'a'-10+d;
	return '0';        // not Hex digit
}
// Helper to convert hex digit to integer
int xtod(char c) {
	if (c>='0' && c<='9') return c-'0';
	if (c>='A' && c<='F') return c-'A'+10;
	if (c>='a' && c<='f') return c-'a'+10;
	return c=0;        // not Hex digit
}
// This is a hashing function
void hash_hmac(const char *key, char *data, char *result) {
	unsigned char mac[SHA512_DIGEST_SIZE];
	hmac_sha512((unsigned char *)key, strlen(key), (unsigned char *)data, strlen(data), mac, SHA512_DIGEST_SIZE);
	for (int i=0; i<SHA512_DIGEST_SIZE; i++) {
		unsigned char c = mac[i];
		*(result+2*i) = dtox((int)c/16);
		*(result+2*i+1) = dtox(c%16);
	}
}
// Helper to replace substrings
string& replaceAll(string& context, const string& from, const string& to)
{
    size_t lookHere = 0;
    size_t foundHere;
    while((foundHere = context.find(from, lookHere)) != string::npos)
    {
          context.replace(foundHere, from.size(), to);
          lookHere = foundHere + to.size();
    }
    return context;
}
// Basic parsing
int ParseResponse(const char *xmlstring,const char *rslCode,const char *what,char *response) {

// <?xml version="1.0" encoding="UTF-8"?><PayLid><TransactionReference>111111111111111</TransactionReference><ResultCryptogram>B7AFD916C387DCF87DB91C9EAA9828DA</ResultCryptogram></PayLid>
	using namespace SimpleXMLParser;

	string xml(xmlstring);
	string lto = "&lt;";
	string gto = "&gt;";
	string ltr = "<";
	string gtr = ">";
	xml = replaceAll(xml,lto, ltr);
	xml = replaceAll(xml,gto, gtr);
	xml = "<PayLid>" + xml + "</PayLid>";

#ifdef TEST
	cout << "RSL: " << rslCode << endl;
	cout << "RAW: " << xmlstring << endl;
	cout << "XML: " << xml << endl;
#endif

	int rsl = atoi(rslCode);

   // logfmt( FLOG_MAX, "Response: %s", paylid__directAirTimeTopUpResponse_.Data.c_str() );
   	logfmt( FLOG_MAX, "Response: rsl %d", rsl );

	if (rsl!=10000) {
		switch (rsl) {
			case 20000: strcpy(response,"Invalid terminal ID");break;
			case 30000: strcpy(response,"Hash HMAC validation failed");break;
			case 40000: strcpy(response,"Service database error");break;
			case 50000: strcpy(response,"IP access denied");break;
			case 90000: strcpy(response,"Custom base error");break;
			default: strcpy(response,"Unknown code"); break;
		}
		return rsl;
	}

	XmlParser parser;
	const Element& root = parser.Parse(xml.c_str(),strlen(xml.c_str()));

	if (root.GetName()!="PayLid") {
#ifdef TEST
		cout << "Unknown XML response" << endl;
#endif
		return -1;
	}
	int ret = rsl; // Empty result cryptogram error
	const Elements* classes = root.GetChildren();
	if(classes != 0) {
		for(Elements::const_iterator class_it = classes->begin();class_it != classes->end(); ++class_it) {


			if ((*class_it)->GetName()==what) {
				const string value = (*class_it)->GetValue();
				strcpy(response,value.c_str());
				// There is no cryptogram - override
				if ((*class_it)->GetName()=="TransResponseCode") {
					ret = atoi(value.c_str());
				}

				if ((*class_it)->GetName()=="delivery_status") {
									ret = atoi(value.c_str());
				}
			} else {
#ifdef TEST
				cout << (*class_it)->GetName() << endl;
#endif
			}
		}
	}
	return ret;
}

void GenerateUniqueRef(char *buffer,int bSize) {
	buffer[0] = 'P';
	buffer[1] = 'L';
	buffer[2] = 'I';
	buffer[3] = 'D';
	int i = 4, n = bSize - 4;
	time_t seconds;
	seconds = time (NULL);
	while (seconds>0 && n>0) {
		int d = seconds % 10;
		buffer[i++] = '0'+d;
		seconds /= 10;
		n--;
	}
	while (n>0) {
		buffer[i++] = '\0';
		n--;
	}
}

long storeface_transferFund(const char *endpoint, const char *tid, const char *toDestination, const char *key, char *error) {

	//plog->SetFileName( PREFIX"/logs/SendMoneyPayLid.log" );

	long result = -1;
	string status = "Unknown error";
	soap_ssl_init(); /* init OpenSSL (just once) */
	paylidwsdlBindingProxy *q = new paylidwsdlBindingProxy();
	q->soap_endpoint = endpoint;
	paylid__transferFundResponse paylid__transferFundResponse_; // = new paylid__transferFundResponse();

	string terminalID(tid);

  logfmt( FLOG_MAX, "XML SENT-> %s", toDestination);
	string toDestinationString(toDestination);

	string data = tid;
	data += toDestination;
	//logfmt( FLOG_MAX, "DATA-> %s", data.c_str());
	// Get hash HMAC
	const int mac_512_size = 2*SHA512_DIGEST_SIZE+1;
	char hmac[mac_512_size];
	memset(hmac,0,mac_512_size);
	hash_hmac(key, (char *)data.c_str(), hmac);
	logfmt( FLOG_MAX, "HMAC is '%s'", hmac);

	if (hmac!=NULL) {
		string hmacString(hmac);

		if (soap_ssl_client_context(q,SOAP_SSL_SKIP_HOST_CHECK|SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE|SOAP_SSL_NO_AUTHENTICATION,NULL,NULL,NULL,NULL,NULL)) {
			soap_print_fault(q, stderr);
			status = "Cannoy initalize SOAP/SSL";
			strncpy(error,status.c_str(),strlen(error));
			return -1L;
		}

#ifdef TEST
		cout << "terminalID=" << terminalID.c_str() << endl;
		cout << "toDestination=" << toDestinationString.c_str() << endl;
		cout << "hmac=" << hmacString.c_str() << endl;
#endif
   	result = SOAP_ERR;
   	try {
			result = q->transferFund(terminalID, toDestinationString, hmacString, paylid__transferFundResponse_);

   	} catch (...) {
			status = "Proxy exception";
    }
   	if (result == SOAP_OK) {
			char response[BUFSIZE];
			memset(response,0,BUFSIZE);
			result = ParseResponse(paylid__transferFundResponse_.Data.c_str(),paylid__transferFundResponse_.Code.c_str(),"TransactionReference",response);
/*
struct paylid__transferFundResponse
{
    std::string                         Code;
    std::string                         Data;
    std::string                         Hmac;
};
*/
			char c[32];
			sprintf(c, "%d", result);
			if (result<0) {
				status = "ParseResponse() error: ";
				status += c;
			} else {
				status = response;
			}
   	} else {
			if (q!=NULL && q->soap_fault_string()!=NULL) {
	   		status = "Fault string: ";
				status += q->soap_fault_string();
			}
			if (q!=NULL && q->soap_fault_detail()) {
   	    	status += "Fault detail: ";
					status += q->soap_fault_detail();
			}
			if (q==NULL) {
	   		status = "Bad proxy";
			} else {
	   		//cout << "Unknown error: " << result << endl;
			}
   	}
	} else {
   	status = "Hash HMAC error";
  }
	//delete paylid__transferFundResponse_;
	delete q;
	strncpy(error,status.c_str(),strlen(error));
	return result;
}

long storeface_checkTransferStatus(const char *endpoint, const char *tid, const char *transactionRef, const char *key, char *error) {
	long result = -1;
	string status = "Unknown error";
	soap_ssl_init(); /* init OpenSSL (just once) */
	paylidwsdlBindingProxy *q = new paylidwsdlBindingProxy();
	q->soap_endpoint = endpoint;
	paylid__checkTransferStatusResponse paylid__checkTransferStatusResponse_;
	string terminalID(tid);
	//No session key required
  string transactionRefString(transactionRef);

	// Get hash HMAC
	const int mac_512_size = 2*SHA512_DIGEST_SIZE+1;
	char hmac[mac_512_size];
	memset(hmac,0,mac_512_size);
	hash_hmac(key, (char *)tid, hmac);

	string hmacString(hmac);

    if (soap_ssl_client_context(q,SOAP_SSL_SKIP_HOST_CHECK|SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE|SOAP_SSL_NO_AUTHENTICATION,NULL,NULL,NULL,NULL,NULL)) {
      soap_print_fault(q, stderr);
      status = "Cannoy initalize SOAP/SSL";
      strncpy(error,status.c_str(),strlen(error));
      return -1L;
    }

    result = SOAP_ERR;
    try {
			result = q->checkTransferStatus(terminalID, transactionRefString, hmacString, paylid__checkTransferStatusResponse_);

    } catch (...) {
				status = "Proxy exception";
    }
    if (result == SOAP_OK) {
			char response[BUFSIZE];
			memset(response,0,BUFSIZE);
			result = ParseResponse(paylid__checkTransferStatusResponse_.Data.c_str(),paylid__checkTransferStatusResponse_.Code.c_str(),"TransResponseCode",response);
			char c[32];
			sprintf(c, "%d", result);
			if (result<0) {
				status = "ParseResponse() error: ";
				status += c;
			} else {
				status = response;
			}
   	} else {
			if (q!=NULL && q->soap_fault_string()!=NULL) {
	   		status = "Fault string: ";
				status += q->soap_fault_string();
			}
			if (q!=NULL && q->soap_fault_detail()) {
   	    	status += "Fault detail: ";
					status += q->soap_fault_detail();
			}
			if (q==NULL) {
	   		status = "Bad proxy";
			} else {
	   		//cout << "Unknown error: " << result << endl;
			}
   	}
  delete q;
	strncpy(error,status.c_str(),strlen(error));

	return result;
}

long storeface_getAccountBalance(const char *endpoint, const char *tid, const char *key, char *error) {
	logfmt( FLOG_MAX, "long storeface_getAccountBalance(const char *endpoint, const char *tid, const char *key, char *error)");
	logfmt( FLOG_MAX, "endpoint=%s", endpoint);

	int result = -1;
	string status = "Unknown error";
	soap_ssl_init(); /* init OpenSSL (just once) */
	//paylidwsdlBindingProxy *q = new paylidwsdlBindingProxy();
	paylidwsdlBindingProxy q;
	q.soap_endpoint = endpoint;
	paylid__getAccountBalanceResponse paylid__getAccountBalanceResponse_;
	string terminalID(tid);

	// Get hash HMAC
	const int mac_512_size = 2*SHA512_DIGEST_SIZE+1;
	char hmac[mac_512_size];
	memset(hmac,0,mac_512_size);
	hash_hmac(key, (char *)tid, hmac);

	if (hmac!=NULL) {
		string hmacString(hmac);

    if (soap_ssl_client_context(&q,SOAP_SSL_SKIP_HOST_CHECK|SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE|SOAP_SSL_NO_AUTHENTICATION,NULL,NULL,NULL,NULL,NULL)) {
      soap_print_fault(&q, stderr);
      status = "Cannot initalize SOAP/SSL";
      strncpy(error,status.c_str(),strlen(error));
      return -1L;
    }

#ifdef TEST
		cout << "terminalID=" << terminalID.c_str() << endl;
		cout << "hmac=" << hmacString.c_str() << endl;
#endif

    result = SOAP_ERR; // -1
    try {
	result = q.getAccountBalance(terminalID, hmacString, paylid__getAccountBalanceResponse_);

    } catch (...) {
	status = "Proxy exception";
    }

    logfmt( FLOG_MAX, "SOAP call result = %d", result );

    if (result == SOAP_OK) {
			char response[BUFSIZE];
			memset(response,0,BUFSIZE);
			result = ParseResponse(paylid__getAccountBalanceResponse_.Data.c_str(),paylid__getAccountBalanceResponse_.Code.c_str(),"balance",response);
			char c[32];
			sprintf(c, "%d", result);
			if (result<0) {
				status = "ParseResponse() error: ";
				status += c;
			} else {
				status = response;
			}
   	} else {/*
			if (q!=NULL && q.soap_fault_string()!=NULL) {
	   		status = "Fault string: ";
				status += q.soap_fault_string();
			}
			if (q!=NULL && q.soap_fault_detail()) {
   	    	status += "Fault detail: ";
					status += q.soap_fault_detail();
			}
			if (q==NULL) {
	   		status = "Bad proxy";
			} else {
	   		//cout << "Unknown error: " << result << endl;
			}
*/
   	}
	} else {
   	status = "Hash hmac error";
  }
  // WARNING: momory leak!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  q.destroy();
  //delete q;
  //q = NULL;
  
	strncpy(error,status.c_str(),strlen(error));
	logfmt( FLOG_MAX, "/long storeface_getAccountBalance(const char *endpoint, const char *tid, const char *key, char *error)");
	return (long)result;
}

long storeface_directAirTimeTopUp(const char *endpoint, const char *tid, const char *topupData, const char *key, char *error, long &delivery_status) {
	long result = -1;
	string status = "Unknown error";
	soap_ssl_init(); /* init OpenSSL (just once) */
	paylidwsdlBindingProxy *q = new paylidwsdlBindingProxy();
	q->soap_endpoint = endpoint;
	paylid__directAirTimeTopUpResponse paylid__directAirTimeTopUpResponse_;
	string terminalID(tid);
	string topupDataString(topupData);

	// Get hash HMAC
	const int mac_512_size = 2*SHA512_DIGEST_SIZE+1;
	char hmac[mac_512_size];
	memset(hmac,0,mac_512_size);
	hash_hmac(key, (char *)tid, hmac);

	if (hmac!=NULL) {
		string hmacString(hmac);

    if (soap_ssl_client_context(q,SOAP_SSL_SKIP_HOST_CHECK|SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE|SOAP_SSL_NO_AUTHENTICATION,NULL,NULL,NULL,NULL,NULL)) {
      soap_print_fault(q, stderr);
      status = "Cannoy initalize SOAP/SSL";
      strncpy(error,status.c_str(),strlen(error));
      return -1L;
    }
delivery_status = 56789;
    result = SOAP_ERR;
    try {
			result = q->directAirTimeTopUp(terminalID, topupDataString, hmacString, paylid__directAirTimeTopUpResponse_);

    } catch (...) {
				status = "Proxy exception";
    }
    if (result == SOAP_OK) {
			logfmt( FLOG_MAX, "Response: %s", paylid__directAirTimeTopUpResponse_.Data.c_str() );
			char response[BUFSIZE];
			memset(response,0,BUFSIZE);
			result = 1L;
			// result =  ParseResponse(paylid__directAirTimeTopUpResponse_->return_->c_str(),sessKey,"Balance",response);
            //ParseResponse(paylid__directAirTimeTopUpResponse_.Data.c_str(),paylid__directAirTimeTopUpResponse_.Code.c_str(),"result",response);
			delivery_status = ParseResponse(paylid__directAirTimeTopUpResponse_.Data.c_str(),paylid__directAirTimeTopUpResponse_.Code.c_str(),"delivery_status",response);

			//delivery_status = (long)response;

// delivery_status = 56789;
			char c[32];
			sprintf(c, "%d", result);
			if (result<0) {
				status = "ParseResponse() error: ";
				status += c;
			} else {
				status = response;
			}
   	} else {
			if (q!=NULL && q->soap_fault_string()!=NULL) {
	   		status = "Fault string: ";
				status += q->soap_fault_string();
			}
			if (q!=NULL && q->soap_fault_detail()) {
   	    	status += "Fault detail: ";
					status += q->soap_fault_detail();
			}
			if (q==NULL) {
	   		status = "Bad proxy";
			} else {
	   		//cout << "Unknown error: " << result << endl;
			}
   	}
	} else {
   	status = "Hash hmac error";
  }
  delete q;
	strncpy(error,status.c_str(),strlen(error));
	return result;
}

#ifdef TEST
int main(void) {
	// Common
	long ret = 0;
	const char *endpoint = "http://oameye.works.storeface.com/rsl/service.php";
	const char *tid = "INTERRA001";
	// WARNING: Key is case-sensetive!
	const char *key = "29832d0e1a51eff1cb673feda90293abec053aa5b81f9649d9ed53de86f79ed7";
	const int mac_512_size = 2*SHA512_DIGEST_SIZE+1;
  char hmac[mac_512_size];
	// 78fa6a576f52342df61628e2ad48f11027c3d619c00c598c1ca1a9ce086febcd603b846da02eaa5f3a77a97478a8fa0d1832afe2ef7a0715629d7f8f2f4140d1

	// Specific
	const char *account = "4852010016214"; //"00010080023868"; //"0910010905070015"; //"0910010705222055";
	char* status = (char *)malloc(BUFSIZE);
	memset(status,32,BUFSIZE);
	status[BUFSIZE] = '\0';

	//memset(hmac,0,mac_512_size);
	//hash_hmac(key, (char *)tid, hmac);
	cout << "==========================================================" << endl;
	cout << "getAccountBalance(tid,hmac,status)" << endl;
	ret = storeface_getAccountBalance(endpoint,tid,key,status);
	cout << "Return: " << ret << endl << "Status: " << status << endl;
	memset(status,32,BUFSIZE);
	status[BUFSIZE] = '\0';

	char *uniqueRef = (char *)malloc(32);
	GenerateUniqueRef(uniqueRef,32);

	memset(hmac,0,mac_512_size);
	hash_hmac(key, (char *)tid, hmac);
	cout << "==========================================================" << endl;
	cout << "transferFund(tid,toDestination,hmac)" << endl;
	cout << "Unique Reference: " << uniqueRef << endl;
	ret = storeface_transferFund(endpoint,tid,uniqueRef,hmac,status); // 100.00
	cout << "Return: " << ret << endl << "Status: " << status << endl;
	cout << "Unique Reference: " << uniqueRef << endl;
	memset(status,32,BUFSIZE);
	status[BUFSIZE] = '\0';

	memset(hmac,0,mac_512_size);
	hash_hmac(key, (char *)tid, hmac);
	cout << "==========================================================" << endl;
	cout << "checkTransferStatus(tid,transactionRef,hmac,status)" << endl;
	cout << "Unique Reference: " << uniqueRef << endl;
	ret = storeface_checkTransferStatus(endpoint,tid,uniqueRef,hmac,status);
	cout << "Return: " << ret << endl << "Status: " << status << endl;

	free(uniqueRef);
	free(status);

	return 0;
}
#endif

// vi:ts=2

