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

#define _XOPEN_SOURCE 700

#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "sion.h"

int main(int argc, char **argv)
{
  /* ------------------------------------ */
  /* TEST A: write file with empty blocks */
  /* ------------------------------------ */
  {
#define BUFSIZE 10000
    int ntasks = 4;
    char buffer[BUFSIZE];
    size_t bwrote;

    sion_options *options = sion_options_new();
    if (!options) {
      fprintf(stderr, "TEST A: cannot allocate options, aborting ...\n");
      return 1;
    }

    int64_t *chunksizes = malloc(ntasks * sizeof(int64_t));
    if (chunksizes == NULL) {
      fprintf(stderr, "TEST A: cannot allocate chunksizes of size %lu, aborting ...\n", (unsigned long)ntasks * sizeof(int64_t));
      return 1;
    }
    for (int t = 0; t < ntasks; t++) {
      chunksizes[t] = 1000 + t * 200;
    }
    sion_options_set_chunksizes(options, ntasks, chunksizes);

    int32_t fsblksize = 10;
    sion_options_set_fsblksize(options, fsblksize);

    int sid = sion_open("testA.out", SION_OPEN_WRITE, ntasks, options);

    if (sid >= 0) {
      assert(SION_SUCCESS == sion_optimize_fp_buffer(sid));
      for (int j = 0; j < 2; j++) {
        /* only write on first two tasks */
        for (int t = 0; t < 2; t++) {
          memset(buffer, 'A' + t, chunksizes[t]);
          sion_switch_logical_file(sid, t);
          /* don't fill the chunk */
          bwrote = sion_fwrite(buffer, 1, chunksizes[t] - 9, sid);
          long sum = 0;
          for (int c = 0; c < bwrote; c++) {
            sum = sum + buffer[c];
          }
          printf("on rank %d: bytes_written=%d blocksum=%ld\n", t, (int)bwrote, sum);
        }
      }

      sion_close(sid);
    } else {
      fprintf(stderr, "TEST A: sion_open returned %d\n", sid);
    }
    free(chunksizes);
    sion_options_delete(options);
  }

  /* --------------------- */
  /* TEST B: read all data */
  /* --------------------- */
  {
#define BUFSIZE 10000
    int sid = sion_open("testA.out", SION_OPEN_READ, 0, NULL);
    if (sid >= 0) {
      int64_t *chunksizes = sion_get_chunksizes(sid);
      int ntasks = sion_get_number_of_logical_files(sid);
      for (int j = 0; j < 2; j++) {
        for (int t = 0; t < ntasks; t++) {
          char buffer[BUFSIZE];
          memset(buffer, '-', chunksizes[t]);
          sion_switch_logical_file(sid, t);
          size_t btoread = sion_bytes_avail_in_chunk(sid);
          long sum = 0;
          size_t bread = 0;
          if (btoread) {
            bread = sion_fread(buffer, 1, btoread, sid);
            for (int c = 0; c < bread; c++) {
              sum = sum + buffer[c];
            }
          }
          printf("TEST B read: rank=%d chunksizes=%ld  bread=%d blocksum=%ld\n", t, (long)chunksizes[t], (int)bread, sum);
        }
      }

      sion_close(sid);
    } else {
      fprintf(stderr, "TEST B: sion_open returned %d\n", sid);
    }
  }

  /* ------------------------------------ */
  /* TEST C: seek relative to end of file */
  /* ------------------------------------ */
  {
#define BUFSIZE 10000
    int maxchunks = 0;
    int newblocknr = 0;
    int64_t newposinblk = 0;
    int poslen = 9;
    int posidx = 0;
    int64_t pos = 0;
    /* Offsets for rank = 1 */
    int64_t offsets1[] = {1, 0, -1, -1190, -1191, -1192, -2381, -2382, -2383};
    int64_t *dummy = NULL;

    int sid = sion_open("testA.out", SION_OPEN_READ, 0, NULL);
    if (sid >= 0) {
      for (int iteration = 0; iteration < 2; iteration++) {
        int rank = 1;
        sion_switch_logical_file(sid, rank);

        for (posidx = 0; posidx < poslen; posidx++) {
          pos = offsets1[posidx];
          sion_seek(sid, pos, SION_SEEK_END);
          sion_get_current_location(sid, &newblocknr, &newposinblk, &maxchunks, &dummy);
          size_t btoread = sion_bytes_avail_in_chunk(sid);
          printf("TEST C: rank = %d, iteration = %d, pos = %" PRId64 ", newblocknr = %d, newposinblk = %" PRId64
                 ", btoread = %zd\n",
            rank, iteration, pos, newblocknr, newposinblk, btoread);
        }
      }

      sion_close(sid);
    } else {
      fprintf(stderr, "TEST C: sion_open returned %d\n", sid);
    }
  }

  /* ----------------------- */
  /* TEST D: write mode seek */
  /* ----------------------- */
  {
    sion_options *options = sion_options_new();
    if (!options) {
      return 1;
    }
    sion_options_set_fsblksize(options, 10);
    const int64_t chunksizes[] = { 10, 10 };
    sion_options_set_chunksizes(options, 2, chunksizes);
    int sid = sion_open("testD.out", SION_OPEN_WRITE, 2, options);
    sion_options_delete(options);
    if (sid >=0) {
      const char *buffer = "0123456789ABCDE";
      sion_fwrite(buffer, 1, strlen(buffer), sid); // write more than one chunk
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek(sid, 5, SION_SEEK_CURRENT); // skip 5 ahead
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer + 5, 1, 5, sid); // write middle five
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek(sid, 15, SION_SEEK_BEGINNING); // skip back via absolute positioning
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer, 1, 5, sid); // write front five
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek(sid, 0, SION_SEEK_END); // skip to end
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer + 10, 1, 5, sid); // write back five
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek_chunk(sid, 1, SION_SEEK_END); // move to new chunk
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer, 1, 5, sid); // write front five
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek_chunk(sid, 1, SION_SEEK_CURRENT); // move to next chunk
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer + 10, 1, 5, sid); // write back five
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek_chunk(sid, -1, SION_SEEK_END); // move to previous chunk
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek(sid, -5, SION_SEEK_CHUNK_END); // move to middle of chunk
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer + 5, 1, 5, sid); // write middle five
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      assert(SION_SUCCESS == sion_switch_logical_file(sid, 1));
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek(sid, 5, SION_SEEK_CHUNK_BEGINNING);
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_fwrite(buffer, 1, strlen(buffer), sid); // write more than one chunk
      printf("TEST D: position: %" PRId64 "\n", sion_tell(sid));
      sion_close(sid);
    } else {
      fprintf(stderr, "TEST D: sion_open returned %d\n", sid);
    }
  }

  /* ----------------- */
  /* TEST E: read back */
  /* ----------------- */
  {
    int sid = sion_open("testD.out", SION_OPEN_READ, 0, NULL);
    if (sid >= 0) {
      printf("TEST E: position: %" PRId64 "\n", sion_tell(sid));
      const char *reference = "0123456789ABCDE";
      char buffer[16];
      for (unsigned i = 0; i < 3; i++) {
        memset(buffer, 0, 16);
        sion_fread(buffer, 1, 15, sid);
        printf("TEST E: read data: %s, match: %s\n", buffer, (!strcmp(reference, buffer)) ? "true" : "false");
        printf("TEST E: position: %" PRId64 "\n", sion_tell(sid));
      }
      assert(SION_SUCCESS == sion_switch_logical_file(sid, 1));
      printf("TEST E: position: %" PRId64 "\n", sion_tell(sid));
      sion_seek(sid, 5, SION_SEEK_CURRENT);
      printf("TEST E: position: %" PRId64 "\n", sion_tell(sid));
      memset(buffer, 0, 16);
      sion_fread(buffer, 1, 15, sid);
      printf("TEST E: read data: %s, match: %s\n", buffer, (!strcmp(reference, buffer)) ? "true" : "false");
      printf("TEST E: position: %" PRId64 "\n", sion_tell(sid));
      sion_close(sid);
    } else {
      fprintf(stderr, "TEST E: sion_open returned %d\n", sid);
    }
  }
  return 0;
}
