/*
  NetCDF4/HDF5 benchmark test.
  Sergei.Shibaev@ukaea.org.uk

  Read one data item (integer-only ADC data) and measure time.
*/

#include <hdf5.h>
#include <hdf5_hl.h>
#include <stdlib.h>

#ifdef _WIN32

#include <windows.h>
#include <io.h>

#else

#include <unistd.h>
#include <sys/time.h>

#endif

size_t  size = 0;
float*  pft = 0;
double* pdt = 0;

int get_scale(hid_t dset, float* pfo, float* pfs, double* pdo, double* pds)
{
  int st = 0;
  hid_t attr = H5Aopen(dset, "offset", H5P_DEFAULT);
  if(attr < 0) return(0);
  hid_t atype = H5Aget_type(attr);
  if(atype >= 0) {
    H5T_class_t aclass = H5Tget_class(atype);
    size_t asize = H5Tget_size(atype);
    if(aclass == H5T_FLOAT) {
      if(asize == 4) {
        if(H5Aread(attr, atype, pfo) >= 0) st = 1;
      } else if(asize == 8) {
        if(H5Aread(attr, atype, pdo) >= 0) st = 2;
      }
    }
    H5Tclose(atype);
  }
  H5Aclose(attr);
  if(st) {
    attr = H5Aopen(dset, "scale", H5P_DEFAULT);
    if(attr < 0)
      st = 0;
    else {
      atype = H5Aget_type(attr);
      if(atype < 0)
        st = 0;
      else {
        if(st == 1) {
          if(H5Aread(attr, atype, pfs) < 0) st = 0;
        } else {
          if(H5Aread(attr, atype, pds) < 0) st = 0;
        }
        H5Tclose(atype);
      }
      H5Aclose(attr);
    }
  }
  return(st);
}
// callback function for scale iteration: take first only
herr_t iter_scale(hid_t did, unsigned dim, hid_t dsid, void *data)
{
  data = 0;
  hid_t dspace = H5Dget_space(dsid);
  if(dspace < 0) return(-1);
  hsize_t hdims[2];
  int rank = H5Sget_simple_extent_dims(dspace, hdims, 0);
  H5Sclose(dspace);
  int ret = -1;
  if(rank == 0) {
    float  foff;
    float  fscl;
    double doff;
    double dscl;
    int st = get_scale(dsid, &foff, &fscl, &doff, &dscl);
    if(st == 1) {
      pft = (float*)malloc(size * sizeof(float));
      if(pft) {
        for(unsigned u = 0; u < size; u++) pft[u] = foff + fscl * u;
        ret = 1;
      }
    } else {
      pdt = (double*)malloc(size * sizeof(double));
      if(pdt) {
        for(unsigned u = 0; u < size; u++) pdt[u] = doff + dscl * u;
        ret = 2;
      }
    }
  } else if(rank == 1) {
    if(hdims[0] < size) return(-1);
    hid_t dtype = H5Dget_type(dsid);
    if(dtype >= 0) {
      H5T_class_t dclass = H5Tget_class(dtype);
      if(dclass == H5T_FLOAT) {
        size_t sz = H5Tget_size(dtype);
        if(sz == 4) {
          pft = (float*)malloc(unsigned(hdims[0]) * sizeof(float));
          if(pft) {
            hid_t mspace = H5Screate_simple(1, hdims, hdims);
            if(H5Dread(dsid, H5T_NATIVE_FLOAT, mspace, H5S_ALL, H5P_DEFAULT, pft) >= 0) {
              ret = 1;
            }
            H5Sclose(mspace);
          }
        } else if(sz == 8) {
          pdt = (double*)malloc(unsigned(hdims[0]) * sizeof(double));
          if(pdt) {
            hid_t mspace = H5Screate_simple(1, hdims, hdims);
            if(H5Dread(dsid, H5T_NATIVE_DOUBLE, mspace, H5S_ALL, H5P_DEFAULT, pdt) >= 0) {
              ret = 2;
            }
            H5Sclose(mspace);
          }
        }
      }
      H5Tclose(dtype);
    }
  }
  return(ret);
}

int main(int argc, char **argv)
{
  if(argc < 2) {
    // Usage
    printf("Simple read test for ADC data. Sergei.Shibaev@ukaea.org.uk.\n");
    printf("Usage:\n");
    printf("\th5read hdf5_file_name dataset_name\n");
    return(0);
  }
#ifdef _WIN32
  if(_access(argv[1], 04)) {
#else
  if(access(argv[1], R_OK)) {
#endif
    printf("Can't find file %s\n", argv[1]);
    return(1);
  }
#ifdef _WIN32
  LARGE_INTEGER freq;
  QueryPerformanceFrequency(&freq);
  LARGE_INTEGER sstart;
  QueryPerformanceCounter(&sstart);
#else
  struct timeval sstart;
  gettimeofday(&sstart, 0);
#endif
  hid_t file = H5Fopen(argv[1], H5F_ACC_RDONLY, H5P_DEFAULT);
  if(file < 0) {
    printf("Can't open file %s\n", argv[1]);
    return(1);
  }
  hid_t dset = H5Dopen(file, argv[2], H5P_DEFAULT);
  if(dset < 0) {
    printf("Can't open dataset %s\n", argv[2]);
    H5Fclose(file);
    return(1);
  }
  void*  pdata = 0;
  int    dsize = 0;
  H5T_sign_t dsign;
  double doff;
  double dscl;
  float  foff;
  float  fscl;
  int    scale = 0; // -1 - error; 0 - scale not present; 1 - float; 2 - double
  hid_t dspace = H5Dget_space(dset);
  if(dspace >= 0) {
    hsize_t hdims[2];
    int rank = H5Sget_simple_extent_dims(dspace, hdims, 0);
    if(rank == 1) {
      size = unsigned(hdims[0]);
      hid_t dtype = H5Dget_type(dset);
      if(dtype >= 0) {
        H5T_class_t dclass = H5Tget_class(dtype);
        dsize = H5Tget_size(dtype);
        dsign = H5Tget_sign(dtype);
        if((dclass == H5T_INTEGER) && (dsize > 0) && (dsize < 3)) {
          pdata = malloc(size * dsize);
          if(pdata) {
            if(0 <= H5Dread(dset, dtype, dspace, H5S_ALL, H5P_DEFAULT, pdata)) {
              scale = get_scale(dset, &foff, &fscl, &doff, &dscl);
              if(scale) scale = H5DSiterate_scales(dset, 0, 0, iter_scale, 0);
            }
          }
        } else {
          scale = -1;
          printf("Can't read dataset %s: class %d, data size %d\n", argv[2], dclass, dsize);
        }
        H5Tclose(dtype);
      }
    }
    H5Sclose(dspace);
  }
  H5Dclose(dset);
  H5Fclose(file);
  if(scale >= 0) {
    double dt;
#ifdef _WIN32
    LARGE_INTEGER send;
    QueryPerformanceCounter(&send);
    dt = ((double)send.QuadPart - (double)sstart.QuadPart) / freq.QuadPart;
#else
    struct timeval send;
    gettimeofday(&send, 0);
    dt = ((double)send.tv_sec + 1e-6 * send.tv_usec) -
         ((double)sstart.tv_sec + 1e-6 * sstart.tv_usec);
#endif
    printf("Finish read for %g ms\n", 1e3 * dt);
    printf("Enter 'd' for data or any other key to exit\n");
    if(getchar() == 'd') {
      printf("time\tdata\n");
      if(!scale) {
        if(dsize == 2) {
          if(dsign == H5T_SGN_NONE) {
            unsigned short* pword = (unsigned short*)pdata;
            for(unsigned u = 0; u < size; u++) printf("%g\t%hu\n", pft[u], pword[u]);
          } else {
            short* pshort = (short*)pdata;
            for(unsigned u = 0; u < size; u++) printf("%g\t%hd\n", pft[u], pshort[u]);
          }
        } else if(dsize == 1) {
          if(dsign == H5T_SGN_NONE) {
            unsigned char* pbyte = (unsigned char*)pdata;
            for(unsigned u = 0; u < size; u++) {
              unsigned short w = pbyte[u];
              printf("%g\t%hu\n", pft[u], w);
            }
          } else {
            char* pchar = (char*)pdata;
            for(unsigned u = 0; u < size; u++) {
              short sh = pchar[u];
              printf("%g\t%hd\n", pft[u], sh);
            }
          }
        }
      } else if(scale == 1) {
        if(dsize == 2) {
          if(dsign == H5T_SGN_NONE) {
            unsigned short* pword = (unsigned short*)pdata;
            for(unsigned u = 0; u < size; u++) printf("%g\t%g\n", pft[u], foff + fscl * pword[u]);
          } else {
            short* pshort = (short*)pdata;
            for(unsigned u = 0; u < size; u++) printf("%g\t%g\n", pft[u], foff + fscl * pshort[u]);
          }
        } else if(dsize == 1) {
          if(dsign == H5T_SGN_NONE) {
            unsigned char* pbyte = (unsigned char*)pdata;
            for(unsigned u = 0; u < size; u++) {
              unsigned short w = pbyte[u];
              printf("%g\t%g\n", pft[u], foff + fscl * w);
            }
          } else {
            char* pchar = (char*)pdata;
            for(unsigned u = 0; u < size; u++) {
              short sh = pchar[u];
              printf("%g\t%g\n", pft[u], foff + fscl * sh);
            }
          }
        }
      } else {
        if(dsize == 2) {
          if(dsign == H5T_SGN_NONE) {
            unsigned short* pword = (unsigned short*)pdata;
            for(unsigned u = 0; u < size; u++) printf("%g\t%g\n", pft[u], doff + dscl * pword[u]);
          } else {
            short* pshort = (short*)pdata;
            for(unsigned u = 0; u < size; u++) printf("%g\t%g\n", pft[u], doff + dscl * pshort[u]);
          }
        } else if(dsize == 1) {
          if(dsign == H5T_SGN_NONE) {
            unsigned char* pbyte = (unsigned char*)pdata;
            for(unsigned u = 0; u < size; u++) {
              unsigned short w = pbyte[u];
              printf("%g\t%g\n", pft[u], doff + dscl * w);
            }
          } else {
            char* pchar = (char*)pdata;
            for(unsigned u = 0; u < size; u++) {
              short sh = pchar[u];
              printf("%g\t%g\n", pft[u], doff + dscl * sh);
            }
          }
        }
      }
    }
  }
  if(pdata) free(pdata);
  if(pft) free(pft);
  if(pdt) free(pdt);
  return(0);
}
