/***************************************************************************


DO NOT USE THIS SAM, INSTEAD USE sini.c
DO NOT USE THIS SAM, INSTEAD USE sini.c
DO NOT USE THIS SAM, INSTEAD USE sini.c
DO NOT USE THIS SAM, INSTEAD USE sini.c
DO NOT USE THIS SAM, INSTEAD USE sini.c


    SIMPLE INI FILE SAM MODULE  (c) Jesse Liley 2000 @ Netwin Ltd

	examine ini.h file for function parameter comments

	simple useage:

		#include "ini.h"

		enum{INI_SETTINGS, INI_IDENT1 .....}
		char *ini_identifiers = {"settings", "ident1", ....}
	
		Ini_File *My_Ini_File = NULL;

		load_ini_file(&My_Ini_File, "filename", ini_identifiers));

		printf("An Ini Setting is: {%s}\n", get_ini(My_Ini_File, INI_SETTING);

		ini_close(&My_Ini_File); 





	A more indepth example:


	Ini_File *My_Ini_File = NULL;
	if (!load_ini_file(&My_Ini_File, "filename", ini_identifiers)){  
		if (My_Ini_File == NULL){ / * handle no memory error * /}
		if (My_Ini_File->error_message != NULL){
			 printf("There was a problem loading the ini file.\n"
				    "The error was (%s)\n", My_Ini_File->error_message);
			ini_close(&My_Ini_File); / * just frees the pointers * /
		}
	}
	/ * main part of program * /
	printf("Setting is: {%s}\n", get_ini(My_Ini_File, INI_SETTING);
	printf("INI_SETTING is {%s}\n", My_Ini_File->output);
	printf("INI_SETTING string length is %d\n", My_Ini_File->output_strlen);
	printf("number of ini identifiers missing %d\n", My_Ini_File->number_missing);
	ini_close(&My_Ini_File); / * just frees the pointers * /




	WARNING - code is obfiscated, email jesse@netiwn.co.nz if you wish
              to understand it.



***************************************************************************/


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

/* ************ from ini.h ************ */
typedef struct INI_FILE_JESSES_IMPLEMENTATION{
	int number_missing;      /* is set on load, is number of missing identifiers from INI file */
	char *error_message;     /* is set on load, error message, points to a malloc structure */
	char *filename;          /* filename of the loaded file, points to a malloc structure*/
	char *output;            /* output buffer, points to a malloc structure */
	int output_strlen;       /* the length of the string in the output buffer */
	int output_len;          /* the length of the output buffer */
	int output_max_len;      /* maximum possible size of output_strlen */
	void *version_specific_data_storage;     /* is set on load, where the data is stored, points to a malloc structure */ 
	int version_specific_data_storage_size;  /* is set on load, size in bytes */ 
}Ini_File;
/* ************ from ini.h ************ */


#define MYBFSZ 10000

#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif


typedef struct INI_INFO{
	int id_len;
	char *str;
}Ini_Info; /* Temporary data structure, DO NOT CONFUSE WITH INI_FILE */

typedef struct VSDS{ /* Header for data of a version_specific_data_storage */
	int number_missing;
	int number_of_records;
	int output_max_len;
	int output_len;
	char **string_array;
	char *last_buffer_pointer;
}Vsds;


void process_ini_file_line(char *line, char **Ini_Identifiers, Ini_Info *ini_info){
	/* This takes a line from the ini file and grabs the result from its identifier (if any) */
	char *start, *beginning;
	int i;
	if (line == NULL) return;
	for(start = line; *start==' ' || *start=='\t';) start++;
	for(i = 0; Ini_Identifiers[i] != NULL; i++){
		if (ini_info[i].str == NULL){
			if (strnicmp(start, Ini_Identifiers[i], ini_info[i].id_len) == 0){
				for(beginning = start + ini_info[i].id_len; *beginning==' ' || *beginning=='\t';) beginning++;
				if (beginning != start + ini_info[i].id_len){
					ini_info[i].str = strdup(beginning);
					return;
}	}	}	}	}


void my_nstrcpy(char *dest, int destlen, char *src){
	/* This is like strcpy with buffer length. */
	char *s, *d;
	int n = 1; /*for last char being null */
	s = src; 
	d = dest;
	for (;*s != 0 && n < destlen;) {*d++ = *s++; n++;}
	if (n < destlen) *d = 0;
}

void removeCR(char *str){
	/* This removes CR from a string */
	char *s;
	s = str;
	for (;*s != 0;s++) if (*s == '\r' || *s == '\n') {*s = 0; return;}
}

char *ini_file_set_error(Ini_File *ini_file, char *str){
	if (ini_file == NULL) return NULL;
	if (ini_file->error_message != NULL)
		free(ini_file->error_message);
	ini_file->error_message = strdup(str);
	return NULL;
}
 
int load_in_ini_file_information(char *filename, Ini_File *ini_file, char **Ini_Identifiers, Ini_Info *ini_info){
	FILE *file;
	char Buffer[MYBFSZ];
	file = fopen(filename, "r");
	if (file == NULL){
		ini_file_set_error(ini_file, strerror(errno));
		return FALSE;
	}
	for(;!feof(file);){
		fgets(Buffer, MYBFSZ -2, file);
		Buffer[MYBFSZ -2] = 0; /* Just in case */
		removeCR(Buffer);
		process_ini_file_line(Buffer, Ini_Identifiers, ini_info);
	}
	fclose(file);
	return TRUE;
}

void initialise_ini_file(Ini_File *ini_file){
	ini_file->number_missing = 0;
	ini_file->error_message = NULL;
	ini_file->filename = NULL;
	ini_file->output = NULL;
	ini_file->output_strlen = 0;
	ini_file->output_len = 0;
	ini_file->output_max_len = 0;
	ini_file->version_specific_data_storage = NULL;
	ini_file->version_specific_data_storage_size = 0;
}

Ini_Info *setup_ini_info_structure(int number_of_identifiers, char **Ini_Identifiers, Ini_File *ini_file){
	int i;	
	Ini_Info *ini_info;

	ini_info = (Ini_Info *) malloc(number_of_identifiers * sizeof(Ini_Info));
	if (ini_info == NULL){ 
		ini_file_set_error(ini_file, "INI ERROR: Unable to allocate memory(1)");
		return NULL;
	}

	for (i=0; Ini_Identifiers[i] != NULL; i++){
		ini_info[i].id_len = strlen(Ini_Identifiers[i]);
		ini_info[i].str = NULL;
	}
	return ini_info;
}

void calculate_stats(int *final_malloc_size, int *max_str_len, int *missing, int number_of_identifiers, char **Ini_Identifiers, Ini_Info *ini_info){
	int len, i;
	(*final_malloc_size) = sizeof(Vsds);
	(*max_str_len) = 0;
	(*final_malloc_size) += (number_of_identifiers+1) * sizeof(char *);
	(*missing) = 0;
	for (i=0; Ini_Identifiers[i] != NULL; i++){
		if (ini_info[i].str != NULL){
			len = strlen(ini_info[i].str);
			(*final_malloc_size) += len + 2;
			if ((*max_str_len) < len) (*max_str_len) = len;
		}else{
			(*missing)++;
		}
	}
	(*final_malloc_size) += 3; /* for a default return string value */
}

Vsds *get_final_storage_structure(int final_malloc_size, Ini_File *ini_file, Ini_Info *ini_info, int number_of_identifiers, int max_str_len, int missing, char **Ini_Identifiers){
	Vsds *final_vsds;
	char **final_strptrs;
	char *final_strings;
	char *end_ptr;
	char *next_string;
	int i;

	final_vsds = (Vsds *)malloc(final_malloc_size);
	if (final_vsds == NULL) {
		ini_file_set_error(ini_file, "INI ERROR: Could not allocate memory for data structure.");
		return NULL;
	}
	final_strptrs = (char **)(((char *)(final_vsds)) + sizeof(Vsds)); /* We MUST cast (char *)(final_vsds) otherwise "+ sizeof(Vsds)" is seen as "+ sizeof(Vsds) * 4" */
	final_strings = (char *)(final_strptrs) + ((number_of_identifiers+1) * sizeof(char *));

	/* Copy info into Vsds structure */
	final_vsds->last_buffer_pointer = NULL;
	final_vsds->number_of_records = number_of_identifiers;
	final_vsds->output_len = 0;
	final_vsds->output_max_len = max_str_len+2;
	final_vsds->string_array = final_strptrs;
	final_vsds->number_missing = missing;

	end_ptr = (char *)(ini_file->version_specific_data_storage) + final_malloc_size;

	/* copy the strings into the allocated space, and set string pointers in allocated space */
	next_string = final_strings;
	*final_strings = 0; /* this is the default empty string if there was no identifier in ini file */
	next_string++;
	for (i=0; Ini_Identifiers[i] != NULL; i++){
		if (ini_info[i].str != NULL){
			final_strptrs[i] = next_string;
			my_nstrcpy(next_string, (int)(end_ptr - next_string),  ini_info[i].str);
			free(ini_info[i].str);
			next_string += strlen(next_string) + 2;
		}else{
			final_strptrs[i] = final_strings; /* if a identifier is not set in ini file, we set its value to empty string */
		}
	}
	final_strptrs[number_of_identifiers] = NULL;
	return final_vsds;
}



int load_ini_file(Ini_File **My_Ini_File, char *filename, char **Ini_Identifiers){
/* load_ini_file returns TRUE or FALSE if successful or not. 
   failure message (if any) is in error_message.
   Ini_Identifiers MUST BE A NULL terminated char * list. 
*/
	int i, final_malloc_size, max_str_len;
	int number_of_identifiers;
	Ini_Info *ini_info;
	Ini_File *ini_file;
	int missing;

	if (*My_Ini_File != NULL) return FALSE;
	(*My_Ini_File) = (Ini_File *)malloc(sizeof(Ini_File));
	if (*My_Ini_File == NULL)	return FALSE;

	ini_file = *My_Ini_File;

	initialise_ini_file(ini_file); /* sets parameters to null or 0 */

	number_of_identifiers = 0;    /* calculate the number of identifiers */
	for (i=0; Ini_Identifiers[i] != NULL;i++) {number_of_identifiers++;}

	/* setup temporary data structure ini_info */
	ini_info = setup_ini_info_structure(number_of_identifiers, Ini_Identifiers, ini_file);
	if (ini_info == NULL) {
		return FALSE; /* error, no memory */
	}

	load_in_ini_file_information(filename, ini_file, Ini_Identifiers, ini_info);

	calculate_stats(&final_malloc_size, &max_str_len, &missing, number_of_identifiers, Ini_Identifiers, ini_info);

	ini_file->version_specific_data_storage = (void *)get_final_storage_structure(final_malloc_size, ini_file, ini_info, number_of_identifiers, max_str_len, missing, Ini_Identifiers);
	if (ini_file->version_specific_data_storage == NULL){
		free(ini_info);
		return FALSE;
	}
	ini_file->version_specific_data_storage_size = final_malloc_size;
	free(ini_info);
	return TRUE;
}

char *get_ini(Ini_File *ini_file, int enumd_identifier){
/* return value == My_Ini_File->output.
   if you free the returned value, set {My_Ini_File->output = NULL;}
*/
	int num, len;
	char **strings;
	Vsds *myvsds;

	/* some parameter error checking */
	if (ini_file == NULL) return NULL;
	if (enumd_identifier < 0) return ini_file_set_error(ini_file,"INI ERROR: get_ini(): parameter error: enumd_identifier < 0: bad human!!");
	if (ini_file->version_specific_data_storage == NULL) return ini_file_set_error(ini_file,"INI ERROR: get_ini(): parameter error: ini_file->version_specific_data_storage == NULL: bad human, what have you done?!?!");
	myvsds = (Vsds *)(ini_file->version_specific_data_storage);
	num = (myvsds)->number_of_records;
	if (enumd_identifier >= num) return ini_file_set_error(ini_file,"INI ERROR: get_ini(): parameter error: enumd_identifier >= number_of_identifiers: bad human!!");
	strings = ((Vsds *)(ini_file->version_specific_data_storage))->string_array;
	if (strings[enumd_identifier]== NULL) return ini_file_set_error(ini_file,"INI ERROR: get_ini(): Hmmmm, there seems to be a problem with my data structure :-(, encountered a NULL where there shouldn't be one");;
	len = strlen(strings[enumd_identifier]);

	/* set the information about the returned string and some other ini file info too */
	ini_file->output_max_len = ((Vsds *)(ini_file->version_specific_data_storage))->output_max_len;
	ini_file->output_len = ((Vsds *)(ini_file->version_specific_data_storage))->output_len;
	ini_file->number_missing = ((Vsds *)(ini_file->version_specific_data_storage))->number_missing;
	
	/* set the information about the returned string and some other ini file info too */
	/* and make sure that all parameters are set correctly!!!!! */
	if (ini_file->output == NULL){
		ini_file->output = strdup(strings[enumd_identifier]);
	}else{
		if (ini_file->output == ((Vsds *)(ini_file->version_specific_data_storage))->last_buffer_pointer){
			if (len+2 < ini_file->output_strlen){
				strcpy(ini_file->output, strings[enumd_identifier]);
				ini_file->output_strlen = len;
				ini_file->output_len = len;
				return ini_file->output;
			}
		}
		free(ini_file->output);
		ini_file->output = strdup(strings[enumd_identifier]);
		((Vsds *)(ini_file->version_specific_data_storage))->output_max_len = len;
		((Vsds *)(ini_file->version_specific_data_storage))->output_len = len;
	}
	ini_file->output_strlen = len;
	ini_file->output_len = len;
	return ini_file->output;
}

void ini_close(Ini_File **My_Ini_File){
	if (My_Ini_File == NULL) return;
	if (*My_Ini_File == NULL) return;
	if ((*My_Ini_File)->error_message                 != NULL) free((*My_Ini_File)->error_message);
	if ((*My_Ini_File)->filename                      != NULL) free((*My_Ini_File)->filename);
	if ((*My_Ini_File)->output                        != NULL) free((*My_Ini_File)->output);
	if ((*My_Ini_File)->version_specific_data_storage != NULL) free((*My_Ini_File)->version_specific_data_storage);
	if ((*My_Ini_File)                                != NULL) free((*My_Ini_File));
	(*My_Ini_File)->error_message                 = NULL;
	(*My_Ini_File)->filename                      = NULL;
	(*My_Ini_File)->output                        = NULL;
	(*My_Ini_File)->version_specific_data_storage = NULL;
	(*My_Ini_File) = NULL;
}

