/*
 * Secure X Message Passing Library v2 implementation.
 * (c) Askele Group 2013-2015 <http://askele.com>
 * (c) Alexander Vdolainen 2013-2015 <avdolainen@gmail.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 <fcntl.h>

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

#include <sxmp/sxmp.h>
#include <sxmp/limits.h>

static long __cmp_int(const void *a, const void *b)
{
  return *(int *)a - *(int *)b;
}

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

int sxmp_rpclist_init(usrtc_t *tree)
{
  usrtc_init(tree, USRTC_REDBLACK, MAX_RPC_LIST, __cmp_int);
  return 0;
}

int sxmp_rpclist_add(usrtc_t *tree, int type, const char *description,
                     const char *version)
{
  rpc_typed_list_t *nlist = NULL;
  sxl_rpclist_t *rpc_list = NULL;
  usrtc_t *rtree = NULL;
  usrtc_node_t *node = NULL;
  int r = ENOMEM;

  /* check for existing one */
  node = usrtc_lookup(tree, &type);
  if(node) return EEXIST;

  /* allocate all */
  if(!(nlist = malloc(sizeof(rpc_typed_list_t)))) goto __fail;
  else memset(nlist, 0, sizeof(rpc_typed_list_t));
  if(!(rpc_list = malloc(sizeof(sxl_rpclist_t)))) goto __fail;
  else memset(rpc_list, 0, sizeof(sxl_rpclist_t));
  if(!(rtree = malloc(sizeof(usrtc_t)))) goto __fail;

  /* version and description */
  if(!(nlist->description = strdup(description))) goto __fail_a;
  if(version) { /* in case of existing version */
    if(!(rpc_list->opt_version = strdup(version))) goto __fail_a;
  }
  /* initialize all */
  nlist->type_id = type;
  nlist->rpc_list = rpc_list;
  rpc_list->rpc_tree = rtree;
  usrtc_node_init(&(nlist->lnode), nlist);
  usrtc_init(rtree, USRTC_SPLAY, MAX_RPC_LIST, __cmp_cstr);
  node = &(nlist->lnode);
  /* insert it */
  usrtc_insert(tree, node, &(nlist->type_id));

  return 0;
 __fail_a:
  if(nlist->description) free(nlist->description);
  if(rpc_list->opt_version) free(rpc_list->opt_version);
 __fail:
  if(nlist) free(nlist);
  if(rpc_list) free(rpc_list);
  if(rtree) free(rtree);
  return r;
}

int sxmp_rpclist_add_function(usrtc_t *tree, int type, const char *fu_name,
                              int (*rpcf)(void *, sexp_t *))
{
  usrtc_node_t *node;
  rpc_typed_list_t *tlist;
  sxl_rpclist_t *rlist;
  sxl_rpc_t *rentry = NULL;

  if(*fu_name == '!') return EINVAL; /* reserve such names for ither purposes */

  node = usrtc_lookup(tree, &type);
  if(!node) return ENOENT;
  else tlist = (rpc_typed_list_t *)usrtc_node_getdata(node);
  rlist = tlist->rpc_list; /* get rpc list */

  /* ok, we don't allow dupes */
  node = usrtc_lookup(rlist->rpc_tree, fu_name);
  if(node) return EEXIST;
  if(!(rentry = malloc(sizeof(sxl_rpc_t)))) return ENOMEM;
  else if(!(rentry->name = strdup(fu_name))) {
    free(rentry);
    return ENOMEM;
  }

  /* init */
  usrtc_node_init(&(rentry->node), rentry);
  rentry->rpcf = rpcf;
  node = &(rentry->node);
  usrtc_insert(rlist->rpc_tree, node, rentry->name); /* insert it */

  return 0;
}

int sxmp_rpclist_filter(usrtc_t *source, usrtc_t **dest, int flag, int *filter)
{
  int r = 0, *f = filter, i;
  usrtc_t *destination = NULL;
  usrtc_node_t *node;
  rpc_typed_list_t *tlist, *tlist_filter;

  if(!filter) return EINVAL;
  if(!(destination = malloc(sizeof(usrtc_t)))) return ENOMEM;
  else usrtc_init(destination, USRTC_REDBLACK, MAX_RPC_LIST, __cmp_int);

  switch(flag) {
  case SXMP_FILTER_EXC:
    for(node = usrtc_first(source); ; node = usrtc_next(source, node)) {
      tlist = (rpc_typed_list_t *)usrtc_node_getdata(node);
      r = ENOENT;
      for(i = *f; i != SXMP_FILTER_END; f++, i = *f) {
        if(tlist->type_id == i) {
          r = 0; break;
        }
      }
      if(r) { /* we should clone it */
        if(!(tlist_filter = malloc(sizeof(rpc_typed_list_t)))) continue; /* skip */
        else {
          tlist_filter->type_id = tlist->type_id;
          tlist_filter->description = tlist->description;
          tlist_filter->rpc_list = tlist->rpc_list;
          usrtc_node_init(&(tlist_filter->lnode), tlist_filter);
          usrtc_insert(destination, &(tlist_filter->lnode), &(tlist_filter->type_id));
        }
      }

      if(node == usrtc_last(source)) break;
    }
    r = 0;
    break;
  case SXMP_FILTER_INC:
    for(i = *f; i != SXMP_FILTER_END; f++, i = *f) {
      node = usrtc_lookup(source, &i);
      if(!node) continue; /* skip it at all */
      else tlist = (rpc_typed_list_t *)usrtc_node_getdata(node);
      /* clone tlist */
      if(!(tlist_filter = malloc(sizeof(rpc_typed_list_t)))) continue; /* skip */
      else {
        tlist_filter->type_id = tlist->type_id;
        tlist_filter->description = tlist->description;
        tlist_filter->rpc_list = tlist->rpc_list;
        usrtc_node_init(&(tlist_filter->lnode), tlist_filter);
        usrtc_insert(destination, &(tlist_filter->lnode), &(tlist_filter->type_id));
      }
    }
    break;
  default:
    r = EINVAL;
    goto __fail;
  }

  if(!usrtc_count(destination)) { /* we have an empty list */
    r = EINVAL;
    goto __fail;
  }

  *dest = destination;

  return 0;
 __fail:
  if(destination) free(destination);
  return r;
}