#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>

char plus(char a, char b);
char minus(char a, char b);
char inv(char a);

int filter_char(char *c);

void help_and_exit();

int main ( int argc, char **argv){

	FILE* in = NULL;
	FILE* out = NULL;
	char* key = NULL;
	static int flag_dec = 0;
	static int flag_pretty = 0;	
	static int flag_show_freqs = 0;	
	
	// LETTURA ARGOMENTI
	while (1){
		static struct option long_options[] = {               
			{"pretty-print",no_argument, 		&flag_pretty, 1},
			{"filter-print",no_argument, 		&flag_pretty, 0},   
			{"decipher",	no_argument, 		&flag_dec, 1},
			{"cipher",		no_argument, 		&flag_dec, 0},  
			{"freqs",		no_argument, 		0, 'f'},
			{"help",		no_argument,		0, 'h'},
			{"in",  		required_argument,	0, 'i'},
			{"out",  		required_argument, 	0, 'o'},
			{0, 0, 0, 0}
		};
		int option_index = 0;
		int c = getopt_long_only (argc, argv, "hpdfi:o:",long_options, &option_index);
		if (c == -1)
			break;
		switch (c) {
			case 0:
               break;
			case 'h':
				help_and_exit();
				break;
			case 'd':
				flag_dec = 1;
				break;
			case 'p':
				flag_pretty = 1;
				break;
			case 'f':
				flag_show_freqs = 1;
				break;
			case 'i':
				if(in != NULL) fclose(in);
				in = fopen(optarg, "r");
				break;
			case 'o':
				if(out != NULL) fclose(out);
				out = fopen(optarg, "w");
				break;
			case '?':
				break;
			default:
				help_and_exit();
		}
	}
	if (optind < argc){
		key = argv[optind++];
	}else{
		help_and_exit();
	}
	if(in == NULL) in = stdin;
	if(out == NULL) out = stdout;	
	
	int i;
	/* Un contatore per la frequenza di ogni carattere nel testo in chiaro e in
	 * quello cifrato. 
	 * Visualizza l'informazione se è fornito il parametro --freqs (-f) */
	int plain_freqs[26], enc_freqs[26];
	for(i = 0;i<26;i++)
		plain_freqs[i] = enc_freqs[i] = 0;
	
	int key_len = strlen(key);
	
	/* se flag_dec = 1 il programma lavora in decifrazione.
	 * grazie alla simmetria del cifrario decifrare rispetto a una chiave k
	 * equivale a cifrare con un'opportuna chiave k'.
	 * k' è ottenibile da k (e viceversa) calcolando, per ogni suo carattere,
	 * l'inverso.
	 * a <-> z, b <-> y, c <-> x ...
	 * Visti come numeri modulo 26, a = 0 <-> 25 = z
	 * Quindi, dato x, numero modulo 26, il suo inverso sarà -x modulo 26.
	 * A causa del comportamento di % in C rispetto ai numeri negativi, invece
	 * di considerare -x % 26 si utilizzerà (26-x) % 26.
	 * Il tutto deve esser spostato dall'intervallo 0-25 a 97-122. */
	if(flag_dec)
		for(i = key_len;i > 0;)
			key[--i] = inv(key[i]);
	
	/* Processa un carattere alla volta dell'input.
	 * la procedura filter_char restituisce 1 se il carattere deve essere 
	 * cifrato e 0 se deve essere ignorato.
	 * Se questo è maiuscolo lo rimpiazza con l'equivalente minuscolo. 
	 */	
	i=0;	
	char c;
	while((c = fgetc(in)) != EOF){
		if(filter_char(&c)){
			plain_freqs[c-97]++; //incrementa la frequenza di c nel testo in chiaro
			/* L'indice i seleziona il carattere della chiave key, e 
			 * conseguentemente il cifrario mono-alfabetico, da utilizzare
			 * per cifrare c.
			 * Se k = key[i] allora c è sostituito con c + k (in modulo)
			 * Visti come numeri modulo 26 :
			 * 'c' = 3, k = 'k' = 10 --> 'c' + 'k' = c + k mod 26 = 13 = 'n'
			 * Lavorando in ASCII, il tutto è spostato nell'intervallo 97-122 */
			c = plus(c,key[i]);
			i = ++i % key_len;
			fputc(c,out);
			enc_freqs[c-97]++; //incrementa la frequenza di c nel testo in cifra
		}else if(flag_pretty){
			// Stampa i caratteri non alfabetici (altrimenti ignorati)
			fputc(c,out);
			if(c == '\n') fflush(out);
		}	
	}
	if(flag_show_freqs){
		// Visualizza le frequenze dei caratteri nel testo in chiaro e in cifra
		puts("    ┌────────┬────────┐\n    │   in   │  out   │\n┌───┼────────┼────────┤");
		for(i = 0;i<26;i++){
			printf("│ %c │ %6d │ %6d │\n",i+97,plain_freqs[i],enc_freqs[i]);
		}
		puts("└───┴────────┴────────┘");
	}
	fclose(in);
	fclose(out);
	exit(0);
}

/* Somma, sottrazione e inverso di caratteri a-z
 * Visti come elementi 0-25 implementa operazioni modulo 26
 * 'c' + 'x' = 3 + 23 = 26 mod 26 = 0  = 'a' = 97
 * 'c' - 'x' = 3 - 23 = 20 mod 26 = 20 = 'u' = 117
 * -'c' = -3 = 0-3 mod 26 = 23 = 'x' =  120
 * Il tutto è allineato all'intervallo 97-122 del codice ASCII*/
char plus(char a, char b){
	return (a - 97 + b - 97) % 26 + 97;
}
char minus(char a, char b){
	return (a - b + 26) % 26 + 97;
}
char inv(char a){
	return minus('a',a);
}

int filter_char(char *c){
	if(*c >= 97 && *c <= 122)
		// c è nell'intervallo a-z
		return 1;
	else if(*c >= 65 && *c <= 90){
		// c è nell'intervallo A-Z, va convertito in minuscolo
		*c = *c + 32;
		return 1;
	}else
		// gli altri caratteri vengono ignorati
		return 0;
}

void help_and_exit(){
	printf("Utilizzo: vigenere [options] key\n");
	printf("  - la chiave deve contenere solamente caratteri minuscoli\n");
	printf("  - simboli non alfabetici vengono filtrati dal testo in ingresso, \n");
	printf("    mentre quelli maiuscoli sono convertiti in minuscolo.\n");
	printf("Opzioni:\n");
	printf("  --decipher\t (-d) Opera in decifrazione\n");
	printf("  --in <file>\t (-i) File di input, se omesso utilizza stdin\n");
	printf("  --out <file>\t (-o) File di output, se omesso utilizza stdout\n");
	printf("  --help\t (-h) Visualizza questo messaggio\n");
	printf("  --freqs\t (-f) Visualizza le frequenze dei caratteri nel testo\n");
	printf("  --pretty-print (-p) Stampa spaziatura e punteggiatura\n");
	printf("  --filter-print Stampa solamente i caratteri dell'alfabeto\n");
	exit(0);
}
