#include <stdio.h>			/* printf(), sscanf(), stdin, EOF */
#include <stdlib.h>			/* calloc(), free(), srand(), rand() */
#include <string.h>			/* strchr(), strlen(), strcpy() */
#include <limits.h>			/* INT_MAX */
#include <time.h>			/* time() */

#define MAXINPUT	(1024+1)

#define pressENTER()								\
	do{									\
		char mYcHAr;							\
		printf( "press ENTER..." );					\
		while ( (mYcHAr=getchar()) != '\n' && mYcHAr != EOF )		\
			;							\
	}while(0)

typedef struct Alphabet{
	char 	*str;			/* c-string to hold the alphabet */
	int	len;			/* length of the alphabet */
} Alphabet;

/*********************************************************//**
 * Get a c-string from stdin (return str unchanged on error)
 ************************************************************/
char *stdin_getstring( char *str )
{
	int lenstr = 0;

	if ( !str || !fgets(str, MAXINPUT, stdin) )
		return str;

	lenstr = strlen(str);
	if ( str[ lenstr - 1 ] == '\n' )
		str[ lenstr -1 ] = '\0';

	return str;
}

/*********************************************************//**
 * Get an int from stdin (return INT_MAX on error).
 ************************************************************/
int stdin_getint( int *intvar )
{
	int try = 0;
	char input[ MAXINPUT ] = {'\0'};

	if ( !fgets(input, MAXINPUT, stdin) )
		return INT_MAX;

	if ( EOF == (try=sscanf(input, "%d", intvar)) || try < 1 )
		return INT_MAX;

	return *intvar;
}

/*********************************************************//**
 * Shuffle the contents of a given c-string.
 ************************************************************/
char *str_shuffle( char *str )
{
	char	*cp = NULL, c = '\0';
	int	len = 0;
	int	i = 0;

	/* sanity check */
	if ( !str || !*str || (len = strlen(str)) < 2 )
		return str;

	srand( time(NULL) );

	for (cp=str; *cp; cp++)
	{
		i = rand() % len;
		c = *cp;
		*cp = str[i];
		str[i] = c;
	}

	return str;
}

/*********************************************************//**
 * Initialize the alphabet either to str,
 * or if str is NULL then from cstart to cstart+ablen-1
 ************************************************************/
int ab_init( Alphabet *ab, const char *str, const char cstart, const int ablen )
{
	int i = 0, len = 0;

	/* sanity check */
	if ( !ab )
		return 0;		/* FALSE */

	ab->len = 0;

	/* when str exists and is not empty */
	if ( str && *str )
	{
		len = strlen( str );
		ab->str = calloc(len+1, sizeof(char) );
		if ( !ab->str )
			return 0;	/* FALSE */
		strcpy(ab->str, str);
		ab->len = len;
	}

	/* when str is NULL or empty, fill alphabet from cstart to (cstart+ablen-1) */
	else if (cstart > '\0' && ablen > 0 && cstart + ablen - 1 < 256)
	{
		ab->str = calloc(ablen+1, sizeof(char) );
		if ( !ab->str )
			return 0;	/* FALSE */

		for (i=0; i < ablen; i++)
			ab->str[i] = cstart + i;
		ab->len = ablen;
	}

	/* error */
	else
		return 0;		/* FALSE */

	return 1;			/* TRUE */
}

/*********************************************************//**
 * Cleanup the alphabet structure
 ************************************************************/
void ab_cleanup( Alphabet *ab )
{
	if ( !ab )
		return;

	if ( ab->str )
		free( ab->str );
	ab->len = 0;
	ab->str = NULL;

	return;
}

/*********************************************************//**
 * Get the alphabet index of a given char ( -1 if char is not in alphabet )
 ************************************************************/
int abindex( const char c, const Alphabet *ab)
{
	char *cp = NULL;

	if ( !ab || !ab->str || !*(ab->str) )
		return -1;

	return (cp = strchr(ab->str,c)) ? (int)(cp - ab->str) : -1;
}

/*********************************************************//**
 * Encrypt a given char with a given Caesar key.
 * 	http://e...content-available-to-author-only...a.org/wiki/Caesar_cipher#Example
 ************************************************************/
char c_encrypt( const char c, const int keyval, const Alphabet *ab )
{
	int idx = -1;

	/* sanity checks */
	if (!ab || !ab->str || !*(ab->str) || -1 == (idx=abindex(c,ab)) || keyval < 0)
		return '\0';

	return ab->str[ (idx + keyval) % ab->len ];
}

/*********************************************************//**
 * Decrypt a given char that was encrypted with a given Caesar key.
 * 	http://e...content-available-to-author-only...a.org/wiki/Caesar_cipher#Example (
 ************************************************************/
char c_decrypt( const char c, const int keyval, const Alphabet *ab )
{
	int idx = -1;

	/* sanity checks */
	if (!ab || !ab->str || !*(ab->str) || -1 == (idx=abindex(c,ab)) || keyval < 0)
		return '\0';

	idx = (idx - keyval) % ab->len;
	if ( idx < 0 )
		idx += ab->len;

	return ab->str[ idx ];
}

/*********************************************************//**
 * Encrypt a given c-string with a given key, using the Caesar algorithm
 ************************************************************/
char *s_encrypt( char *s, const int keyval, const Alphabet *ab )
{
	int  i = 0;

	/* sanity checks */
	if ( !ab || !ab->str || !*(ab->str) || !s || !*s || keyval < 0 )
		return NULL;

	for (i=0; s[i]; i++)
		if ( '\0' == (s[i] = c_encrypt(s[i], keyval, ab)) )
			return NULL;
	return s;
	
}

/*********************************************************//**
 * Decrypt a given c-string that was encrypted with a given Caesar key.
 ************************************************************/
char *s_decrypt( char *s, const int keyval, const Alphabet *ab )
{
	int  i = 0;

	/* sanity checks */
	if ( !ab || !ab->str || !*(ab->str) || !s || !*s || keyval < 0 )
		return NULL;

	for (i=0; s[i]; i++)
		if ( '\0' == (s[i] = c_decrypt(s[i], keyval, ab)) )
			return NULL;
	return s;
	
}

/*********************************************************//**
 *
 ************************************************************/
int main( void )
{
	Alphabet ab = {NULL, 0 };			/* our alphabet       */
	char text[ MAXINPUT ] = {'\0'};			/* text input         */
	int  key = 0;					/* shifting value     */

	/* initialize and shuffle the alphabet */
	if ( !ab_init( &ab, NULL, ' ', 223 ) ) {
		puts("\nalphabet initialization failed!");
		goto exit_prog;
	}
	str_shuffle( ab.str );
	printf("Alphabet (%u chars):\n%s\n\n", ab.len, ab.str );

	/* get the text to be encrypted */
	printf("Text to be ciphered: ");
	if ( *stdin_getstring(text) == '\0' ) {
		puts("\nempty text was given, nothing to cipher");
		goto exit_prog;
	}

	/* get a valid, positive Caesar key */
	do
		printf("Key (0 or positive): ");
	while ( INT_MAX == stdin_getint( &key ) || key < 0 );

	/* encrypt given text with the given Caesar key */
	if ( NULL == s_encrypt(text, key, &ab) ) {
		puts("\tencryption error (perhaps an invalid char was found in text)");
		goto exit_prog;
	}
	printf("\nEncrypted text: %s\n", text );

	/* decrypt given text using given Caesar key */
	if ( NULL == s_decrypt(text, key, &ab) ) {
		puts("\tdecryption error (perhaps an invalid char was found in text)");
		goto exit_prog;
	}
	printf("Decrypted text: %s\n\n", text );


exit_prog:
	pressENTER();
	ab_cleanup( &ab );
	exit( EXIT_SUCCESS );
}
