/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            miav_config.cc
 *
 *  Sat Feb 19 14:13:19 CET 2005
 *  Copyright  2005 Bent Bisballe
 *  deva@aasimon.org
 ****************************************************************************/
/*
 *  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 Library 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.
 */
/*
 * $Id$
 */
/*
 * $Log$
 * Revision 1.7  2005/05/01 09:56:26  deva
 * Added Id and Log tags to all files
 *
 */
#include <config.h>
#include "miav_config.h"

MiavConfig *config;

MiavConfig::MiavConfig(char *file, Error* err)
{
  error = err;
  configs = NULL;
  
  filename = string(file);

  // Read config file
  FILE* fp = fopen(file, "r");
  
  if(!fp) {
    char errbuf[256];
    sprintf(errbuf, "Error reading configuration file %s\n", file);
    if(error) error->pushError(errbuf);
    return;
  }
  fseek(fp, 0, SEEK_END);
  int fsz = ftell(fp);
  fseek(fp, 0, SEEK_SET);
  
  char *raw = (char*)calloc(fsz, 1);
  fread(raw, 1, fsz, fp);

  fclose(fp);

  parse(raw);
  free(raw);
}

MiavConfig::~MiavConfig()
{
  _cfg *die = NULL;
  _cfg *cfg = configs;

  while(cfg) {
    if(die) free(die);
    die = cfg;
    cfg = cfg->next;
  }
  if(die) free(die);
}

/**
 * Prints a reasonable error message when a parse error occurres.
 */
_cfg *MiavConfig::parseError(char* msg, char* line)
{
  char errbuf[512];
  sprintf(errbuf, "Error parsing file %s at line:\n\t%s\n\t%s\n", filename.c_str(), line, msg);
  if(error) error->pushError(errbuf);
  return NULL;
}

/** 
 * Adds one configuration entry, from a single zero terminated line.
 */
_cfg *MiavConfig::addConfig(_cfg *parent, char* conf)
{
  // Check for wellformed input:
  // Check for =
  if(strstr(conf, "=") == 0) return parseError("Missing '='", conf);
  /*
  if(strstr(conf, "\"")) {
    if(strstr(conf, "=") > strstr(conf, "\""))
    return parseError("Missing '=', first occurrence inside string", conf);
  }
  */

  // Check for nonempty left side
  if(strstr(conf, "=") == conf) return parseError("Empty left side", conf);

  // Check for nonempty right side
  if(strstr(conf, "=") == conf + strlen(conf) - 1) return parseError("Empty right side.", conf);

  // Parse this wellformed input.
  _cfg *cfg;

  cfg = (_cfg*) malloc(sizeof(_cfg));
  if(!parent) configs = cfg;

  int namelen = strchr(conf, '=') - conf;
  char* name = (char*)calloc(namelen + 1, 1);
  strncpy(name, conf, namelen);

  int vallen = conf + strlen(conf) - (strchr(conf, '=') + 1);
  char* val = (char*)calloc(vallen + 1, 1);
  strncpy(val, conf + strlen(conf) - vallen, vallen);

  // TODO: Check valid rightside (true, false, number or "..")

  cfg->name = new string((const char*)name);
  free(name);

  cfg->stringval = new string((const char*)val);
  cfg->intval = atoi(val);
  cfg->floatval = atof(val);
  cfg->boolval = atoi(val) != 0;
  free(val);

  cfg->next = NULL;

  if(parent) parent->next = cfg;
  return cfg;
}

/**
 * Main parse function, iterates the lines of the file.
 */
int MiavConfig::parse(char* raw)
{
  // Strip the string
  char *conf = strip(raw);
  char *conf_end = conf + strlen(conf);
  char *start = conf;
  char *p;

  _cfg *cfg = NULL;

  // Iterate the lines in the string
  for(p = conf; p < conf_end; p++) {
    if(*p == '\n') {
      *p = '\0';
      if(!(cfg = addConfig(cfg, start))) return 1;
      start = p+1;
    }
  }
  // Allocated in strip
  free(conf);
  return 0;
}

/**
 * Strip all unwanted data from the string, initial to parsing.
 */
char* MiavConfig::strip(char* conf)
{
  // Freed in parse!!!
  char *stripped = (char*)calloc(strlen(conf) + 2, 1);
  char *r;
  char *w = stripped;

  bool instring = false;
  bool incomment = false;

  // Iterate over the characters in the input string.
  for(r = conf; r < conf + strlen(conf); r++) {
    if(strchr("#", *r)) incomment = true;
    if(strchr("\n", *r)) incomment = false;

    if(!incomment) {
      if(instring) {
        // When in a string, accept anything, except ".
        if(*r != '\"') {
          *w = *r;
          w++;
        }
      } else {
        // Only copy valid characters
        if(strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-_,.=", *r)) {
          // Change comma into dot
          if(*r == ',') *r = '.';
          *w = *r;
          w++;
        }
        // We don't want double newlines and initial newline!
        if((*r == '\n') && (*(w-1) != '\n') && (w != stripped)) {
          *w = *r;
          w++;
        }
      }
    }

    if(strchr("\"", *r)) instring = !instring;
  }

  // If we are not ending on a newline, we better append one
  if(*(w-1) != '\n') {
    *w = '\n';
    w++;
  }

  // Make sure we are nullterminated.
  *w = '\0';

  return stripped;
}

int MiavConfig::readInt(char *node)
{
  _cfg* n = findNode(node);
  if(n) return n->intval;
  else return 0;
}

bool MiavConfig::readBool(char *node)
{
  _cfg* n = findNode(node);
  if(n) return n->boolval;
  else return false;
}

string *MiavConfig::readString(char *node)
{
  _cfg* n = findNode(node);
  if(n) return n->stringval;
  else return &emptyString;
}

float MiavConfig::readFloat(char *node)
{
  _cfg* n = findNode(node);
  if(n) return n->floatval;
  else return 0.0f;
}

_cfg *MiavConfig::findNode(char* node)
{
  _cfg *cfg = configs;

  while(cfg) {
    if(!strcmp(node, cfg->name->c_str())) return cfg;
    cfg = cfg->next;
  }
  char errbuf[256];
  sprintf(errbuf, "Request for nonexisting node \"%s\"!\n", node);
  if(error) error->pushError(errbuf);
  return NULL;
}

Error* MiavConfig::getErrorObj()
{
  return error;
}