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

///
///  HDF5 simple wrapper for basics operations
///  If H5Lite is available, use https://www.hdfgroup.org/HDF5/doc/HL/RM_H5LT.html
///


// Max. number of dimensions
#define MAX_DIMENSION 8


hid_t h5_create_file(char* fname) {
  // H5F_ACC_EXCL  makes the whole process explode is the file exists ; maybe use H5F_ACC_TRUNC instead
  // Check if the file already exists. fopen() might return (< 0) if wrong permissions ; consider using stat() [unistd.h]
  FILE* fid = fopen(fname, "rb");
  if (fid) {
    printf("h5_create_file() : %s already exists\n", fname);
    return -1;
  }
  hid_t file = H5Fcreate(fname, H5F_ACC_EXCL, H5P_DEFAULT, H5P_DEFAULT);
  return file;
}

hid_t h5_get_datatype_fromstring(const char* datatype) {
  hid_t memtypeid;
  if (!strcasecmp(datatype, "float") || !strcasecmp(datatype, "float32")) memtypeid = H5T_NATIVE_FLOAT;
  else if (!strcasecmp(datatype, "char")) memtypeid = H5T_NATIVE_CHAR;
  else if (!strcasecmp(datatype, "uchar")) memtypeid = H5T_NATIVE_UCHAR;
  else if (!strcasecmp(datatype, "int")) memtypeid = H5T_NATIVE_INT;
  else if (!strcasecmp(datatype, "uint")) memtypeid = H5T_NATIVE_UINT;
  // TODO: short, unsigned short, unsigned long, unsigned long, long long, unsigned long long, double, long double, hsize_t, hssize_t, herr_t, hbool_t
  else {
    printf("h5_get_datatype_fromstring(): unknown data type : %s\n", datatype);
    return -1;
  }
  return memtypeid;
}

char* h5_get_dataname_fromtype(hid_t dtype, int* dtypesize) {
  if (dtype == H5T_NATIVE_FLOAT || dtype == H5T_FLOAT) {
    *dtypesize = 4;
    return strdup("float32");
  }
  else { //TODO !!
    *dtypesize = 4;
    return strdup("int32");
  }
}


herr_t h5_write_attribute(hid_t dataset, char* attrname, int ndims, const hsize_t* size, void* attrvalue, const char* datatype) {

  // Create dataspace for the attribute
  hid_t aid, attr;
  herr_t ret;

  aid = H5Screate(H5S_SIMPLE);
  ret = H5Sset_extent_simple(aid, ndims, size, NULL);

  // Create attribute in the current dataset
  hid_t memtypeid = h5_get_datatype_fromstring(datatype);
  attr = H5Acreate2(dataset, attrname, /*memtypeid*/H5T_NATIVE_FLOAT , aid, H5P_DEFAULT, H5P_DEFAULT);

  // Write array attribute
  ret = H5Awrite(attr, memtypeid, attrvalue);

  return ret;
}



hid_t h5_fopen(const char* fname, const char* mode) {
  FILE* fid0 = fopen(fname, "rb");
  if (!fid0) {
//    printf("h5_fopen(): could not open %s\n", fname);
    return -1;
  }
  else fclose(fid0);
  unsigned hmode;
  // TODO: insert here all the crazy stuff that developper can put
  if (!strcasecmp(mode, "r") || !strcasecmp(mode, "rb")) hmode = H5F_ACC_RDONLY;
  if (!strcasecmp(mode, "w") || !strcasecmp(mode, "wb") || !strcasecmp(mode, "wr")) hmode = H5F_ACC_RDWR;
  hid_t fid = H5Fopen(fname, hmode, H5P_DEFAULT);
  return fid;
  // this file identifier should be closed by calling H5Fclose when it is no longer needed.
}


hid_t h5_get_dataset(hid_t fid, const char* dname) {
  return H5Dopen2(fid, dname, H5P_DEFAULT);
  // A dataset opened with this macro should be closed with H5Dclose when the dataset is no longer needed so that resource leaks will not develop.
}

herr_t h5_get_dataset_value(hid_t dataset, const char* datatype, void* rawptr) {
    hid_t memtype = h5_get_datatype_fromstring(datatype);
    if (memtype < 0) return -2;
    return H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, rawptr);
}


int h5_get_attribute_scalar(hid_t dataset, const char* attname, const char* datatype, void* val) {
  hid_t attr = H5Aopen(dataset, attname, H5P_DEFAULT);
  if (attr < 0) {
    printf("h5_get_attribute_scalar() : could not open the attribute %s\n", attname);
    return -1;
  }
  hid_t memtype = h5_get_datatype_fromstring(datatype);
  herr_t ret  = H5Aread(attr, memtype, val);
  H5Aclose(attr);
  return ret;
} // TODO : same for string



herr_t h5_write_attribute_scalar(hid_t dataset, const char* attname, const char* datatype, void* val) {

  hid_t aid = H5Screate(H5S_SCALAR);
  hid_t memtype = h5_get_datatype_fromstring(datatype);
  hid_t attr = H5Acreate2(dataset, attname, memtype, aid, H5P_DEFAULT, H5P_DEFAULT);
  if (attr < 0) {
    printf("h5_write_attribute_scalar() : could not get attribute %s of data type %s\n", attname, datatype);
    return -1;
  }
  herr_t ret = H5Awrite(attr, memtype, val);
  H5Aclose(attr);
  H5Sclose(aid);

  return ret;
}


hid_t h5_create_dataset(hid_t file, const char* dataset_name, int ndims, const hsize_t* size, const char* datatype) {

  hid_t fid = H5Screate(H5S_SIMPLE);
  herr_t ret = H5Sset_extent_simple(fid, ndims, size, NULL);
  if (ret < 0) {
    puts("h5_create_dataset() : unable to create the dataset with specified shape");
    return -1;
  }
  hid_t memtype = h5_get_datatype_fromstring(datatype);
  if (memtype < 0) return -2;
  hid_t dataset = H5Dcreate2(file, dataset_name, memtype, fid, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
  H5Sclose(fid);

  return dataset;
  // should be closed with H5Dclose once finished
}


herr_t h5_write_dataset(hid_t dataset, void* data, const char* datatype) {
  hid_t memtype = h5_get_datatype_fromstring(datatype);
  if (memtype < 0) return -1;
  return H5Dwrite(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data);
}




typedef struct h5_struct {
  int ndims;				// Number of dimensions
  const hsize_t* size;	// Size of each dimension
  size_t npts;			// Total number of elements
  hid_t datatype;			// native type
  size_t datatype_size;	// datatype size
  void* data;				// raw pointer to the data
} h5_struct;

void h5_print_attribute_structure(h5_struct s) {
  printf("Number of dimensions : %d\n", s.ndims);
  printf("Size of each dimension : ");
  int i;
  for (i=0; i < s.ndims; i++) printf("%d : %d\t", i, (int) s.size[i]); puts(" ");
  printf("Total number of elements : %d\n", (int) s.npts);
  printf("datatype id : %d\n", s.datatype); // TODO : enum
  printf("datatype size : %d\n", (int) s.datatype_size);
}

/*
/// Operator function used by h5_get_attribute_iterate()
static herr_t attr_info(hid_t dataset, const char* attname, const H5A_info_t *ainfo, void *opdata) {

  // Attribute
  hid_t attr = H5Aopen(dataset, attname, H5P_DEFAULT);
  // Attribute : datatype
  hid_t atype  = H5Aget_type(attr);
  // Attribute: dataspace
  hid_t aspace = H5Aget_space(attr);

  h5_struct* result = opdata;
  // Number of dimensions
  result->ndims = H5Sget_simple_extent_ndims(aspace);
  // Size of each dimension
  hsize_t sdim[MAX_DIMENSION];
  H5Sget_simple_extent_dims(aspace, sdim, NULL);
  result->size = sdim;
  // Total number of elements
  result->npts = H5Sget_simple_extent_npoints(aspace);
  // Native datatype
  result->datatype = H5Tget_native_type(atype, H5T_DIR_ASCEND);
  // Size of datatype
  result->datatype_size = H5Tget_size(atype);
  // Pointer to the data
  void *raw_array;
  raw_array = calloc(result->npts, sizeof(result->datatype_size));
  H5Aread(attr, atype, raw_array);
  result->data = raw_array;

  H5Tclose(atype);
  H5Sclose(aspace);
  H5Aclose(attr);

  return 0;
}


h5_struct h5_get_attribute_iterate(hid_t dataset) {
  h5_struct ret;
  H5Aiterate2(dataset, H5_INDEX_NAME, H5_ITER_INC, NULL, attr_info, &ret);
  return ret;
}
*/

h5_struct h5_get_attribute(hid_t dataset, const char* attname) {

  // Attribute
  hid_t attr = H5Aopen(dataset, attname, H5P_DEFAULT);
  // Attribute : datatype
  hid_t atype  = H5Aget_type(attr);
  // Attribute: dataspace
  hid_t aspace = H5Aget_space(attr);

  h5_struct result;
  // Number of dimensions
  result.ndims = H5Sget_simple_extent_ndims(aspace);
  // Size of each dimension
  hsize_t sdim[MAX_DIMENSION];
  H5Sget_simple_extent_dims(aspace, sdim, NULL);
  result.size = sdim;
  // Total number of elements
  result.npts = H5Sget_simple_extent_npoints(aspace);
  // Native datatype
  result.datatype = H5Tget_native_type(atype, H5T_DIR_ASCEND);
  // Size of datatype
  result.datatype_size = H5Tget_size(atype);
  // Pointer to the data
  void *raw_array;
  raw_array = calloc(result.npts, sizeof(result.datatype_size));
  H5Aread(attr, atype, raw_array);
  result.data = raw_array;

  H5Tclose(atype);
  H5Sclose(aspace);
  H5Aclose(attr);

  return result;
}



int main(int args, char* argv[]) {

  float* mydata = (float*) calloc(7, sizeof(float));
  int i, val;
  hid_t fid, did;
  for (i=0; i < 7; i++) mydata[i] = i;
  int ndims = 1;
  hsize_t size[] = {7};


  if (args > 1 && !strcasecmp(argv[1], "write")) {
    // Create the HDF5 file
    fid = h5_create_file("hello.h5");
    if (fid < 0) return 1;

    // Create a new dataset
    did = h5_create_dataset(fid, "mydataset", ndims, size, "float");

    // Write a scalar attribute in this dataset
    val = 1000;
    h5_write_attribute_scalar(did, "num_projs", "int", &val);

    // Write an array attribute in this dataset
    h5_write_attribute(did, "data", ndims, size, mydata, "float");

    puts("wrote one scalar attribute and one array attribute");
  }
  if (args > 1 && !strcasecmp(argv[1], "read")) {
    // Open the HDF5 file
    fid = h5_fopen("hello.h5", "r");
    if (fid < 0) return 1;

    // Open a dataset
    did = h5_get_dataset(fid, "mydataset");

    // Read a scalar attribute
    val = 0;
    h5_get_attribute_scalar(did, "num_projs", "int", &val);
    printf("num_projs =  : %d\n", val);

    // Read an array attribute
    h5_struct s = h5_get_attribute(did, "data");
    h5_print_attribute_structure(s);
    for (i = 0; i < s.npts; i++) printf("%f ", ((float*) s.data)[i]); puts(" ");


  }

  return 0;


}






