/* $Id: configfile.c 3978 2003-05-23 10:19:38Z micah $
 *
 * configfile.c - Utilities for loading, storing, and retrieving
 *                configuration options
 *
 * PicoGUI small and efficient client/server GUI
 * Copyright (C) 2000-2003 Micah Dowty <micahjd@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * Contributors:
 * 
 * 
 * 
 */

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

#include "configfile.h"

/* Maximum parseable line size in a config file */
#define LINESIZE  256

/******** Data structures */

/* The config options are stored as a linked list of sections,
 * each section with a linked list of key-value pairs.
 */
struct cfg_item {
  char *key;              /* Does not need to be freed */
  char *value;            /* Does need to be freed */
  struct cfg_item *next;
};
struct cfg_section {
  char *name;             /* Does not need to be freed */
  struct cfg_item *items;
  struct cfg_section *next;
};

struct cfg_section *sections;

/******** Internal functions */

/* Get a pointer to the named section. If it does not exist return NULL */
struct cfg_section *configfile_getsection(const char *section) {
  static struct cfg_section *cache = NULL;
  struct cfg_section *p;

  /* Cache the last lookup */
  if (sections) {
    if (cache && !strcmp(cache->name,section))
      return cache;
  } else
    cache = NULL;

  p = sections;
  while (p) {
    if (!strcmp(p->name,section)) {
      cache = p;
      return p;
    }
    p = p->next;
  }
  return NULL;
}

/* Get the section, creating it if it doesn't exist */
struct cfg_section *configfile_makesection(const char *section) {
  struct cfg_section *sect;
  
  /* Find the proper section, create one if it doesn't exist */
  sect = configfile_getsection(section);
  if (!sect) {
    /* create the section and section name */
    sect = malloc(sizeof(struct cfg_section) + strlen(section) + 1);
    memset(sect,0,sizeof(struct cfg_section));
    sect->next = sections;
    sections = sect;
    sect->name = ((char*)sect) + sizeof(struct cfg_section);
    strcpy(sect->name,section);
  }
  return sect;
}

g_error configfile_set(struct cfg_section *sect, const char *key,
		       const char *value) {
  struct cfg_item *p;

  /* Find/create the item */
  p = sect->items;
  while (p) {
    if (!strcmp(p->key,key))
      break;
    p = p->next;
  }
  if (!p) {
    /* Create the new item */
    p = malloc(sizeof(struct cfg_item) + strlen(key) + 1);
    //errorcheck;
    memset(p,0,sizeof(struct cfg_item));
    p->next = sect->items;
    sect->items = p;
    p->key = ((char*)p) + sizeof(struct cfg_item);
    strcpy(p->key,key);
  }
  else {
    /* Free the existing value */
    free(p->value);
  }
  
  /* Allocate a new value */
  p->value = malloc(strlen(value)+1);
  //errorcheck;
  strcpy(p->value,value);

  return 1;
}

/* little utilities to strip whitespace from the head/tail of a string */

char *strip_head(char *s) {
  while (*s && isspace(*s))
    s++;
  return s;
}  

void strip_tail(char *s) {
  char *p;
  p = s;
  while (*p)
    p++;
  p--;
  while (p>=s) {
    if (!isspace(*p))
      break;
    *p = 0;
    p--;
  }
}

/******** Public functions */

g_error sub_configfile_parse(const char *filename, struct cfg_section **section) {
  FILE *f;
  char line[LINESIZE];
  char *p,*q;

  f = fopen(filename,"r");
  if (!f)
    return 0;      /* Can't open config file */

  while (fgets(line,LINESIZE,f)) {
    p = line;

    /* Chomp up all the leading whitespace */
    p = strip_head(p);
    
    /* Skip blank lines and comments */
    if ((!*p) || *p=='#')
      continue;

    /* Process section lines */
    if (*p=='[') {
      /* Skip the open bracket */
      p++;
      /* Stick a null in place of the close bracket */
      q = strchr(p,']');
      if (!q)
	return -2; /* Missing ']' */
      *q = 0;
      *section = configfile_makesection(p);
      continue;
    }

    if (!*section)
      return -3;   /* Undefined section */

    /* Want to source another file ? */
    if (p[0]=='.' && (p[1]==' '||p[1]=='\t')) {
      g_error err;
      q = p+2;
      q = strip_head(q);
      strip_tail(q);
      err = sub_configfile_parse(q, section);
    }

    /* If we got this far, it's a key/value line to a defined section */

    /* Get a pointer to the key in p and a pointer to the value in q */
    q = strchr(p,'=');
    if (!q)
      return 0;   /* Missing '=' */
    *q = 0;
    q++;

    /* Cut leading whitespace from q */
    q = strip_head(q);

    /* Chop off trailing whitespace from p and q */
    strip_tail(p);
    strip_tail(q);

    /* Set the parameter! */
    configfile_set(*section,p,q);
  }

  fclose(f);
  return 1;
}

g_error configfile_parse(const char *filename) {
  struct cfg_section *section = NULL;
  return sub_configfile_parse(filename, &section);
}

void configfile_free(void) {
  struct cfg_section *sect;
  struct cfg_item *p;
  void *condemn;

  sect = sections;
  while (sect) {

    p = sect->items;
    while (p) {
      if (p->value)
	free(p->value);

      condemn = p;
      p = p->next;
      free(condemn);
    }

    condemn = sect;
    sect = sect->next;
    free(condemn);
  }

  sections = NULL;
}

g_error set_param_str(const char *section, const char *key,
		      const char *value) {
  return configfile_set(configfile_makesection(section),key,value);
}

int get_param_int(const char *section, const char* key, int def) {
  const char *strval;
  strval = get_param_str(section,key,NULL);
  if (strval)
    return atoi(strval);
  return def;
}

const char *get_param_str(const char *section, const char* key, 
			  const char *def) {
  struct cfg_section *sect;
  struct cfg_item *p;

  sect = configfile_getsection(section);
  if (!sect)
    return def;
  
  p = sect->items;
  while (p) {
    if (!strcmp(p->key,key))
      return p->value;
    p = p->next;
  }
  return def;
}

const char **get_section_params(const char *section, int *count){
  struct cfg_section *sect;
  struct cfg_item *p;
  int paramCount = 0;
  char **paramList;

  sect = configfile_getsection(section);
  if (!sect)
    return NULL;
  
  p = sect->items;
  while (p) {
    paramCount++;
    p = p->next;
  }

  if(paramCount > 0){
    
    paramList = malloc(sizeof(char *)*paramCount);
    paramCount = 0;
    
    p = sect->items;
    while (p) {
      paramList[paramCount] = p->value;
      paramCount++;
      p = p->next;
    }
    
    *count = paramCount;
    return paramList;
  }else{
    *count = 0;
    return NULL;
  }
}

g_error configfile_write(const char *filename){
  FILE *outputFile = NULL;
  struct cfg_section *thisSection;
  struct cfg_item *thisItem;

  if(sections){
    thisSection = &sections[0];
    if((outputFile = fopen(filename, "w"))){
      while(thisSection){
	fprintf(outputFile, "[%s]\n", thisSection->name);
	thisItem = &thisSection->items[0];
	while(thisItem){
	  fprintf(outputFile, "%s=%s\n", thisItem->key, thisItem->value);
	  thisItem = thisItem->next;
	}
	thisSection = thisSection->next;
      }
      fclose(outputFile);
      return 1;
    }else{
      return 0;
    }
  }else{
    return 0;
  }
}

/* The End */



