/*
 * Anthyの設定のデータベース
 * conf_initで設定される変数とconf_overrideで設定される
 * 変数の関係に注意
 *
 * Copyright (C) 2000-2007 TABATA Yusuke
 */
/*
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <anthy/alloc.h>
#include <anthy/conf.h>
#include <anthy/logger.h>

#include <config.h>

/** 設定の変数と値 */
struct val_ent {
  /** 変数名 */
  const char *var;
  /** 値 */
  const char *val;
  /** リストの次要素 */
  struct val_ent *next;
};

static struct val_ent *ent_list;
/** 初期化済みフラグ */
static int confIsInit;
static allocator val_ent_ator;

static void
val_ent_dtor(void *p)
{
  struct val_ent *v = p;
  free((void *)v->var);
  if (v->val) {
    free((void *)v->val);
  }
}

/** 変数名に対応するval_entを取得する */
static struct val_ent *
find_val_ent(const char *v)
{
  struct val_ent *e;
  for (e = ent_list; e; e = e->next) {
    if(!strcmp(v, e->var)) {
      return e;
    }
  }
  e = malloc(sizeof(struct val_ent));
  if (!e) {
    return NULL;
  }
  e->var = strdup(v);
  e->val = 0;
  e->next = ent_list;
  ent_list = e;
  return e;
}

/** ${変数名}の形の変数の値を取得する
 */
static const char *
get_subst(const char *s)
{
  if (s[0] == '$' && s[1] == '{' &&
      strchr(s, '}')) {
    struct val_ent *val;
    char *var = strdup(&s[2]);
    char *k = strchr(var, '}');
    *k = 0;
    val = find_val_ent(var);
    free(var);
    if (!val || !val->val) {
      return "";
    }
    return val->val;
  }
  return NULL;
}

struct expand_buf {
  int len;
  int size;
  char *buf;
  char *cur;
};

static void
ensure_buffer(struct expand_buf *eb, int count)
{
  int required = count - (eb->size - eb->len) + 16;
  if (required > 0) {
    eb->size += required;
    eb->buf = realloc(eb->buf, eb->size);
    eb->cur = &eb->buf[eb->len];
  }
}

static char *
expand_string(const char *s)
{
  struct expand_buf eb;
  char *res;
  eb.size = 256;
  eb.buf = malloc(eb.size);
  eb.cur = eb.buf;
  eb.len = 0;

  while (*s) {
    const char *subst = get_subst(s);
    if (subst) {
      int len = strlen(subst);
      ensure_buffer(&eb, len + 1);
      *eb.cur = 0;
      strcat(eb.buf, subst);
      eb.cur += len;
      eb.len += len;
      s = strchr(s, '}');
      s ++;
    } else {
      *eb.cur = *s;
      /**/
      eb.cur ++;
      s++;
      eb.len ++;
    }
    /**/
    ensure_buffer(&eb, 256);
  }
  *eb.cur = 0;
  /**/
  res = strdup(eb.buf);
  free(eb.buf);
  return res;
}

static void
add_val(const char *var, const char *val)
{
  struct val_ent *e;
  e = find_val_ent(var);
  if (e->val) {
    free((void *)e->val);
  }
  e->val = expand_string(val);
}

static void
read_conf_file(void)
{
  const char *fn;
  FILE *fp;
  char buf[1024];
  fn = anthy_conf_get_str("CONFFILE");
  fp = fopen(fn, "r");
  if (!fp){
    anthy_log(0, "Failed to open %s\n", fn);
    return ;
  }
  while(fgets(buf, 1024, fp)) {
    if (buf[0] != '#') {
      char var[1024], val[1024];
      if (sscanf(buf, "%s %s", var, val) == 2){
	add_val(var, val);
      }
    }
  }
  fclose(fp);
}

void
anthy_do_conf_override(const char *var, const char *val)
{
  if (!strcmp(var,"CONFFILE")) {
    add_val(var, val);
    anthy_do_conf_init();
  }else{
    anthy_do_conf_init();
    add_val(var, val);
  }
}

/* ユニークなセッションIDを確保する */
#define SID_FORMAT	"%s-%08x-%05d" /* HOST-TIME-PID */
#define MAX_SID_LEN  	(MAX_HOSTNAME+8+5+2)
#define MAX_HOSTNAME 	64

static void
alloc_session_id(void)
{
  time_t t;
  pid_t pid;
  char hn[MAX_HOSTNAME];
  char sid[MAX_SID_LEN];
  t = time(0);
  pid = getpid();
  gethostname(hn, MAX_HOSTNAME);
  hn[MAX_HOSTNAME-1] = '\0';
  sprintf(sid, SID_FORMAT, hn, (unsigned int)t & 0xffffffff, pid & 0xffff);
  add_val("SESSION-ID", sid);
}

void
anthy_do_conf_init(void)
{

  if (!confIsInit) {
    const char *fn;
    struct passwd *pw;
    val_ent_ator = anthy_create_allocator(sizeof(struct val_ent), val_ent_dtor);
    /*デフォルトの値を設定する。*/
    add_val("VERSION", VERSION);
    fn = anthy_conf_get_str("CONFFILE");
    if (!fn){
      add_val("CONFFILE", CONF_DIR"/anthy-conf");
    }
    pw = getpwuid(getuid());
    add_val("HOME", pw->pw_dir);
    alloc_session_id();
    read_conf_file();
    confIsInit = 1;
  }
}

void
anthy_conf_free(void)
{
  struct val_ent *e, *next;
  for (e = ent_list; e; e = next) {
    free((char *)e->var);
    free((char *)e->val);
    next = e->next;
    free(e);
  }
  ent_list = NULL;
  confIsInit = 0;
}

const char *
anthy_conf_get_str(const char *var)
{
  struct val_ent *e;
  e = find_val_ent(var);
  return e->val;
}
