/*

  This is a tutorial example that shows how to use external and user-defined links.
  
 */

#include <stdlib.h>
#include "hdf5.h"
/* 
 * The actual traversal function simply needs to open the correct object by
 * name and return its ID.
 */
static hid_t my_soft_traverse(const char *link_name,
			      hid_t cur_group,
			      const void *udata,
			      size_t udata_size,
			      hid_t lapl_id)
{
    const char *target = (const char *) udata;
    hid_t ret_value;

    /* Pass the udata straight through to HDF5. If it's invalid, let HDF5
     * return an error.
     */
    ret_value = H5Oopen(cur_group, target, lapl_id);
    return ret_value;
}

/* Callbacks for User-defined hard links. */
/* my_hard_create
 * The most important thing this callback does is to increment the reference
 * count on the target object. Without this step, the object could be
 * deleted while this link still pointed to it, resulting in possible data
 * corruption!
 * The create callback also checks the arguments used to create this link.
 * If this function returns a negative value, the call to H5Lcreate_ud()
 * will also return failure and the link will not be created.
 */
static herr_t my_hard_create(const char *link_name,
			     hid_t loc_group,
			     const void *udata,
			     size_t udata_size,
			     hid_t lcpl_id)
{
    haddr_t addr;
    hid_t target_obj = -1;
    herr_t ret_value = 0;

    /* Make sure that the address passed in looks valid */
    if(udata_size != sizeof(haddr_t))
    {
      ret_value = -1;
      goto done;
    }

    addr = *((const haddr_t *) udata);

    /* Open the object this link points to so that we can increment
     * its reference count. This also ensures that the address passed
     * in points to a real object (although this check is not perfect!) */
    target_obj= H5Oopen_by_addr(loc_group, addr);
    if(target_obj < 0)
    {
      ret_value = -1;
      goto done;
    }

    /* Increment the reference count of the target object */
    if(H5Oincr_refcount(target_obj) < 0)
    {
      ret_value = -1;
      goto done;
    }

done:
    /* Close the target object if we opened it */
    if(target_obj >= 0)
        H5Oclose(target_obj);
    return ret_value;
}

/* my_hard_delete
 * Since the creation function increments the object's reference count,
 * it's important to decrement it again when the link is deleted.
 */
static herr_t my_hard_delete(const char *link_name, hid_t loc_group,
    const void *udata, size_t udata_size)
{
    haddr_t addr;
    hid_t target_obj = -1;
    herr_t ret_value = 0;

    /* Sanity check; we have already verified the udata's size in the creation
     * callback.
     */
    if(udata_size != sizeof(haddr_t))
    {
      ret_value = -1;
      goto done;
    }

    addr = *((const haddr_t *) udata);

    /* Open the object this link points to */
    target_obj= H5Oopen_by_addr(loc_group, addr);
    if(target_obj < 0)
    {
      ret_value = -1;
      goto done;
    }

    /* Decrement the reference count of the target object */
    if(H5Odecr_refcount(target_obj) < 0)
    {
      ret_value = -1;
      goto done;
    }

done:
    /* Close the target object if we opened it */
    if(target_obj >= 0)
        H5Oclose(target_obj);
    return ret_value;
}

/* my_hard_traverse
 * The actual traversal function simply needs to open the correct object and
 * return its ID.
 */
static hid_t my_hard_traverse(const char *link_name, hid_t cur_group,
    const void *udata, size_t udata_size, hid_t lapl_id)
{
    haddr_t       addr;
    hid_t         ret_value = -1;

    /* Sanity check; we have already verified the udata's size in the creation
     * callback.
     */
    if(udata_size != sizeof(haddr_t))
      return -1;

    addr = *((const haddr_t *) udata);

    /* Open the object by address. If H5Oopen_by_addr fails, ret_value will
     * be negative to indicate that the traversal function failed.
     */
    ret_value = H5Oopen_by_addr(cur_group, addr);

    return ret_value;
}

/*-------------------------------------------------------------------------
 * Function:	main
 *
 * Purpose:	Test external and user-defined soft/hard links.
 *
 *-------------------------------------------------------------------------
 */


int
main(void)
{
    const char  *file;		/* File from external link */
    const char  *path;		/* Path from external link */

    /* Define the link class that we'll use to register "user-defined soft
     * links" using the callbacks we defined above.
     * A link class can have NULL for any callback except its traverse
     * callback.
     */
    const H5L_class_t my_soft_class[1] = {{
        H5L_LINK_CLASS_T_VERS,      /* Version number for this struct.
                                     * This field is always H5L_LINK_CLASS_T_VERS */
        65,                         /* Link class id number. This can be any
                                     * value between H5L_TYPE_UD_MIN (64) and
                                     * H5L_TYPE_MAX (255). It should be a
                                     * value that isn't already being used by
                                     * another kind of link. We'll use 65. */
        "my_soft_link",             /* Link class name for debugging  */
        NULL,                       /* Creation callback              */
        NULL,                       /* Move callback                  */
        NULL,                       /* Copy callback                  */
        my_soft_traverse,           /* The actual traversal function  */
        NULL,                       /* Deletion callback              */
        NULL                        /* Query callback                 */
    }};
    
    /* Define the link class that we'll use to register "user-defined hard
     * links" using the callbacks we defined above.
     * A link class can have NULL for any callback except its traverse
     * callback.
     */

    const H5L_class_t my_hard_class[1] = {{
        H5L_LINK_CLASS_T_VERS,      /* Version number for this struct.
                                     * This field is always H5L_LINK_CLASS_T_VERS */
        66,		            /* Link class id number. This can be any
                                     * value between H5L_TYPE_UD_MIN (64) and
                                     * H5L_TYPE_MAX (255). It should be a
                                     * value that isn't already being used by
                                     * another kind of link. We'll use 66. */
        "my_hard_link",             /* Link class name for debugging  */
        my_hard_create,             /* Creation callback              */
        NULL,                       /* Move callback                  */
        NULL,                       /* Copy callback                  */
        my_hard_traverse,           /* The actual traversal function  */
        my_hard_delete,             /* Deletion callback              */
        NULL                        /* Query callback                 */
    }};


    H5L_info_t	linfo;		/* Link information */
    
    char objname[128];		/* Object name */
    char prefix[128];	        /* Prefix applied to external link paths */
    
    hid_t lapl_id = (-1);	/* Prop List ID */    
    hid_t source_file_id, target_file_id;
    hid_t group_id, group2_id, group3_id;

    
    /* Create two files, a source and a target */
    source_file_id = H5Fcreate("extlink_source.h5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
    target_file_id = H5Fcreate("extlink_target.h5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);

    /* Create a group in the target file for the external link to point to. */
    group_id = H5Gcreate2(target_file_id, "target_group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);

    /* Close the group and the target file */
    H5Gclose(group_id);

    /* Create an external link in the source file pointing to the target group.
     * We could instead have created the external link first, then created the
     * group it points to; the order doesn't matter.
     */
    H5Lcreate_external("extlink_target.h5", "target_group", source_file_id,
		       "ext_link", H5P_DEFAULT, H5P_DEFAULT);

    
    
    /* Now we can use the external link to create a new group inside the
     * target group (even though the target file is closed!).  The external
     * link works just like a soft link.
     */
    group_id = H5Gcreate2(source_file_id, "ext_link/new_group", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);

    /* The group is inside the target file and we can access it normally.
     * Here, group_id and group2_id point to the same group inside the
     * target file.
     */
    group2_id = H5Gopen2(target_file_id, "target_group/new_group", H5P_DEFAULT);

    /* Don't forget to close the IDs we opened. */
    H5Gclose(group2_id);
    H5Gclose(group_id);
    
    /* Create a link access property list with the path to the current dir */    
    if((lapl_id = H5Pcreate(H5P_LINK_ACCESS)) < 0){
      fprintf(stderr, "H5Pcreate() failed.\n");
    }
    if(H5Pset_elink_prefix(lapl_id, ".") < 0){
      fprintf(stderr, "H5Pset_elink_prefix() failed.\n");
    }
    /* Read the prefix back.*/
    if(H5Pget_elink_prefix(lapl_id, prefix, 2) < 0){
      fprintf(stderr, "H5Pget_elink_prefix() failed.\n");
    }

      
    /* Check information for external link. */
    if(H5Lget_info(source_file_id, "ext_link", &linfo, H5P_DEFAULT) < 0){
      fprintf(stderr, "H5Lget_info() failed.\n");
    }
    /* Get the value of external link. */ 
    if(H5Lget_val(source_file_id, "ext_link", objname, sizeof(objname), H5P_DEFAULT) < 0){
      fprintf(stderr, "H5Lget_val() failed.\n");
    }
    /* Decodes external link information. */ 
    if(H5Lunpack_elink_val(objname, linfo.u.val_size, NULL, &file, &path) < 0) {
      fprintf(stderr, "H5Lget_elink_val() failed.\n");
    }
    else{
      printf("ext_link's file:%s path:%s prefix:%s\n", file, path, prefix);
    }

    
    /*
     * To do the same thing using a user-defined link, we first have to
     * register the link class we defined.
     */
    H5Lregister(my_soft_class);
  
    /*
     * Now create a user-defined link.  We give it the path to the group
     * as its udata.1
     */
    H5Lcreate_ud(target_file_id, "my_soft_link", 65, "target_group",
                 strlen("target_group") + 1, H5P_DEFAULT, H5P_DEFAULT);
    
    /*
     * Ensure we can open the group through the UD link
     */
    if((group_id=H5Gopen2(target_file_id, "my_soft_link", H5P_DEFAULT)) < 0)
      {
	fprintf(stderr, "H5Gopen2() failed for user-defined link\n");
      }
    else{
      H5Gclose(group_id);
    }
    /*
     * Unregister the user defined link.
     * This prevents it from being traversed, queried, moved, etc.
     */
    if(H5Lunregister(65)< 0)
      fprintf(stderr, "H5Lunregister() failed.");

    /*
     * This should fail since we unregistered the user defined link.
     */

    if((group2_id=H5Gopen2(target_file_id, "my_soft_link", H5P_DEFAULT)) < 0)
      {
	fprintf(stderr, "H5Gopen2() failed for user-defined link after unregister\n");
      }
    else{
      H5Gclose(group2_id);
    }

    
    /* This is how we create a normal hard link to the group. This
     * creates a second "name" for the group.
     */
    H5Lcreate_hard(target_file_id, "target_group",
		   target_file_id, "hard_link_to_target_group", H5P_DEFAULT, H5P_DEFAULT);
    
    /* To do the same thing using a user-defined link, we first have to
     * register the link class we defined.
     */
    H5Lregister(my_hard_class);
    /* Since hard links link by object address, we'll need to retrieve
     * the target group's address. We do this by calling H5Lget_info
     * on a hard link to the object.
     */
    H5Lget_info(target_file_id, "target_group", &linfo, H5P_DEFAULT);
    
    /* Now create a user-defined hard link.  We give it the group's address
     * as its udata.
     */
    H5Lcreate_ud(target_file_id, "my_hard_link", 66, &(linfo.u.address),
                 sizeof(linfo.u.address), H5P_DEFAULT, H5P_DEFAULT);    

    /* The my hard link has now incremented the group's reference count
     * like a normal hard link would.  This means that we can unlink the
     * other two links to that group and it won't be deleted until the
     * UD hard link is deleted.
     */
    H5Ldelete(target_file_id, "target_group", H5P_DEFAULT);
    H5Ldelete(target_file_id, "hard_link_to_target_group", H5P_DEFAULT);
    
    /* The group is still accessible through my hard link. If this were
     * a soft link instead, the object would have been deleted when the last
     * hard link to it was unlinked. */
    if((group3_id = H5Gopen2(target_file_id, "my_hard_link", H5P_DEFAULT)) < 0)
      {
	fprintf(stderr, "H5Gopen2() failed for user-defined hard link.n");
      }
    else{
      /* The group is now open normally.  Don't forget to close it! */
      H5Gclose(group3_id);
    }

    /* Removing the user-defined hard link will delete the group. */
    H5Ldelete(target_file_id, "my_hard_link", H5P_DEFAULT);
    
    H5Fclose(target_file_id);
    H5Fclose(source_file_id);

    return 0;
}
