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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "sion.h"

#define FILENAME_LENGTH 1024
#define MAXNULLCNT 1000

#define MB *1024*1024

void usage(char *name)
{
  fprintf(stderr, "Usage: %s options <insionfn> <outfn>\n\nwith the following optional options (default values in parathesis):\n\n", name);

  fprintf(stderr, "  [-Q <fsblksize>]       filessystem blocksize for new sion file in MB    (default from input file)\n");
  fprintf(stderr, "  [-q <fsblksize>]       filessystem blocksize for new sion file in bytes \n");
  fprintf(stderr, "  [-S <fsblksize>]       local data size on each rank in MB               (default from input file)\n");
  fprintf(stderr, "  [-s <fsblksize>]       local data size on each rank in bytes\n");
  fprintf(stderr, "  [-v]                   verbose mode \n");
  exit(1);
}


int main(int argc, char **argv)
{
  FILE     *fp, *outfp;
  char      infilename[FILENAME_LENGTH];
  char      outfilename[FILENAME_LENGTH];

  int       i, rank, blknum;
  int       nullcount;
  sion_int64 chunksize = 0;
  char     *localbuffer;
  sion_int64 left, bread, bsumread, bwrote, bsumwrote;
  sion_int64 *chunksizes = NULL;
  int      *globalranks = NULL;

  /* options */
  int       verbose = 0;
  sion_int64 opt_localsize = -1;
  sion_int32 opt_fsblocksize = -1;

  /* for file infomation */
  int       ntasks, nfiles;
  sion_int32 fsblksize;
  int       sid, outsid, size, blocks;
  sion_int64 globalskip;
  sion_int64 start_of_varheader;
  sion_int64 *sion_chunksizes;
  sion_int64 *sion_globalranks;
  sion_int64 *sion_blockcount;
  sion_int64 *sion_blocksizes;
  sion_int64 *siondefrag_sum_bytes_per_task;
  sion_int64 siondefrag_sum_bytes;
  sion_int64 siondefrag_filesize;

  /* parse command line */
  i = 1;
  if (argc < 3)
    usage(argv[0]);

  while (i < argc) {
    if (argv[i][0] == '-') {
      switch (argv[i][1]) {
      case 's':
        opt_localsize = atoi(argv[++i]);
        break;
      case 'S':
        opt_localsize = atoi(argv[++i]) MB;
        break;
      case 'q':
        opt_fsblocksize = atoi(argv[++i]);
        break;
      case 'Q':
        opt_fsblocksize = atoi(argv[++i]) MB;
        break;
      case 'v':
        verbose++;
        break;
      default:
        usage(argv[0]);
      }
    }
    i++;
  }

  strcpy(infilename, argv[argc - 2]);
  strcpy(outfilename, argv[argc - 1]);

  printf("siondefrag: infilename:                %-30s\n", infilename);
  printf("siondefrag: outfilename:               %-30s\n", outfilename);

  chunksizes = NULL;
  globalranks = NULL;           /* will be allocated by sion_open */

  sid = sion_open(infilename, "rb", &ntasks, &nfiles, &chunksizes, &fsblksize, &globalranks, &fp);

  printf("siondefrag: sid:                       %d\n", sid);
  printf("siondefrag: filename:                  %-30s\n", infilename);
  printf("siondefrag: fsblksize:                 %lu bytes (%6.2f MB)\n", (unsigned long) fsblksize, fsblksize / 1024.0 / 1024.0);
  printf("siondefrag: current endianness:        %s\n", (sion_get_endianness())? "big" : "little");
  printf("siondefrag: file endianness:           %s\n", (sion_get_file_endianness(sid)) ? "big" : "little");

  sion_get_locations(sid, &size, &blocks, &globalskip, &start_of_varheader, &sion_chunksizes, &sion_globalranks, &sion_blockcount,
                     &sion_blocksizes);

  printf("siondefrag: max number of blocks:      %d\n", blocks);

  /* analysis */
  siondefrag_sum_bytes_per_task = (sion_int64 *) malloc(ntasks * sizeof(sion_int64));
  for (rank = 0; rank < size; rank++)
    siondefrag_sum_bytes_per_task[rank] = 0;
  for (rank = 0; rank < size; rank++) {
    for (blknum = 0; blknum < blocks; blknum++) {
      siondefrag_sum_bytes_per_task[rank] += sion_blocksizes[size * blknum + rank];
    }
  }
  siondefrag_sum_bytes = 0;
  for (rank = 0; rank < size; rank++)
    siondefrag_sum_bytes += siondefrag_sum_bytes_per_task[rank];
  printf("siondefrag: datasize in file (aggr.):  %lld bytes  (%6.2f MB)\n", siondefrag_sum_bytes, siondefrag_sum_bytes / 1024.0 / 1024.0);
  siondefrag_filesize = start_of_varheader + (blocks + 1) * rank * sizeof(sion_int64);
  printf("siondefrag: start_of_varheader:        %lld bytes  (%6.2f MB)\n", start_of_varheader, start_of_varheader / 1024.0 / 1024.0);
  printf("siondefrag: size of file:              %lld bytes  (%6.2f MB)\n", siondefrag_filesize, siondefrag_filesize / 1024.0 / 1024.0);
  if (siondefrag_filesize > 0) {
    printf("siondefrag: file usage:                %8.6f%%\n", (double) siondefrag_sum_bytes / (double) siondefrag_filesize * 100.0);
  }

  /* scan for block with max. number of bytes  */
  for (rank = 0; rank < size; rank++) {
    if (sion_chunksizes[rank] > chunksize)
      chunksize = sion_chunksizes[rank];
    for (blknum = 0; blknum < sion_blockcount[rank]; blknum++) {
      if (sion_blocksizes[size * blknum + rank] > chunksize)
        chunksize = sion_blocksizes[size * blknum + rank];
    }
  }

  printf("siondefrag: max chunksize:             %lld\n", chunksize);
  localbuffer = (char *) malloc(chunksize * sizeof(char));
  if (localbuffer == NULL) {
    fprintf(stderr, "cannot allocate localbuffer of size %lld , aborting ...\n", chunksize * sizeof(char));
    return (1);
  }

  for (rank = 0; rank < size; rank++)
    chunksizes[rank] = (sion_int64) siondefrag_sum_bytes_per_task[rank];

  if (opt_fsblocksize != -1)
    fsblksize = opt_fsblocksize;
  outsid = sion_open(outfilename, "wb", &ntasks, &nfiles, &chunksizes, &fsblksize, &globalranks, &outfp);

  printf("siondefrag: outsid:                    %d\n", outsid);
  printf("siondefrag: fsblksize outfile:         %lu\n", (unsigned long) fsblksize);

  for (rank = 0; rank < size; rank++) {

    /* set position in outfile to current rank */
    if (verbose)
      printf("siondefrag: ->rank:                    %d\n", rank);
    if (!verbose)
      if (rank % 16 == 0) {
        printf("[%d]", rank);
        fflush(stdout);
      }
    sion_seek(outsid, rank, SION_CURRENT_BLK, SION_CURRENT_POS);

    if (verbose)
      printf("siondefrag:                            copy now %lld blocks on rank=%d \n", sion_blockcount[rank], rank);
    for (blknum = 0; blknum < sion_blockcount[rank]; blknum++) {

      /* seek position of block */
      sion_seek(sid, rank, blknum, 0);

      /* read data from block */
      left = sion_blocksizes[size * blknum + rank];
      if (verbose)
        printf("siondefrag:                               copy now block #%d with %lld bytes \n", blknum, left);
      bsumread = 0;
      nullcount = 0;
      while (left > 0) {
        bread = fread(localbuffer + bsumread, 1, left, fp);
        if (bread == 0)
          nullcount++;
        else
          nullcount = 0;
        left -= bread;
        bsumread += bread;
/*         printf("siondefrag:                            %lld read left=%lld \n", bread, left); */
        if (nullcount > MAXNULLCNT) {
          fprintf(stderr, "timeout on read data , aborting ...\n");
          exit(0);
        }
      }

      /* write data to outfile */
      left = sion_blocksizes[size * blknum + rank];
      bsumwrote = 0;
      while (left > 0) {
        bwrote = fwrite(localbuffer + bsumwrote, 1, left, outfp);
        if (bwrote == 0)
          nullcount++;
        else
          nullcount = 0;
        left -= bwrote;
        bsumwrote += bwrote;
        /*      printf("siondefrag:                            %ld wrote left=%ld position=%lld\n",bwrote,left,_sion_get_position(outfp)); */
        if (nullcount > MAXNULLCNT) {
          fprintf(stderr, "timeout on write data , aborting ...\n");
          exit(0);
        }
      }
    }

  }
  if (!verbose)
    printf("\n");

  free(siondefrag_sum_bytes_per_task);
  free(localbuffer);

  sion_close(sid);

  sion_close(outsid);

  return (0);
}
