#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include "sysdep.h"
#include "version.h"
#include "memory.h"
#include "registry.h"
#include "al.h"

#include "profileP.h"

/* TBD: provide a way to reference one profile component from
 * another:
 *
 *  ActionDirectory: $[LensDirectory]/actions
 * 
 * This probably needs a second pass.  Watch out for nested references.
 * Watch out for recursive references.  Watch out for propagation
 * of changes.
 * 
 */

static Registry the_profile_registry = NULL;
static Bool profile_setup = Bool_FALSE;

NORET AlProfile_init(NOARGS)
{
  int i;
  assert(the_profile_registry == NULL);

  the_profile_registry = Registry_create(Registry_strcmp,
					 Registry_strhash);
  
  /* This starts at 1, not 0, since the 0 element is the error flag, 
     not a real component. */
  for (i = 1; i < ALPROF_NUM_COMPS; ++i) {
    Bool temp;

    temp = Registry_add(the_profile_registry,
			(VOIDP)profile_components[i].name,
			(VOIDP)&profile_components[i]);
    assert(temp == Bool_TRUE);
    profile_comp_values[i].tag = ALPROFT_VOID;
  }
}

static NORET profile_error(msg, path)
     const char *msg;
     const char *path;
{
  static char err_buf[200];

  (NORET)sprintf(&err_buf[0], msg, path);
  Al_fatal_error(&err_buf[0]);
  abort();			/* Al_fatal_error should not return */
}

static NORET profile_warning(msg, path)
     const char *msg;
     const char *path;
{
  static char err_buf[200];

  (NORET)sprintf(&err_buf[0], msg, path);
  Al_warning(&err_buf[0]);
}

static NORET interpret_value_string(out, in, path)
     char *out;
     const char *in;
     const char *path;
{
  char *outp = out;
  const char *inp;

  for (inp = in; *inp != '\0'; ++inp) {
    switch (*inp) {
    case '\\':
      if (*(inp+1) == '\0') {
	*outp++ = '\\';
	*outp = '\0'; 
	return;
      }
      *outp++ = *++inp;
      break;
    case '$':
      if (*(inp+1) != '(')
	*outp++ = '$';
      else {
	const char *temp = (const char *)strchr(inp+2, (char *)')');
	if (temp == (const char *)NULL)
	  *outp++ = '$';
	else {
	  char env_var_name[100], *env_var_val;

	  strncpy(&env_var_name[0], inp+2, temp-(inp+2));
	  env_var_name[temp-(inp+2)] = '\0';
	  env_var_val = getenv(&env_var_name[0]);
	  if (env_var_val == NULL) {
	    profile_error("Undefined environment variable in profile %s",
			  path);
	  } else {
	    strcpy(outp, env_var_val);
	    outp = (char *)strchr(outp, '\0');
	    inp = temp;
	  }
	}
      }
      break;
    default:
      *outp++ = *inp;
    }
  }
  *outp = '\0';
}

static NORET AlProfile_read_file(path)
     const char *path;
{
  FILE *prf;
  static char profile_line[1000];
  static char key_buf[100], value_buf[1000];
  int line_len;
  const char *value_ptr;
  const char *colon_ptr;
  AlProfile_Component comp_being_set;

  if (AlProfile_has_value(ALPROF_NEXTPTH))
    AlProfile_unset_val(ALPROF_NEXTPTH);
  if ((prf = fopen(path, "r")) == (FILE *)NULL) {
    return;
  }

  profile_line[(sizeof profile_line)-1] = '\0';
  while(fgets(&profile_line[0], (sizeof profile_line)-1, prf) != NULL) {
    line_len = strlen(profile_line);
    profile_line[--line_len] = '\0'; /* remove/replace '\n' */
    if (line_len == 0 || profile_line[0] == '#')
      continue;
    if (line_len == (sizeof profile_line)-2)
      profile_error("Line too long in profile %s", path);

    colon_ptr = strchr(&profile_line[0], ':');
    if (colon_ptr == (const char *)NULL)
      profile_error("Missing : in profile %s", path);
    strncpy(&key_buf[0], &profile_line[0], colon_ptr-&profile_line[0]);
    key_buf[colon_ptr-&profile_line[0]] = '\0';
    comp_being_set = AlProfile_get_code(&key_buf[0]);
    if (comp_being_set == ALPROF_NONEXST)
      profile_error("Invalid profile component in %s", path);

    value_ptr = colon_ptr + 1;
    while (*value_ptr != '\0' &&
	   isspace(*value_ptr))
      ++value_ptr;
    if (*value_ptr == '\0') {
      if (AlProfile_has_value(comp_being_set)) {
	AlProfile_unset_val(comp_being_set);
      }
    } else {
      interpret_value_string(&value_buf[0], value_ptr, path);
      switch (AlProfile_get_type(comp_being_set)) {
      case ALPROFT_CHAR_PTR:
	AlProfile_set_CP_val(comp_being_set, &value_buf[0]);
	break;
      case ALPROFT_BOOL:
	if (strcmp(&value_buf[0], "True") == 0)
	  AlProfile_set_BL_val(comp_being_set, Bool_TRUE);
	else if (strcmp(&value_buf[0], "False") == 0)
	  AlProfile_set_BL_val(comp_being_set, Bool_FALSE);
	else
	  profile_error("Boolean parse error in profile %s", path);
	break;
      default:
	abort();		/* other parsers not implemented */
      }
    }
  }

  if (fclose(prf) == EOF)
    profile_error("Can not close profile %s", path);
}

NORET AlProfile_setup(NOARGS)
{
  const char *path;
  assert(the_profile_registry != NULL);

  path = (const char *)strcat(
	                 strcat(
                           strcpy(
				  (char *)Memory_allocate(
				      strlen(SYSTEM_LENS_PROFILE)+
				      strlen(&AlVersion[0])+2),
				  SYSTEM_LENS_PROFILE),
				"."),
			      &AlVersion[0]);
  while (path != NULL) {
    AlProfile_read_file(path);
    Memory_free((VOIDP)path), path = NULL;
    if (AlProfile_has_value(ALPROF_NEXTPTH)) {
      path = (char *)AlProfile_get_CP_val(ALPROF_NEXTPTH);
      path = strcpy((char *)Memory_allocate(strlen(path)+1), path);
    }
    profile_setup = Bool_TRUE;
  }
}

AlProfile_Component AlProfile_get_code(name)
     const char *name;
{
  struct profile_comp_desc *comp_desc_p;
  
  assert(the_profile_registry != NULL);
  comp_desc_p = 
    (struct profile_comp_desc *)Registry_get(the_profile_registry, 
					     (CONSTVOIDP)name);
  if (comp_desc_p == (struct profile_comp_desc *)NULL) 
    return ALPROF_NONEXST;
  else
    return comp_desc_p->code;
}

AlProfile_Comp_Type AlProfile_get_type(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);

  return profile_components[code].type;
}

const char *AlProfile_get_name(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);

  return profile_components[code].name;
}

Bool AlProfile_has_value(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);

  if (profile_comp_values[code].tag == ALPROFT_VOID)
    return Bool_FALSE;
  else
    return Bool_TRUE;
}

const char *AlProfile_get_CP_val(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_CHAR_PTR);
  assert(AlProfile_has_value(code) == Bool_TRUE);

  return profile_comp_values[code].val.char_ptr_val;
}

Bool AlProfile_get_BL_val(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_BOOL);
  assert(AlProfile_has_value(code) == Bool_TRUE);

  return profile_comp_values[code].val.bool_val;
}

unsigned int AlProfile_get_UI_val(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_UINT);
  assert(AlProfile_has_value(code) == Bool_TRUE);

  return profile_comp_values[code].val.uns_int_val;
}

int AlProfile_get_IN_val(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_INT);
  assert(AlProfile_has_value(code) == Bool_TRUE);

  return profile_comp_values[code].val.int_val;
}

double AlProfile_get_DB_val(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_DOUBLE);
  assert(AlProfile_has_value(code) == Bool_TRUE);

  return profile_comp_values[code].val.double_val;
}

NORET AlProfile_unset_val(code)
     AlProfile_Component code;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_comp_values[code].tag != ALPROFT_VOID);

  if (profile_comp_values[code].tag == ALPROFT_CHAR_PTR) 
    Memory_free((VOIDP)profile_comp_values[code].val.char_ptr_val);
  profile_comp_values[code].tag = ALPROFT_VOID;
}

NORET AlProfile_set_CP_val(code, val)
     AlProfile_Component code;
     const char *val;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_CHAR_PTR);

  if (AlProfile_has_value(code) == Bool_TRUE)
    AlProfile_unset_val(code);
  profile_comp_values[code].tag = ALPROFT_CHAR_PTR;
  profile_comp_values[code].val.char_ptr_val = 
    (char *)strcpy((char *)Memory_allocate(strlen(val)+1), val);
}

NORET AlProfile_set_BL_val(code, val)
     AlProfile_Component code;
     Bool val;
{
  assert(the_profile_registry != NULL);
  assert(code > ALPROF_NONEXST && code < ALPROF_NUM_COMPS);
  assert(profile_components[code].type == ALPROFT_BOOL);

  profile_comp_values[code].tag = ALPROFT_BOOL;
  profile_comp_values[code].val.bool_val = val;
}


