//
//random.c
//
//
//
//todo: noting of entropy sources and testing for actual bit quality
//

#include "misc/compat.h"
#include <stdlib.h>
//#include <stdio.h>
#include <limits.h>
#include <time.h>
#include "crypt/random.h"
#include "base/dblock.h"
#include "crypt/entropy.h"
#include "crypt/rand-key.h"
#include "base/str.h"
#include "file/file.h"

#ifdef _WINDOZE_
	#define random() rand()
	#define srandom(x) srand(x)
#endif

int fastpoolselect = 0;

EntropyPool *fastpool = NULL;
EntropyPool *slowpool = NULL;

RandomKey *randomkey = NULL;

char defaultrandomseedfilename[] = DEFAULT_RANDOMSEEDFILENAME;
char *randomseedfilename = NULL;

int randomentropyneeded = 1;


uint8 tempbuffer[8];

char *randomGetSeedFilename(void) {
	if(randomseedfilename != NULL) {
		return randomseedfilename;
	} else {
		return defaultrandomseedfilename;
	}
}

void randomSetSeedFilename(char *filename) {
	stringFree(randomseedfilename);
	randomseedfilename = NULL;
	if(filename == NULL) {
		return;
	}
	randomseedfilename = stringCopy(filename);
}

void randomReadSeed(void) {
	DataBlock *db;
	FileHandle *fh;
	int i;
	fh = fileOpen(randomGetSeedFilename(), "rb");
	if(fh == NULL) {
		return;
	}
	db = dblockMake(RANDOM_SEED_SIZE, RANDOM_SEED_SIZE, "randomseed");
	db->size = fileRead(fh, db->data, RANDOM_SEED_SIZE);
	for(i = 0; i <db->size; i += 4) {
		randomAddEntropyBuffer(&db->data[i], 4, 32);
	}
	if(db->size >= RANDOM_SEED_SIZE) {
		randomentropyneeded = 0;
	}
	fileClose(fh);
	dblockFree(db);
}

void randomWriteSeed(void) {
	DataBlock *db;
	FileHandle *fh;
	fh = fileOpen(randomGetSeedFilename(), "wb");
	if(fh == NULL) {
		return;
	}
	db = randomDBlock(RANDOM_SEED_SIZE);
	fileWrite(fh, db->data, db->size);
	dblockFree(db);
	
	fileClose(fh);

	randomentropyneeded = 0;
}

//reseed the generator key by
//calling makekey with (fastpool + generator key)
void randomFastPoolReseed(void) {
	DataBlock *db = entropyGetDigest(fastpool, NULL);
	db = dblockAppendBlock(db, randomkey->key);

	randomkeyMakeKey(randomkey, db);

	dblockFree(db);
}

//reseed the generator key by
//calling makekey with (fastpool + slowpool + generator key)
void randomSlowPoolReseed(void) {
	DataBlock *db = entropyGetDigest(fastpool, NULL);
	DataBlock *dbs = entropyGetDigest(slowpool, NULL);
	db = dblockAppendBlock(db, dbs);
	db = dblockAppendBlock(db, randomkey->key);

	entropyAddDBlock(fastpool, dbs, dbs->size * 8);

	randomkeyMakeKey(randomkey, db);

	dblockFree(dbs);
	dblockFree(db);


//	DataBlock *db = entropyGetDigest(slowpool, NULL);
//	entropyAddDBlock(fastpool, db, db->size * 8);
//	randomFastPoolReseed();
//	dblockFree(db);

	randomWriteSeed();
}

void randomReseedDecide(void) {
	if(fastpoolselect) {
		if(fastpool->PoolSize > FAST_THRESHOLD) {
			randomFastPoolReseed();
		}
	} else {
		if(slowpool->PoolSize > SLOW_THRESHOLD * 2) {
			//todo: testing of individual sub-pool sizes of random sources
			randomSlowPoolReseed();
		}
	}
}

void randomAddEntropy(int v, int bits) {
	fastpoolselect = !fastpoolselect;

	if(bits > sizeof(v) * 8) {
		bits = sizeof(v) * 8;
	}
	if(fastpoolselect) {
		entropyAddInt(fastpool, v, bits);
	} else {
		entropyAddInt(slowpool, v, bits);
	}
	randomReseedDecide();
}

void randomAddEntropyBuffer(uint8 *buffer, int length, int bits) {
	fastpoolselect = !fastpoolselect;

	if(bits > length * 8) {
		bits = length * 8;
	}
	if(fastpoolselect) {
		entropyAddBuffer(fastpool, buffer, length, bits);
	} else {
		entropyAddBuffer(slowpool, buffer, length, bits);
	}
	randomReseedDecide();
}

//todo: revisit: this is probably not the best way to do it
void randomAddEntropyClock(int bits) {
	randomAddEntropy((int) clock(), bits);
}

void randomInit(void) {
	DataBlock *db;
	srandom(time(NULL));
	if(fastpool == NULL) {
		fastpool = entropyMake();
	}
	if(slowpool == NULL) {
		slowpool = entropyMake();
	}
	if(randomkey == NULL) {
		randomkey = randomkeyMake();
		db = dblockMake(sizeof(time_t) + sizeof(clock_t), sizeof(int), "randominit");
		*((time_t *)(db->data)) = time(NULL); //very weak but guards against the entropy/random system being completely empty
		*((clock_t *)((time_t *)(db->data) + 1)) = clock();
		randomkeyMakeKey(randomkey, db);
		dblockFree(db);
	}

	randomReadSeed();

}

//returns an integer from 0 to (max - 1)
//returns 0 if the parameter is 0
unsigned int randomint(unsigned int i) {
	//return random() % i;
	if(i == 0) {
		return 0;
	}
	do {
		randomkeyGetBuffer(randomkey, tempbuffer, sizeof(unsigned int));
	} while (*((unsigned int *)tempbuffer) < (UINT_MAX - i + 1U) % i);
	return (*((unsigned int *)tempbuffer)) % i;
}


//fill an existing buffer with length bytes of random data
void randomBuffer(char *dstbuffer, int length) {
	//int i;
	//for(i = 0; i < length; i++) {
	///	dstbuffer[i] = randomint(256);
	//}
	randomkeyGetBuffer(randomkey, dstbuffer, length);
}

//create a datablock with length bytes of random data.
DataBlock *randomDBlock(int length) {
	DataBlock *db = dblockMake(length, length, "randomDBlock");
	randomBuffer(db->data, length);
	return db;
}

