#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <assert.h>

#include <savant.h>
#include <template.h>
   
static Template *read_new_template(FILE *);
static Pattern *parse_pattern(FILE *);
static Pattern_Token *parse_token(FILE *);
static int set_variable(char *, char *);
static int read_word(FILE *, char *, size_t);

List_of_Templates *All_Templates;
UserVars Config;

#if !defined(PATH_MAX) || (PATH_MAX < 1024)
#define PATHLEN 1023
#else
#define PATHLEN PATH_MAX
#endif

int load_config(char *config_name)
{
  char word[64], value[64], config_path[PATHLEN+1];
  List_of_Templates *AT_tail;
  FILE *config_file;

  /* set initial defaults for Config */
  Config.windowing = 1;
  Config.lines_per_window = 50;
  Config.source_field_width = 17;
  Config.ellipses = 1;
  Config.index_dotfiles = 0;

  /* Determine where the config file is at */
  if (config_name == NULL) {  /* use $HOME/.savantrc */
    strncpy(config_path, getenv("HOME"), PATHLEN - 10);
    config_path[PATHLEN-11] = '\0';
    strcat(config_path, "/.savantrc");
  } else if(config_name[0] != '/') {  /* if not absolute pathname */
#ifdef HAVE_GETCWD
    getcwd(config_path, PATHLEN);
#else
    getwd(config_path); /* and hope this doesn't overrun. :-( */
#endif
    strcat(config_path, "/");
    strncat(config_path, config_name, PATHLEN-strlen(config_path));
  } else    /* absolute pathname: just use this pathname */
    strncpy(config_path, config_name, PATHLEN); 
  config_path[PATHLEN-1] = '\0';

  if(SavantVerbose) {
    printf("Reading configuration file...\n");
    fflush(stdout);
  }

  All_Templates = AT_tail = NULL;

  if((config_file = fopen(config_path,"r")) == NULL) {
    fprintf(stderr,"load_config: Unable to open config-file %s\n",
	    config_path);
    fflush(stderr);
    exit(-1);
  }

  while(read_word(config_file, word, sizeof(word)) != EOF) {
    if (strcasecmp(word, "template") == 0) {
      if(All_Templates == NULL) {
	All_Templates = AT_tail =
	  (List_of_Templates *)malloc(sizeof(List_of_Templates));
	AT_tail->template = read_new_template(config_file);
	AT_tail->next = NULL;
      }
      else {
	AT_tail->next = 
	  (List_of_Templates *)malloc(sizeof(List_of_Templates));
	AT_tail->next->template = read_new_template(config_file);
	AT_tail = AT_tail->next;
	AT_tail->next = NULL;
      }
      /*
      printf("Template \"%s\"; Biases: ", AT_tail->template->name); 
      for(i=0; i<NUM_FIELD_TYPES; i++) {
	printf("%d, ", AT_tail->template->biases[i]);
      }
      printf("\n");
      */
    }
    else {
      read_word(config_file, value, sizeof(word));
      /* should we check for EOF here? */
      set_variable(word, value);
    }
  } 

  fclose(config_file);

  return(0);
}

static Template *read_new_template(FILE *config_file)
{
  int i;
  char str[64];
  Template *new_tmpl = (Template *)malloc(sizeof(Template));

  read_word(config_file, str, sizeof(str));
#ifdef HAVE_STRDUP
  new_tmpl->name = strdup(str);
#else
  new_tmpl->name = malloc(strlen(str));
  if (new_tmpl->name != NULL)
    strcpy(new_tmpl->name, str);
#endif

  read_word(config_file, str, sizeof(str));
  if (str[0] != '{') {
    fprintf(stderr, "Error reading template: '{' expected.\n");
    exit(-1);
  }

  read_word(config_file, str, sizeof(str));
  if (strcasecmp(str, "recognize") != 0) {
    fprintf(stderr, "Error reading template: \"Recognize\" expected.\n");
    exit(-1);
  }
  new_tmpl->recognize = parse_pattern(config_file);

  read_word(config_file, str, sizeof(str));
  if (strcasecmp(str, "delimiter") == 0) {
    new_tmpl->delimiter = parse_pattern(config_file);
    read_word(config_file, str, sizeof(str));
    if (strcasecmp(str, "format") != 0) {
      fprintf(stderr, "Error reading template: \"Format\" expected.\n");
      exit(-1);
    }
    new_tmpl->format = parse_pattern(config_file);
  }
  else if (strcasecmp(str, "format") == 0) {
    new_tmpl->delimiter = NULL;
    new_tmpl->format = parse_pattern(config_file);
  }
  else if (strcasecmp(str, "reject") == 0) {
    new_tmpl->delimiter = new_tmpl->format = NULL;
  }
  else {
    fprintf(stderr, "Error reading template: \"Delimiter\", \"Format\" or \"Reject\" expected.\n");
    exit(-1);
  }

  /* Here's the new code for bias variables */
  read_word(config_file, str, sizeof(str));
  if (strcasecmp(str, "bias") == 0) {
    for(i=0; i<NUM_FIELD_TYPES; i++) {
      read_word(config_file, str, sizeof(str));
      new_tmpl->biases[i] = atoi(str);
    }
    read_word(config_file, str, sizeof(str));
  }
  else {  /* default bias values */
    for(i=0; i<NUM_FIELD_TYPES; i++) {
      new_tmpl->biases[i] = 1;
    }
  }
  
  if (str[0] != '}') {
    fprintf(stderr, "Error reading template: '}' expected.\n");
    exit(-1);
  }

  return(new_tmpl);
}

static Pattern *parse_pattern(FILE *config_file)
{

  Pattern *head = NULL, *tail = NULL, *prev=NULL;
  static int nest_level = 0;

  do {  /* this loop builds the block structure of the text into the pattern */
    if (head == NULL) {
      head = tail = 
	(Pattern *)malloc(sizeof(Pattern));
    }
    else {
      prev = tail;
      tail->next = (Pattern *)malloc(sizeof(Pattern));
      tail = tail->next;
    }
    tail->next = NULL;
    tail->token = parse_token(config_file);
    switch(tail->token->ID) {
    case LBRACE:
      nest_level++;
      tail->token->ID = PATTERN;
      tail->token->ptr.pat = parse_pattern(config_file);
      break;
    case RBRACE:
      nest_level--;
      free(tail->token);
      free(tail);
      prev->next = NULL;
      return(head);
      break;
    default:
      break;
    }
  } while (nest_level > 0);

  return(head);
}

static Pattern_Token *parse_token(FILE *file)
{
  int cur_line, ptr;
  char word[64];
  signed char literal[256];
  Pattern_Token *token;

  cur_line = read_word(file, word, sizeof(word));
  if(cur_line == EOF) {
    fprintf(stderr, "Error: unexpected EOF while parsing pattern.\n");
    fflush(stderr);
    exit(-1);
  }

  token = malloc(sizeof(Pattern_Token));
  token->ptr.str = NULL;
  switch(word[0]) {
  case '{':
    token->ID = LBRACE;
    break;
  case '}':
    token->ID = RBRACE;
    break;
  case '"':
    token->ID = LITERAL;
    literal[0] = fgetc(file);
    if(literal[0] != '"') {
      ptr = 1;
      literal[1] = fgetc(file);
      while((literal[ptr] != '"') || (literal[ptr-1] == '\\')) {
	if(literal[ptr] == EOF) {
	  fprintf(stderr, 
		  "Error in config file, line %d: EOF while reading string.\n", 
		  cur_line);
	  exit(-1);
	}
	if(ptr == 255) {
	  fprintf(stderr, 
		  "Error in config file, line %d: string too long.\n", cur_line);
	  exit(-1);
	}
	literal[++ptr] = fgetc(file);
      }
      literal[ptr] = '\0';
      token->ptr.str = strdup((char *)literal);
    }
    else {
      fprintf(stderr, 
	      "Error in config file, line %d: empty string.\n", cur_line);
      exit(-1);
    }
    break;
  default: /* ignore, no_order, or TEXT[%d] */
    if (strcasecmp(word, "startline") == 0) {
      token->ID = STARTLINE;
    } 
    else if (strcasecmp(word, "ignore") == 0) {
      token->ID = IGNORE;
    } 
    else if (strcasecmp(word, "anyorder") == 0) {
      token->ID = ANYORDER;
    }
    else if (strcasecmp(word, "anyof") == 0) {
      token->ID = ANYOF;
    }
    else if (strcasecmp(word, "icase") == 0) {
      token->ID = ICASE;
    }
    else if (strcasecmp(word, "optional") == 0) {
      token->ID = OPTIONAL;
    }
    else if (strcasecmp(word, "subject") == 0) {
      token->ID = SUBJECT;
    }
    else if (strcasecmp(word, "location") == 0) {
      token->ID = LOCATION;
    }
    else if (strcasecmp(word, "date") == 0) {
      token->ID = DATE;
    }
    else if (strcasecmp(word, "source") == 0) {
      token->ID = SOURCE;
    }
    else if (strcasecmp(word, "body") == 0) {
      token->ID = BODY;
    }
    else {
      fprintf(stderr, "unknown token at line %d in config file: %s\n",
	      cur_line, word);
      fflush(stderr);
      exit(-1);
    }
    break;
  }   

  return(token);
}


static int set_variable(char *var, 
			char *value)
{
  if (strcasecmp(var, "source_field_width") == 0) {
    if (isdigit(value[0])) {
      Config.source_field_width = atoi(value);
    }
    else if (SavantVerbose) {
      fprintf(stderr, "Bad value for source_field_width: %s.  Using default, %d\n",
	      value, Config.source_field_width);
      fflush(stderr);
    }
  }
  else if (strcasecmp(var, "ellipses") == 0) {
    if ((strcasecmp(value, "false") == 0) ||
	(strcasecmp(value, "off") == 0) ||
	(strcasecmp(value, "no") == 0))
      Config.ellipses = 0;
  }
  else if (strcasecmp(var, "document_windowing") == 0) {
    if ((strcasecmp(value, "false") == 0) ||
	(strcasecmp(value, "off") == 0) ||
	(strcasecmp(value, "no") == 0))
      Config.windowing = 0;
  }
  else if (strcasecmp(var, "lines_per_window") == 0) {
    if (isdigit(value[0])) {
      Config.lines_per_window = atoi(value);
    }
    else if (SavantVerbose) {
      fprintf(stderr, "Bad value for lines_per_window: %s.  Using default, %d\n", 
	      value, Config.lines_per_window);
      fflush(stderr);
    }
  }
  else if (strcasecmp(var, "index_dotfiles") == 0) {
    if ((strcasecmp(value, "true") == 0) ||
	(strcasecmp(value, "on") == 0) ||
	(strcasecmp(value, "yes") == 0))
      Config.index_dotfiles = 1;
  }
  else if (SavantVerbose) {
    fprintf(stderr, "Ignoring unrecognized config variable %s.\n", var);
    fflush(stderr);
  }
  
  return(0);
}

static int read_word(FILE *file, 
		     char *str,
		     size_t len)
{
  int i=0, maybe_eof=0;
  static int linecount = 0;
  char discard[256];
  static char overread='\0';

  assert(len > 2);

  if (overread != '\0') { /* could be {, }, / or " */
    str[0] = overread;
    overread = '\0';
  }
  else do {  /* discard whitespace and commas, and comments */
    str[0] = maybe_eof = fgetc(file);
    if(str[0] == '\n')
      linecount++;
    else if(str[0] == '/') {
      fgets(discard, 255, file); /* discard rest of line */
      linecount++;
      str[0] = ' ';
    }
  } while (isspace(str[0]) || (str[0] == ','));
  
  if (maybe_eof == EOF) {
    str[0] = '\0';
    return (EOF);
  }
  else if((str[0] == '{') || (str[0] == '}') || (str[0] == '"'))
    str[1] = '\0';
  else {
    do {
      str[++i] = maybe_eof = fgetc(file);
      if(str[i] == '\n') 
	linecount++;
    } while (!isspace(str[i]) && (str[i] != ',') &&
	     (str[i] != '{') && (str[i] != '}') &&
	     (str[i] != '"') && (str[i] != '/') &&
	     (maybe_eof != EOF) && (i < len));
    if(maybe_eof == EOF) {
      str[i] = '\0';
      return(EOF);
    }
    else if(str[i] == '\n') {
      linecount++;
    }
    else if(str[i] == '/') {
      fgets(discard, 255, file); /* discard rest of line */
      linecount++;
    }
    else if ((str[i] == '{') || (str[i] == '}') || (str[i] == '"')) {
      overread = str[i];
    }
    str[i] = '\0';
  }

  return(linecount);

}


