/****************************************************************************
**  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 <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "sion.h"

#define ONRANK if (rank == 0)

#define SERIALIZE \
  for (TT = 0; (TT < size); TT++, MPI_Barrier(MPI_COMM_WORLD)) \
    if (TT == rank)

#include "test_genapi_INC_MPI_api.c"

int main(int argc, char **argv)
{
  int rank, size, TT;
  int aid;
  _sample_api_commdata gcomm;

  /* -------------------------- */
  /* PROLOG */
  /* -------------------------- */

  MPI_Init(&argc, &argv);
  MPI_Comm_size(MPI_COMM_WORLD, &size);
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  /* -------------------------- */
  /* TEST 0: define new API     */
  /* -------------------------- */
  {
    aid = sion_generic_create_api("SampleAPI");
    SERIALIZE printf("on rank %d: new API: aid=%d\n", rank, aid);

    gcomm.comm = MPI_COMM_WORLD;
    gcomm.commset = 1;
    gcomm.local = 0;
    gcomm.rank = rank;
    gcomm.rank = size;

    sion_generic_register_create_local_commgroup_cb(aid, &_sample_create_lcg_cb);
    sion_generic_register_free_local_commgroup_cb(aid, &_sample_free_lcg_cb);

    sion_generic_register_barrier_cb(aid, &_sample_barrier_cb);
    sion_generic_register_bcastr_cb(aid, &_sample_bcastr_cb);
    sion_generic_register_gatherr_cb(aid, &_sample_gatherr_cb);
    sion_generic_register_scatterr_cb(aid, &_sample_scatterr_cb);
    sion_generic_register_gathervr_cb(aid, &_sample_gathervr_cb);
    sion_generic_register_scattervr_cb(aid, &_sample_scattervr_cb);

    SERIALIZE printf("on rank %d: END of TEST 0\n", rank);
  }

  /* ------------------------------------------------------ */
  /* TEST B: write a small file with some different pattern */
  /* ------------------------------------------------------ */
  {
#define BUFSIZE 1000
#define CHUNKSIZE 200

    sion_generic_options *options = sion_generic_options_new();
    sion_generic_options_set_chunksize(options, CHUNKSIZE);
    sion_generic_options_set_fsblksize(options, 10);
    sion_generic_options_set_keyval_mode(options, SION_KEYVAL_MODE_INLINE);
    int sid = sion_generic_paropen(aid, "testB.out", SION_OPEN_WRITE, &gcomm, rank, size, 0, 1, rank, size, options);
    sion_generic_options_delete(options);
    if (sid >= 0) {
      char logfilename[256];
      sprintf(logfilename, "keys_rank%04d_in.log", rank);
      FILE *logfp = fopen(logfilename, "w");
      if (logfp == NULL) {
        fprintf(stderr, "cannot open outfile %s , aborting ...\n", logfilename);
        return 1;
      }

      char buffer[BUFSIZE];
      for (size_t i = 0; i < BUFSIZE; i++) {
        buffer[i] = 'A' + rank;
      }

      const size_t len = 16;
      const int cycle_len = 3;
      const int cycle_full = 26;
      size_t bwrote = 0;
      long sum = 0;
      for (int j = 0; j < CHUNKSIZE / (len + 32); j++) {
        uint64_t key = 4711 + (rank + j) % cycle_len;
        for (size_t i = 0; i < len; i++) {
          buffer[i] = 'A' + ((key - 4711 + j / cycle_len) % cycle_full);
        }
        bwrote = sion_fwrite_key(buffer, key, (size_t)1, (size_t)len, sid);
        fprintf(logfp, "on rank %d: key=%6d len=%ld val=", rank, (int)key, (long)len);
        sum = 0;
        for (size_t c = 0; c < len; c++) {
          fprintf(logfp, "%c", buffer[c]);
          sum = sum + buffer[c];
        }
        fprintf(logfp, "\n");
      }
      int64_t bytes_written = sion_get_bytes_written(sid);

      printf("on rank %d: bytes_written=%" PRId64 "\n", rank, bytes_written);
      printf("on rank %d: bwrote=%3d blocksum=%8d \n", rank, (int)bwrote, (int)sum);

      fclose(logfp);
      sion_generic_parclose(sid);

    } else {
      fprintf(stderr, "on rank %d: error sid = %d\n", rank, sid);
    }
  }

  fflush(stderr);
  fflush(stdout);
  MPI_Barrier(MPI_COMM_WORLD);
  printf("on rank %d: END of TEST B write\n", rank);
  MPI_Barrier(MPI_COMM_WORLD);

#undef BUFSIZE
#undef CHUNKSIZE

  /* ------------------------------------------------------ */
  /* TEST B: read small file with some different pattern    */
  /* ------------------------------------------------------ */
  if (1) {
#define BUFSIZE 1000
#define CHUNKSIZE 200
    int sid = sion_generic_paropen(aid, "testB.out", SION_OPEN_READ, &gcomm, rank, size, 0, 0, 0, 0, NULL);

    if (sid >= 0) {
      printf("on rank %d: sid           =%d\n", rank, sid);
      printf("on rank %d: numfiles      =%d\n", rank, sion_get_number_of_files(sid));
      printf("on rank %d: chunksize     =%d\n", rank, (int)sion_get_chunksize(sid));
      printf("on rank %d: globalrank    =%d\n", rank, sion_get_globalrank(sid));

      char logfilename[256];
      sprintf(logfilename, "keys_rank%04d_out.log", rank);
      FILE *logfp = fopen(logfilename, "w");
      if (logfp == NULL) {
        fprintf(stderr, "cannot open outfile %s , aborting ...\n", logfilename);
        return 1;
      }

      char buffer[BUFSIZE];
      for (int i = 0; i < BUFSIZE; i++) {
        buffer[i] = 'Q' + rank;
      }

      const size_t len = 16;
      const int cycle_len = 3;
      for (uint64_t key = 4711; key <= 4711 + cycle_len; key++) {
        /* find key */
        printf("key = %ld\n", (long)key);
        for (int i = 0; i < 15; i++) {
          size_t bread = sion_fread_key(buffer, key, 1, len, sid);
          if (bread != len) {
            printf("on rank %d: end of data for key %d \n", rank, (int)key);
            break;
          }

          printf("%3hhu | %c\n", buffer[0], buffer[0]);
          for (size_t c = 1; c < 16; c++) {
            if (buffer[0] != buffer[c]) {
              printf("ERROR: inconsistent buffer\n");
            }
          }

          fprintf(logfp, "on rank %d: key=%6d len=%ld val=", rank, (int)key, (long)len);
          long sum = 0;
          for (size_t c = 0; c < len; c++) {
            fprintf(logfp, "%c", buffer[c]);
            sum = sum + buffer[c];
          }
          fprintf(logfp, "\n");
        }
      }
      fclose(logfp);
      sion_generic_parclose(sid);
    } else {
      fprintf(stderr, "on rank %d: error sid = %d\n", rank, sid);
    }
  }

  fflush(stderr);
  fflush(stdout);
  MPI_Barrier(MPI_COMM_WORLD);
  printf("on rank %d: END of TEST B read collective\n", rank);
  MPI_Barrier(MPI_COMM_WORLD);

  sion_generic_free_api(aid);

  /* -------------------------- */
  /* EPILOG */
  /* -------------------------- */
  MPI_Finalize();

  return 0;
}
