/* ==========================================================================
 * Program	:	keygen
 * Author	:	migf1
 * Language	:	C (C99)
 * Description	:	a simple, slow & and rather unsafe generator of unique keys
 * Source files	:	keygen.c
 * gcc options	:	-std=c99
 * CmdLn Usage	:	keygen [ >fname.txt ]
 *
 * Extra Notes	:
 *
 * In the source code, you may easily...
 *	- change the size and the total count of the generated keys
 *	- exclude/include any of the available char-sets from the key generation process
 *	- demand at least 1 char from any of the available char-sets to be present
 *	  in every generated key (even if the char-set is generally excluded )
 * 
 * ==========================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

#define KEYSIZE			10+1			// μέγεθος κλειδιού
#define NKEYS			15			// πλήθος κλειδιών

// Bitflag Options for the program (do not touch them, unless you are a programer)
#define DEMAND_LOWER		(1 << 0)		// 0x01
#define DEMAND_UPPER		(1 << 1)		// 0x02
#define DEMAND_DIGITS		(1 << 2)		// 0x04
#define DEMAND_SPECL		(1 << 3)		// 0x08
#define USE_LOWER		(1 << 4)		// 0x10
#define USE_UPPER		(1 << 5)		// 0x20
#define USE_DIGITS		(1 << 6)		// 0x40
#define USE_SPECL		(1 << 7)		// 0x80

// Available Char-Sets (you may freely add or remove chars in any of those sets)
#define POOL_LOWER		"abcdefghijklmnopqrstuvwxyz"
#define POOL_UPPER		"ABCDEGFHIJKLMNOPQRSTUVWXYZ"
#define POOL_DIGITS		"0123456789"
#define POOL_SPECL		"/?.,><|!@#$%^&*()_+=-~;:][}{ \\\""

// -------------------------------------------------------------------------------------
// Return true if the cstring keys[ istop ] is not already contained between the
// 0th and the (istop-1)th element of the keys[] array of cstrings,
// otherwise return false
//
_Bool unique_key( const int istop, char keys[ NKEYS ][ KEYSIZE ] )
{
	register int ikey = 0;

	for (; ikey < istop; ikey++)
		if ( strcmp( keys[ikey], keys[istop] ) == 0 )
			return false;

	return true;
}
// -------------------------------------------------------------------------------------
// This function makes sure that requested demands for keys to contain
// at least 1 char from any of the available char-sets are fulfilled
//
void apply_demanded( const unsigned char demand, char *key, const size_t keylen )
{
	int i; char c;
	_Bool iused[ keylen ];					// map for used i (pos)
	memset(iused, false, keylen);				// zero the map

	/*
	 * if demanded, put a LOWER-case letter inside the key
	 */
	if ( demand & DEMAND_LOWER )
	{
		c = POOL_LOWER[ rand() % strlen(POOL_LOWER) ];	// random LOWR letter
		i = rand() % keylen;				// random pos in key
		key[ i ] = c;					// put letter at pos
		iused[ i ] = true;				// mark pos as used
	}

	/*
	* if demanded, put an UPPER-case letter inside the key
	 */
	if ( demand & DEMAND_UPPER )
	{
		c = POOL_UPPER[ rand() % strlen(POOL_UPPER) ];	// random UPPR letter
		do {
			i = rand() % keylen;			// random pos in key
		} while( iused[ i ] == true );			// make sure it's unused

		key[ i ] = c;					// put letter at pos
		iused[ i ] = true;				// mark pos as used
	}

	/*
	 * if demanded, put a DIGIT inside the key
	 */
	if ( demand & DEMAND_DIGITS )
	{
		c = POOL_DIGITS[ rand() % strlen(POOL_DIGITS) ];// random digit
		do {
			i = rand() % keylen;			// random pos in key
		} while( iused[ i ] == true );			// make sure it's unused

		key[ i ] = c;					// put digit at pos
		iused[ i ] = true;				// mark pos as used
	}

	/*
	 * if demanded, put a SPECIAL CHAR inside the key
	 */
	if ( demand & DEMAND_SPECL)
	{
		c = POOL_SPECL[ rand() % strlen(POOL_SPECL) ];	// random special char
		do {
			i = rand() % keylen;			// random pos in key
		} while( iused[ i ] == true );			// make sure it's unused

		key[ i ] = c;					// put digit at pos
		iused[ i ] = true;				// mark pos as used
	}

	return;
}
// -------------------------------------------------------------------------------------
// Calc and return the appropriate size for the pool, based on the values of the USE_
// bits contained in: options
//
size_t pool_calcsize( const unsigned char options )
{
	size_t ret = 0;

	if ( options & USE_LOWER )		// make room for lower-case letters
		ret += strlen( POOL_LOWER );

	if ( options & USE_UPPER )		// make room for upper-case letters
		ret += strlen( POOL_UPPER );

	if ( options & USE_DIGITS )		// make room for digits
		ret += strlen( POOL_DIGITS );

	if ( options & USE_SPECL )		// make room for special chars
		ret += strlen( POOL_SPECL );

	return ++ret;				// make room for '\0' at the end
}
// -------------------------------------------------------------------------------------
// Initialize the chars pool, based on the values of the USE_ bits contained in: options.
// Return the initialized pool (which is as a cstring)
//
char *pool_init( char *pool, const size_t poolsize, const unsigned char options  )
{
	if ( !pool )
		return NULL;

	memset( pool, 0, poolsize );		// clear the pool

	if ( options & USE_LOWER )		// include lower-case letters
		strncat( pool, POOL_LOWER, poolsize-1 );

	if ( options & USE_UPPER )		// include upper-case letters
		strncat( pool, POOL_UPPER, poolsize-1 );

	if ( options & USE_DIGITS )		// include digits
		strncat( pool, POOL_DIGITS, poolsize-1 );

	if ( options & USE_SPECL )		// include special chars
		strncat( pool, POOL_SPECL, poolsize-1 );

	return pool;
}
// -------------------------------------------------------------------------------------
int main( void )
{
	/*
	 * To disable any of the following options, delete or comment-out its
	 * corresponding line below.
	 *
	 * 	USE_ options specify whether you want chars from that particular
	 * 	char-set to participate in the key generation process or not
	 *
	 *	DEMAND_ options specify whether yoy want at least 1 char of that
	 *	particular char-set to be included in every generated key or not
	 *
	 *	Note that you can disable a USE_ option without disabling its
	 *	corresponding DEMAND_ option. In such cases, the disabled char-set
	 *	does not generally participate in the generation process, but 1
	 *	of its chars (picked randomly) will be present inside every key,
	 *	at a random position.
	 *
	 *	To completely prevent a char-set from participating in the
	 *	the generation process, delete both its USE_ and its DEMAND_ option
	 */
	unsigned char options =				// bit-flagged byte
			DEMAND_LOWER			// ... demand lower letter
			+ DEMAND_UPPER			// ... demand upper letter
			+ DEMAND_DIGITS			// ... demand digit
			+ DEMAND_SPECL			// ... demand special char
			+ USE_LOWER			// ... use lower letters
			+ USE_UPPER			// ... use upper letters
			+ USE_DIGITS			// ... use digits
			+ USE_SPECL			// ... use special chars
			;
	const size_t POOLSIZE = pool_calcsize(options);	// calc the size of chars pool
	char pool[ POOLSIZE ];				// the pool itself (cstring)
	char keys[ NKEYS ][ KEYSIZE ] = { {0, 0} };	// array of (cstring) keys
	int i=0, ikey=0;				// i for chars, ikey for keys

	srand( time(NULL) );				// init the pseudo-random seed

	pool_init( pool, POOLSIZE, options );		// initialize the chars pool

	for (ikey=0; ikey < NKEYS; ikey++ )		// generate & store keys
	{ 						// into the keys[NKEYS] array
		do {					// ...
			for (i=0; i < KEYSIZE-1; i++)	// ... generate a key
				keys[ ikey ][ i ] = pool[ rand() % (POOLSIZE-1) ];
		} while ( !unique_key(ikey, keys) );	// ... make sure it is unique

							// apply any char demands
		apply_demanded( options, keys[ikey], KEYSIZE-1 );

		puts( keys[ikey] );             	// print the generated key
	}

	return 0;
}
