/* -*- 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 file is part of MIaV.
 *
 *    MIaV 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.
 *
 *    MIaV 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 MIaV; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include <config.h>
#include "miav_config.h"

MiavConfig *config;

MiavConfig::MiavConfig(char *file, Info *i)
{
  info = i;
  configs = NULL;
  
  filename = string(file);

  // Read config file
  FILE* fp = fopen(file, "r");
  
  if(!fp) {
    if(info) info->error("Error reading configuration file %s\n", file);
    else fprintf(stderr, "Error reading configuration file %s\n", file);
    return;
  }
  fseek(fp, 0, SEEK_END);
  int fsz = ftell(fp) + 1;
  fseek(fp, 0, SEEK_SET);
  
  char *raw = (char*)calloc(fsz, 1);
  fread(raw, 1, fsz, fp);

  fclose(fp);

  configs = 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.
 */
void MiavConfig::parseError(char* msg, _cfg* cfg)
{
  if(info) info->error("Error parsing file %s at line %d:\n\t%s\n\t%s\n", 
                       filename.c_str(), 
                       cfg->line,
                       cfg->orig,
                       msg);
  else fprintf(stderr, "Error parsing file %s at line %d:\n\t%s\n\t%s\n",
               filename.c_str(), 
               cfg->line,
               cfg->orig,
               msg);
}

_cfg* MiavConfig::readLines(char* raw)
{
  int line = 1;
  
  _cfg *first = (_cfg*)calloc(1, sizeof(_cfg));
  _cfg *current = first;
  _cfg *next = NULL;

  char *nl = strchr(raw, '\n');

  while(nl != NULL) {
    int len = nl - raw;

    current->line = line;

    current->orig = (char*) calloc(len + 1, 1);
    strncpy(current->orig, raw, len);

    // Find next newline
    raw = nl+1;
    nl = strchr(raw, '\n');

    line++;

    // Add _cfg
    if(nl != NULL) {
      next = (_cfg*)calloc(1, sizeof(_cfg));
      current->next = next;
      current = next;
    } else {
      current->next = NULL;
    }
  }

  return first;
}

_cfg* MiavConfig::parseLines(_cfg *cfg)
{
  if(cfg == NULL) return NULL;

  char *l = cfg->left = (char*)calloc(1, strlen(cfg->orig));
  char *r = cfg->right = (char*)calloc(1, strlen(cfg->orig));

  char *p = cfg->orig;

  // Skip leftmost whitespace
  while(p < cfg->orig + strlen(cfg->orig) && strchr("\t ", *p)) {
    p++;
  }

  // Empty line, with whitespaces
  if(p == cfg->orig + strlen(cfg->orig)) {
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return parseLines(next);
  }

  // Parse left side
  while(p < cfg->orig + strlen(cfg->orig) && !strchr("\t ", *p)) {
    if(strchr("#", *p)) {
      if(l != cfg->left) parseError("Incomplete line.", cfg);
      _cfg* next = cfg->next;
      free(cfg->orig);
      free(cfg->left);
      free(cfg->right);
      free(cfg);
      return parseLines(next);
    }

    if(strchr("=", *p)) break;

    if(strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_", *p)) {
      *l = *p;
      l++;
    } else {
      char buf[256];
      sprintf(buf, "Invalid left hand side character at [%s].", p);
      parseError(buf, cfg);
      _cfg* next = cfg->next;
      free(cfg->orig);
      free(cfg->left);
      free(cfg->right);
      free(cfg);
      return parseLines(next);
    }

    p++;
  }

  // Skip whitespace
  while(p < cfg->orig + strlen(cfg->orig) && strchr("\t ", *p)) {
    p++;
  }

  if(*p != '=') {
     parseError("Expected '='.", cfg);
      _cfg* next = cfg->next;
      free(cfg->orig);
      free(cfg->left);
      free(cfg->right);
      free(cfg);
      return parseLines(next);
  }
  p++; // Get past the '='

  // Skip whitespace
  while(p < cfg->orig + strlen(cfg->orig) && strchr("\t ", *p)) {
    p++;
  }

  // Parse right hand side
  int instring = 0;
  while(p < cfg->orig + strlen(cfg->orig) && !(strchr("\t ", *p) && instring != 1)) {
    if(*p == '\"') instring++;
    if(instring > 2) {
      parseError("Too many '\"'.", cfg);
      _cfg* next = cfg->next;
      free(cfg->orig);
      free(cfg->left);
      free(cfg->right);
      free(cfg);
      return parseLines(next);
    } 
    
    if(instring == 1) {
      // Accept all chars
      *r= *p;
      r++;
    } else {
      // Accept only those chars valid for the data types.
      if(strchr("truefalseyesnoTRUEFALSEYESNO1234567890\",.-", *p)) {
        if(*p == ',') *r= '.';
        *r = *p;
        r++;
      } else if(!strchr("\n", *p)) {
        char buf[256];
        sprintf(buf, "Invalid right hand side character at [%s].", p);
        parseError(buf, cfg);
        _cfg* next = cfg->next;
        free(cfg->orig);
        free(cfg->left);
        free(cfg->right);
        free(cfg);
        return parseLines(next);
      }
      if(*p == '#') break;
    }

    p++;
  }

  // Skip whitespace
  while(p < cfg->orig + strlen(cfg->orig) && strchr("\t ", *p)) {
    p++;
  }

  // Detect if whitespace ocurred inside righthand value.
  if(p != cfg->orig + strlen(cfg->orig)) {
    parseError("Invalid use of whitespace.", cfg);
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return parseLines(next);
  }

  // Check for instring (string not ended)
  if(instring == 1) {
    parseError("String not closed.", cfg);
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return parseLines(next);
  }

  // Check for empty line
  if(l == cfg->left && r == cfg->right) {
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return parseLines(next);
  }

  // Check for empty left side.
  if(l == cfg->left) {
    parseError("Empty left side.", cfg);
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return parseLines(next);
  }

  // Check for empty right side.
  if(r == cfg->right) {
    parseError("Empty right side.", cfg);
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return parseLines(next);
  }

  cfg->next = parseLines(cfg->next);
  return cfg;
}


_cfg *MiavConfig::createSemantics(_cfg *cfg) {
  if(cfg == NULL) return NULL;

  cfg->type = CONFIG_UNKNOWN;

  // Boolean - true
  if(strcasecmp(cfg->right, "yes") == 0 ||
     strcasecmp(cfg->right, "true")  == 0) {
    cfg->type = CONFIG_BOOL;
    cfg->boolval = true;
  }

  // Boolean - false
  if(strcasecmp(cfg->right, "no") == 0 ||
     strcasecmp(cfg->right, "false") == 0) {
    cfg->type = CONFIG_BOOL;
    cfg->boolval = false;
  }

  // String
  if(cfg->right[0] == '\"') {
    cfg->type = CONFIG_STRING;
    cfg->right[strlen(cfg->right) - 1] = '\0';
    cfg->stringval = new string(cfg->right + 1);
    
  }

  // Number
  bool number = true;
  char *p = cfg->right;
  while(p < cfg->right + strlen(cfg->right)) {
    if(!strchr("01234567890.-", *p)) number = false;
    p++;
  }
    
  // Integer
  if(number && strstr(cfg->right, ".") == NULL ) {
    cfg->type = CONFIG_INT;
    cfg->intval = atoi(cfg->right);
  }

  // Float
  if(number && strstr(cfg->right, ".") != NULL) {
    cfg->type = CONFIG_FLOAT;
    cfg->floatval = atof(cfg->right);
  }

  if(cfg->type == CONFIG_UNKNOWN) {
    parseError("Unknown type (see 'man miav.conf' for valid right hand sides).", cfg);
    _cfg* next = cfg->next;
    free(cfg->orig);
    free(cfg->left);
    free(cfg->right);
    free(cfg);
    return createSemantics(next);
  }

  // Create name
  cfg->name = new string(cfg->left);

  cfg->next = createSemantics(cfg->next);
  return cfg;
}


_cfg* MiavConfig::parse(char* raw)
{
  _cfg *first = readLines(raw);
  first = parseLines(first);

  first = createSemantics(first);

  /*
  _cfg* cfg = first;
  while(cfg) {
    printf("Node:\n");
    printf("\tLine: [%d]\n", cfg->line);
    printf("\tOrig: [%s]\n", cfg->orig);
    printf("\tLeft: [%s]\n", cfg->left);
    printf("\tRight: [%s]\n", cfg->right);

    switch(cfg->type) {
    case CONFIG_INT:
      printf("\tInt value: %d\n", cfg->intval);
      break;
    case CONFIG_BOOL:
      printf("\tBool value: %d\n", cfg->boolval);
      break;
    case CONFIG_FLOAT:
      printf("\tFloat value: %f\n", cfg->floatval);
      break;
    case CONFIG_STRING:
      printf("\tString value: %s\n", cfg->stringval->c_str());
      break;
    case CONFIG_UNKNOWN:
      printf("\tUnknown type: %s\n", cfg->right);
      break;
    }

    cfg= cfg->next;
  }
  */
  return first;
}

int MiavConfig::readInt(char *node)
{
  _cfg* n = findNode(node);
  if(n) {
    if(n->type == CONFIG_INT) return n->intval;
    parseError("Expected integer.", n);
  }
  return 0;
}

bool MiavConfig::readBool(char *node)
{
  _cfg* n = findNode(node); 
  if(n) {
    if(n->type == CONFIG_BOOL) return n->boolval;
    if(n->type == CONFIG_INT) return (n->intval != 0);
    parseError("Expected boolean.", n);
  }
  return false;
}

string *MiavConfig::readString(char *node)
{
  _cfg* n = findNode(node);
  if(n) {
    if(n->type == CONFIG_STRING) return n->stringval;
    parseError("Expected string.", n);
  }
  return &emptyString;
}

float MiavConfig::readFloat(char *node)
{
  _cfg* n = findNode(node);
  if(n) {
    if(n->type == CONFIG_FLOAT) return n->floatval;
    if(n->type == CONFIG_INT) return (float)n->intval;
    parseError("Expected float.", n);
  }
  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;
  }
  if(info) info->error("Missing line in configuration file: \"%s\"!\n", node);
  else fprintf(stderr, "Missing line in configuration file: \"%s\"!\n", node);

  return NULL;
}

#ifdef __TEST_MIAV_CONFIG

int main(int argc, char *argv[]) {
  if(argc < 2) {
    fprintf(stderr, "usage:\n\tmiav_config [filename]\n");
    return 1;
  }

  MiavConfig cfg(argv[1]);
  printf("Server user: [%s]\n", cfg.readString("server_user")->c_str());
  printf("Resolution: [%f]\n", cfg.readFloat("screensize"));
  printf("Resolution (as int): [%d]\n", cfg.readInt("screensize"));
  printf("Width: [%d]\n", cfg.readInt("pixel_width"));
  printf("Width (as float): [%f]\n", cfg.readFloat("pixel_width"));
  printf("Frame quality: [%d]\n", cfg.readInt("frame_quality"));
  printf("Skip frames: [%d]\n", cfg.readBool("player_skip_frames"));
  printf("Skip frames (as int): [%d]\n", cfg.readInt("player_skip_frames"));
  printf("Frame quality (as bool): [%d]\n", cfg.readBool("frame_quality"));

}

#endif/* __TEST_MIAV_CONFIG*/