/****************************************************************************
 *              Basic manipulation of a H5DSetDescriptor struct             *
 *                            Author: H. Pag\`es                            *
 ****************************************************************************/
#include "H5DSetDescriptor.h"

#include "H5File.h"
#include "global_errmsg_buf.h"

#include <stdlib.h>  /* for malloc, free */
#include <string.h>  /* for strlen, strcmp */
#include <limits.h>  /* for INT_MAX */


/* Make sure to keep map_native_type_to_predef_type() and
   predef_native_type_as_string() in sync! */
#define	RETURN_PREDEF_TYPE_IF(predef_type_id) \
	if (H5Tequal(native_type_id, predef_type_id) > 0) \
		return predef_type_id;
static hid_t map_native_type_to_predef_type(hid_t native_type_id)
{
	/* See H5Tpublic.h for authoritative list of native types.
	   Note that the list published at
	     https://portal.hdfgroup.org/display/HDF5/Predefined+Datatypes
	   might be incomplete or out-of-sync with H5Tpublic.h. */
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_CHAR)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_SCHAR)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UCHAR)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_SHORT)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_USHORT)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_LONG)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_ULONG)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_LLONG)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_ULLONG)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_FLOAT)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_DOUBLE)
#ifdef H5T_NATIVE_LDOUBLE
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_LDOUBLE)
#endif
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_B8)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_B16)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_B32)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_B64)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_OPAQUE)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_HADDR)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_HSIZE)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_HSSIZE)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_HERR)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_HBOOL)

	/* C9x integer types */

	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT8)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT8)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_LEAST8)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_LEAST8)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_FAST8)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_FAST8)

	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT16)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT16)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_LEAST16)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_LEAST16)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_FAST16)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_FAST16)

	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT32)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT32)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_LEAST32)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_LEAST32)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_FAST32)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_FAST32)

	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT64)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT64)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_LEAST64)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_LEAST64)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_INT_FAST64)
	RETURN_PREDEF_TYPE_IF(H5T_NATIVE_UINT_FAST64)

	PRINT_TO_ERRMSG_BUF("failed to map native type id %lld "
			    "to predef type id", (long long) native_type_id);
	return -1;
}

/* Make sure to keep map_native_type_to_predef_type() and
   predef_native_type_as_string() in sync! */
#define	RETURN_TYPE_STRING_IF(predef_type_id) \
	if (native_type_id == predef_type_id) \
		return #predef_type_id;  /* stringize 'predef_type_id' */
static const char *predef_native_type_as_string(hid_t native_type_id)
{
	static char s[50];

	RETURN_TYPE_STRING_IF(H5T_NATIVE_CHAR)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_SCHAR)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UCHAR)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_SHORT)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_USHORT)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_LONG)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_ULONG)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_LLONG)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_ULLONG)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_FLOAT)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_DOUBLE)
#ifdef H5T_NATIVE_LDOUBLE
	RETURN_TYPE_STRING_IF(H5T_NATIVE_LDOUBLE)
#endif
	RETURN_TYPE_STRING_IF(H5T_NATIVE_B8)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_B16)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_B32)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_B64)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_OPAQUE)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_HADDR)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_HSIZE)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_HSSIZE)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_HERR)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_HBOOL)

	/* C9x integer types */

	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT8)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT8)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_LEAST8)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_LEAST8)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_FAST8)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_FAST8)

	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT16)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT16)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_LEAST16)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_LEAST16)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_FAST16)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_FAST16)

	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT32)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT32)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_LEAST32)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_LEAST32)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_FAST32)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_FAST32)

	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT64)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT64)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_LEAST64)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_LEAST64)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_INT_FAST64)
	RETURN_TYPE_STRING_IF(H5T_NATIVE_UINT_FAST64)

	/* Should never happen if predef_native_type_as_string()
	   is kept in sync with map_native_type_to_predef_type(). */
	snprintf(s, sizeof(s), "unknown native type (%lld)",
			       (long long) native_type_id);
	return s;
}

/* Gosh, it really sucks that H5Tget_native_type() doesn't actually return
   the id of a predefined native type like H5T_NATIVE_INT. Instead it returns
   a stupid random type id that cannot be compared directly with the
   predefined ones using something like ==. Nope, you need to compare with
   H5Tequal()! And to make the matter even worse, the documentation for
   H5Tget_native_type() doesn't say anything about this! */
static hid_t map_h5type_to_predef_native_type(hid_t h5type_id)
{
	hid_t native_type_id, predef_native_type_id;

	native_type_id = H5Tget_native_type(h5type_id, H5T_DIR_DEFAULT);
	if (native_type_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Tget_native_type() returned an error");
		return -1;
	}
	predef_native_type_id = map_native_type_to_predef_type(native_type_id);
	H5Tclose(native_type_id);
	return predef_native_type_id;
}

static const char *H5class2str(H5T_class_t h5class)
{
	static char s[32];

	switch (h5class) {
	    case H5T_INTEGER:   return "H5T_INTEGER";
	    case H5T_FLOAT:     return "H5T_FLOAT";
	    case H5T_STRING:    return "H5T_STRING";
	    case H5T_TIME:      return "H5T_TIME";
	    case H5T_BITFIELD:  return "H5T_BITFIELD";
	    case H5T_OPAQUE:    return "H5T_OPAQUE";
	    case H5T_COMPOUND:  return "H5T_COMPOUND";
	    case H5T_REFERENCE: return "H5T_REFERENCE";
	    case H5T_ENUM:      return "H5T_ENUM";
	    case H5T_VLEN:      return "H5T_VLEN";
	    case H5T_ARRAY:     return "H5T_ARRAY";
	/* Only to avoid gcc warning about enumeration value not handled
	   in switch. */
	    default: break;
	}
	snprintf(s, sizeof(s), "unknown class (%d)", h5class);
	return s;
}

static const char *H5layout2str(H5D_layout_t H5layout)
{
	static char s[32];

	switch (H5layout) {
	    case H5D_COMPACT:    return "H5D_COMPACT";
	    case H5D_CONTIGUOUS: return "H5D_CONTIGUOUS";
	    case H5D_CHUNKED:    return "H5D_CHUNKED";
	    case H5D_VIRTUAL:    return "H5D_VIRTUAL";
	/* Only to avoid gcc warning about enumeration value not handled
	   in switch. */
	    default: break;
	}
	snprintf(s, sizeof(s), "unknown layout (%d)", H5layout);
	return s;
}

hsize_t *_alloc_hsize_t_buf(size_t buflength, int zeroes, const char *what)
{
	hsize_t *buf = (hsize_t *) malloc(buflength * sizeof(hsize_t));
	if (buf == NULL) {
		PRINT_TO_ERRMSG_BUF("failed to allocate memory for %s", what);
		return NULL;
	}
	if (zeroes) {
		for (size_t i = 0; i < buflength; i++)
			buf[i] = 0;
	}
	return buf;
}

/* Does not distinguish between no name and an empty name (i.e. a name
   that is the empty string). */
static char *get_h5name(hid_t obj_id)
{
	ssize_t name_size;
	char *h5name;

	name_size = H5Iget_name(obj_id, NULL, 0);
	if (name_size < 0) {
		PRINT_TO_ERRMSG_BUF("H5Iget_name() returned an error");
		return NULL;
	}
	name_size++;
	h5name = (char *) malloc((size_t) name_size);
	if (h5name == NULL) {
		PRINT_TO_ERRMSG_BUF("failed to allocate memory "
				    "for 'h5name'");
		return NULL;
	}
	name_size = H5Iget_name(obj_id, h5name, (size_t) name_size);
	if (name_size < 0) {
		PRINT_TO_ERRMSG_BUF("H5Iget_name() returned an error");
		return NULL;
	}
	return h5name;
}


/****************************************************************************
 * _get_h5attrib_as_one_string()
 * get_h5attrib_as_one_int()
 */

static int h5attr_is_scalar(hid_t attr_id)
{
	hid_t attr_space_id = H5Aget_space(attr_id);
	if (attr_space_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Aget_space() returned an error");
		return -1;
	}
	/* If the attribute value is effectively stored as a scalar (DATASPACE
	   SCALAR in h5dump's output) then H5Sget_simple_extent_ndims() will
	   return 0. */
	int rank = H5Sget_simple_extent_ndims(attr_space_id);
	if (rank != 1) {
		H5Sclose(attr_space_id);
		if (rank < 0) {
			PRINT_TO_ERRMSG_BUF("H5Sget_simple_extent_ndims() "
					    "returned an error");
			return -1;
		}
		return rank == 0 ? 1 : 0;
	}
	/* We also consider the attribute value to be a scalar if it's stored
	   as a 1D dataset of length 1. */
	hsize_t attr_len;
	if (H5Sget_simple_extent_dims(attr_space_id, &attr_len, NULL) != rank) {
		H5Sclose(attr_space_id);
		PRINT_TO_ERRMSG_BUF("H5Sget_simple_extent_dims() returned "
				    "an unexpected value");
		return -1;
	}
	H5Sclose(attr_space_id);
	return attr_len == 1;
}

static int read_h5attrib_string_val(hid_t attr_id, hid_t attr_type_id,
				    CharAE *val)
{
	int attr_is_var_str = H5Tis_variable_str(attr_type_id);
	if (attr_is_var_str < 0) {
		PRINT_TO_ERRMSG_BUF("H5Tis_variable_str() returned an error");
		return -1;
	}
	size_t val_nelt;
	if (attr_is_var_str) {
		/* When the attribute value is a variable-length string, it
		   seems that H5Aread() expects 'buf' to be an array of n
		   string pointers where n is the number of strings that make
		   the attribute value. So in the general case 'buf' would
		   be declared with something like 'char *buf[n]'. However,
		   in our case n is 1 because we know that the attibute is a
		   scalar string.
		   Also it seems that H5Aread() takes care of allocating the
		   memory needed to store the individual strings. So we need
		   to remember to free() that memory when we are done!
		   Surprisingly, the official documentation of H5Aread() does
		   not explain any of this. All it says about the 'buf'
		   argument is:
			buf: Buffer for data to be read
		   Luckily I was able to figure these things out by looking
		   at Bernd Fischer code for the implementation of the
		   H5Aread_helper_STRING() function in the rhdf5 package (see
		   file rhdf5/src/H5A.c). */
		char *buf[1];
		int ret = H5Aread(attr_id, attr_type_id, buf);
		if (ret < 0) {
			PRINT_TO_ERRMSG_BUF("H5Aread() returned an error");
			return -1;
		}
		val_nelt = strlen(buf[0]) + 1;
		if (val_nelt > val->_buflength)
			CharAE_extend(val, val_nelt);
		/* memcpy() should be slightly faster than strcpy() because
		   we already know how many characters to copy. */
		memcpy(val->elts, buf[0], sizeof(char) * val_nelt);
		free(buf[0]);
	} else {
		val_nelt = (size_t) H5Aget_storage_size(attr_id);
		if (val_nelt == 0) {
			PRINT_TO_ERRMSG_BUF("H5Aget_storage_size() returned 0");
			return -1;
		}
		if (val_nelt > val->_buflength)
			CharAE_extend(val, val_nelt);
		int ret = H5Aread(attr_id, attr_type_id, val->elts);
		if (ret < 0) {
			PRINT_TO_ERRMSG_BUF("H5Aread() returned an error");
			return -1;
		}
	}
	CharAE_set_nelt(val, val_nelt);
	return 0;
}

/* Get an attribute value as a single string. The attribute value is
   expected to be a scalar string, that is, a 1D dataset of length 1 and
   class H5T_STRING. The function returns:
    -1: if an error occurs;
     0: if dataset 'dset_id' has no attribute with the name specified
        in 'attr_name';
     1: if dataset 'dset_id' has an attribute with the name specified
        in 'attr_name' but the attribute value is not a scalar string;
     2: if dataset 'dset_id' has an attribute with the name specified
        in 'attr_name' and the attribute value is a scalar string (in
        which case, and only in this case, the attribute value is copied
        to 'val').
 */
int _get_h5attrib_as_one_string(hid_t dset_id, const char *attr_name,
				CharAE *val)
{
	int ret = H5Aexists(dset_id, attr_name);
	if (ret < 0) {
		PRINT_TO_ERRMSG_BUF("H5Aexists() returned an error");
		return -1;
	}
	if (ret == 0)
		return 0;
	hid_t attr_id = H5Aopen(dset_id, attr_name, H5P_DEFAULT);
	if (attr_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Aopen() returned an error");
		return -1;
	}
	int attr_is_scalar = h5attr_is_scalar(attr_id);
	if (attr_is_scalar <= 0) {
		H5Aclose(attr_id);
		return attr_is_scalar < 0 ? -1 : 1;
	}
	hid_t attr_type_id = H5Aget_type(attr_id);
	if (attr_type_id < 0) {
		H5Aclose(attr_id);
		PRINT_TO_ERRMSG_BUF("H5Aget_type() returned an error");
		return -1;
	}
	H5T_class_t attr_H5class = H5Tget_class(attr_type_id);
	if (attr_H5class == H5T_NO_CLASS) {
		H5Tclose(attr_type_id);
		H5Aclose(attr_id);
		PRINT_TO_ERRMSG_BUF("H5Tget_class() returned an error");
		return -1;
	}
	if (attr_H5class != H5T_STRING) {
		H5Tclose(attr_type_id);
		H5Aclose(attr_id);
		return 1;
	}
	ret = read_h5attrib_string_val(attr_id, attr_type_id, val);
	H5Tclose(attr_type_id);
	H5Aclose(attr_id);
	return ret < 0 ? -1 : 2;
}

/* Get an attribute value as a single integer. The attribute value is
   expected to be a scalar integer, that is, a 1D dataset of length 1 and
   class H5T_INTEGER. The function returns:
    -1: if an error occurs;
     0: if dataset 'dset_id' has no attribute with the name specified
        in 'attr_name';
     1: if dataset 'dset_id' has an attribute with the name specified
        in 'attr_name' but the attribute value is not a scalar integer;
     2: if dataset 'dset_id' has an attribute with the name specified
        in 'attr_name' and the attribute value is a scalar integer (in
        which case, and only in this case, the attribute value is copied
        to 'val').
 */
static int get_h5attrib_as_one_int(hid_t dset_id, const char *attr_name,
				   int *val)
{
	int ret = H5Aexists(dset_id, attr_name);
	if (ret < 0) {
		PRINT_TO_ERRMSG_BUF("H5Aexists() returned an error");
		return -1;
	}
	if (ret == 0)
		return 0;
	hid_t attr_id = H5Aopen(dset_id, attr_name, H5P_DEFAULT);
	if (attr_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Aopen() returned an error");
		return -1;
	}
	int attr_is_scalar = h5attr_is_scalar(attr_id);
	if (attr_is_scalar <= 0) {
		H5Aclose(attr_id);
		return attr_is_scalar < 0 ? -1 : 1;
	}
	hid_t attr_type_id = H5Aget_type(attr_id);
	if (attr_type_id < 0) {
		H5Aclose(attr_id);
		PRINT_TO_ERRMSG_BUF("H5Aget_type() returned an error");
		return -1;
	}
	H5T_class_t attr_H5class = H5Tget_class(attr_type_id);
	if (attr_H5class == H5T_NO_CLASS) {
		H5Tclose(attr_type_id);
		H5Aclose(attr_id);
		PRINT_TO_ERRMSG_BUF("H5Tget_class() returned an error");
		return -1;
	}
	if (attr_H5class != H5T_INTEGER) {
		H5Tclose(attr_type_id);
		H5Aclose(attr_id);
		return 1;
	}
	/* Note that this check seems unnecessary because we've already
	   checked that the attribute is a scalar integer above. */
	size_t attr_size = (size_t) H5Aget_storage_size(attr_id);
	if (attr_size != sizeof(int)) {
		H5Tclose(attr_type_id);
		H5Aclose(attr_id);
		return 1;
	}
	ret = H5Aread(attr_id, attr_type_id, val);
	H5Tclose(attr_type_id);
	H5Aclose(attr_id);
	if (ret < 0) {
		PRINT_TO_ERRMSG_BUF("H5Aread() returned an error");
		return -1;
	}
	return 2;
}


/****************************************************************************
 * Handle Rtype
 */

static int map_numeric_h5class_to_Rtype(H5T_class_t h5class,
		size_t h5type_size, int h5type_signedness, SEXPTYPE *Rtype)
{
	switch (h5class) {
	    case H5T_INTEGER:
		if (h5type_size <= sizeof(char) && h5type_signedness == 0) {
			*Rtype = RAWSXP;
		} else if (h5type_size <= sizeof(int)) {
			*Rtype = INTSXP;
		} else {
			*Rtype = REALSXP;
		}
		return 1;
	    case H5T_FLOAT:
		*Rtype = REALSXP;
		return 1;
	/* Only to avoid gcc warning about enumeration value not handled
	   in switch. */
	    default: break;
	}
	return 0;
}

static int map_storage_mode_to_Rtype(const char *storage_mode, SEXPTYPE *Rtype)
{
	if (strcmp(storage_mode, "logical") == 0) {
		*Rtype = LGLSXP;
		return 1;
	}
	if (strcmp(storage_mode, "integer") == 0) {
		*Rtype = INTSXP;
		return 1;
	}
	if (strcmp(storage_mode, "double") == 0 ||
	    strcmp(storage_mode, "numeric") == 0) {
		*Rtype = REALSXP;
		return 1;
	}
	if (strcmp(storage_mode, "character") == 0) {
		warning("dataset has a \"storage.mode\" attribute "
			"set to \"character\" -- we ignore it");
		return 0;
	}
	if (strcmp(storage_mode, "raw") == 0) {
		*Rtype = RAWSXP;
		return 1;
	}
	warning("dataset has a \"storage.mode\" attribute "
		"set to unsupported value \"%s\" -- we ignore it",
		storage_mode);
	return 0;
}

/* See hdf5-1.10.3/src/H5Tpublic.h for the list of datatype classes. We only
   support H5T_INTEGER, H5T_FLOAT, and H5T_STRING for now. */
static int map_h5type_to_Rtype(H5T_class_t h5class,
		size_t h5type_size, int h5type_signedness,
		int as_int, const char *storage_mode,
		SEXPTYPE *Rtype)
{
	int Rtype_is_set;

	if (h5class == H5T_STRING) {
		if (as_int)
			warning("'as.integer' is ignored when "
				"dataset class is H5T_STRING");
		*Rtype = STRSXP;
		if (storage_mode == NULL
		 || strcmp(storage_mode, "character") == 0)
			return 1;
		warning("dataset class is H5T_STRING but it has a "
			"\"storage.mode\" attribute set to %s -- we ignore it",
			storage_mode);
		return 1;
	}
	if (as_int) {
		*Rtype = INTSXP;
		return 1;
	}
	Rtype_is_set = map_numeric_h5class_to_Rtype(h5class,
					h5type_size, h5type_signedness, Rtype);
	if (storage_mode == NULL)
		return Rtype_is_set;
	return map_storage_mode_to_Rtype(storage_mode, Rtype) || Rtype_is_set;
}

static size_t get_Rtype_size(SEXPTYPE Rtype, size_t h5type_size)
{
	switch (Rtype) {
	    case LGLSXP: case INTSXP: return sizeof(int);
	    case REALSXP:             return sizeof(double);
	    case STRSXP:              return h5type_size;
	    case RAWSXP:              return sizeof(char);
	}
	PRINT_TO_ERRMSG_BUF("unsupported Rtype: %s", CHAR(type2str(Rtype)));
	return 0;
}

/* Must NOT be called on STRSXP! */
static hid_t map_Rtype_to_predef_native_type(SEXPTYPE Rtype)
{
	switch (Rtype) {
	    case LGLSXP: case INTSXP: return H5T_NATIVE_INT;
	    case REALSXP:             return H5T_NATIVE_DOUBLE;
	    case RAWSXP:              return H5T_NATIVE_UCHAR;
	}
	/* Should never happen. */
	PRINT_TO_ERRMSG_BUF("failed to map Rtype %s to a native type",
			    CHAR(type2str(Rtype)));
	return -1;
}


/****************************************************************************
 * destroy_H5TypeDescriptor()
 * new_H5TypeDescriptor()
 * print_H5TypeDescriptor()
 */

#define	MAX_SUPPORTED_NESTED_DEPTH 100
static int nested_depth = 0;
static char margin[4 * MAX_SUPPORTED_NESTED_DEPTH + 1];
static void set_margin()
{
	int indent = 4 * nested_depth;
	for (int i = 0; i < indent; i++)
		margin[i] = ' ';
	margin[indent] = '\0';
}

static void destroy_H5TMemberDescriptor(H5TMemberDescriptor *h5tmember);

static H5TMemberDescriptor *new_H5TMemberDescriptor(hid_t h5type_id, int i,
						    H5T_class_t h5class,
						    int get_Rtype_only);

static void print_H5TMemberDescriptor(H5TMemberDescriptor *h5tmember);

static void destroy_H5TypeDescriptor(H5TypeDescriptor *h5type)
{
	if (h5type->num_h5tmembers != 0) {
		for (int i = 0; i < h5type->num_h5tmembers; i++) {
			if (h5type->h5tmembers[i] != NULL)
			    destroy_H5TMemberDescriptor(h5type->h5tmembers[i]);
		}
		free(h5type->h5tmembers);
	}
	free(h5type);
	return;
}

static H5TypeDescriptor *new_H5TypeDescriptor(
		hid_t h5type_id,
		int as_int, const char *storage_mode,
		int get_Rtype_only)
{
	H5TypeDescriptor *h5type =
		(H5TypeDescriptor *) malloc(sizeof(H5TypeDescriptor));
	if (h5type == NULL) {
		PRINT_TO_ERRMSG_BUF("failed to allocate memory "
				    "for H5TypeDescriptor");
		return NULL;
	}

	/* Initialize H5TypeDescriptor struct members that control
	   what destroy_H5TypeDescriptor() needs to free or close. */
	h5type->num_h5tmembers = 0;

	/* Set struct member 'h5type_id'. */
	h5type->h5type_id = h5type_id;

	/* Set struct member 'h5class'. */
	h5type->h5class = H5Tget_class(h5type_id);
	if (h5type->h5class == H5T_NO_CLASS) {
		PRINT_TO_ERRMSG_BUF("H5Tget_class() returned an error");
		goto on_error;
	}

	/* Set struct member 'h5type_size'. */
	h5type->h5type_size = H5Tget_size(h5type_id);
	if (h5type->h5type_size == 0) {
		PRINT_TO_ERRMSG_BUF("H5Tget_size(h5type_id) returned 0");
		goto on_error;
	}

	/* Initialize struct member 'Rtype_is_set'. It might or might not
	   get updated below. */
	h5type->Rtype_is_set = 0;

	if (h5type->h5class == H5T_COMPOUND || h5type->h5class == H5T_ENUM) {
		/* Set struct members 'num_h5tmembers' and 'h5tmembers'. */
		int num_h5tmembers = H5Tget_nmembers(h5type_id);
		if (num_h5tmembers < 0) {
			PRINT_TO_ERRMSG_BUF("H5Tget_nmembers() "
					    "returned an error");
			goto on_error;
		}
		if (num_h5tmembers == 0)
			return h5type;
		if (nested_depth == MAX_SUPPORTED_NESTED_DEPTH) {
			PRINT_TO_ERRMSG_BUF("Wow, this HDF5 dataset has a "
					    "type with more than %d levels of "
					    "nested members! This is nuts.",
					    MAX_SUPPORTED_NESTED_DEPTH);
			goto on_error;
		}
		h5type->h5tmembers = (H5TMemberDescriptor **)
			malloc(num_h5tmembers * sizeof(H5TMemberDescriptor *));
		if (h5type->h5tmembers == NULL) {
			PRINT_TO_ERRMSG_BUF("failed to allocate "
					    "memory for 'h5tmembers'");
			goto on_error;
		}
		nested_depth++;
		for (int i = 0; i < num_h5tmembers; i++) {
			h5type->h5tmembers[i] =
				new_H5TMemberDescriptor(h5type_id, i,
							h5type->h5class,
							get_Rtype_only);
			if (h5type->h5tmembers[i] == NULL)
				goto on_error;
			h5type->num_h5tmembers++;
		}
		nested_depth--;
		return h5type;
	}

	if (h5type->h5class == H5T_INTEGER) {
		/* Set struct member 'signedness'. */
		int signedness = H5Tget_sign(h5type_id);
		if (signedness < 0) {
			PRINT_TO_ERRMSG_BUF("H5Tget_sign() returned an error");
			goto on_error;
		}
		h5type->signedness = signedness;
	}

	/* Set struct members 'Rtype_is_set' and 'Rtype'. */
	h5type->Rtype_is_set = map_h5type_to_Rtype(h5type->h5class,
					h5type->h5type_size, h5type->signedness,
					as_int, storage_mode, &h5type->Rtype);

	if (get_Rtype_only)
		return h5type;

	if (h5type->h5class == H5T_STRING) {
		/* Set struct member 'is_variable_str'. */
		h5type->is_variable_str = H5Tis_variable_str(h5type_id);
		if (h5type->is_variable_str < 0) {
			PRINT_TO_ERRMSG_BUF("H5Tis_variable_str() "
					    "returned an error");
			goto on_error;
		}
	} else {
		h5type->is_variable_str = 0;
	}

	if (h5type->Rtype_is_set && h5type->is_variable_str == 0) {
		/* Set struct member 'Rtype_size'. */
		h5type->Rtype_size = get_Rtype_size(h5type->Rtype,
						    h5type->h5type_size);
		if (h5type->Rtype_size == 0)
			goto on_error;
	}

	if (h5type->h5class != H5T_INTEGER && h5type->h5class != H5T_FLOAT)
		return h5type;

	/* Set struct member 'native_type_id'. */
	hid_t native_type_id = map_h5type_to_predef_native_type(h5type_id);
	if (native_type_id < 0)
		goto on_error;
	h5type->native_type_id = native_type_id;

	/* Set struct member 'native_type_size'. */
	size_t native_type_size = H5Tget_size(h5type->native_type_id);
	if (native_type_size == 0) {
		PRINT_TO_ERRMSG_BUF("H5Tget_size(native_type_id) returned 0");
		goto on_error;
	}
	h5type->native_type_size = native_type_size;

	if (h5type->Rtype_is_set) {
		/* Set struct member 'native_type_id_for_Rtype'. */
		hid_t native_type_id_for_Rtype =
			map_Rtype_to_predef_native_type(h5type->Rtype);
		if (native_type_id_for_Rtype < 0)
			goto on_error;
		h5type->native_type_id_for_Rtype = native_type_id_for_Rtype;
	}

	return h5type;

    on_error:
	destroy_H5TypeDescriptor(h5type);
	return NULL;
}

static void print_H5TypeDescriptor(H5TypeDescriptor *h5type)
{
	int i;
	const char *what;
	const char *k, *v;

	Rprintf("%s- h5type:\n", margin);
	Rprintf("%s  * h5type_id = %ld\n", margin, h5type->h5type_id);
	Rprintf("%s  * h5class = %s\n", margin, H5class2str(h5type->h5class));
	Rprintf("%s  * h5type_size = %lu\n", margin, h5type->h5type_size);
	Rprintf("%s  * Rtype_is_set = %d\n", margin, h5type->Rtype_is_set);

	if (h5type->num_h5tmembers != 0) {
		what = h5type->h5class == H5T_COMPOUND ? "compound" : "enum";
		for (i = 0; i < h5type->num_h5tmembers; i++) {
			Rprintf("%s  o %s type member #%d/%d:\n", margin,
				what, i + 1, h5type->num_h5tmembers);
			print_H5TMemberDescriptor(h5type->h5tmembers[i]);
		}
		return;
	}

	/* signedness */
	if (h5type->h5class != H5T_INTEGER) {
		v = "none (h5class is not H5T_INTEGER)";
	} else {
		v = h5type->signedness == H5T_SGN_NONE ? "unsigned" : "signed";
	}
	Rprintf("%s  * signedness = %s\n", margin, v);

	/* Rtype */
	if (h5type->Rtype_is_set) {
		v = CHAR(type2str(h5type->Rtype));
		Rprintf("%s  * Rtype = \"%s\"\n", margin, v);
	} else {
		Rprintf("%s  * Rtype = none\n", margin);
	}

	/* is_variable_str */
	k = "is_variable_str";
	if (h5type->h5class == H5T_STRING) {
		Rprintf("%s  * %s = %s\n", margin, k,
			h5type->is_variable_str ? "yes" : "no");
	} else {
		Rprintf("%s  * %s = %d\n", margin, k, h5type->is_variable_str);
	}

	/* Rtype_size */
	if (h5type->Rtype_is_set && h5type->is_variable_str == 0)
		Rprintf("%s  * Rtype_size = %lu\n", margin, h5type->Rtype_size);

	if (h5type->h5class != H5T_INTEGER && h5type->h5class != H5T_FLOAT) {
		Rprintf("%s  * native_type_id, native_type_size, "
			"and native_type_id_for_Rtype\n", margin);
		Rprintf("%s    are not set because h5class is not "
			"H5T_INTEGER or H5T_FLOAT\n", margin);
		return;
	}

	/* native_type_id */
	Rprintf("%s  * native_type_id = %s\n", margin,
		predef_native_type_as_string(h5type->native_type_id));

	/* native_type_size */
	Rprintf("%s  * native_type_size = %lu\n", margin,
		h5type->native_type_size);

	/* native_type_id_for_Rtype */
	if (h5type->Rtype_is_set) {
		v = predef_native_type_as_string(
			h5type->native_type_id_for_Rtype);
	} else {
		v = "none (Rtype is not set)";
	}
	Rprintf("%s  * native_type_id_for_Rtype = %s\n", margin, v);
	return;
}


/****************************************************************************
 * destroy_H5TMemberDescriptor()
 * new_H5TMemberDescriptor()
 * print_H5TMemberDescriptor()
 */

static void destroy_H5TMemberDescriptor(H5TMemberDescriptor *h5tmember)
{
	if (h5tmember->h5type != NULL) {
		H5Tclose(h5tmember->h5type->h5type_id);
		destroy_H5TypeDescriptor(h5tmember->h5type);
	}

	if (h5tmember->name != NULL)
		H5free_memory(h5tmember->name);

	free(h5tmember);
	return;
}

static H5TMemberDescriptor *new_H5TMemberDescriptor(hid_t h5type_id, int i,
						    H5T_class_t h5class,
						    int get_Rtype_only)
{
	H5TMemberDescriptor *h5tmember;
	hid_t member_h5type_id;

	h5tmember =
		(H5TMemberDescriptor *) malloc(sizeof(H5TMemberDescriptor));
	if (h5tmember == NULL) {
		PRINT_TO_ERRMSG_BUF("failed to allocate memory "
				    "for H5TMemberDescriptor");
		return NULL;
	}

	/* Initialize H5TMemberDescriptor struct members that control
	   what destroy_H5TMemberDescriptor() needs to free or close. */
	h5tmember->name = NULL;
	h5tmember->h5type = NULL;

	/* Set struct member 'name'. */
	h5tmember->name = H5Tget_member_name(h5type_id, i);
	if (h5tmember->name == NULL) {
		PRINT_TO_ERRMSG_BUF("H5Tget_member_name() returned an error");
		goto on_error;
	}

	if (h5class == H5T_ENUM)
		return h5tmember;

	/* Set struct member 'h5class'. */
	h5tmember->h5class = H5Tget_member_class(h5type_id, i);
	if (h5tmember->h5class < 0) {
		PRINT_TO_ERRMSG_BUF("H5Tget_member_class() returned an error");
		goto on_error;
	}

	/* Set struct member 'h5type'. */
	member_h5type_id = H5Tget_member_type(h5type_id, i);
	if (member_h5type_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Tget_member_type() returned an error");
		goto on_error;
	}
	h5tmember->h5type = new_H5TypeDescriptor(member_h5type_id,
						 0, NULL,
						 get_Rtype_only);
	if (h5tmember->h5type == NULL)
		goto on_error;

	return h5tmember;

    on_error:
	destroy_H5TMemberDescriptor(h5tmember);
	return NULL;
}

static void print_H5TMemberDescriptor(H5TMemberDescriptor *h5tmember)
{
	nested_depth++;
	set_margin();
	Rprintf("%s- name = \"%s\"\n", margin, h5tmember->name);
	if (h5tmember->h5type != NULL) {
		/* These struct members are set for a compound H5 member, but
		   not for an enumeration H5 member. */
		Rprintf("%s- member h5class = %s\n", margin,
			H5class2str(h5tmember->h5class));
		print_H5TypeDescriptor(h5tmember->h5type);
	}
	nested_depth--;
	set_margin();
	return;
}


/****************************************************************************
 * _init_H5DSetDescriptor() / _destroy_H5DSetDescriptor()
 */

void _destroy_H5DSetDescriptor(H5DSetDescriptor *h5dset)
{
	if (h5dset->h5nchunk != NULL)
		free(h5dset->h5nchunk);

	if (h5dset->h5chunkdim != NULL &&
	    h5dset->h5chunkdim != h5dset->h5dim)
		free(h5dset->h5chunkdim);

	if (h5dset->h5dim != NULL)
		free(h5dset->h5dim);

	if (h5dset->h5plist_id != -1)
		H5Pclose(h5dset->h5plist_id);

	if (h5dset->h5space_id != -1)
		H5Sclose(h5dset->h5space_id);

	if (h5dset->h5type != NULL) {
		H5Tclose(h5dset->h5type->h5type_id);
		destroy_H5TypeDescriptor(h5dset->h5type);
	}

	if (h5dset->storage_mode_attr != NULL)
		free(h5dset->storage_mode_attr);

	if (h5dset->h5name != NULL)
		free(h5dset->h5name);
	return;
}

int _init_H5DSetDescriptor(H5DSetDescriptor *h5dset, hid_t dset_id,
		int as_int, int get_Rtype_only)
{
	/* Initialize H5DSetDescriptor struct members that control
	   what _destroy_H5DSetDescriptor() needs to free or close. */
	h5dset->h5name = NULL;
	h5dset->storage_mode_attr = NULL;
	h5dset->h5type = NULL;
	h5dset->h5space_id = -1;
	h5dset->h5plist_id = -1;
	h5dset->h5dim = NULL;
	h5dset->h5chunkdim = NULL;
	h5dset->h5nchunk = NULL;

	/* Set struct member 'dset_id'. */
	h5dset->dset_id = dset_id;

	/* Set struct member 'h5name'. */
	char *h5name = get_h5name(dset_id);
	if (h5name == NULL)
		goto on_error;
	h5dset->h5name = h5name;

	/* Set struct member 'storage_mode_attr'. */
	CharAE *buf = new_CharAE(0);
	int ret = _get_h5attrib_as_one_string(dset_id, "storage.mode", buf);
	if (ret < 0)
		goto on_error;
	if (ret == 1) {
		PRINT_TO_ERRMSG_BUF("attribute \"storage.mode\" is "
				    "not a scalar string");
		goto on_error;
	}
	if (ret == 2) {
		size_t buf_size = sizeof(char) * CharAE_get_nelt(buf);
		char *storage_mode_attr = (char *) malloc(buf_size);
		if (storage_mode_attr == NULL) {
			PRINT_TO_ERRMSG_BUF("failed to allocate memory "
					    "for 'storage_mode_attr'");
			goto on_error;
		}
		/* memcpy() should be slightly faster than strcpy() because
		   we already know how many characters to copy. */
		memcpy(storage_mode_attr, buf->elts, buf_size);
		h5dset->storage_mode_attr = storage_mode_attr;
	}

	/* Set struct member 'h5type'. */
	hid_t h5type_id = H5Dget_type(dset_id);
	if (h5type_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Dget_type() returned an error");
		goto on_error;
	}
	H5TypeDescriptor *h5type = new_H5TypeDescriptor(h5type_id,
					as_int, h5dset->storage_mode_attr,
					get_Rtype_only);
	if (h5type == NULL)
		goto on_error;
	h5dset->h5type = h5type;

	if (get_Rtype_only)
		return 0;

	/* Set struct member 'as_na_attr'. */
	int as_na_attr;
	ret = get_h5attrib_as_one_int(dset_id, "as.na", &as_na_attr);
	if (ret < 0)
		goto on_error;
	if (ret == 1) {
		PRINT_TO_ERRMSG_BUF("attribute \"as.na\" is not a "
				    "scalar integer of class H5T_INTEGER");
		goto on_error;
	}
	h5dset->as_na_attr = ret == 2 ? as_na_attr : 0;

	/* Set struct member 'h5space_id'. */
	hid_t h5space_id = H5Dget_space(dset_id);
	if (h5space_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Dget_space() returned an error");
		goto on_error;
	}
	h5dset->h5space_id = h5space_id;

	/* Set struct member 'ndim'. */
	int ndim = H5Sget_simple_extent_ndims(h5dset->h5space_id);
	if (ndim < 0) {
		PRINT_TO_ERRMSG_BUF(
			"H5Sget_simple_extent_ndims() returned an error");
		goto on_error;
	}
	h5dset->ndim = ndim;

	/* Set struct member 'h5plist_id'. */
	hid_t h5plist_id = H5Dget_create_plist(dset_id);
	if (h5plist_id < 0) {
		PRINT_TO_ERRMSG_BUF("H5Dget_create_plist() returned an error");
		goto on_error;
	}
	h5dset->h5plist_id = h5plist_id;

	/* Set struct member 'h5dim'. */
	hsize_t *h5dim = _alloc_hsize_t_buf(ndim, 0, "'h5dim'");
	if (h5dim == NULL)
		goto on_error;
	if (H5Sget_simple_extent_dims(h5space_id, h5dim, NULL) != ndim) {
		PRINT_TO_ERRMSG_BUF("H5Sget_simple_extent_dims() returned "
				    "an unexpected value");
		goto on_error;
	}
	h5dset->h5dim = h5dim;

	/* Set struct member 'h5layout'. */
	H5D_layout_t h5layout = H5Pget_layout(h5dset->h5plist_id);
	if (h5layout < 0) {
		PRINT_TO_ERRMSG_BUF("H5Pget_layout() returned an error");
		goto on_error;
	}
	h5dset->h5layout = h5layout;

	/* Set struct member 'h5chunkdim'. */
	if (h5dset->h5layout == H5D_CHUNKED) {
		hsize_t *h5chunkdim =
			_alloc_hsize_t_buf(ndim, 0, "'h5chunkdim'");
		if (h5chunkdim == NULL)
			goto on_error;
		if (H5Pget_chunk(h5plist_id, ndim, h5chunkdim) != ndim) {
			PRINT_TO_ERRMSG_BUF("H5Pget_chunk() returned "
					    "an unexpected value");
			goto on_error;
		}
		h5dset->h5chunkdim = h5chunkdim;
	} else if (h5type->Rtype == STRSXP) {
		/* Even though the dataset is contiguous, we treat it as
		   if it was made of a single chunk. This is so that we
		   can use h5mread() method 4 on it, which works only on
		   chunked data and is the only method that knows how to
		   handle string data. */
		h5dset->h5chunkdim = h5dset->h5dim;
	}

	/* Set struct member 'h5nchunk'. */
	if (h5dset->h5chunkdim != NULL) {
		size_t *h5nchunk = (size_t *) malloc(ndim * sizeof(size_t));
		if (h5nchunk == NULL) {
			PRINT_TO_ERRMSG_BUF("failed to allocate memory "
					    "for 'h5nchunk'");
			goto on_error;
		}
		for (int h5along = 0; h5along < ndim; h5along++) {
			hsize_t d = h5dim[h5along];
			if (d == 0) {
				h5nchunk[h5along] = 0;
				continue;
			}
			hsize_t chunkd = h5dset->h5chunkdim[h5along];
			size_t nchunk = d / chunkd;
			if (d % chunkd != 0)
				nchunk++;
			h5nchunk[h5along] = nchunk;
		}
		h5dset->h5nchunk = h5nchunk;
	}

	return 0;

    on_error:
	_destroy_H5DSetDescriptor(h5dset);
	return -1;
}


/****************************************************************************
 * _get_dset_id()
 *
 * Always called at the very beginning of the various .Call entry points
 * before any resource is allocated so it's ok to error() immediately in
 * case of error i.e. no need to use the PRINT_TO_ERRMSG_BUF() mechanism.
 */

hid_t _get_dset_id(hid_t file_id, SEXP name, SEXP filepath)
{
	SEXP name0;
	hid_t dset_id;

	if (!(IS_CHARACTER(name) && LENGTH(name) == 1))
		error("'name' must be a single string");
	name0 = STRING_ELT(name, 0);
	if (name0 == NA_STRING)
		error("'name' cannot be NA");
	dset_id = H5Dopen(file_id, CHAR(name0), H5P_DEFAULT);
	if (dset_id < 0) {
		if (!isObject(filepath))
			H5Fclose(file_id);
		error("failed to open dataset '%s' from file '%s'",
		      CHAR(name0), _get_file_string(filepath));
	}
	return dset_id;
}


/****************************************************************************
 * Used in R/H5DSetDescriptor-class.R
 */

/* --- .Call ENTRY POINT --- */
SEXP C_destroy_H5DSetDescriptor_xp(SEXP xp)
{
	H5DSetDescriptor *h5dset;

	h5dset = R_ExternalPtrAddr(xp);
	if (h5dset != NULL) {
		//printf("Destroying H5DSetDescriptor struct at address %p ... ",
		//       h5dset);
		_destroy_H5DSetDescriptor(h5dset);
		H5Dclose(h5dset->dset_id);
		free(h5dset);
		R_SetExternalPtrAddr(xp, NULL);
		//printf("OK\n");
	}

	return R_NilValue;
}

/* --- .Call ENTRY POINT --- */
SEXP C_new_H5DSetDescriptor_xp(SEXP filepath, SEXP name, SEXP as_integer)
{
	int as_int;
	hid_t file_id, dset_id;
	H5DSetDescriptor *h5dset;

	/* Check 'as_integer'. */
	if (!(IS_LOGICAL(as_integer) && LENGTH(as_integer) == 1))
		error("'as_integer' must be TRUE or FALSE");
	as_int = LOGICAL(as_integer)[0];

	file_id = _get_file_id(filepath, 1);  /* read-only */
	dset_id = _get_dset_id(file_id, name, filepath);

	h5dset = (H5DSetDescriptor *) malloc(sizeof(H5DSetDescriptor));
	if (h5dset == NULL) {
		H5Dclose(dset_id);
		if (!isObject(filepath))
			H5Fclose(file_id);
		error("C_new_H5DSetDescriptor_xp(): malloc() failed");
	}

	if (_init_H5DSetDescriptor(h5dset, dset_id, as_int, 0) < 0) {
		H5Dclose(dset_id);
		if (!isObject(filepath))
			H5Fclose(file_id);
		error("%s", _h5mread_global_errmsg_buf());
	}
	if (!isObject(filepath))
		H5Fclose(file_id);
	//printf("H5DSetDescriptor struct created at address %p\n", h5dset);

	return R_MakeExternalPtr(h5dset, R_NilValue, R_NilValue);
}

/* --- .Call ENTRY POINT --- */
SEXP C_show_H5DSetDescriptor_xp(SEXP xp)
{
	const H5DSetDescriptor *h5dset = R_ExternalPtrAddr(xp);
	if (h5dset == NULL) {
		Rprintf("Expired H5DSetDescriptor\n");
		return R_NilValue;
	}

	Rprintf("H5DSetDescriptor:\n");
	Rprintf("- dset_id = %ld\n", h5dset->dset_id);

	Rprintf("- h5name = \"%s\"\n", h5dset->h5name);

	Rprintf("- storage_mode_attr = ");
	if (h5dset->storage_mode_attr == NULL) {
		Rprintf("NULL");
	} else {
		Rprintf("\"%s\"", h5dset->storage_mode_attr);
	}
	Rprintf("\n");

	nested_depth = 0;
	set_margin();
	print_H5TypeDescriptor(h5dset->h5type);

	Rprintf("- as_na_attr = %d\n", h5dset->as_na_attr);

	Rprintf("- h5space_id = %ld\n", h5dset->h5space_id);

	Rprintf("- ndim = %d\n", h5dset->ndim);

	Rprintf("- h5plist_id = %ld\n", h5dset->h5plist_id);

	Rprintf("- h5dim =");
	for (int h5along = 0; h5along < h5dset->ndim; h5along++)
		Rprintf(" %lu", h5dset->h5dim[h5along]);
	Rprintf("\n");

	Rprintf("- h5layout = %s\n", H5layout2str(h5dset->h5layout));

	Rprintf("- h5chunkdim =");
	if (h5dset->h5chunkdim == NULL) {
		Rprintf(" NULL\n");
	} else {
		for (int h5along = 0; h5along < h5dset->ndim; h5along++)
			Rprintf(" %lu", h5dset->h5chunkdim[h5along]);
		if (h5dset->h5layout != H5D_CHUNKED &&
		    h5dset->h5chunkdim == h5dset->h5dim)
			Rprintf(" (artificially set to h5dim)");
		Rprintf("\n");
		Rprintf("    h5nchunk =");
		for (int h5along = 0; h5along < h5dset->ndim; h5along++)
			Rprintf(" %lu", h5dset->h5nchunk[h5along]);
		Rprintf("\n");
	}

	return R_NilValue;
}

