/*
 * 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 <tdata/usrtc.h>
#include <sexpr/sexp.h>

#include <ydaemon/ydaemon.h>

int yd_mod_add(yd_context_t *ctx, const char *name, yd_mod_type_t type)
{
  yd_mod_t *mod = malloc(sizeof(yd_mod_t));
  usrtc_node_t *node = NULL;
  int r = 0;

  if(!mod) return ENOMEM;
  else if(!(mod->name = strdup(name))) {
    free(mod);
    return ENOMEM;
  }
  mod->type = type;
  usrtc_node_init(&mod->node, mod);

  yd_mod_ctx_wrlock(ctx);
  node = usrtc_lookup(ctx->modules, mod->name);
  if(node) r = EEXIST;
  else usrtc_insert(ctx->modules, &mod->node, mod->name);
  yd_mod_ctx_unlock(ctx);

  return r;
}

int yd_mod_load(yd_context_t *ctx, yd_mod_t *mod)
{
  char buf[256];
  int r = 0;

  /* let's try to find it and load */
  if(!mod->pathname || !mod->name) return EINVAL;

  if(!(mod->dlhandle = dlopen(mod->pathname, RTLD_LAZY|RTLD_GLOBAL)))
    return ENOENT;

  /* ok now we can resolve all symbols */
  snprintf(buf, 256, "%s_preinit", mod->prefix);
  mod->preinit = dlsym(mod->dlhandle, buf);
  if(!mod->preinit) {
  __einval:
    dlclose(mod->dlhandle);
    return EINVAL;
  }

  snprintf(buf, 256, "%s_init", mod->prefix);
  if(!(mod->init = dlsym(mod->dlhandle, buf))) goto __einval;
  snprintf(buf, 256, "%s_run", mod->prefix);
  if(!(mod->run = dlsym(mod->dlhandle, buf))) goto __einval;
  snprintf(buf, 256, "%s_shutdown", mod->prefix);
  if(!(mod->shutdown = dlsym(mod->dlhandle, buf))) goto __einval;
  snprintf(buf, 256, "%s_getobject", mod->prefix);
  if(!(mod->getobject = dlsym(mod->dlhandle, buf))) goto __einval;

  if((r = mod->preinit(ctx))) {
    blub;
  __fail:
    dlclose(mod->dlhandle);
    return r;
  }

  if(mod->cnfname) {
    blub;
    r = yd_eval_ctx(ctx, (const char *)mod->cnfname);
    if(r) goto __fail;
  }

  if((r = mod->init(ctx))) goto __fail;

  if(mod->type == YD_SERVICE_MUX) {
    if((r = mod->run(ctx))) goto __fail;
  }

  return 0;
}

int yd_mod_get(yd_context_t *ctx, const char *name, yd_mod_t **rmod)
{
  int r = 0;
  usrtc_node_t *node;

  yd_mod_ctx_rdlock(ctx);
  node = usrtc_lookup(ctx->modules, name);
  if(!node) r = ENOENT;
  else *rmod = (yd_mod_t *)usrtc_node_getdata(node);
  yd_mod_ctx_unlock(ctx);

  return r;
}

int yd_mod_run(yd_context_t *ctx, yd_mod_t *mod)
{
  return mod->run(ctx);
}

int yd_mod_shutdown(yd_context_t *ctx, yd_mod_t *mod)
{
  return mod->shutdown(ctx);
}

void *yd_mod_getobject(yd_context_t *ctx, yd_mod_t *mod,
                       void *priv, const char *name)
{
  return mod->getobject(priv, name);
}

void *yd_mod_getsym(yd_context_t *ctx, yd_mod_t *mod, const char *name)
{
  return dlsym(mod->dlhandle, name);
}

/* object store */

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

#define __obs_wrlock(s)  pthread_rwlock_wrlock(&((s)->rwlock))
#define __obs_rdlock(s)  pthread_rwlock_rdlock(&((s)->rwlock))
#define __obs_unlock(s)  pthread_rwlock_unlock(&((s)->rwlock))
#define __obs_tree(s)    &((s)->objects)

int obj_store_init(obj_store_t *st)
{
  usrtc_t *tree = &st->objects;

  if(pthread_rwlock_init(&(st->rwlock), NULL)) return ENOMEM;
  else usrtc_init(tree, USRTC_SPLAY, MAX_OBJECTS, __cmp_cstr);

  return 0;
}

int obj_store_set(obj_store_t *st, const char *nm, void *od)
{
  usrtc_node_t *node = NULL;
  obj_store_node_t *onode = NULL;

  __obs_rdlock(st);
  node = usrtc_lookup(__obs_tree(st), nm);
  __obs_unlock(st);

  if(!node) {
    if(!(onode = malloc(sizeof(obj_store_node_t)))) return ENOMEM;
    else memset(onode, 0, sizeof(obj_store_node_t));

    if(!(onode->name = strdup(nm))) {
      free(onode);
      return ENOMEM;
    }

    onode->objdata = od;
    node = &(onode->node);
    usrtc_node_init(node, onode);

    __obs_wrlock(st);
    usrtc_insert(__obs_tree(st), node, (const void *)onode->name);
    __obs_unlock(st);
  } else {
    onode = (obj_store_node_t *)usrtc_node_getdata(node);

    __obs_wrlock(st);
    onode->objdata = od;
    __obs_unlock(st);
  }

  return 0;
}

int obj_store_get(obj_store_t *st, const char *nm, void **rod)
{
  usrtc_node_t *node = NULL;
  obj_store_node_t *onode = NULL;

  __obs_rdlock(st);
  node = usrtc_lookup(__obs_tree(st), nm);
  __obs_unlock(st);

  if(!node) return ENOENT;
  else onode = (obj_store_node_t *)usrtc_node_getdata(node);

  *rod = onode->objdata;

  return 0;
}

int obj_store_remove(obj_store_t *st, const char *nm)
{
  usrtc_node_t *node = NULL;
  obj_store_node_t *onode = NULL;

  __obs_rdlock(st);
  node = usrtc_lookup(__obs_tree(st), nm);
  __obs_unlock(st);

  if(!node) return ENOENT;
  else {
    __obs_wrlock(st);
    usrtc_delete(__obs_tree(st), node);
    __obs_unlock(st);

    onode = (obj_store_node_t *)usrtc_node_getdata(node);
    if(onode->name) free(onode->name);
    free(onode);
  }

  return 0;
}