/****************************************************************************
**  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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sion_common.h"
#include "sion_const.h"
#include "sion_debug.h"
#include "sion_enums.h"
#include "sion_error_handler.h"
#include "sion_flags.h"
#include "sion_internal.h"
#include "sion_serial.h"

int lsion_open(char *fname, const char *file_mode, int *ntasks, int *nfiles, int64_t **chunksizes, int32_t *fsblksize,
  int **globalranks, FILE **fileptr)
{
  int sid = 0;
  _sion_flags_store *flags_store = _lsion_parse_flags(file_mode);
  if (!flags_store) {
    return _lsion_errorprint(
      SION_ID_NOT_VALID, _SION_ERROR_RETURN, "_lsion_open: could not parse file mode in %s, aborting ...\n", file_mode);
  }

  DPRINTFP((1, "lsion_open", 0, "enter open of file %s in %s mode\n", fname, file_mode));

  sid = _lsion_open(fname, flags_store, ntasks, nfiles, chunksizes, fsblksize, globalranks, fileptr);

  _lsion_flags_destroy_store(&flags_store);

  return sid;
}

int lsion_open_rank(char *fname, const char *file_mode, int64_t *chunksize, int32_t *fsblksize, int *rank, FILE **fileptr)
{
  int sid;

  DPRINTFTS(*rank, "before open rank");
  DPRINTFP((1, "lsion_open_rank", *rank, "enter open of file %s in %s mode\n", fname, file_mode));
  sid = _lsion_open_rank(fname, file_mode, chunksize, fsblksize, rank, fileptr);
  DPRINTFP((1, "lsion_open_rank", 0, "leave open of file %s in %s mode sid=%d\n", fname, file_mode, sid));
  DPRINTFTS(*rank, "after open rank");

  return sid;
}

int lsion_close(int sid)
{
  int rc = 0;
  DPRINTFP((1, "lsion_close", -1, "enter close   sid=%d\n", sid));
  rc = _lsion_close_sid(sid);
  DPRINTFP((1, "lsion_close", -1, "leave close   sid=%d\n", sid));

  lsion_dclose();

  return rc;
}

sion_options lsion_options_new()
{
  return (sion_options){.n_chunksizes = 0,
    .chunksizes = NULL, // FIXME: default to fsblksize (which is only known on open)
    .fsblksize = -1,

    .n_multifile = 1,

    .keyval_mode = SION_KEYVAL_MODE_NONE,

    .lowlevel_api = SION_LOWLEVEL_API_C};
}

void lsion_options_set_chunksizes(sion_options *options, int n, int64_t chunksizes[])
{
  options->chunksizes = chunksizes;
  options->n_chunksizes = n;
}

void lsion_options_set_fsblksize(sion_options *options, int32_t fsblksize)
{
  options->fsblksize = fsblksize;
}

void lsion_options_set_multifile_number(sion_options *options, int multifile_number)
{
  options->n_multifile = multifile_number;
}

void lsion_options_set_keyval_mode(sion_options *options, sion_keyval_mode keyval_mode)
{
  options->keyval_mode = keyval_mode;
}

void lsion_options_set_lowlevel_api(sion_options *options, sion_lowlevel_api lowlevel_api)
{
  options->lowlevel_api = lowlevel_api;
}

_sion_flags_store *_lsion_options_into_flags_store(sion_open_mode mode, const sion_options *options)
{
  _sion_flags_store *flags_store = _lsion_flags_create_store();
  if (!flags_store) {
    /* FIXME: add error message */
    fprintf(stderr, "could not create flags store.\n");
    return NULL;
  }

  switch (mode) {
  case SION_OPEN_READ:
    _lsion_flags_add(flags_store, "br", "");

    switch (options->keyval_mode) {
    case SION_KEYVAL_MODE_NONE:
      /* do nothing */
      break;
    case SION_KEYVAL_MODE_DEFAULT:
    /* FALLTHROUGH */
    case SION_KEYVAL_MODE_INLINE:
      _lsion_flags_add(flags_store, "keyval", "inline");
      break;
    case SION_KEYVAL_MODE_META:
      _lsion_flags_add(flags_store, "keyval", "meta");
      break;
    case SION_KEYVAL_MODE_HASH:
      _lsion_flags_add(flags_store, "keyval", "hash");
      break;
    case SION_KEYVAL_MODE_UNKNOWN:
      _lsion_flags_add(flags_store, "keyval", "unknown");
      break;
    default:
      /* FIXME: add error message */
      fprintf(stderr, "wrong keyval mode in read mode.\n");
      _lsion_flags_destroy_store(&flags_store);
      return NULL;
    }

    break;
  case SION_OPEN_WRITE:
    _lsion_flags_add(flags_store, "bw", "");

    switch (options->keyval_mode) {
    case SION_KEYVAL_MODE_NONE:
      /* do nothing */
      break;
    case SION_KEYVAL_MODE_DEFAULT:
    /* FALLTHROUGH */
    case SION_KEYVAL_MODE_INLINE:
      _lsion_flags_add(flags_store, "keyval", "inline");
      break;
    case SION_KEYVAL_MODE_META:
      _lsion_flags_add(flags_store, "keyval", "meta");
      break;
    case SION_KEYVAL_MODE_HASH:
      _lsion_flags_add(flags_store, "keyval", "hash");
      break;
    default: /* 'unknown' keyval mode is not allowed in write mode */
      /* FIXME: add error message */
      fprintf(stderr, "wrong keyval mode in read mode.\n");
      _lsion_flags_destroy_store(&flags_store);
      return NULL;
    }

    break;
  }

  switch (options->lowlevel_api) {
  case SION_LOWLEVEL_API_C:
    _lsion_flags_add(flags_store, "ansi", "");
    break;
  case SION_LOWLEVEL_API_POSIX:
    _lsion_flags_add(flags_store, "posix", "");
    break;
  }

  _lsion_flags_update_mask(flags_store);

  return flags_store;
}

int lsion_open_with_options(const char *fname, sion_open_mode mode, int n, const sion_options *options_)
{
  // FIXME: need local copy to satisfy signature of lsion_open()
  sion_options options = (options_) ? *options_ : lsion_options_new();

  int64_t *chunksizes = NULL;
  if (!options.chunksizes) {
    chunksizes = malloc(n * sizeof(int64_t));
    if (chunksizes == NULL) {
      return _lsion_errorprint(SION_ID_NOT_VALID, _SION_ERROR_RETURN, "%s: could not allocate memory for chunksizes\n", __func__);
    }
    for (size_t i = 0; i < n; ++i) {
      chunksizes[i] = 1024 * 1024; // FIXME: default to fsblksize (which is only known on open)
    }
  } else {
    if (options.n_chunksizes != n) {
      return _lsion_errorprint(SION_ID_NOT_VALID, _SION_ERROR_RETURN,
        "%s: size of chunksizes array in options (%d) does not match number of logical files (%d)\n", __func__,
        options.n_chunksizes, n);
    }
  }

  _sion_flags_store *flags_store = _lsion_options_into_flags_store(mode, &options);
  if (!flags_store) {
    return -1;
  }

  int *globalranks = calloc(n, sizeof(int));
  if (globalranks == NULL) {
    free(chunksizes);
    return _lsion_errorprint(SION_ID_NOT_VALID, _SION_ERROR_RETURN, "%s: could not allocate memory for globalranks\n", __func__);
  }

  int sid = _lsion_open(fname, flags_store, &n, &options.n_multifile, (options.chunksizes) ? &options.chunksizes : &chunksizes,
    &options.fsblksize, &globalranks, NULL);

  free(globalranks);
  free(chunksizes);

  _lsion_flags_destroy_store(&flags_store);
  return sid;
}
