//
//debug.c
//
//Currently has functions to verify certain functions work.
//
//

#include <stdlib.h>
#include "misc/compat.h"
#include "base/logger.h"
#include "base/bignum.h"
#include "iip/debug.h"
#include "misc/global.h"
#include "misc/exitcode.h"
#include "crypt/dh.h"
#include "crypt/random.h"
#include <time.h>

#define TESTERR(x, y) if(x) {i++; LOGERROR(y);}

int32 endiantest = 0x12345678;

int dhTestDH(void);
int bntMulDiv(void);
int bntAddSub(void);
int bntInt(void);
int doBigNumTests(void);
int doEndianTests(void);
int doRandomTests(void);



int dhTestDH(void) {
	BigNum *dh1priv;
	BigNum *dh2priv;
	BigNum *dh1pub;
	BigNum *dh2pub;

	BigNum *dh1shared;
	BigNum *dh2shared;

	int t = 0;

	clock_t clockstop = 0;
	clock_t clockdiff = 0;
	clock_t clockstart = clock();
	clock_t clockpersec = CLOCKS_PER_SEC;
	
	LOGMINOR("Starting DH keygen test...");
	
#define DHKEYLENGTH 2048

	dh1priv = dhGeneratePrivKey(dhGetExponentLength(DHKEYLENGTH));
	dh2priv = dhGeneratePrivKey(dhGetExponentLength(DHKEYLENGTH));

	dh1pub = dhGeneratePubKey(DHKEYLENGTH, dh1priv);
	dh2pub = dhGeneratePubKey(DHKEYLENGTH, dh2priv);

	dh1shared = dhGenerateSharedKey(DHKEYLENGTH, dh1priv, dh2pub);
	dh2shared = dhGenerateSharedKey(DHKEYLENGTH, dh2priv, dh1pub);

	clockstop = clock();

	clockdiff = clockstop - clockstart;
	
	if(bignumCompare(dh1shared, dh2shared)) {
		t = 1;
		LOGERROR("DH keys mismatched!");
	}

	LOGMINOR(stringAppend3(
			"Computed 2 DH key exchanges in:", 
			intToString((int) ((float) clockdiff * 1000 / clockpersec)),
			" micro seconds")
		);

	bignumFree(dh1priv);
	bignumFree(dh1pub);
	bignumFree(dh1shared);
	bignumFree(dh2priv);
	bignumFree(dh2pub);
	bignumFree(dh2shared);

	return t;
}


int bntMulDiv(void) {
	int t = 0;
	int i, j;
	unsigned int vi, vj;
	BigNum *bi, *bj, *br;

	vi = 0;
	for(i = 0; i < 16; i++) {
		vi = (vi << 1) | 1;
		vj = 0;
		bi = bignumFromInt(vi);
		for(j = 0; j < 16; j++) {
			vj = (vj << 1) | 1;
			bj = bignumFromInt(vj);
			br = bignumMake(0, 0);
			br = bignumMulKeep(br, bi, bj);
			if(bignumToInt(br) != vi * vj) {
				t++;
			}
			br = bignumDiv(br, bj);
			if(bignumToInt(br) != (vi * vj) / vj) {
				t++;
			}
			bignumFree(br);
			bignumFree(bj);
		}
		bignumFree(bi);
	}
	return t;
}


int bntAddSub(void) {
	int t = 0;
	int i, j;
	unsigned int vi, vj;
	BigNum *bi, *bj, *br;

	vi = 0;
	for(i = 0; i < 24; i++) {
		vi = (vi << 1) | 1;
		vj = 0;
		bi = bignumFromInt(vi);
		for(j = 0; j < 24; j++) {
			vj = (vj << 1) | 1;
			bj = bignumFromInt(vj);
			br = bignumAddKeep(bi, bj);
			if(bignumToInt(br) != vi + vj) {
				t++;
			}
			br = bignumSubtract(br, bj);
			if(bignumToInt(br) != (vi + vj) - vj) {
				t++;
			}
			bignumFree(br);
			bignumFree(bj);
		}
		bignumFree(bi);
	}
	return t;
}

int bntInt(void) {
	int t = 0;
	int i;
	unsigned int v = 0;
	BigNum *tmp;
	for(i = 0; i < sizeof(v) * 8; i++) {
		v = (v << 1) | 1;
		tmp = bignumFromInt(v);
		if(bignumToInt(tmp) != v) {
			t++;
		}
		bignumFree(tmp);
	}
	return t;
}

int doBigNumTests(void) {
	int i = 0;
	LOGMINOR("Testing BigNum...");
	TESTERR(bntInt(), "Failed basic int<->BigNum conversion.");
	TESTERR(bntAddSub(), "Failed bignum add/subtract.");
	TESTERR(bntMulDiv(), "Failed bignum multiply/divide.");
	return i;
}

int doEndianTests() {
	int i = 0;
	int x;
	LOGMINOR("Testing Endian");
	switch(*((uint8 *)&endiantest)) {
	case 0x12: //big endian
#ifndef BIG__ENDIAN
		i++;
		LOGERROR("Program was run on a big endian machine but the program was not compiled for a big endian machine.")
		exit(EXIT_ENDIAN_ERROR);
#endif
		break;
	case 0x78: //little endian
#ifndef LITTLE__ENDIAN
		i++;
		LOGERROR("Program was run on a little endian machine but the program was not compiled for a little endian machine.")
		exit(EXIT_ENDIAN_ERROR);
#endif
		break;
	default: //probably middle endian or broken
		i++;
		LOGERROR("Endian test of machine failed!")
		exit(EXIT_ENDIAN_ERROR);
	}

	x = ENDIANSWAP32(0x12345678);
	if(x != 0x78563412) {
		LOGERROR("ENDIANSWAP32() is broken.")
		exit(EXIT_ENDIAN_ERROR);
	}
	x = ENDIANSWAP16(0x1234);
	if(x != 0x3412) {
		LOGERROR("ENDIANSWAP16() is broken.")
		exit(EXIT_ENDIAN_ERROR);
	}
	//todo: test endian macros/functions
	return i;
}

int doRandomTests(void) {
	int i = 0;
	int j;
	int total = 0;
	uint8 c;
	uint8 v;
	DataBlock *db = randomDBlock(1024);

	v = db->data[1023];
	for(j = 0; j < 1024; j++) {
		c = v ^ db->data[j];
		v = db->data[j];
		for(; c != 0; c >>= 1) {
			if(c & 1) {
				total++;
			}
		}
	}

	LOGMINOR(stringJoinMany("Random test: number of bits changed per byte: 1024 bytes:", 
			intToString(total), 
			", average:",
			intToString(total/1024), 
		NULL));
	
	LOGDEBUGSPAM(bufferToHexString(db->data, db->size));


	dblockFree(db);

	return i;
}

void doTests(void) {
	int i = 0;
	if(!quiet) {
		LOGNORMAL("Starting internal tests...");
	}
	i += doEndianTests();
	i += doRandomTests();
	i += doBigNumTests();
	//i += dhTestDH();


	if(i != 0) {
		LOGERROR(stringJoin(intToString(i), " test(s) failed!"));
		exit(EXIT_TESTS_FAILED);
	}
	if(!quiet) {
		LOGNORMAL("Tests passed.");
	}

}

