/*********************************************************************
 *   Copyright 2014, UCAR/Unidata
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *********************************************************************/

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

/* General rule
try to avoid any obscure string functions.
We currently use
- strcasecmp
- strchr
- strndup
- strlen
*/

#undef DEBUG

/* Define the legal leading key characters */
#define KEYCHARS1 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_."

/*forward*/
static const char* ncsettings_text;

static char** lines;
static int nlines;
static char* dup;
static char** map = NULL;

/*forward*/
static void parse();
static int parseline(const char* line, int keypos);
static int iskeyline(const char* line);
static void preprocess();

const char*
nc_settings(const char* key)
{
    char** mapp;
    if(map == NULL)
	parse();
    for(mapp=map;*mapp != NULL;mapp+=2) {
	/* Note this assumes that no key is a prefix of another */
	if(strcasecmp(*mapp,key)==0) {
	    return mapp[1];
	}
    }    
    return NULL;
}

const char**
nc_settings_all()
{
    if(map == NULL)
	parse();
    return (const char**)map;
}

const char*
nc_settings_text()
{
    return ncsettings_text;
}

static void
parse()
{
    int i,keypos;
    int nkeys;
    const char** line;

    preprocess();

    nkeys = 0;
    /* Count # of key lines */
    for(i=0;i<nlines;i++) {
	const char* line = lines[i];
#ifdef DEBUG
	printf("testing: %s\n",line);
#endif
	if(iskeyline(line)) {
#ifdef DEBUG
	    printf("keyline: %s\n",line);
#endif
	    nkeys++;
	}
    }    
#ifdef DEBUG
    fflush(stdout);
#endif
    /* Create the map of proper size */
    map = (char**)malloc(((2*nkeys)+1) * sizeof(char*));/*+1 for terminating null*/
    if(map == NULL) {
	fprintf(stderr,"ncsettings: out of memory\n");
	return;
    }
    map[2*nkeys] = NULL; /* pre-insert terminating null */
    /* parse the keylines only */
    keypos = 0;
    for(i=0;i<nlines;i++) {
	const char* line = lines[i];
	if(!iskeyline(line)) continue;
	if(!parseline(line,keypos))
	    return;
	keypos+=2;
    }
}

/*
We assume that each key starts with an alphanumeric character.
*/
static int
parseline(const char* line, int keypos)
{
    const char* p;
    const char* r;
    char* q;
    ptrdiff_t delta;
    char* key;
    char* value;
    /* find colon ending of the key */
    r = strchr(line,':');
    if(r == NULL) { /* malformed */
	fprintf(stderr,"malformed libnetcdf.settings file: %s\n,line");
	return 0;
    }
    /* back up from the colon to the first non-blank */
    for(p=r;p != line;p--) {
	if(*p != ' ') break;
    }
    if(p == line) {/* empty key */
	fprintf(stderr,"malformed libnetcdf.settings file: %s\n,line");
	return 0;
    }
    delta = p - line;
    key = strndup(line,delta);
    /* skip post ':' blanks */
    for(p=r+1;;p++) {
	if(*p != ' ') break;
    }
    if(*p == '\0') /* empty value */
	value = strdup("");
    else { /* assert value is not empty */
	value = strdup(p);
	size_t len = strlen(value);
	q = value + (len - 1); /* point to last char before trailing nul */
        /* back up to the first non-blank */
	for(;(*q == ' ');q--); /* will always terminate at value at least */
	q[1] = '\0'; /* artificial end */
    }
    /* Append to the map */	
    map[keypos] = key;
    map[keypos+1] = value;
    return 1;    
}

/* We assume that each key starts with an alphanumeric character. */
static int
iskeyline(const char* line)
{
   if(line == NULL || strlen(line) == 0)
	return 0;
   if(line[0] == '#' || line[0] == ' ')
	return 0;
   if(strchr(KEYCHARS1,line[0]) ==  NULL)
	return 0;
   return 1;
}

/* We need to process the text as follows:
1. convert tabs and \r to blanks
2. convert \n to EOL (\0)
3. remove leading and trailing blanks from each line
*/
static void
preprocess()
{
    int c,i;
    const char* p;
    char* q;
    char* r;

#ifdef DEBUG
    printf("input: %s\n",ncsettings_text);
    fflush(stdout);
#endif
    dup = (char*)malloc(strlen(ncsettings_text)+1);
    nlines = 0;
    /* steps 1 and 2 */
    for(p=ncsettings_text,q=dup;(c=*p);p++) {
	switch (c) {
	case '\r': case '\t': *q++ = ' '; break;
	case '\n': nlines++; *q++ = '\0'; break;
	default: *q++ = c; break;
	}
    }
    /* step 3 */    
    lines = (char**)malloc(nlines*sizeof(char*));
    r = dup;
    for(i=0;i<nlines;i++) {
	int suppress;
	lines[i] = r;
	r += strlen(r);
	r++; /* skip terminating nul */
    }
    for(i=0;i<nlines;i++) { 
	char* line = lines[i];
	p = line;
	for(;(c=*p);p++) {
	    if(c != ' ')
		break;
	}
	strcpy(line,p); /* remove leading blanks */
	q = line+strlen(line); /* terminating nul */
	while((c=*(--q))) {
	    if(c != ' ')
		break;
	}
	/* terminate */
	q++;
	*q = '\0';
#ifdef DEBUG
        printf("processed: %s\n",line);
#endif
    }
#ifdef DEBUG
    fflush(stdout);
#endif
}

void
nc_settings_reclaim()
{
    if(lines != NULL) free(lines);
    if(dup != NULL) free(dup);
    if(map != NULL) free(map);
    lines = NULL;
    dup = NULL;
    map = NULL;
}

static const char* ncsettings_text =