/*
 * Yet another daemon library especially designed to be used
 * with libsxmp based daemons.
 *
 * (c) Alexander Vdolainen 2016 <avdolainen@zoho.com>
 *
 * libsxmp 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 3 of the License, or
 * (at your option) any later version.
 *
 * libsxmp 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 program.  If not, see <http://www.gnu.org/licenses/>.";
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <ctype.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <tdata/usrtc.h>
#include <sexpr/sexp.h>

#include <ydaemon/ydaemon.h>

static long __cmp_cstr(const void *a, const void *b)
{
  return strcmp((const char *)a, (const char *)b);
}

/* config entries */
static scret_t __ymconf_create_pgroup(yd_context_t *cnf, sexp_t *sx, void *priv)
{
  char *grp_name;
  scret_t rets;
  int r = EINVAL;

  /* get the group name */
  sx->list = sx->list->next;
  if(!(sx->list)) { RETURN_SRET_IRES(rets, r); }
  else grp_name = sx->list->val;

  /* now we will add this */
  r = ydc_conf_create_pgroup(cnf->values, (char *)grp_name);
  RETURN_SRET_IRES(rets, r);
}

static scret_t __module_add(yd_context_t *ctx, sexp_t *sx, void *priv)
{
  char *mod_name, *mod_type;
  yd_mod_type_t type;
  scret_t rets;
  int r = 0;

  /* get the name */
  sx->list = sx->list->next;
  if(!(sx->list)) { r = EINVAL; goto __fail; }
  else mod_name = sx->list->val;

  sx->list = sx->list->next;
  if(!(sx->list)) { r = EINVAL; goto __fail; }
  else mod_type = sx->list->val;

  if(!strcmp(mod_type, "service")) type = YD_SERVICE_MUX;
  else if(!strcmp(mod_type, "sxmp")) type = YD_SXMP_MOD;
  else if(!strcmp(mod_type, "datastore")) type = YD_DATASTORE_MOD;
  else if(!strcmp(mod_type, "other")) type = YD_OTHER;
  else r = EINVAL;

  if(!r) r = yd_mod_add(ctx, mod_name, type);

 __fail:
  RETURN_SRET_IRES(rets, r);
}

static scret_t __object(yd_context_t *ctx, sexp_t *sx, void *priv)
{
  char *mod_name, *mod_obj;
  yd_mod_t *lmod;
  scret_t rets;
  int r = 0;
  void *fdata;

  /* get the name */
  sx->list = sx->list->next;
  if(!(sx->list)) { r = EINVAL; goto __fail; }
  else mod_name = sx->list->val;

  sx->list = sx->list->next;
  if(!(sx->list)) { r = EINVAL; goto __fail; }
  else mod_obj = sx->list->val;

  r = yd_mod_get(ctx, mod_name, &lmod);
  if(r) {
    RETURN_SRET_SERR(rets, r);
  } else fdata = yd_mod_getobject(ctx, lmod, NULL, mod_obj);

  if(!fdata) {
    r = ENOENT;
    goto __fail;
  }

  RETURN_SRET_ORES(rets, fdata);

 __fail:
  RETURN_SRET_IRES(rets, r);
}

static scret_t __module_load(yd_context_t *ctx, sexp_t *sx, void *priv)
{
  register int state = 0;
  register int idx;
  yd_mod_t *lmod;
  sexp_t *isx;
  int r = EINVAL;
  scret_t rets;

  SEXP_ITERATE_LIST(sx, isx, idx) {
    if(isx->ty == SEXP_LIST) goto __fail;
    else {
      if(!state) {
        if(strcmp(isx->val, MODULE_LOAD_SX)) goto __fail;
        else state++;
      } else { /* find module */
        if(isx->aty != SEXP_SQUOTE) goto __fail;

        r = yd_mod_get(ctx, (const char *)isx->val, &lmod);
        if(r) goto __fail;

        /* load it */
        r = yd_mod_load(ctx, lmod);
      }
    }
  }

 __fail:
  RETURN_SRET_IRES(rets, r);
}

static scret_t __module_run(yd_context_t *ctx, sexp_t *sx, void *priv)
{
  register int state = 0;
  register int idx;
  yd_mod_t *lmod;
  sexp_t *isx;
  int r = EINVAL;
  scret_t rets;

  SEXP_ITERATE_LIST(sx, isx, idx) {
    if(isx->ty == SEXP_LIST) goto __fail;
    else {
      if(!state) {
        if(strcmp(isx->val, MODULE_RUN_SX)) goto __fail;
        else state++;
      } else { /* find module */
        if(isx->aty != SEXP_SQUOTE) goto __fail;

        r = yd_mod_get(ctx, (const char *)isx->val, &lmod);
        if(r) goto __fail;

        /* load it */
        r = yd_mod_run(ctx, lmod);
      }
    }
  }

 __fail:
  RETURN_SRET_IRES(rets, r);
}

static scret_t __module_set(yd_context_t *ctx, sexp_t *sx, void *priv)
{
  register int state = 0; /* states - 0 begin, 1 - rpc function checked, 2 - module found */
  register int pst = 0; /* 0 - nothing, 1 - name set */
  char *pname, *pval, *pss;
  yd_mod_t *lmod;
  int r = 0, idx, pidx;
  sexp_t *isx, *pisx;
  ydc_conf_val_t *rval;
  scret_t rets;

  r = EINVAL; /* invalid syntax */
  SEXP_ITERATE_LIST(sx, isx, idx) {
    if(isx->ty == SEXP_LIST) {
      r = EINVAL;
      if(state < 2)        goto __err_fail;
      pst = 0;
      SEXP_ITERATE_LIST(isx, pisx, pidx) {
        if(pisx->ty != SEXP_VALUE) { /* values are not acceptable */
          goto __err_fail;
        }

        if(!pst && *(pisx->val) != ':') goto __err_fail; /* syntax error */

        if(!pst) {
          pname = pisx->val + sizeof(char); pst++;
        } else {
          if(pisx->aty == SEXP_DQUOTE || pisx->aty == SEXP_SQUOTE) pval = pisx->val;
          else if((pisx->aty == SEXP_BASIC) && (pss = strchr((const char *)pisx->val, '/'))) {
            /* it's a variable */
            r = ydc_conf_get_val(ctx->values, (const char *)pisx->val, &rval);
            if(r) goto __err_fail;

            if(rval->type != STRING) goto __err_fail;
            else pval = (char *)rval->value;
          } else goto __err_fail;

          /* set the right part of the module */
          if(!strcmp(pname, MODULE_PREFIX)) yd_mod_set_prefix(lmod, pval);
          else if(!strcmp(pname, MODULE_PATHNAME)) yd_mod_set_pathname(lmod, pval);
          else if(!strcmp(pname, MODULE_CNFPATH)) yd_mod_set_cfgpath(lmod, pval);
          else goto __err_fail;
        }
      }
    } else if(isx->ty == SEXP_VALUE) { /* got the value */
      switch(state) {
      case 0:
        if(strcmp((const char *)isx->val, MODULE_SET_SX)) goto __err_fail;
        else state++;
        break;
      case 1:
        if(isx->aty != SEXP_SQUOTE) goto __err_fail;
        else {
          r = yd_mod_get(ctx, (const char *)isx->val, &lmod);
          if(r) goto __err_fail;
          else state++;
        }
        break;
      default:
        goto __err_fail;
      }
    }
  }

  r = 0;

 __err_fail:

  RETURN_SRET_IRES(rets, r);
}

static scret_t __ymconf_set_cv(yd_context_t *cnf, sexp_t *sx, void *priv)
{
  char *value_expression, *value;
  char *buf = priv;
  scret_t rets;
  int r = EINVAL;
  int cstr = 0;

  /* skip keyword */
  sx->list = sx->list->next;
  /* check out this */
  if(sx->ty != SEXP_LIST) { RETURN_SRET_IRES(rets, r); }
  else sx = sx->list;
  value_expression = sx->list->val;
  sx->list = sx->list->next;
  //if(sx->ty == SEXP_LIST) return -EINVAL;
  value = sx->list->val;
  /* check for the forcing of cstring */
  if(sx->list->aty == SEXP_DQUOTE) cstr = 1;

  snprintf(buf, sizeof(char)*4096, "%s:%s", value_expression, value);

  r = ydc_conf_add_val_p(cnf->values, (const char *)buf, cstr);

  RETURN_SRET_IRES(rets, r);
}

static scret_t __ymconf_uselogfile(yd_context_t *cnf, sexp_t *sx, void *priv)
{
  char *logfile;
  FILE *ls;
  ydc_conf_val_t *rval;
  scret_t rets;
  int r = EINVAL;

  r = ydc_conf_get_val(cnf->values, "daemon/logfile", &rval);
  if(r) goto __fini;
  if(rval->type != STRING) goto __fini;
  else logfile = (char *)rval->value;

  ls = fopen(logfile, "a+");
  if(!ls) r = errno;
  else {
    cnf->logcontext->logstream = ls;
    setbuf(ls, NULL);
    r = 0;
  }

  if(cnf->daemon && !r) {
    fclose(stdout);
    fclose(stderr);
    stdout = fopen(logfile, "a+");
    stderr = fopen(logfile, "a+");
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
  }

 __fini:
  RETURN_SRET_IRES(rets, r);
}

int yd_init_ctx(yd_context_t *ctx)
{
  int r = 0;
  usrtc_t *services = malloc(sizeof(usrtc_t));
  usrtc_t *modules = malloc(sizeof(usrtc_t));
  scm_function_tree_t *ft = NULL;
  ydc_conf_t *c = malloc(sizeof(ydc_conf_t));
  yd_log_t *zdl = malloc(sizeof(yd_log_t));
  char *pbuf = malloc(sizeof(char)*4096);

  if(!ctx) return EINVAL;
  else memset(ctx, 0, sizeof(yd_context_t));

  if((r = pthread_rwlock_init(&ctx->modlock, NULL))) goto __fini;
  if((r = pthread_mutex_init(&ctx->looplock, NULL))) {
    pthread_rwlock_destroy(&ctx->modlock);
    goto __fini;
  }

  if(!zdl) goto __fail;
  /* init trees */
  if(!services || !modules) goto __fail;
  else {
    usrtc_init(services, USRTC_SPLAY, MAX_SERVICES, __cmp_cstr);
    usrtc_init(modules, USRTC_SPLAY, MAX_MODULES, __cmp_cstr);
    ctx->services = services;
    ctx->modules = modules;
    ctx->daemon = 0;
    ctx->logcontext = zdl;
    zdl->logstream = stdout;
    zdl->verbose_level = YL_INFO;
    zdl->se = 1;
  }

  r = scm_func_tree_init(&ft, ctx);
  if(r) goto __fail;
  else { /* fill up with default functions */
    if(!pbuf) goto __fail;
    if(!c) goto __fail; /* let's init config */
    r = ydc_conf_init(c);
    if(r) goto __fail;
    else {
      ctx->values = c;
      ctx->func = ft;
    }

    /* defaults ones */
    scm_func_tree_insert(ctx, VAR_CREATE_GROUP,
                         __ymconf_create_pgroup, NULL);
    scm_func_tree_insert(ctx, VAR_SET,
                         __ymconf_set_cv, pbuf);
    scm_func_tree_insert(ctx, MODULE_ADD_SX,
                         __module_add, NULL);
    scm_func_tree_insert(ctx, MODULE_SET_SX,
                         __module_set, NULL);
    scm_func_tree_insert(ctx, MODULE_LOAD_SX,
                         __module_load, NULL);
    scm_func_tree_insert(ctx, MODULE_RUN_SX,
                         __module_run, NULL);
    scm_func_tree_insert(ctx, SCMOBJECT,
                         __object, NULL);
    scm_func_tree_insert(ctx, YDOPENLOGS,
                         __ymconf_uselogfile, NULL);

  }

  if(!r) pthread_mutex_lock(&ctx->looplock);

 __fini:
  return r;

 __fail:
  pthread_rwlock_destroy(&ctx->modlock);
  pthread_mutex_destroy(&ctx->looplock);
  if(c) free(c);
  if(zdl) free(zdl);
  if(pbuf) free(pbuf);
  if(services) free(services);
  if(modules) free(modules);
  if(ft) scm_func_tree_free(ft);

  return r;
}

scret_t yd_eval_sexp(yd_context_t *ctx, sexp_t *sx)
{
  scret_t rets;
  int r;
  char *keyw;
  char ibuf[64];

  if(sx->ty == SEXP_LIST) keyw = sx->list->val;
  else {
    r = EINVAL;
    RETURN_SRET_SERR(rets, r);
  }

  rets = scm_func_tree_call(ctx, ctx, sx, keyw);

  if(rets.type == SCINT && rets.ec != 0) {
    r = rets.ec;
    print_sexp(ibuf, 63, sx);
    if(strlen(ibuf) >= 63) ydlog(ctx, YL_WARN, "%s... return %d (%s)\n", ibuf, r, strerror(r));
    else ydlog(ctx, YL_WARN, "%s return %d (%s)\n", ibuf, r, strerror(r));
  }

  return rets;
}

int yd_eval_ctx(yd_context_t *ctx, const char *confpath)
{
  sexp_t *sx;
  sexp_iowrap_t *iow;
  int fd, r = 0;
  char *keyw;
  scret_t ret;
  char ibuf[64];

  /* try to open it */
  if((fd = open(confpath, O_RDONLY)) < 0) {
    r = errno;
    return r;
  }

  if(!(iow = init_iowrap(fd))) {
    r = errno;
    close(fd);
    return r;
  }

  /* now load the configuration scenario */
  while((sx = read_one_sexp(iow)) != NULL) {
    if(sx->ty == SEXP_LIST) keyw = sx->list->val;
    else keyw = sx->val;

    ret = scm_func_tree_call(ctx, ctx, sx, keyw);
    /* not found */
    if(ret.serr) {
      close(fd);
      ydlog(ctx, YL_ERROR, "%s: unknown keyword ``%s''\n", __FUNCTION__, keyw);
      return r;
    }
    /* more information */
    if(ret.type == SCINT && ret.ec != 0) {
      r = ret.ec;
      print_sexp(ibuf, 63, sx);
      if(strlen(ibuf) >= 63) ydlog(ctx, YL_WARN, "%s... return %d (%s)\n", ibuf, r, strerror(r));
      else ydlog(ctx, YL_WARN, "%s return %d (%s)\n", ibuf, r, strerror(r));
    }
  }

  r = 0;

  close(fd);

  return r;
}