/****************************************************************************
**  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       **
****************************************************************************/

#define _XOPEN_SOURCE 700

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

#include "sion_buffer.h"
#include "sion_common.h"
#include "sion_const.h"
#include "sion_debug.h"
#include "sion_dup.h"
#include "sion_error_handler.h"
#include "sion_fd.h"
#include "sion_file.h"
#include "sion_filedesc.h"
#include "sion_hints.h"
#include "sion_internal.h"
#include "sion_internal_positioning.h"
#include "sion_internal_seek.h"
#include "sion_lock.h"

sion_io_stat_t *lsion_get_io_info_buddy(int sid, int roles, int flag);

int lsion_get_locations(int sid, int *ntasks, int *maxchunks, int64_t *globalskip, int64_t *start_of_varheader,
  int64_t **sion_chunksizes, int64_t **sion_globalranks, int64_t **sion_blockcount, int64_t **sion_blocksizes)
{
  int rc = SION_SUCCESS;
  _sion_filedesc *sion_filedesc;
#ifdef SION_SERIAL_MASTER
  _sion_filedesc *help;
#endif

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
  }
  DPRINTFP((1, "lsion_get_locations", -1, "enter\n"));

#ifdef SION_SERIAL_MASTER
  if ((sion_filedesc->state == SION_FILESTATE_SEROPENMASTER) && (sion_filedesc->all_blockcount == NULL)) {
    int i;

    /* collect info from sub files */
    _lsion_alloc_filedesc_arrays(sion_filedesc);
    _lsion_alloc_filedesc_block_arrays_only(sion_filedesc);

    for (i = 0; i < sion_filedesc->ntasks; i++) {
      int lfile = sion_filedesc->mapping[i * 2 + 0];
      int lrank = sion_filedesc->mapping[i * 2 + 1];
      sion_filedesc->all_chunksizes[i] = sion_filedesc->multifiles[lfile]->all_chunksizes[lrank];
      sion_filedesc->all_globalranks[i] = sion_filedesc->multifiles[lfile]->all_globalranks[lrank];
      sion_filedesc->all_blockcount[i] = sion_filedesc->multifiles[lfile]->all_blockcount[lrank];
    }
    for (i = 0; i < sion_filedesc->ntasks; i++) {
      int lfile = sion_filedesc->mapping[i * 2 + 0];
      int lrank = sion_filedesc->mapping[i * 2 + 1];
      int blknum;
      help = sion_filedesc->multifiles[lfile];
      for (blknum = 0; blknum < sion_filedesc->all_blockcount[i]; blknum++) {
        sion_filedesc->all_blocksizes[sion_filedesc->ntasks * blknum + i] = help->all_blocksizes[help->ntasks * blknum + lrank];
      }
    }
  }
#endif

  *ntasks = sion_filedesc->ntasks;
  *maxchunks = sion_filedesc->maxusedchunks;
  *sion_chunksizes = sion_filedesc->all_chunksizes;
  *sion_globalranks = sion_filedesc->all_globalranks;
  *sion_blockcount = sion_filedesc->all_blockcount;
  *sion_blocksizes = sion_filedesc->all_blocksizes;
  *globalskip = sion_filedesc->globalskip;
  *start_of_varheader = sion_filedesc->start_of_varheader;

  DPRINTFP((1, "lsion_get_locations", -1, "leave\n"));

  return rc;
}

int _lsion_close_sid(int sid)
{
  int rc = SION_SUCCESS;
  _sion_filedesc *sion_filedesc;

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

  if (rc == SION_SUCCESS) {
    _lsion_freevcd(sid);
    _lsion_free_filedesc(sion_filedesc);
  }

  return rc;
}

int lsion_get_current_location(int sid, int *currentchunknr, int64_t *currentpos, int *maxchunks, int64_t **chunksizes)
{
  int rc = SION_SUCCESS;
  _sion_filedesc *sion_filedesc;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
  }
  DPRINTFP((1, "lsion_get_current_location", -1, "enter\n"));

  *currentchunknr = sion_filedesc->currentblocknr;
  *currentpos = sion_filedesc->currentpos - (sion_filedesc->startpos + sion_filedesc->currentblocknr * sion_filedesc->globalskip);
  *maxchunks = sion_filedesc->lastchunknr + 1;
  *chunksizes = sion_filedesc->blocksizes;

  DPRINTFP((1, "lsion_get_current_location", -1, "leave\n"));
  return rc;
}

int lsion_get_mapping(int sid, int *mapping_size, int32_t **mapping, int *numfiles)
{
  int rc = SION_SUCCESS;
  _sion_filedesc *sion_filedesc;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
  }
  DPRINTFP((1, "lsion_get_mapping", -1, "enter\n"));

  if (sion_filedesc->mapping_size > 0) {
    *mapping_size = sion_filedesc->mapping_size;
    *mapping = sion_filedesc->mapping;
  } else {
    *mapping_size = -1;
  }
  *numfiles = sion_filedesc->nfiles;

  DPRINTFP((1, "lsion_get_mapping", -1, "leave (mapping_size=%d)\n", *mapping_size));

  return rc;
}

int lsion_get_file_endianness(int sid)
{
  _sion_filedesc *sion_filedesc;

  DPRINTFP((1, "lsion_get_file_endianness", -1, "enter (sid=%d)\n", sid));

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

  DPRINTFP((1, "lsion_get_file_endianness", -1, "leave (sid=%d)\n", sid));
  /* return endianness of user data */
  return (sion_filedesc->endianness >> 8) & 1;
}

int lsion_endianness_swap_needed(int sid)
{
  int swap_needed = 0;

  DPRINTFP((1, "lsion_endianness_swap_needed", -1, "enter (sid=%d)\n", sid));

  swap_needed = (lsion_get_file_endianness(sid) != lsion_get_endianness());

  DPRINTFP((1, "lsion_endianness_swap_needed", -1, "leave (swap_needed=%d)\n", swap_needed));

  return swap_needed;
}

int lsion_get_current_locations(int sid, int *ntasks, int64_t **sion_currentpos, int64_t **sion_currentblocknr)
{
  int rc = SION_SUCCESS;
  _sion_filedesc *sion_filedesc;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
  }
  DPRINTFP((1, "lsion_get_current_locations", -1, "enter\n"));

  *ntasks = sion_filedesc->ntasks;
  *sion_currentpos = sion_filedesc->all_currentpos;
  *sion_currentblocknr = sion_filedesc->all_currentblocknr;

  DPRINTFP((1, "lsion_get_current_locations", -1, "leave\n"));
  return rc;
}

int lsion_get_number_of_files(int sid)
{
  _sion_filedesc *sion_filedesc;

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

  return sion_filedesc->nfiles;
}

int lsion_get_filenumber(int sid)
{
  _sion_filedesc *sion_filedesc;

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

  return sion_filedesc->filenumber;
}

int lsion_is_serial_opened(int sid)
{
  _sion_filedesc *sion_filedesc;

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

  return sion_filedesc->state == SION_FILESTATE_SEROPEN || sion_filedesc->state == SION_FILESTATE_SEROPENRANK
    || sion_filedesc->state == SION_FILESTATE_SEROPENMASTER;
}

int lsion_using_hints(int sid)
{
  _sion_filedesc *sion_filedesc;

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

  return sion_filedesc->usehints;
}

int64_t lsion_get_bytes_written(int sid)
{
  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);
  if (filedesc == NULL) {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
  }

  if (filedesc->mode == SION_FILEMODE_WRITE) {
    if (filedesc->usebuffer) {
      _lsion_buffer_flush(filedesc);
    }
    return filedesc->size;
  } else {
    return SION_SIZE_NOT_VALID;
  }
}

int64_t lsion_get_bytes_read(int sid)
{
  _sion_filedesc *sion_filedesc;
  int64_t bytes = SION_SIZE_NOT_VALID;

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

  if (sion_filedesc->mode == SION_FILEMODE_READ) {
    int i;
    sion_filedesc->currentpos = _lsion_file_get_position(sion_filedesc->fileptr);

    bytes = 0;
    for (i = 0; i < sion_filedesc->currentblocknr; i++) {
      bytes += sion_filedesc->blocksizes[i];
    }
    /* current chunk */
    bytes += sion_filedesc->currentpos - (sion_filedesc->startpos + sion_filedesc->currentblocknr * sion_filedesc->globalskip);
  }

  return bytes;
}

/*   return list of filenames created and the size of their payload, and the task role on this file  */
sion_io_stat_t *lsion_get_io_info(int sid)
{
  return lsion_get_io_info_by_spec(sid, SION_ROLE_COLLECTOR | SION_ROLE_WRITER | SION_ROLE_NOWRITER, SION_GET_IO_INFO_FLAG_NONE);
}

/*   return list of filenames created and the size of their payload, and the task role on this file  */
sion_io_stat_t *lsion_get_io_info_by_spec(int sid, int roles, int flag)
{
  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);
  if (filedesc == NULL) {
    _lsion_errorprint(0, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
    return NULL;
  }

  /* branch to buddy CP */
  if (filedesc->usebuddy) {
    return lsion_get_io_info_buddy(sid, roles, flag);
  } else {
    return NULL;
  }
}

int lsion_free_io_info(sion_io_stat_t *p)
{
  return _lsion_free_io_info(p);
}

size_t lsion_fwrite(const void *data, size_t size, size_t nitems, int sid)
{
  int64_t bytes_to_write, bbytes;
  _sion_filedesc *sion_filedesc;
  size_t rc = 0, frc;
  void *bdata;

  DPRINTFP((1, "lsion_fwrite", -1, "enter size=%ld nitems=%ld\n", (long)size, (long)nitems));

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

  _lsion_check_on_collective_mode(sion_filedesc);

  bytes_to_write = size * nitems;

  /* buffering? */
  if (sion_filedesc->usebuffer) {
    const char *data_ptr = data;
    /* buffer data */
    int64_t bytes_buffered = _lsion_buffer_push(sion_filedesc, data_ptr, bytes_to_write);
    bytes_to_write -= bytes_buffered;
    data_ptr += bytes_buffered;

    while (bytes_to_write > 0) {
      /* flush filled buffer to file, not all data could be bufferd */
      _lsion_buffer_get_data_ptr(sion_filedesc, &bdata, &bbytes);
      frc = _lsion_write_multi_chunk(sion_filedesc, bdata, bbytes);
      if (frc != bbytes) {
        return _lsion_errorprint_on_rank(SION_ANSI_SIZE_NOT_VALID, _SION_ERROR_RETURN, sion_filedesc->rank,
          "could not write data (%d bytes) to file (sid=%d) ...", (int)bbytes, sid);
      }

      /* next try to push data into buffer */
      bytes_buffered = _lsion_buffer_push(sion_filedesc, data_ptr, bytes_to_write);
      bytes_to_write -= bytes_buffered;
      data_ptr += bytes_buffered;
    }
    /* return code: bytes stored  */
    rc = size ? nitems : 0;

  } else {
    /* normal write */
    frc = _lsion_write_multi_chunk(sion_filedesc, data, bytes_to_write);
    if (frc != bytes_to_write) {
      return _lsion_errorprint_on_rank(SION_ANSI_SIZE_NOT_VALID, _SION_ERROR_RETURN, sion_filedesc->rank,
        "could not write data (%d bytes) to file (frc=%d sid=%d) ...", (int)bytes_to_write, (int)frc, sid);
    }
    rc = size ? nitems : 0;
  }

  DPRINTFP((1, "lsion_fwrite", -1, "leave rc=%ld\n", (long)rc));
  return rc;
}

size_t lsion_fread(void *data, size_t size, size_t nitems, int sid)
{
  _sion_filedesc *sion_filedesc;
  size_t bread = 0;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_ANSI_SIZE_NOT_VALID, _SION_ERROR_RETURN, "invalid sion_filedesc, aborting %d ...\n", sid);
  }
  if (!data) {
    /* FIXME: replace SION_ANSI_SIZE_NOT_VALID? */
    return _lsion_errorprint(SION_ANSI_SIZE_NOT_VALID, _SION_ERROR_RETURN, "invalid pointer, aborting %d ...\n", data);
  }

  DPRINTFP((1, "lsion_fread", -1, "enter\n"));
  DPRINTFP((4, "lsion_fread", -1, " parameter size=%zu nitems=%zu\n", size, nitems));

  _lsion_check_on_collective_mode(sion_filedesc);

  if (!lsion_feof(sid)) {
    bread = _lsion_read_multi_chunk(sion_filedesc, data, size * nitems);
    DPRINTFP((256, "lsion_fread", -1, "  _lsion_file_read returns %zu\n", bread));
  }

  bread = size ? bread / size : 0;

  DPRINTFP((4, "lsion_fread", -1, " return value  %zu\n", bread));
  DPRINTFP((1, "lsion_fread", -1, "leave\n"));

  return bread;
}

int lsion_seek(int sid, int64_t offset, sion_seek_mode whence)
{
  /* TBD: check if not lsion_open and multi file */
  /* TBD: implement lsion_open and sion_seek_fp for multi-files */

  DPRINTFP((1, __func__, -1, " enter seek with sid=%d\n", sid));

  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);
  if (filedesc == NULL) {
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "lsion_seek: invalid sion_filedesc, aborting %d ...\n", sid);
  }

  /* check state */
  if ((filedesc->mode != SION_FILEMODE_READ) && (filedesc->mode != SION_FILEMODE_WRITE)) {
    return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
      "lsion_seek: unknown file open state !(READ|WRITE), aborting %d ...", sid);
  }
  if (filedesc->usebuffer) {
    _lsion_buffer_flush(filedesc);
  }
  if ((filedesc->state != SION_FILESTATE_PAROPEN) && (filedesc->state != SION_FILESTATE_SEROPEN)
    && (filedesc->state != SION_FILESTATE_SEROPENMASTER) && (filedesc->state != SION_FILESTATE_SEROPENRANK)
    && (filedesc->state != SION_FILESTATE_PAROPENMAPPEDMASTER)) {
    return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
      "lsion_seek: unknown file open state !(PAR|SER|SERRANK|MAPPED), aborting %d ...", sid);
  }

  int rc;
  if (filedesc->mode == SION_FILEMODE_READ) { /* READ */
    rc = _lsion_seek_on_current_rank_read(filedesc, offset, whence);
  } else { /* WRITE */
    if (filedesc->state == SION_FILESTATE_SEROPENRANK) {
      return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
        "lsion_seek: seek not supported for this type (write, lsion_open_rank), aborting ...");
    } else {
      rc = _lsion_seek_on_current_rank_write(filedesc, offset, whence);
    }
  }

  DPRINTFP((1, __func__, -1, "leave seek rc=%d\n", rc));
  return rc;
}

int lsion_seek_chunk(int sid, int32_t offset, sion_seek_mode whence)
{
  DPRINTFP((1, __func__, -1, " enter seek with sid=%d\n", sid));

  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);
  if (filedesc == NULL) {
    return _lsion_errorprint(
      SION_NOT_SUCCESS, _SION_ERROR_RETURN, "lsion_seek_chunk: invalid sion_filedesc, aborting %d ...\n", sid);
  }

  int32_t new_blocknr;
  switch (whence) {
  case SION_SEEK_BEGIN:
    new_blocknr = offset;
    break;
  case SION_SEEK_CURRENT:
    new_blocknr = _lsion_get_current_chunk_number(filedesc) + offset;
    break;
  case SION_SEEK_END:
    new_blocknr = filedesc->lastchunknr + offset;
    break;
  default:
    return _lsion_errorprint(SION_NOT_SUCCESS, _SION_ERROR_RETURN, "sion_seek_block: seek mode not supported, aborting ...\n");
  }

  if (new_blocknr < 0) {
    return _lsion_errorprint(
      SION_NOT_SUCCESS, _SION_ERROR_RETURN, "lsion_seek_chunk: cannot seek before first block, aborting ...\n");
  }
  if (filedesc->mode == SION_FILEMODE_READ && new_blocknr > filedesc->lastchunknr) {
    return _lsion_errorprint(
      SION_NOT_SUCCESS, _SION_ERROR_RETURN, "lsion_seek_chunk: cannot seek past last block in read mode, aborting ...\n");
  }

  _lsion_apply_hints(filedesc, SION_HINTS_FREE_TYPE_CHUNK);
  _sion_chunk_sequence sequence = _lsion_chunk_sequence_from_filedesc(filedesc);
  _sion_range chunk = _lsion_chunk_sequence_chunk_from_number(&sequence, new_blocknr);
  filedesc->currentpos = chunk.start;
  filedesc->currentblocknr = new_blocknr;
  _lsion_apply_hints(filedesc, SION_HINTS_ACCESS_TYPE_CHUNK);

  _lsion_file_set_position(filedesc->fileptr, filedesc->currentpos);

  DPRINTFP((1, __func__, -1, "leave seek rc=%d\n", SION_SUCCESS));
  return SION_SUCCESS;
}

int lsion_switch_logical_file(int sid, int file_number)
{
  DPRINTFP((1, __func__, -1, " enter switch_logical_file with sid=%d\n", sid));

  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);
  if (filedesc == NULL) {
    return _lsion_errorprint(
      SION_NOT_SUCCESS, _SION_ERROR_RETURN, "lsion_seek_chunk: invalid sion_filedesc, aborting %d ...\n", sid);
  }

  int rc;
  if (filedesc->mode == SION_FILEMODE_READ) {
    if (filedesc->state == SION_FILESTATE_SEROPEN) {
      rc = _lsion_switch_logical_file_read(filedesc, file_number);
    } else if (filedesc->state == SION_FILESTATE_SEROPENMASTER) {
      rc = _lsion_switch_logical_file_read_master(filedesc, file_number);
    } else if (filedesc->state == SION_FILESTATE_PAROPENMAPPEDMASTER) {
      rc = _lsion_switch_logical_file_read_mapped(filedesc, file_number);
    } else if ((filedesc->state == SION_FILESTATE_SEROPENRANK) || (filedesc->state == SION_FILESTATE_PAROPEN)) {
      return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
        "lsion_switch_logical_file: switching logical file not supported for this type (lsion_open_rank, sion_paropen_...), "
        "aborting ...\n");
    } else {
      return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
        "lsion_switch_logical_file: switching logical file not supported for unknown file state, aborting ...\n");
    }
  } else {
    if (filedesc->state == SION_FILESTATE_SEROPEN) {
      rc = _lsion_switch_logical_file_write(filedesc, file_number);
    } else if (filedesc->state == SION_FILESTATE_PAROPENMAPPEDMASTER) {
      rc = _lsion_switch_logical_file_write_mapped(filedesc, file_number);
    } else if ((filedesc->state == SION_FILESTATE_SEROPENRANK) || (filedesc->state == SION_FILESTATE_PAROPEN)) {
      return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
        "lsion_switch_logical_file: switching logical file not supported for this type (lsion_open_rank, sion_paropen_...), "
        "aborting ...\n");
    } else {
      return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, filedesc->rank,
        "lsion_switch_logical_file: switching logical file not supported for unknown file state, aborting ...\n");
    }
  }

  DPRINTFP((1, __func__, -1, "leave switch_logical_file rc=%d\n", rc));
  return rc;
}

int lsion_feof(int sid)
{
  int rc = SION_NOT_SUCCESS;
  int64_t maxpos;

  _sion_filedesc *sion_filedesc;
  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_SIZE_NOT_VALID, _SION_ERROR_RETURN, "lsion_feof: invalid sion_filedesc, aborting %d ...\n", sid);
  }

  DPRINTFP((8, "lsion_feof", -1, "enter feof   sid=%d currentpos=%15lld\n", sid, sion_filedesc->currentpos));

  maxpos = sion_filedesc->startpos + sion_filedesc->currentblocknr * sion_filedesc->globalskip
    + sion_filedesc->blocksizes[sion_filedesc->currentblocknr];

  DPRINTFP((16, "lsion_feof", -1, " after flush sid=%d currentpos=%15lld maxpos= %lld startpos=%lld curblknr=%d\n", sid,
    sion_filedesc->currentpos, maxpos, sion_filedesc->startpos, sion_filedesc->currentblocknr));

  if (sion_filedesc->currentpos < maxpos) {
    /* end of current block not reached */
    rc = 0;
    DPRINTFP((16, "lsion_feof", -1, " end of current block %d not reached sid=%d\n", sion_filedesc->currentblocknr, sid));
  } else {
    if (sion_filedesc->currentblocknr < sion_filedesc->lastchunknr) {
      /* apply hint for freeing current chunk */
      _lsion_apply_hints(sion_filedesc, SION_HINTS_FREE_TYPE_CHUNK);

      /* end of current block reached, skip to next block */
      sion_filedesc->currentblocknr++;
      sion_filedesc->currentpos = sion_filedesc->startpos + sion_filedesc->currentblocknr * sion_filedesc->globalskip;

      /* apply hint for access current (new) chunk */
      _lsion_apply_hints(sion_filedesc, SION_HINTS_ACCESS_TYPE_CHUNK);

      _lsion_file_set_position(sion_filedesc->fileptr, sion_filedesc->currentpos);
      rc = SION_NOT_SUCCESS;
      DPRINTFP((8, "lsion_feof", -1, " end of block %d reached, skipping to next %lld -> %lld position=%lld gs=%lld sid=%d\n",
        sion_filedesc->currentblocknr - 1, sion_filedesc->currentpos, _lsion_file_get_position(sion_filedesc->fileptr),
        sion_filedesc->currentpos, sion_filedesc->globalskip, sid));
    } else {
      /* end of last block reached */
      DPRINTFP((8, "lsion_feof", -1, " end of last block %d reached sid=%d\n", sion_filedesc->currentblocknr, sid));
      rc = SION_SUCCESS;
    }
  }

  DPRINTFP((8, "lsion_feof", -1, "leave feof   sid=%d currentpos=%15lld rc=%d\n", sid, sion_filedesc->currentpos, rc));

  return rc;
}

int64_t lsion_bytes_avail_in_block(int sid)
{
  return lsion_bytes_avail_in_chunk(sid);
}

int64_t lsion_bytes_avail_in_chunk(int sid)
{
  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);
  if (filedesc == NULL) {
    return _lsion_errorprint(
      SION_SIZE_NOT_VALID, _SION_ERROR_RETURN, "lsion_bytes_avail_in_chunk: invalid filedesc, aborting %d ...\n", sid);
  }

  assert(filedesc->mode & SION_FILEMODE_WRITE);

  _sion_limited_chunk_sequence sequence = _lsion_limited_chunk_sequence_from_filedesc(filedesc);
  int64_t bytes_avail;
  if (_lsion_limited_chunk_sequence_position_oob(&sequence, filedesc->currentpos)) {
    bytes_avail = 0;
  } else {
    _sion_range chunk = _lsion_limited_chunk_sequence_chunk_from_physical_position(&sequence, filedesc->currentpos);
    bytes_avail = chunk.length;
  }

  DPRINTFP((8, __func__, -1, "leave sid=%d rank=%4d  currentpos=%lld  maxpos= %lld -> bytes to read %lld\n",
    sid, filedesc->rank, filedesc->currentpos, bytes_avail));

  return bytes_avail;
}

int64_t lsion_get_position(int sid)
{
  _sion_filedesc *sion_filedesc;

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

  return _lsion_file_get_position(sion_filedesc->fileptr);
}

int lsion_optimize_fp_buffer(int sid)
{
  int rc = SION_SUCCESS;
  _sion_filedesc *sion_filedesc;

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

  sion_filedesc->fpbuffer = malloc(sion_filedesc->fsblksize);
  if (sion_filedesc->fpbuffer == NULL) {
    return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, sion_filedesc->rank,
      "lsion_optimize_fp_buffer: cannot allocate internal buffer of size %lu , aborting ...",
      (unsigned long)sion_filedesc->fsblksize);
  }
  sion_filedesc->fpbuffer_size = sion_filedesc->fsblksize;

  rc = _lsion_file_set_buffer(sion_filedesc->fileptr, sion_filedesc->fpbuffer, sion_filedesc->fpbuffer_size);
  return rc;
}

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

  return SION_SUCCESS;
}

int lsion_ensure_free_space(int sid, int64_t bytes)
{
  _sion_filedesc *sion_filedesc;
  int64_t byteswritten;
  int rc = SION_SUCCESS;

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

  DPRINTFP((8, "_sion_ensure_free_space", -1, "enter ensure_free   sid=%d bytes=%lld\n", sid, bytes));

  if (sion_filedesc->mode != SION_FILEMODE_WRITE) {
    DPRINTFP((8, "_sion_ensure_free_space", -1, "invalid opened\n"));
    return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, sion_filedesc->rank,
      "lsion_ensure_free_space[%2d]: file is opened invalid sion_mode, returning ...", sion_filedesc->rank);
  }
  if (!sion_filedesc->usecoll) {
    if (sion_filedesc->fileptr == NULL) {
      DPRINTFP((8, "_sion_ensure_free_space", -1, "file not opened\n"));
      return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, sion_filedesc->rank,
        "lsion_ensure_free_space[%2d]: file is not open, returning ...", sion_filedesc->rank);
    }
  }

  if (bytes > sion_filedesc->chunksize) {
    DPRINTFP((8, "_sion_ensure_free_space", -1, "could not write %lld bytes, chunksize=%lld\n", bytes, sion_filedesc->chunksize));
    return _lsion_errorprint_on_rank(SION_NOT_SUCCESS, _SION_ERROR_RETURN, sion_filedesc->rank,
      "lsion_ensure_free_space[%2d]: could not write %lld bytes, chunksize=%lld, returning ...", sion_filedesc->rank, bytes,
      sion_filedesc->chunksize);
  }

  DPRINTFP((16, "_sion_ensure_free_space", -1, "  after getpos sid=%d fileposition is %lld bytes=%lld\n", sid,
    sion_filedesc->currentpos, bytes));

  byteswritten = sion_filedesc->blocksizes[sion_filedesc->currentblocknr];

  DPRINTFP((16, "_sion_ensure_free_space", -1, " sid=%d byteswritten(%lld) + new bytes (%lld) = %lld\n", sid, byteswritten, bytes,
    byteswritten + bytes));

  DPRINTFP((8, "_sion_ensure_free_space", -1,
    "leave ensure_free   sid=%d fileposition is %lld bw+bytes=%lld <= chunksize=%lld (fsblksize=%d)\n", sid,
    sion_filedesc->currentpos, byteswritten + bytes, sion_filedesc->chunksize, sion_filedesc->fsblksize));

  return rc;
}

int lsion_is_thread_safe()
{
#ifdef SION_PTHREADS
  return SION_SUCCESS;
#else
  return SION_NOT_SUCCESS;
#endif
}

int lsion_get_sizeof(int sid, int *numbytes, int *numfds)
{
  _sion_filedesc *sion_filedesc;
  int rc = SION_NOT_SUCCESS;

  *numbytes = SION_SIZE_NOT_VALID;
  *numfds = SION_SIZE_NOT_VALID;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(
      SION_SIZE_NOT_VALID, _SION_ERROR_RETURN, "lsion_get_sizeof: invalid sion_filedesc, returning %d ...\n", sid);
  }
  DPRINTFP((2, "lsion_get_sizeof", -1, "enter sid=%d\n", sid));

  rc = _lsion_get_size_of_filedesc(sion_filedesc, numbytes, numfds);

  DPRINTFP((2, "lsion_get_sizeof", -1, "leave sid=%d numbytes=%d numfds=%d\n", sid, *numbytes, *numfds));

  return rc;
}

#define DFUNCTION "lsion_dup"
int lsion_dup(int sid, int mode, int rank, uint64_t key)
{
  int new_sid = SION_ID_NOT_VALID;
  _sion_filedesc *sion_filedesc;

  if ((sid < 0) || (_lsion_vcdtype(sid) != SION_FILEDESCRIPTOR) || !(sion_filedesc = _lsion_vcdtovcon(sid))) {
    return _lsion_errorprint(SION_ID_NOT_VALID, _SION_ERROR_RETURN, "lsion_dup: invalid sion_filedesc, returning %d ...\n", sid);
  }
  DPRINTFP((8, DFUNCTION, -1, "enter sid=%d\n", sid));

  if (sion_filedesc->mode != SION_FILEMODE_READ) {
    DPRINTFP((8, DFUNCTION, -1, "invalid opened\n"));
    return _lsion_errorprint_on_rank(SION_ID_NOT_VALID, _SION_ERROR_RETURN, sion_filedesc->rank,
      DFUNCTION "[%2d]: file is not opened in read mode, returning ...", sion_filedesc->rank);
  }

  new_sid = _lsion_dup(sid, mode, rank, key);

  DPRINTFP((8, DFUNCTION, -1, "leave sid=%d new_sid=%d\n", sid, new_sid));

  return new_sid;
}
#undef DFUNCTION

#define DFUNCTION "lsion_dedup"
int lsion_dedup(int sid)
{
  int rc = 0;
  DPRINTFP((8, DFUNCTION, -1, "enter sid=%d\n", sid));

  rc = _lsion_dedup(sid);

  DPRINTFP((8, DFUNCTION, -1, "leave sid=%d rc=%d\n", sid, rc));

  return rc;
}
#undef DFUNCTION

#define DFUNCTION "lsion_lock_register_lock_callbacks"
int lsion_lock_register_lock_callbacks(int lock(void *), int unlock(void *), void *lock_data)
{
  int rc = 0;
  DPRINTFP((8, DFUNCTION, -1, "enter\n"));

  rc = _lsion_lock_register_lock_callbacks(lock, unlock, lock_data);

  DPRINTFP((8, DFUNCTION, -1, "leave rc=%d\n", rc));

  return rc;
}
#undef DFUNCTION

#define DFUNCTION "lsion_lock_user_callbacks_defined"
int lsion_lock_user_callbacks_defined()
{
  int rc = 0;
  DPRINTFP((8, DFUNCTION, -1, "enter\n"));

  rc = _lsion_lock_user_callbacks_defined();

  DPRINTFP((8, DFUNCTION, -1, "leave rc=%d\n", rc));

  return rc;
}
#undef DFUNCTION

char *lsion_get_fname(int sid)
{
  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);

  return filedesc->fname;
}

int64_t lsion_get_chunksize(int sid)
{
  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);

  return filedesc->chunksize;
}

int32_t lsion_get_globalrank(int sid)
{
  _sion_filedesc *filedesc = _lsion_get_filedesc(sid);

  return filedesc->globalrank;
}

int lsion_get_endianness(void)
{
  /* Are we little or big endian?  From Harbison&Steele.  */
  union {
    long l;
    char c[sizeof(long)];
  } u;

  u.l = 1;
  return u.c[sizeof(long) - 1] == 1;
}

int lsion_get_version(int *main_version, int *sub_version, int *patch_level, int *fileformat_version)
{
  *main_version = SION_MAIN_VERSION;
  *sub_version = SION_SUB_VERSION;
  *patch_level = SION_VERSION_PATCHLEVEL;
  *fileformat_version = SION_FILEFORMAT_VERSION;
  return SION_SUCCESS;
}
