/*
 *   Small .ini file reader for z88dk/anything
 *
 *   Will parse those Windows style files, with basic functionality -
 *   handles booleans, ints and strings
 * 
 *   Arrays aren't available for z88dk
 *
 *   (C) D.J.Morris <dom@jadara.org.uk> 7/8/2001
 *
 */



#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "libini.h"


/* Maximum length of line to read in from the file*/

#define LINE_MAX 128


struct _option {
    char               *group;
    char               *word;
    char               *lopt;
    char                sopt;
    char               *desc;
    unsigned char       type;
    void               *value;
    int                *count;
    struct _option *next;
};

typedef struct _option option_t;

static option_t      *list;
static char          *current_section;

/* Internal functions */

static char       *skpws(char *ptr);
static void        strip_ws(char *ptr);
static void        get_section(char *ptr);
static int         option_set(char *opt, char *it);
static int         option_set_sopt(char sopt, char *it);
static void        option_do_set(option_t *option, char *value);
static void        iniread_file(FILE *fp);



void iniparse_cleanup()
{
    option_t *next;
    option_t *opt = list;

    while ( opt != NULL ) {
        free(opt->desc);
        free(opt->lopt);
        free(opt->group);
        free(opt->word);
        next = opt->next;
        free(opt);
        opt = next;
    }
    free(current_section);
}


int iniparse_init(char *default_section)
{
    current_section = strdup(default_section);
    list = NULL;

    return 0;
}

int iniparse(char *filename, int argc, char *argv[])
{
    if ( ( iniparse_args(argc, argv) ) < 0 ) {
        return -1;
    }
    return iniparse_file(filename);
}


int iniparse_args(int argc, char *argv[])
{
    int  i;

    for ( i = 1; i < argc; i++ ) {
        if ( argv[i] && argv[i][0] == '-' ) {
            if ( argv[i][1] == '-' ) {
                char  *temp_section = current_section;
                char  *ptr, *option,*value = NULL;
                int    optc = i;
                /* Long option handling - break out the section if present */
                if ( ( ptr = strchr(argv[i],':') ) != NULL ) {
                    *ptr = 0;  /* Truncate section */
                    current_section = &argv[i][2];
                    option = ptr + 1;
                } else {
                    option = &argv[i][2];
                    temp_section = NULL;
                }


                /* Now we have to find where the option is - it could either
                   be after an '=', or the next argument */
                if ( ( ptr = strchr(option,'=') ) != NULL ) {
                    *ptr = 0;
                    value = ptr + 1;
                } else if ( i + 1 < argc ) {
                    value = argv[i+1];
                    optc = i + 1;
                }

                if ( option ) {
                    if ( option_set(option,value) == 0) {
                        argv[i] = NULL;
                        argv[optc] = NULL;
                        i = optc;
                    }
                }
                /* Restore the default section */
                if ( temp_section ) {
                    current_section = temp_section;
                }

                if ( option == NULL ) {                    
                    fprintf(stderr,"Insufficient arguments to parse option %s\n",argv[i]);
                    return -1;
                }
            } else {   /* Short option handling */
                char sopt = argv[i][1];
                if ( i + 1 < argc ) {
                    if ( option_set_sopt(sopt,argv[i+1]) == 0 ) {
                        argv[i] = NULL;
                        argv[i+1] = NULL;
                    }
                    i++;
                } else {
                    if ( option_set_sopt(sopt,NULL) == 0) {
                        argv[i] = NULL;
                    }
                }
            }
        }
    }
    return 0;
}

int iniparse_file(char *name)
{
    FILE        *fp;

    if ( ( fp = fopen(name,"r") ) == NULL ) {
        fprintf(stderr,"Could not open configuration file %s\n",name);
        return (-1);
    }
    iniread_file(fp);
    fclose(fp);
    return 0;
}

/** \brief Dump out all of the arguments to stdout - a sort of help
 *   display
 */
void iniparse_help(FILE *fp)
{
    char      opttext[6];
    option_t *opt = list;



    while ( opt != NULL ) {
        if ( opt->sopt ) {
            sprintf(opttext,"-%c",opt->sopt);
        } else {
            sprintf(opttext,"  ");
        }


        switch ( opt->type ) {
        case OPT_BOOL:
            fprintf(fp,"%s --%-23s (bool)      %s\n", opttext, opt->lopt, opt->desc);
            break;
        case OPT_STR:
            fprintf(fp,"%s --%-23s (string)    %s\n", opttext, opt->lopt, opt->desc);
            break;
        case OPT_INT:
            fprintf(fp,"%s --%-23s (integer)   %s\n", opttext, opt->lopt, opt->desc);
            break;
        case OPT_STR|OPT_ARRAY:
            fprintf(fp,"%s --%-23s (str array) %s\n", opttext, opt->lopt, opt->desc);
            break;
        case OPT_INT|OPT_ARRAY:
            fprintf(fp,"%s --%-23s (int array) %s\n", opttext, opt->lopt,opt->desc);
            break;
        }
            

        opt = opt->next;
    }    


}

#ifndef SMALL_C
int iniparse_add_array(char sopt, char *key2, char *desc, unsigned char type, void *data, int *num_ptr)
{
    option_t *option;
    char         *word;
    char         *key = strdup(key2);
    


    word = strchr(key,':');
    
    if ( word == NULL ) {
        free(key);
        return -1;
    }
    
    *word++ = 0;
    
    option = malloc(sizeof(option_t));
    
    option->next = NULL;

    if ( list == NULL ) {
        list = option;
    } else {
        option_t *arg = list;
        while ( arg->next != NULL )
            arg = arg->next;

        arg->next = option;
    }

    option->sopt   = sopt;
    option->lopt   = strdup(key2);
    option->desc   = strdup(desc);
    option->group  = strdup(key);
    option->word   = strdup(word);
    option->type   = type | OPT_ARRAY;
    option->value  = data;
    option->count  = num_ptr;
    free(key);
    return 0;
}
#endif

int iniparse_add(char sopt, char *key2, char *desc, unsigned char type, void *data)
{
    option_t     *option;
    char         *word;
    char         *key = strdup(key2);
    
    word = strchr(key,':');
    
    if ( word == NULL ) {
        free(key);
        return -1;
    }
    
    *word++ = 0;
    
    option = malloc(sizeof(option_t));
    option->next = NULL;
   
    if ( list == NULL ) {
        list = option;
    } else {
        option_t *arg = list;
        while ( arg->next != NULL )
            arg = arg->next;

        arg->next = option;
    }   
    
    option->sopt   = sopt;
    option->lopt   = strdup(key2);
    option->desc   = strdup(desc);
    option->group  = strdup(key);
    option->word   = strdup(word);
    option->type   = type;
    option->value  = data;
#if 0
    switch ( type ) {
    case OPT_BOOL:
        *(char *)data = 0;
        break;
    case OPT_INT:
        *(int *) data = 0;
        break;
    case OPT_STR:
        *(char **) data = NULL;
        break;
    }
#endif
    free(key);
    return 0;
}

void iniread_file(FILE *fp)
{
    char        buffer[LINE_MAX];
    char   *start,*ptr;
    
    while (fgets(buffer,sizeof(buffer),fp) != NULL ) {
        start = skpws(buffer);
        if ( *start == ';' || *start =='#' || *start ==0)
            continue;
        if ( *start == '[' ) {
            get_section(start);
        } else {        /* Must be an option! */
            ptr = start;
            while ( isalnum(*ptr) )
                ptr++;
            if ( *ptr == '=' ) {
                *ptr++ = 0;
            } else {
                *ptr++ = 0;
                /* Search for the '=' now */
                if ( (ptr = strchr(ptr,'=') ) == NULL ) {
                    continue;
                }
                ptr++;
            }

            ptr = skpws(ptr); /* Skip over any white space */
            if ( *ptr == ';' || *ptr =='#' || *ptr ==0 ) {
                continue;
            }
            if ( strlen(start) ) {
                option_set(start,ptr);
            }
        }
    }
}



static int option_set(char *opt, char *it)
{
    option_t    *option;
    
    option = list;

    while ( option != NULL ) {
        if ( strcmp(option->group,current_section) == 0 &&
             strcmp(option->word,opt) == 0 ) {
            if ( option->type == OPT_BOOL && it == NULL ) {
                *(char *)(option->value) = 1;
            } else {
                option_do_set(option,it);
            }
            return 0;
        }
        option = option->next;
    }
    return -1;
}

static int option_set_sopt(char sopt, char *it)
{
    option_t    *option;
    
    option = list;

    while ( option != NULL ) {
        if ( strcmp(option->group,current_section) == 0 &&
             option->sopt == sopt ) {
            if ( option->type == OPT_BOOL ) {
                *(char *)(option->value) = 1;
            } else {
                option_do_set(option,it);
            }
            return 0;
        }
        option = option->next;
    }
    return -1;
}
        
static void option_do_set(option_t *option, char *value)
{
    int         val;
    char        c;
    char       *ptr;
        char *temp;

    /* Handle comments after the value */
    ptr = strchr(value,';');
    if ( ptr )
        *ptr = 0;

#ifndef SMALL_C
    if ( option->type & OPT_ARRAY ) {
        switch ( option->type ^ OPT_ARRAY ) {
        case OPT_INT:
            val = atoi(value);
            *(int **)option->value = realloc(*(int **)option->value, (*option->count + 1) * sizeof(int));
            (*(int **)(option->value))[*option->count] = val;
            (*option->count)++;
            break;
        case OPT_STR:
            strip_ws(value);
            temp = strdup(value);
            *(char **)option->value = realloc(*(char **)option->value, (*option->count + 1) * sizeof(int));
            (*(char ***)(option->value))[*option->count] = temp;
            (*option->count)++;
        }
        return;

    }
#endif
    switch ( option->type ) {
    case OPT_INT:
        val = atoi(value);
        *(int *)(option->value) = val;
        return;
    case OPT_STR:
        strip_ws(value);
        temp = strdup(value);
        *(char **)(option->value) = strdup(value);
        return;
    case OPT_BOOL:
        c = toupper(value[0]);
        val = 0;
        switch (c) {
        case 'Y':
        case 'T':
        case '1':
            val = 1;
        }
        *(char *)(option->value) = val;
    }
}



/* enters in with ptr pointing to the '[' */

static void get_section(char *ptr)
{
    char *end; 
    end = strchr(ptr,']');
    
    if ( end == NULL )
        return;
    *end = 0;
   
    free(current_section);
    current_section = strdup(ptr+1);
}


static void strip_ws(char *value)
{
    value = value+strlen(value)-1;
    while (*value && isspace(*value)) 
        *value-- = 0;
}

static char *skpws(char *ptr)
{
        while (*ptr && isspace(*ptr) )
                ptr++;
        return (ptr);
}


#ifdef TEST
#ifdef SMALL_C
#define HPSIZE 15000
HEAPSIZE(HPSIZE)
#endif
int main(int argc, char *argv[])
{
    char *ptr;
    int   i,ret;
    char   **arr = NULL;
    int    arr_num = 0;
#ifdef SMALL_C
    heapinit(HPSIZE);
#endif
    iniparse_init("test");
    iniparse_add(0,"test:blah","Bah",OPT_STR,&ptr);
    iniparse_add('c',"test:count","Humbug",OPT_INT,&i);
    iniparse_add_array(0,"test:array","Array",OPT_STR,&arr,&arr_num);

    iniparse_help(stdout);

    ret = iniparse("test.ini",argc,argv);
    iniparse_cleanup();
    // printf("%d <%s> %d\n",ret,ptr,i);
    printf("%d \n",i);
    printf("%d \n",arr_num);

    for ( i = 0; i < arr_num; i++ ) {
        printf("%d %s\n",i,arr[i]);
    }

}
#endif

