/** \file grpattr.c
 * \brief MINC 2.0 group/attribute functions
 * \author Bert Vincent and Leila Baghdadi
 *
 * Functions to manipulate attributes and groups.
 ************************************************************************/
#include <stdlib.h>
#include <hdf5.h>
#include "minc2.h"
#include "minc2_private.h"

#define MILIST_MAX_PATH 256
#define MILIST_RECURSE 0x0001

struct milistframe {
    struct milistframe *next;
    hid_t grp_id;
    int att_idx;
    int grp_idx;
    char relpath[MILIST_MAX_PATH];
};

struct milistdata {
    int flags;
    char *name_ptr;
    int name_len;
    struct milistframe *frame_ptr; /* recursive frame */
};


/*! Start listing the objects in a group.
 */
int
milist_start(mihandle_t vol, const char *path, int flags,
	     milisthandle_t *handle)
{
    hid_t grp_id;
    char fullpath[256];
    struct milistdata *data;
    struct milistframe *frame;
    
    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
   
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    grp_id = H5Gopen(vol->hdf_id, fullpath);
    if (grp_id < 0) {
        return (MI_ERROR);
    }

    data = (struct milistdata *) malloc(sizeof (struct milistdata));
    if (data == NULL) {
        return (MI_ERROR);
    }
    
    frame = (struct milistframe *) malloc(sizeof (struct milistframe));
    frame->next = NULL;
    frame->grp_id = grp_id;
    frame->att_idx = 0;
    frame->grp_idx = 0;
    strcpy(frame->relpath, path);
    data->frame_ptr = frame;

    data->flags = flags;

    *handle = data;

    return (MI_NOERROR);
}

static int
milist_recursion(milisthandle_t handle, char *path)
{
    struct milistdata *data = (struct milistdata *) handle;
    herr_t r;
    int i=0;
    struct milistframe *frame;
  
    for(;;){

        H5E_BEGIN_TRY {
    
            r = H5Gget_objtype_by_idx(data->frame_ptr->grp_id,
                                      data->frame_ptr->grp_idx);
        } H5E_END_TRY;
 
        if (r < 0) {

            /* End of this group, need to pop the frame. */
            frame = data->frame_ptr->next;
            H5Gclose(data->frame_ptr->grp_id);
            free(data->frame_ptr);
            data->frame_ptr = frame;
            if (frame == NULL) {
                return (MI_ERROR);
            }
        }
        else {
    
            data->frame_ptr->grp_idx++;
            /* If the object is a group, we need to recurse into it.
             */
            if (r == H5G_GROUP) {
                char tmp[256];
                int l;
          
                H5Gget_objname_by_idx(data->frame_ptr->grp_id,
                                      data->frame_ptr->grp_idx - 1,
                                      tmp, sizeof(tmp));
                frame = malloc(sizeof(struct milistframe));
                if (frame == NULL) {
                    return (MI_ERROR);
                }
      
                frame->grp_idx = 0;
                frame->att_idx = 0;
                frame->grp_id = H5Gopen(data->frame_ptr->grp_id, tmp);
			
                strcpy(frame->relpath, data->frame_ptr->relpath);
                l = strlen(frame->relpath);
                            
                if (l > 0 && frame->relpath[l - 1] != '/') {
                    strcat(frame->relpath, "/");
                }
                strcat(frame->relpath, tmp);

                /* Start working on the next frame.
                 */
                frame->next = data->frame_ptr;
                data->frame_ptr = frame;
                strncpy(path, data->frame_ptr->relpath, MILIST_MAX_PATH);
                break;  /* Out of inner for(;;) */
            }
        }       
    }  
    return (MI_NOERROR);
}


static herr_t
milist_attr_op(hid_t loc_id, const char *attr_name, void *op_data)
{
    struct milistdata *data = (struct milistdata *) op_data;
    strncpy(data->name_ptr, attr_name, data->name_len);
    return (1);
}

/*! Get attributes at a given path
 */ 
int
milist_attr_next(mihandle_t vol, milisthandle_t handle, 
                 char *path, int maxpath,
                 char *name, int maxname)
{
    struct milistdata *data = (struct milistdata *) handle;
    herr_t r;
    
    data->name_ptr = name;
    data->name_len = maxname;

    for (;;) {
      
     
        H5E_BEGIN_TRY {
            r = H5Aiterate(data->frame_ptr->grp_id, 
                           &data->frame_ptr->att_idx, milist_attr_op, data);
        } H5E_END_TRY;
        if (r > 0) {
	  
	  strncpy(path, data->frame_ptr->relpath, maxpath);
	  return (MI_NOERROR);
        }
        else if (data->flags & MILIST_RECURSE) {
	  
          r = milist_recursion(handle, path);
          if ( r == MI_ERROR)
	    {
	      return(MI_ERROR);
	    }
            
        }
        else {
            return (MI_ERROR);
        }
    }

    return (MI_NOERROR);
}

/*! Finish listing attributes or groups
 */
int
milist_finish(milisthandle_t handle)
{
    struct milistdata *data = (struct milistdata *) handle;
    struct milistframe *frame;

    if (data == NULL) {
        return (MI_ERROR);
    }

    while ((frame = data->frame_ptr) != NULL) {
        data->frame_ptr = frame->next;
        H5Gclose(frame->grp_id);
        free(frame);
    }

    free(data);
    return (MI_NOERROR);
}

static herr_t
milist_grp_op(hid_t loc_id, const char *name, void *op_data)
{
  struct milistdata *data = (struct milistdata *) op_data;
  H5G_stat_t statbuf;
  
  H5Gget_objinfo(loc_id,name, FALSE, &statbuf);
  if (statbuf.type == H5G_GROUP)
    {
      int l;
      l = strlen(data->frame_ptr->relpath);
      if (l > 0 && data->frame_ptr->relpath[l-1] != '/') {
	strcat(data->frame_ptr->relpath, "/");
      }
      strcat(data->frame_ptr->relpath, name);
    }
  return(1);
}

/*! Get the group at given path
 */
int
milist_grp_next(milisthandle_t handle, char *path, int maxpath)
{
  struct milistdata *data = (struct milistdata *) handle;
  herr_t r;
  
  if (!(data->flags & MILIST_RECURSE))
      {
	char fullpath[256];
	char tmp[256];
	
	strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
	strncat(fullpath, data->frame_ptr->relpath, sizeof (fullpath) - strlen(fullpath));
	strcpy(tmp, data->frame_ptr->relpath);

	H5E_BEGIN_TRY {
	  r = H5Giterate(data->frame_ptr->grp_id,fullpath,
			 &data->frame_ptr->grp_idx, milist_grp_op, data);
      
	} H5E_END_TRY;
	if ( r > 0 )
	  {
	    strncpy(path, data->frame_ptr->relpath, maxpath);
	    strncpy(data->frame_ptr->relpath, tmp, maxpath);
	    return (MI_NOERROR);
	  }
	else
      	  {
	    return (MI_ERROR);	   
	  }
      }

  else if (data->flags & MILIST_RECURSE) {
    
    r = milist_recursion(handle, path);
    if ( r == MI_ERROR) {
	return(MI_ERROR);
    }
  }
  else {
    return (MI_ERROR);
  }
   
  return (MI_NOERROR);
}

/*! Create a group at "path" using "name".
 */
int
micreate_group(mihandle_t vol, const char *path, const char *name)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    hid_t hdf_new_grp;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    /* Actually create the requested group.
     */
    hdf_new_grp = H5Gcreate(hdf_grp, name, 0);
    if (hdf_new_grp < 0) {
	return (MI_ERROR);
    }

    /* Close the handles we created.
     */
    H5Gclose(hdf_new_grp);
    H5Gclose(hdf_grp);
  
    return (MI_NOERROR);
}

/*! Delete the named attribute.
 */
int
midelete_attr(mihandle_t vol, const char *path, const char *name)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    herr_t hdf_result;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    /* Delete the attribute from the path.
     */
    hdf_result = H5Adelete(hdf_grp, name);
    if (hdf_result < 0) {
	return (MI_ERROR);
    }

    /* Close the handles we created.
     */
    H5Gclose(hdf_grp);

    return (MI_NOERROR);
}

/** Delete the subgroup \a name from the group \a path
 */
int
midelete_group(mihandle_t vol, const char *path, const char *name)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    herr_t hdf_result;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    H5E_BEGIN_TRY {
        /* Delete the group (or any object, really) from the path.
         */
        hdf_result = H5Gunlink(hdf_grp, name);
        if (hdf_result < 0) {
            hdf_result = MI_ERROR;
        }
        else {
            hdf_result = MI_NOERROR;
        }
    } H5E_END_TRY;

    /* Close the handles we created.
     */
    H5Gclose(hdf_grp);
  
    return (hdf_result);
}

/** Get the length of a attribute
 */
int
miget_attr_length(mihandle_t vol, const char *path, const char *name,
		  int *length)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    hid_t hdf_attr;
    hsize_t hdf_dims[1];   /* TODO: symbolic constant for "1" here? */
    hid_t hdf_space;
    hid_t hdf_type;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    hdf_attr = H5Aopen_name(hdf_grp, name);
    if (hdf_attr < 0) {
	return (MI_ERROR);
    }

    hdf_space = H5Aget_space(hdf_attr);
    if (hdf_space < 0) {
	return (MI_ERROR);
    }

    hdf_type = H5Aget_type(hdf_attr);
    if (hdf_type < 0) {
	return (MI_ERROR);
    }

    switch (H5Sget_simple_extent_ndims(hdf_space)) {
    case 0:			/* Scalar */
	/* String types need to return the length of the string.
	 */
	if (H5Tget_class(hdf_type) == H5T_STRING) {
	    *length = H5Tget_size(hdf_type);
	}
	else {
	    *length = 1;
	}
	break;

    case 1:
	H5Sget_simple_extent_dims(hdf_space, hdf_dims, NULL);
	*length = hdf_dims[0];
	break;

    default:
	/* For now, we allow only scalars and vectors.  No multidimensional
	 * arrays for MINC 2.0 attributes.
	 */
	return (MI_ERROR);
    }

    H5Tclose(hdf_type);
    H5Sclose(hdf_space);
    H5Aclose(hdf_attr);
    H5Gclose(hdf_grp);

    return (MI_NOERROR);
}

/** Get the type of an attribute.
 */
int
miget_attr_type(mihandle_t vol, const char *path, const char *name,
		mitype_t *data_type)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    hid_t hdf_attr;
    hid_t hdf_type;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    hdf_attr = H5Aopen_name(hdf_grp, name);
    if (hdf_attr < 0) {
	return (MI_ERROR);
    }

    hdf_type = H5Aget_type(hdf_attr);
    switch (H5Tget_class(hdf_type)) {
    case H5T_FLOAT:
        if (H5Tget_size(hdf_type) == sizeof(float)) {
            *data_type = MI_TYPE_FLOAT;
        }
        else {
            *data_type = MI_TYPE_DOUBLE;
        }
        break;
    case H5T_STRING:
	*data_type = MI_TYPE_STRING;
        break;
    case H5T_INTEGER:
        *data_type = MI_TYPE_INT;
        break;
    default:
	return (MI_ERROR);
    }

    H5Tclose(hdf_type);
    H5Aclose(hdf_attr);
    H5Gclose(hdf_grp);

    return (MI_NOERROR);
}

/** Get the values of an attribute.
 */
int
miget_attr_values(mihandle_t vol, mitype_t data_type, const char *path, 
		  const char *name, int length, void *values)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    hid_t mtyp_id;
    hid_t hdf_space;
    hid_t hdf_attr;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    hdf_attr = H5Aopen_name(hdf_grp, name);
    if (hdf_attr < 0) {
	return (MI_ERROR);
    }

    switch (data_type) {
    case MI_TYPE_INT:
	mtyp_id = H5Tcopy(H5T_NATIVE_INT);
	break;
    case MI_TYPE_FLOAT:
	mtyp_id = H5Tcopy(H5T_NATIVE_FLOAT);
	break;
    case MI_TYPE_DOUBLE:
	mtyp_id = H5Tcopy(H5T_NATIVE_DOUBLE);
	break;
    case MI_TYPE_STRING:
	mtyp_id = H5Tcopy(H5T_C_S1);
	H5Tset_size(mtyp_id, length);
	break;
    default:
	return (MI_ERROR);
    }

    hdf_space = H5Aget_space(hdf_attr);
    if (hdf_space < 0) {
	return (MI_ERROR);
    }

    /* If we're retrieving a vector, make certain the length passed into this
     * function is sufficient.
     */
    if (H5Sget_simple_extent_ndims(hdf_space) == 1) {
	hsize_t hdf_dims[1];

	H5Sget_simple_extent_dims(hdf_space, hdf_dims, NULL);
	if (length < hdf_dims[0]) {
	    return (MI_ERROR);
	}
    }
    

    H5Aread(hdf_attr, mtyp_id, values);

    H5Aclose(hdf_attr);
    H5Tclose(mtyp_id);
    H5Sclose(hdf_space);
    H5Gclose(hdf_grp);

    return (MI_NOERROR);
}

/** Set the values of an attribute.
 */
int
miset_attr_values(mihandle_t vol, mitype_t data_type, const char *path,
		  const char *name, int length, const void *values)
{
    hid_t hdf_file;
    hid_t hdf_grp;
    int result;
    char fullpath[256];

    /* Get a handle to the actual HDF file 
     */
    hdf_file = vol->hdf_id;
    if (hdf_file < 0) {
	return (MI_ERROR);
    }

    strncpy(fullpath, MI_ROOT_PATH "/" MI_INFO_NAME, sizeof (fullpath));
    if (*path != '/') {
        strncat(fullpath, "/", sizeof (fullpath) - strlen(fullpath));
    }
    strncat(fullpath, path, sizeof (fullpath) - strlen(fullpath));

    /* Search through the path, descending into each group encountered.
     */
    hdf_grp = midescend_path(hdf_file, fullpath);
    if (hdf_grp < 0) {
	return (MI_ERROR);
    }

    result = miset_attr_at_loc(hdf_grp, name, data_type, length, values);

    H5Gclose(hdf_grp);

    return (result);
}


syntax highlighted by Code2HTML, v. 0.9.1