#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "conf.h"
#include "err.h"
#include "mem.h"


/* Allocates memory and loads a file, returns file length
 */

static int load_file (char *path, void **mem)
{
  int fd, len;
  
  if ((fd = open (path, O_RDONLY)) == -1)
    return ERROR;
  
  len = lseek (fd, 0, SEEK_END);
  lseek (fd, 0, SEEK_SET);
  
  mem_resize (mem, len);
  
  if (read (fd, *mem, len) != len) {
    free (*mem);
    *mem = 0;
    close (fd);
    return ERROR;
  }
  close (fd);
  
  return len;
}


/* Returns ERROR if there are no more lines
 */

static int next_line (struct CONFIG *cfg)
{
  while (cfg->line_pos < cfg->file_len){
    if (cfg->file[cfg->line_pos] == 10) {
      cfg->line_pos++;
      break;
    }
    cfg->line_pos++;
  }
  if (cfg->line_pos >= cfg->file_len)
    return ERROR;
  
  return E_OK;
}


/* Returns ERROR if there are no characters on the line
 */

static int set_line_end (struct CONFIG *cfg)
{
  char c;
  int pos = cfg->line_pos;
  
  while (pos < cfg->file_len) {
    c = cfg->file[pos];
    if (c == 10 || c == '#') {
      pos--;
      break;
    }
    pos++;
  }
  if (pos < cfg->line_pos)
    return ERROR;
  
  cfg->line_end = pos;
  return E_OK;
}


/* Returns ERROR if there are no more arguments
 */

static int next_arg (struct CONFIG *cfg)
{
  char c;
  
  while (cfg->arg_pos <= cfg->line_end) {
    c = cfg->file[cfg->arg_pos];
    if (c != 9 && c != 32)
      break;
    cfg->arg_pos++;
  }
  if (cfg->arg_pos > cfg->line_end)
    return ERROR;
  
  return E_OK;
}


/* Returns length of argument
 */

static int get_arg_len (struct CONFIG *cfg)
{
  int c, len = 0;
  
  while (cfg->arg_pos <= cfg->line_end) {
    c = cfg->file[cfg->arg_pos];
    if (c == 9 || c == 32)
      break;
    
    cfg->arg_pos++;
    len++;
  }
  return len;
}


static void evaluate (struct CONFIG *cfg)
{
  int arg_pos, arg_len, args, fno = 0, str_len, i;
  char *s1 = 0, *s2;
  
  do {
    if (set_line_end (cfg) == E_OK) {
      cfg->arg_pos = cfg->line_pos;
      args = 0;
      str_len = 0;
      while (next_arg (cfg) != ERROR) {
	arg_pos = cfg->arg_pos;
	arg_len = get_arg_len (cfg);
	
	if (!args) {
	  fno = cfg->formats;
	  cfg->formats++;
	  mem_resize ((void **)&cfg->format,
		      cfg->formats * sizeof (struct CFG_FORMAT));
	  s1 = cfg->format[fno].strings;
	}
	
	str_len += arg_len + 1;
	if (str_len > CFG_STRLEN) {
	  fprintf (stderr, "error: Arguments in juke.conf longer "
		   "than %d characters\n", CFG_STRLEN);
	  exit (ERROR);
	}
	
	s2 = cfg->file + arg_pos;
	for (i=arg_len; i>0;i--)
	  *s1++ = *s2++;
	*s1++ = 0;
	
	args++;
	if (args > CFG_ARGS) {
	  fprintf (stderr, "error: Arguments in juke.conf more "
		   "than %d\n", CFG_ARGS);
	  exit (ERROR);
	}
      }
      cfg->format[fno].args = args;
      cfg->format[fno].match_len = strlen (cfg->format[fno].strings);
    }
  } while (next_line (cfg) == E_OK);
}


static void build_arg_lists (struct CONFIG *cfg)
{
  int i, j, pos, len;
  
  for (i=0;i<cfg->formats;i++) {
    pos = cfg->format[i].match_len + 1;
    for (j=0; j<cfg->format[i].args-1; j++) {
      len = strlen (cfg->format[i].strings + pos);
      cfg->format[i].arg_list[j] = cfg->format[i].strings + pos;
      pos += len + 1;
    }
    cfg->format[i].arg_list[j+1] = 0;
  }
}


void config_load (struct CONFIG *cfg)
{
  char *home, config[]="/.juke.conf", *str = 0;
  int str_len;
  
  if ((home = getenv ("HOME"))) {
    str_len = strlen (home) + strlen (config) + 1;
    mem_resize ((void *)&str, str_len);
    *str = 0;
    strcat (str, home);
    strcat (str, config);
    
    cfg->file_len = load_file (str, (void **)&cfg->file);
    free (str);
    
    if (cfg->file_len != ERROR) {
      evaluate (cfg);
      if (! cfg->formats) {
	fprintf (stderr, "error: No formats in ~/.juke.conf\n");
	exit (ERROR);
      }
      build_arg_lists (cfg);
      free (cfg->file);
      return;
    }
  }
  cfg->file_len = load_file ("/etc/juke.conf", (void **)&cfg->file);
  if (cfg->file_len != ERROR) {
    evaluate (cfg);
    if (! cfg->formats) {
      fprintf (stderr, "error: No formats in /etc/juke.conf\n");
      exit (ERROR);
    }
    build_arg_lists (cfg);
    free (cfg->file);
  } else {
    fprintf (stderr, "error: Could not find any configuration file\n");
    exit (ERROR);
  }
}
