#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <time.h>

#ifndef TRUE
	#define TRUE 1
#endif

#ifndef FALSE
	#define FALSE 0
#endif


char *progname ;
int debug ;

/* structure holding general options for the rcfile program */
typedef struct {
	FILE *in ;
	FILE *tmp_f ;
	FILE *out ; } opt_s ;

/* structure holding options for one of the program entries */
typedef struct {
	char *designator ;
	char *description ;
	char *defvalue ;
	int required ;
	int type ; } entry_option_s ;

/* structure holding entry */
typedef struct {
	char *name ; /* program name */
	char *branch ;
	char *short_description ; /* short program description */
	char *long_description ; /* long description of the program */
	int option_number ; /* how many options */
	entry_option_s *options ; } entry_s ;

/* function prototypes */

/* parses rc file and returns an array with entries */
entry_s * parse_rc(FILE *in) ;
/* print an entry on stdout */
int entry_print(entry_s *e) ;

int main(int argc, char *argv[]) {
	opt_s opt ;
	entry_s *entries ;

	opt.out = stdout ;
	opt.tmp_f = tmpfile() ;

	if(argc > 1) {
		opt.in = fopen(argv[1],"r") ;
		if(! opt.in)
			fprintf(stderr,"Could not open file %s for reading\n", argv[1]) ;
	} else {
		opt.in = stdin ;
	}

	debug=1 ;

	while( (entries = parse_rc(opt.in)) != NULL)  
		entry_print(entries) ;
	
	fclose(opt.in) ;
	fclose(opt.out) ;
	fclose(opt.tmp_f) ;
	return EXIT_SUCCESS ;
}


/* ANSI C doesn't have a strdup */
char *my_strdup(char *string) {
	int i ;
	char * res ;

	i = strlen(string) ;
	res = malloc(i + 1) ;
	strcpy(res, string) ;
	return res ;
}


/* prints an entry */

int entry_print(entry_s *e) {
	int i ;
	entry_option_s *o ;

	printf("%s - %s\n\n", e->name, e->short_description) ;
	printf("Usage:\n\t%s ", e->name) ;
	for(i = 0 ; i < e->option_number ; i++) {
		o = &e->options[i] ;
		if(o->type == 4) continue ;
		if(o->required) printf("<") ;
		else printf("[") ;
		if(o->designator) {
			printf("-%s", o->designator) ;
			if(o->type) printf(" ") ;
		}
		switch(o->type){
			case 0:
				break ;
			case 1:
				printf("int_value") ;
				break ;
			case 2:
				printf("float_value") ;
				break ;
			case 3:
				printf("file") ;
				break ;
			default:
				break ;
		}
		if(o->required) printf("> ") ;
		else printf("] ") ;
	}
	printf("\n") ;


	for(i = 0 ; i < e->option_number ; i++) {
		if(!i) printf("Options:\n") ;
		o = &e->options[i] ;
		if(o->type == 4) continue ;
		printf("\t") ;
		if(o->designator) printf("-%s ", o->designator) ;
		switch(o->type){
			case 0:
				break ;
			case 1:
				printf("int_value ") ;
				break ;
			case 2:
				printf("float_value ") ;
				break ;
			case 3:
				printf("file ") ;
				break ;
			default:
				break ;
		}

		if(o->description) printf(": %s ", o->description) ;
		if(o->defvalue) printf(" (default: %s) ", o->defvalue) ;
		printf("\n") ;
	}
	printf("\n") ;

	if(e->long_description) printf("Description:\n\n%s\n", e->long_description) ;

	return EXIT_SUCCESS ;
}

/* parses the line starting the program section, which contains program
 * name and the short description */
int parse_program(char *line, entry_s *entries) {
	int current ;
	char *name, *p, *q, *r ;

	p = strchr(line, '(') ;

	/* reading program name; if no name is given, do not do anything */
	if(!p) return EXIT_FAILURE ;

	/* initializing the new entry_s structure */
	entries->short_description = NULL ;
	entries->branch = NULL ;
	entries->long_description = NULL ;
	entries->options = NULL ;
	entries->option_number = 0 ;

	/* start the actual parsing */
	p++ ;
	q = strchr(line, ')') ;
	if(q) { *q = '\0' ; q++ ; }
	entries->name = my_strdup(p) ;

	/* reading branch information */
	p = strchr(q, '(') ;
	if(!p) return EXIT_SUCCESS ;
	p++ ;
	q = strchr(p, ')') ;
	if(q) { *q = '\0' ; q++ ; }  
	entries->branch = my_strdup(p) ;

	/* reading short description */
	p = strchr(q, '(') ;
	if(!p) {
		entries->short_description = my_strdup("no description") ;
		return EXIT_SUCCESS ;
	}

	p++ ;
	q = strchr(p, ')') ;
	if(q) *q = '\0' ; 
	entries->short_description = my_strdup(p) ;

	return EXIT_SUCCESS ;
}


/* adds a line to the long description of the program, allocating memory if
 * needed */
int parse_description(char *line, entry_s *e) {
	int i ;

	if(!e->long_description) 
		e->long_description = malloc(strlen(line) + 1) ;
	else {
		i = strlen(line) + strlen(e->long_description) + 1 ;
		e->long_description = realloc(e->long_description, i) ;
	}

	if(!e->long_description) return EXIT_FAILURE ;
	strcat(e->long_description, line) ;

	return EXIT_SUCCESS ;
}


/* parses an option line and stores it in the apropriate structure */
/* see the sample rcfile for more information */
int parse_option(char *line, entry_s *e) {
	entry_option_s * o ;
	char *p, *q ;

	e->option_number++ ;

	p = strchr(line, '(') ;
	if(!p) return EXIT_FAILURE ;

	/* reserving necessary memory; options are stored in an array */
	if(!e->options) e->options = malloc(sizeof(*(e->options))) ;
	else e->options = realloc(e->options, 
		e->option_number * sizeof(*(e->options))) ;

	/* current option */
	o = &e->options[e->option_number - 1] ;

	/* default settings. From now on, we can exit anytime */
	o->designator = NULL ;
	o->description = NULL ;
	o->defvalue = NULL ;
	o->required = FALSE ;
	o->type = 0 ;

	/* reading designator, that is e.g. 'h' for option '-h' */
	q = strchr(p, ')') ;
	p++ ;

	if(p != q) {
		if(q) { *q = '\0' ; q++ ; } 
		o->designator = my_strdup(p) ;
	}

	/* reading next token: option type, e.g. "float" for a floating number */
	p = strchr(q, '(') ;
	if(!p) return EXIT_SUCCESS ;
	p++ ;
	q = strchr(p, ')') ;
	if(p == q) o->type = 0 ;
	else {
		if(q) { *q = '\0' ; q++ ; }
		if(strcmp(p, "int") == 0) o->type = 1 ;
		if(strcmp(p, "float") == 0) o->type = 2 ;
		if(strcmp(p, "file") == 0) o->type = 3 ;
		if(strcmp(p, "invisible") == 0) o->type = 4 ;
	}

	/* reading default value */
	p = strchr(q, '(') ;
	if(!p) return EXIT_SUCCESS ;
	q = strchr(p, ')') ;
	p++ ;

	if(p != q) {
		if(q) { *q = '\0' ; q++ ; } 
		o->defvalue = my_strdup(p) ;
	}

	/* reading description of the option */
	p = strchr(q, '(') ;
	if(!p) return EXIT_SUCCESS ;
	q = strchr(p, ')') ;
	p++ ;

	if(p != q) {
		if(q) { *q = '\0' ; q++ ; } 
		o->description = my_strdup(p) ;
	}

	/* additional keywords? */
	p = strchr(q, '(') ;
	if(!p) return EXIT_SUCCESS ;
	q = strchr(p, ')') ;
	p++ ;
	if(p != q) {
		if(q) { *q = '\0' ; q++ ; } 
		if(strcmp(p, "required") == 0)
			o->required = TRUE ;
	}

	return EXIT_SUCCESS ;
}


entry_s * parse_rc(FILE *in) {
	
	char bufor[BUFSIZ], **branches, *p, *q ;
	int i = 0, 
		/* the following three variables record the current state of the
		 * reading - that is, in which section of the rcfile we are now */
		reading_options = FALSE, 
		reading_description = FALSE,

		section = 0,
		numofopts = 0,

		/* keeping track of start(program) and end(program) */
		program_found = FALSE,
		program_end_found = FALSE ;
	entry_s *result ;


	result = NULL ;

	/* skipping to the first start(program) entry */
	while( (fgets(bufor, BUFSIZ, in)) != NULL && !program_found) {
		i++ ;

		if( (p = strstr(bufor, "start(program)")) == bufor) {
			result = malloc(sizeof(*result)) ;

			p += strlen("start(program)") ;
			if(parse_program(p, result) == EXIT_FAILURE) {
				fprintf(stderr, "wrong entry in line %i\n", i) ;
				free(result) ;
				return NULL ;
			}
			program_found = TRUE ;
		}
	}

	if(!program_found) return NULL ;

	while( (fgets(bufor, BUFSIZ, in)) != NULL && !program_end_found) {
		i++ ;

		/* skipping comments and empty lines */
		if(*bufor == '\n' || *bufor == '#') continue ;

		section = 0 ;
		if(strstr(bufor, "start") == bufor) section = 1 ;
		if(strstr(bufor, "end") == bufor) section = 2 ;
		
		/* read one of the keywords -- 'start' or 'end' */
		if(section) {
			p = strchr(bufor, '(') ;
			q = strchr(p, ')') ;
			if(!p || !q) {
				fprintf(stderr, "error in configuration file line %i\n", i) ;
				continue ;
			}
			p++ ;
			*q ='\0' ;
			q++ ;
		} 

		/* a 'normal line' */
		if(section == 0) {

			if(reading_options) 
				if(parse_option(bufor, result)) 
					fprintf(stderr, "error parsing option in line %i\n", i) ;

			if(reading_description) 
				if(parse_description(bufor, result)) 
					fprintf(stderr, "error parsing a description line %i\n", i) ;
		}

		/* read a 'start(something)' section */
		if(section == 1) {

			/* section parameter is 'options' */
			if(strcmp(p, "options") == 0 && !reading_description) {
				if(reading_options) {
					fprintf(stderr, "repetition of start(options) on line %i\n", i) ;
				}
				reading_options = TRUE ;
			}

			/* section parameter is 'description' */
			if(strcmp(p, "description") == 0 && !reading_description) {
				if(reading_options) {
					fprintf(stderr, "warn: start(options) unfinished on line %i\n", i) ;
					reading_options = FALSE ;
				}
				reading_description = TRUE ;
			}
					
		} /* end of the 'if(section == 1)' statement '/

		/* bufor starts with an end() statement */
		if(section == 2) {

			/* end of the description */
			if(strcmp(p, "description") == 0) {
				if(!reading_description) {
					fprintf(stderr, 
						"warn: orphan end(description) section at line %i\n", i) ;
				}
				reading_description = FALSE ;
			}

			/* end of a programs section */
			if(strcmp(p, "program") == 0 && !reading_description) {
				if(reading_options) {
					fprintf(stderr, 
						"warn: orphan start(options) section in line %i\n", i) ;
				}
				reading_description = FALSE ;
				reading_options = FALSE ;
				program_end_found = TRUE ;
			}

			/* end of an options section */
			if(strcmp(p, "options") == 0 && !reading_description) {
				if(!reading_options)
					fprintf(stderr, 
						"warn: orphan end(options) section in line %i\n", i) ;
				reading_options = FALSE ;
			}
			
		} /* end of the if(section == 2) statement */


	} /* end of the main 'while' loop */

	if(!program_end_found) 
		fprintf(stderr, "Warning: no end(program) statement found\n") ;

	return result ;
}
