/*
 * 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 <ydaemon/dataobject.h>
#include <ydaemon/cache.h>

int domx_init(domx_t *dt, dataobject_t *dodesc)
{
  if(!dt || !dodesc) return EINVAL;

  /* init first values */
  dt->dox = dodesc;
  dt->name = dodesc->name;
  dt->be = dt->cache = NULL;

  /* init list for sntls lists */
  list_init_head(&dt->sxmp_channels);

  return 0;
}

int domx_set_be(domx_t *dt, void *be, const char *key)
{
  struct be_ops *f = (struct be_ops *)be;
  domx_dsbe_t *dbe;

  if(!dt) return EINVAL;
  if(!f || f->be_magic != BEMAGIC) return EINVAL;

  if(!(dbe = malloc(sizeof(domx_dsbe_t)))) return ENOMEM;

  dbe->domx = dt;
  dbe->priv = NULL;
  dbe->f = f;
  dt->be = dbe;

  return dbe->f->init(dbe, key);
}

int domx_set_cache(domx_t *dt, size_t size)
{
  int r = 0;

  /* paranoic check */
  if(!dt || !dt->dox) return EINVAL;

  if(!(dt->cache = (void *)yd_cache_init(dt->dox, size)))
    r = errno;
  else r = yd_cache_start_thread(dt);

  return r;
}

int domx_set_sxmpchannel(domx_t *dt, const char *instance, int typeid, int access)
{
  sxmp_ch_entry_t *sentry = NULL;

  if(!dt || !instance) return EINVAL;

  if(!(sentry = malloc(sizeof(sxmp_ch_entry_t)))) return ENOMEM;

  /* init sntl channel entry */
  strncpy(sentry->instance, instance, 31);
  sentry->chtid = (uint16_t)typeid;
  sentry->filter = (ydm_access_filter_t)access;
  list_init_node(&sentry->node);

  /* add this entry */
  list_add2tail(&dt->sxmp_channels, &sentry->node);

  return 0;
}

#define __ITEMSTOFREED  32

int domx_get(domx_t *dt, uint64_t oid, void **refdata)
{
  void *data;
  ydata_cache_item_t *itm;
  ydata_qitem_t *qitem;
  domx_dsbe_t *be;
  list_head_t *rsitemslist = NULL;
  list_node_t *iter, *siter;
  int r = 0;

  /* paranoic check up */
  if(!dt || !dt->cache) return EINVAL;
  if(!(be = dt->be)) return EINVAL;

 __retry: /* yep, that's here, because anybody can inject our data while we're trying to refresh cache */
  data = yd_cache_lookup((ydata_cache_t *)dt->cache, oid);

  if(data)    *refdata = data;
  else { /* cache miss occured */
    /* first allocate the required item within the cache */
    data = yd_cache_alloc_item((ydata_cache_t *)dt->cache, &itm);
    if(data) { /* we have a free entries ! */
      if((r = be->f->get(be, oid, data))) yd_cache_discard_alloc_item((ydata_cache_t *)dt->cache, itm);
      else {/* all is fine - confirm cache insertion */
        yd_cache_confirm_alloc_item((ydata_cache_t *)dt->cache, itm, oid);
        *refdata = data;
      }
    } else { /* we don't have free entries */
      if(!(rsitemslist = yd_cache_getrslist((ydata_cache_t *)dt->cache, __ITEMSTOFREED)))
        { r = ENOMEM; goto __fini; }
      /* ok, now we're ready to do something - we have unplugged items clean it up then */
      list_for_each_safe(rsitemslist, iter, siter) {
        qitem = container_of(iter, ydata_qitem_t, node);
        itm = qitem->item;
        if(itm->attr & YDC_DIRTY) /* if dirty - sync, but we don't care result here - if corrupted - data is totally corrupteed*/
          be->f->set(be, itm->oid, (yd_cache_ptrbyidx((ydata_cache_t *)dt->cache, itm->idx)) + sizeof(uint32_t));
      }
      /* now we need to return there list back to the cache and retry */
      yd_cache_qitems_pullback((ydata_cache_t *)dt->cache, rsitemslist);
      goto __retry;
    }
  }

 __fini:
  return r;
}

int domx_set(domx_t *dt, uint64_t oid, const void *refdata)
{
  domx_dsbe_t *be;
  void *data;
  ydata_cache_t *cc;
  int r = 0;

  /* paranoic check up */
  if(!dt || !dt->cache) return EINVAL;
  if(!(be = dt->be)) return EINVAL;

  cc = (ydata_cache_t *)dt->cache;

  data = yd_cache_lookup(cc, oid);
 __again:
  if(data) { /* we have those object with this oid in cache */
    memcpy(data, refdata, cc->object_size); /* copy object data */
    yd_cache_item_dirtyoid(cc, oid); /* just point to the cache - dirty */
  } else { /* we're trying to set invalidated data */
    r = domx_get(dt, oid, &data);
    if(!r) goto __again;
  }

  return r;
}

int domx_remove(domx_t *dt, uint64_t oid)
{
  domx_dsbe_t *be;
  void *data;
  ydata_cache_t *cc;

  /* paranoic check up */
  if(!dt || !dt->cache) return EINVAL;
  if(!(be = dt->be)) return EINVAL;

  cc = (ydata_cache_t *)dt->cache;

  data = yd_cache_lookup(cc, oid);
  if(data) yd_cache_item_invalidateoid(cc, oid);

  return be->f->remove(be, oid);
}

int domx_creat(domx_t *dt, uint64_t *oid, const void *refdata)
{
  domx_dsbe_t *be;
  oid_t r = 0;

  /* paranoic check up */
  if(!dt || !dt->cache) return EINVAL;
  if(!(be = dt->be)) return EINVAL;

  /* here we just create this one and nothing */
  r = be->f->creat(be, refdata);

  if(!r) return errno;

  *oid = r;

  return 0;
}

yd_idx_stream_t *domx_idxl_open(domx_t *dt, ydm_access_filter_t afilter,
                                dataacc_pemctx_t *dacc, yd_filter_t *datafilter)
{
  domx_dsbe_t *be;

  /* paranoic check up */
  if(!dt || !dt->cache) {
  __einval:
    errno = EINVAL;
    return NULL;
  }
  if(!(be = dt->be)) goto __einval;

  return be->f->create_idx_stream(be, afilter, dacc, datafilter);
}

void domx_idxl_close(domx_t *dt, yd_idx_stream_t *idxl)
{
  domx_dsbe_t *be;

  /* paranoic check up */
  if(!dt || !dt->cache) {
  __einval:
    errno = EINVAL;
    return;
  }
  if(!(be = dt->be) || !idxl) goto __einval;

  be->f->destroy_idx_stream(idxl);
}

yd_idx_stream_win_t *domx_idxl_read(domx_t *dt, yd_idx_stream_t *idxl)
{
  domx_dsbe_t *be;

  /* paranoic check up */
  if(!dt || !dt->cache) {
  __einval:
    errno = EINVAL;
    return NULL;
  }
  if(!(be = dt->be) || !idxl) goto __einval;

  return be->f->getportion_idx_stream(idxl);
}