/*
**
** ---> read_ini_file.c <---
**
** Minimodule for reading INI-Files. B. Ulmann fecit SEP-2002.
**
** Available functions:
**
**-------------------------------------------------------------------------------------------------------------------------
**
**   int read_ini_file (char *filename)
**
**     Reads the INI-file specified by *filename into a global data structure for later
**     processing by get_ini_entry (...) or dump_ini_file ().
**
**     Possible return codes:
**
**       INI$FILE_OPEN_ERROR:      The INI-file could not be opened for read.
**       INI$ILLEGAL_SECTION_READ: An illegal section header was found (something unlike 
**                                 "[section-name]").
**       INI$ILLEGAL_LINE_READ:    An illegal line was encountered (missing "=").
**       INI$OUT_OF_MEMORY:        There was not enough free memory for storing the INI-file
**                                 entries.
**       INI$EMPTY_FILE:           The INI-file was empty.
**       INI$SUCCESS:              Everything went very well, the INI-file could be read and
**                                 its entries are stored in the global linked list for further
**                                 processing.
**
**----------------------------------------------------------------------------------------------
**
**   void dump_ini_file ()
**
**     Dumps the INI-file read by an initial call to read_ini_file (...) on stdout.
**
**----------------------------------------------------------------------------------------------
**
**   get_ini_entry (char *section_name, char *entry_name, char *default_value, char *value)
**
**     Reads a single entry of the INI-file. The entry is specified by its name and the name
**     of the section containing the entry. If the entry could not be found, the string
**     *default_value is copied to the result string *value.
**
**     Possible return codes:
**
**       INI$DEFAULT_USED: The specified entry could not be found, instead the default value
**                         has been copied to *value.
**       INI$SUCCESS:      The entry has been found and its value is returned in *value.
**
**----------------------------------------------------------------------------------------------
**
*/

#undef TEST						/* Do not define this constant, it is for testing only */

/* 
**
** Standard includes
**
*/

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

/*
**
** Local includes
**
*/

#include "misc.h"					/* Get global constants, etc. */
#include "read_ini_file.h"				/* Get constant definitions, etc. */

/*
**
** Global variables, sometimes it is just simpler doing it this way... :-)
**
*/

static read_ini_file$ini_list *read_ini_file$head;

/*
**
** Actual code starts here :-)
**
*/

/*
**
** remove_trailing_blanks deletes all trailing white spaces from a string.
**
*/

void remove_trailing_blanks (char *cp)
{
  int i;

  for (i = strlen (cp) - 1; i >= 0 && (*(cp + i) == '\t' || *(cp + i) == ' ');)
    *(cp + i--) = 0;
}

/*
**
** remove_leading_blanks removes all leading white spaces from a string.
**
*/

void remove_leading_blanks (char *string)
{
  char *cp;
  
  cp = string;
  while (*cp == ' ' || *cp == '\t')
    cp++;
    
  strcpy (string, cp);
}

/*
**
**  get_ini_entry searches the global configuration list for a specific entry in a specific section.
** If no appropriate entry is found, an optional default value will be returned, else the value
** specified in the entry will be used as return value.
**
**  Possible return codes are:
**       INI$DEFAULT_USED
**       INI$SUCCESS
**
*/

int get_ini_entry (char *section_name, 			/* Name of section to read value from */
                   char *entry_name, 			/* Name of value to be read */
                   char *default_value, 		/* Default value which is to be used if no entry is found */
                   char *value)				/* Pointer to return string containing the value read */
{
  int section_found;
  read_ini_file$ini_list *list;

  /* Initialize variables */
  
  section_found = FALSE;
  list = read_ini_file$head;
  strcpy (value, default_value);			/* Copy default value, just in case */
  
  while (list)
  {
    if (list -> type == INI$SECTION)
    {
      if (section_found == FALSE)
      {
        if (!strcmp (list -> name, section_name))
          section_found = TRUE;
      }
      else						/* The section is over but no apropriate entry was found */
        return INI$DEFAULT_USED;
    }
    else 						/* Found a name/value pair */
    {
      if (section_found == TRUE) 			/* Is it the correct section? */
        if (!strcmp (list -> name, entry_name)) 	/* ...and the desired entry? */
        {
          strcpy (value, list -> value);		/* Copy value */
          return INI$SUCCESS;
        }
    }
    
    list = list -> next;
  }
  
  return INI$DEFAULT_USED;
}

/*
**
**  dump_ini_file writes the contents of a previously read configuration file to stdout and
** should be used for debugging purposes only.
**
*/

void dump_ini_file ()
{
  int flag;
  read_ini_file$ini_list *list;

  /* Initialize variables */
  
  flag = FALSE;
  list = read_ini_file$head;

  while (list)
  {
    flag = TRUE;
  	
    if (list -> type == INI$SECTION)
      printf ("Section:\n\tName: >>%s<<\n", list -> name);
    else
      printf ("Entry:\n\t>>%s<< = >>%s<<\n", list -> name, list -> value);
      
    list = list -> next;
  }
  
  if (flag == FALSE)
    printf ("List is empty!\n");
}

/*
**
**  read_ini_file opens a configuration file, reads its contents and creates a static, global
** linked list of name/value pairs representing the data read from the configuration file.
**
**  Possible return codes are:
**       INI$FILE_OPEN_ERROR
**       INI$ILLEGAL_SECTION_READ
**       INI$ILLEGAL_LINE_READ
**       INI$OUT_OF_MEMORY
**       INI$EMPTY_FILE
**       INI$SUCCESS
**
*/

int read_ini_file (char *filename)
{
  unsigned int i, flag, type, counter;
  char line [STRING_LENGTH], *cp, *value;
  FILE *handle;
  read_ini_file$ini_list *entry, *previous;

  /* Try to open configuration file to be read */

  if (!(handle = fopen (filename, "r")))
    return INI$FILE_OPEN_ERROR;

  /* Initialize variables */

  read_ini_file$head = 0;
  previous = 0;
  counter = 0;

  for (;;)
  {
    fgets (line, STRING_LENGTH, handle);
    if (feof (handle))
      break;

    if (line [strlen (line) - 1] == '\n') 		/* Remove CR/LF */
      line [strlen (line) - 1] = 0;

    cp = line;						/* Remove leadings blanks and tabs */
    while (*cp == ' ' || *cp == '\t')
      cp++;

    /* Delete comments */
    
    for (i = 0; i < strlen (cp); i++)
      if (*(cp + i) == INI$COMMENT_CHAR || *(cp + i) == '#')
      {
        *(cp + i) = 0;
        break;
      }

    remove_trailing_blanks (cp);
    if (!strlen (cp))					/* Skip this line if nothing is left */
      continue;
    counter++;

    /* Now process the line just read */

    if (*cp == '[')					/* The line denotes a new section */
    {
      if (*(cp + strlen (cp) - 1) != ']')
        return INI$ILLEGAL_SECTION_READ;

      *(cp + strlen (cp) - 1) = 0;			/* Skip closing square bracket */
      cp++;						/* Skip opening bracket */
      remove_trailing_blanks (cp);
      remove_leading_blanks (cp);
      type = INI$SECTION;				/* Remember that it was a section, not a name/value pair */
    }
    else						/* The line is a name/value pair */
    {
      flag = FALSE;
      for (i = 0; i < strlen (cp); i++)			/* Look for an equal sign to split name and value */
      {
        if (*(cp + i) == INI$EQUAL_CHAR)
        {
          flag = TRUE;
          break;
        }
      }
      
      if (flag == FALSE)				/* No equal sign found, this was an illegal line */
        return INI$ILLEGAL_LINE_READ;
        
      type = INI$LINE;
      *(cp + i) = 0;					/* Split name */
      value = cp + i + 1;				/* and value pair */
      remove_trailing_blanks (cp);
      remove_leading_blanks (value);
    }

    if (!(entry = (read_ini_file$ini_list *) malloc (sizeof (read_ini_file$ini_list))))
      return INI$OUT_OF_MEMORY;

    if (!read_ini_file$head)				/* This is the first element of the list, so */
      read_ini_file$head = entry;			/* remember its start point */
  
    entry -> type = type;				/* Copy name and type into the new list element */
    strcpy (entry -> name, cp);
    
    if (type == INI$LINE)				/* If it was a name/value pair, remember the value */
      strcpy (entry -> value, value);
    else						/* Otherwise just mark the value as invalid */
      strcpy (entry -> value, "----");
      
    entry -> next = 0;

    if (previous)					/* Append new element to the list if it is not the first element */
    {
      previous -> next = entry;
      previous = entry;
    }
    else
      previous = read_ini_file$head;			/* If there was no previous element, now there will be */

#ifdef DEBUG    
    if (type == INI$LINE)
      printf ("Name: >>%s<<, Value: >>%s<<\n", cp, value);
    else
      printf ("Name: >>%s<<\n", cp);
#endif
  }
  
  if (!counter)						/* Were there any lines in the configuration file at all? */
    return INI$EMPTY_FILE;
    
  return INI$SUCCESS;
}

/*
**
** Test main program - not for production use! :-)
**
*/

#ifdef TEST
void main()
{
  char string [STRING_LENGTH];
	
  printf ("Result of read_ini_file-Call: %d\n", read_ini_file ("test.ini"));
  dump_ini_file ();
}
#endif
