/****************************************************************************
**  SIONLIB     http://www.fz-juelich.de/jsc/sionlib                       **
*****************************************************************************
**  Copyright (c) 2008-2018                                                **
**  Forschungszentrum Juelich, Juelich Supercomputing Centre               **
**                                                                         **
**  See the file COPYRIGHT in the package base directory for details       **
****************************************************************************/

/// \cond sion_internal
/// \file

#define _XOPEN_SOURCE 700

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include "sion_const.h"
#include "sion_error_handler.h"
#include "sion_fd.h"
#include "sion_lock.h"

#define sion_assert(expr, args) (assert(expr))

/* SION_FD INTERNAL */
#define SION_FD_CHUNK 2
struct _sion_fd {
  int state; /* 0: fresh, 1: in use, 2: used before, now free */
  int type;
  void *data;
};

typedef struct _sion_fddata sion_fddata;

#define SION_FDDATA_INITIALIZER \
  { \
    0, 0, -1, NULL \
  }

static struct _sion_fddata {
  int size;
  int nfree;
  int last_freed;
  struct _sion_fd *list;
} lsion_fd_data = SION_FDDATA_INITIALIZER;

/*!
 * \brief Get a new descriptor and assign data/type to it.
 *
 * @return The new descriptor or -1 in case of an error.
 */
static int _sion_new_fd(sion_fddata *fdd, void *data, int type);

/*!
 * @return The data associated with a descriptor
 */
static void *_sion_fd2ptr(sion_fddata *fdd, int fd);

/*!
 * @return The type (of data) associated with a descriptor
 */
static int _sion_fd2type(sion_fddata *fdd, int fd);

/*!
 * \brief free a descriptor
 */
static int _sion_free_fd(sion_fddata *fdd, int fd);

/*!
 * \brief Assign new data/type to descriptor fd, where fd must be in use
 *
 * @return 1 if successful, 0 otherwise
 */
static int _sion_reassign_fd(sion_fddata *fdd, int fd, void *data, int type);

/* some abbreviations for fd functions */
int _lsion_newvcd(void *data, int type)
{
  return _sion_new_fd(&lsion_fd_data, data, type);
}

int _lsion_freevcd(int sid)
{
  return _sion_free_fd(&lsion_fd_data, sid);
}

void *_lsion_vcdtovcon(int sid)
{
  return _sion_fd2ptr(&lsion_fd_data, sid);
}

int _lsion_vcdtype(int sid)
{
  return _sion_fd2type(&lsion_fd_data, sid);
}

int _lsion_reassignvcd(int sid, void *data, int type)
{
  return _sion_reassign_fd(&lsion_fd_data, sid, data, type);
}

int _sion_new_fd(sion_fddata *fdd, void *data, int type)
{
  int i;

  _lsion_lock();

  /* if we are sure that we have at least 2 free descriptors,
   * we can guarantee, that we don't need to reuse a freed
   * one immediately
   * so enlarge array of descriptors if we have less than 2 free ones
   */
  if (fdd->nfree < 2) {
    int new_size;

    new_size = fdd->size + SION_FD_CHUNK;

    if (fdd->list) {
      fdd->list = realloc(fdd->list, new_size * sizeof(struct _sion_fd));
    } else {
      fdd->list = malloc(new_size * sizeof(struct _sion_fd));
    }
    sion_assert(fdd->list, ("out of memory"));

    for (i = fdd->size; i < new_size; i++) {
      fdd->list[i].state = 0; /* free */
      fdd->list[i].type = 0;
      fdd->list[i].data = NULL;
    }

    fdd->size = new_size;
    fdd->nfree += SION_FD_CHUNK;
  }

  /* use first 'fresh' index */
  for (i = 0; i < fdd->size; i++) {
    if (fdd->list[i].state == 0) {
      fdd->list[i].data = data;
      fdd->list[i].type = type;
      fdd->list[i].state = 1;
      fdd->nfree--;
      _lsion_unlock();
      return i;
    }
  }

  /* if all have been used before, use a 'closed' one,
   * start seraching a new one AFTER the last freed
   * so this one will not be reused immediately
   */
  for (i = fdd->last_freed + 1; i < fdd->last_freed + 1 + fdd->size; i++) {
    int imod = i % fdd->size;
    if (fdd->list[imod].state == -1) {
      fdd->list[imod].data = data;
      fdd->list[imod].type = type;
      fdd->list[imod].state = 1;
      fdd->nfree--;
      _lsion_unlock();
      return imod;
    }
  }
  sion_assert(0, ("oops - we should never get here"));
  return -1;
}

static int _sion_free_fd(sion_fddata *fdd, int fd)
{
  _lsion_lock();
  if ((fd < fdd->size) && (fd >= 0) && (fdd->list) && (fdd->list[fd].state == 1)) {
    fdd->list[fd].state = -1;
    fdd->nfree++;
    fdd->last_freed = fd; /* remember this one to avoid immediate reuse */

    if (fdd->nfree == fdd->size) {
      free(fdd->list);
      fdd->list = NULL;
      fdd->nfree = 0;
      fdd->size = 0;
    }

    _lsion_unlock();
    return 1;
  } else {
    fprintf(stderr, "%s: trying to free an invalid descriptor fd=%d.\n", __func__, fd);
    _lsion_unlock();
    return 0;
  }
}

void *_sion_fd2ptr(sion_fddata *fdd, int fd)
{
  _lsion_lock();
  if ((fd < fdd->size) && (fd >= 0) && (fdd->list[fd].state == 1)) {
    void *ptr = fdd->list[fd].data;
    _lsion_unlock();
    return ptr;
  } else {
    _lsion_unlock();
    return NULL;
  }
}

static int _sion_fd2type(sion_fddata *fdd, int fd)
{
  _lsion_lock();
  if ((fd < fdd->size) && (fd >= 0) && (fdd->list[fd].state == 1)) {
    int type = fdd->list[fd].type;
    _lsion_unlock();
    return type;
  } else {
    fprintf(stderr, "%s: invalid descriptor fd=%d.\n", __func__, fd);
    _lsion_unlock();
    return -1;
  }
}

static int _sion_reassign_fd(sion_fddata *fdd, int fd, void *data, int type)
{
  _lsion_lock();
  if ((fd < fdd->size) && (fd >= 0) && (fdd->list[fd].state == 1)) {
    fdd->list[fd].data = data;
    fdd->list[fd].type = type;
    _lsion_unlock();
    return 1;
  } else {
    fprintf(stderr, "%s: invalid descriptor fd=%d.\n", __func__, fd);
    _lsion_unlock();
    return 0;
  }
}

_sion_filedesc *_lsion_get_filedesc(int sid)
{
  _sion_filedesc *sion_filedesc;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "_lsion_get_filedesc: invalid sion_filedesc sid=%d ...\n", sid);
    return NULL;
  }

  return sion_filedesc;
}

/// \endcond
