/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by  The HDF Group and                                           *
 * The Board of Trustees of the University of Illinois.                      *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of H4H5TOOLS. The full H4H5TOOLS copyright notice,      *
 * including terms governing use, modification, and redistribution, is       *
 * contained in the COPYING file, which can be found at the root of the      *
 * source code distribution tree. The copyright notice can also be found     *
 * at https://www.hdfgroup.org/licenses.  If you do not have access to       *
 * either file, you may request a copy from help@hdfgroup.org.               *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "h4toh5main.h"
#include "h4toh5vector.h"

#include "h4toh5.h"

DECLARE_VECTOR_ALL(challoc, char)

#ifdef HAVE_LIBHDFEOS

/** 
 *  This function is part of EOS2 library.
 *  Although this is 'defined' in EOS2 library, the public header file does not
 *  declare it.
 */
intn GDij2ll(int32 projcode, int32 zonecode, float64 projparm[], int32 spherecode, int32 xdimsize, int32 ydimsize, float64 upleftpt[], float64 lowrightpt[], int32 npnts, int32 row[], int32 col[], float64 longitude[], float64 latitude[], int32 pixcen, int32 pixcnr);



/** 
 * @brief vector-like structures
 * the following preprocessor statements create C++ vector like structures and functions.
 * e.g. DECLARE_VECTOR_ALL(challoc, char) will make bunch of functions whose names are
 * begin with challoc_, and a structure challoc which contains 's' field for actual data and
 * 'len' field for the length.
 */
DECLARE_VECTOR_ALLOCATOR(float64alloc, float64)
DECLARE_VECTOR_ALLOCATOR(intalloc, int)
DECLARE_VECTOR_ALLOCATOR(int32alloc, int32)
DECLARE_VECTOR_ALLOCATOR(hsize_talloc, hsize_t)


typedef struct {
    challoc name;
} namelist_t;

void init_namelist_t(namelist_t *e)
{
    challoc_init(&e->name);
}

void free_namelist_t(namelist_t *e)
{
    challoc_free(&e->name);
}
DECLARE_VECTOR_ALLOCATOR_FREE(namelistalloc, namelist_t)

/** 
 * @brief a dimension in EOS2 is a pair of dimension name and size
 */
typedef struct {
    challoc name;
    int32 dimsize;
} dimension_t;

void init_dimension_t(dimension_t *e)
{
    challoc_init(&e->name);
}

void free_dimension_t(dimension_t *e)
{
    challoc_free(&e->name);
}
DECLARE_VECTOR_ALLOCATOR_FREE(dimensionalloc, dimension_t)


/** 
 * @brief EOS2 Swath Datafield and Geofield are mapped to this structure
 * If EAGER_DATA_FETCH is not defined, actual data is not retrieved until
 * that is required.
 */
typedef struct {
    challoc name;
    int32 rank;
    int32 type;
    dimensionalloc dims;
#ifdef EAGER_DATA_FETCH
    challoc data;
#else
    int datalen;
    challoc *lazydata;
#endif
    challoc filler;
} fieldinfo_t;

void init_fieldinfo_t(fieldinfo_t *e)
{
    challoc_init(&e->name);
    dimensionalloc_init(&e->dims);
#ifdef EAGER_DATA_FETCH
    challoc_init(&e->data);
#else
    e->datalen = -1;
    e->lazydata = NULL;
#endif
    challoc_init(&e->filler);
}

void free_fieldinfo_t(fieldinfo_t *e)
{
    challoc_free(&e->name);
    dimensionalloc_free(&e->dims);
#ifdef EAGER_DATA_FETCH
    challoc_free(&e->data);
#else
    e->datalen = -1;
    if (e->lazydata) {
        challoc_free(e->lazydata);
        free(e->lazydata);
        e->lazydata = NULL;
    }
#endif
    challoc_free(&e->filler);
}
DECLARE_VECTOR_ALLOCATOR_FREE(fieldinfoalloc, fieldinfo_t)


/** 
 * @brief EOS2 attributes
 */
typedef struct {
    challoc name;
    int32 type;
    challoc value;
} attribute_t;

void init_attribute_t(attribute_t *e)
{
    challoc_init(&e->name);
    challoc_init(&e->value);
}

void free_attribute_t(attribute_t *e)
{
    challoc_free(&e->name);
    challoc_free(&e->value);
}
DECLARE_VECTOR_ALLOCATOR_FREE(attributealloc, attribute_t)


/** 
 * @brief Grid information
 * For more information, please refer to Grid section in HDF-EOS2 Reference
 */
typedef struct {
    int32 xdim;
    int32 ydim;
    float64 upleft[2];
    float64 lowright[2];
} gridinfo_t;

void init_gridinfo_t(gridinfo_t *e)
{
}

void free_gridinfo_t(gridinfo_t *e)
{
}

/** 
 * @brief Grid projection information
 * For more information, please refer to Grid section in HDF-EOS2 Reference
 */
typedef struct {
    int32 proj;
    int32 zone;
    int32 sphere;
    float64 param[16];
    int32 pix;
    int32 origin;
} projinfo_t;

void init_projinfo_t(projinfo_t *e)
{
}

void free_projinfo_t(projinfo_t *e)
{
}

/** 
 * @brief one big data structure that holds everything in one EOS2 Grid
 */
typedef struct {
    gridinfo_t info;
    projinfo_t proj;
    dimensionalloc dim;
    fieldinfoalloc field;
    attributealloc attr;
} grid_t;

void init_grid_t(grid_t *e)
{
    init_gridinfo_t(&e->info);
    init_projinfo_t(&e->proj);
    dimensionalloc_init(&e->dim);
    fieldinfoalloc_init(&e->field);
    attributealloc_init(&e->attr);
}

void free_grid_t(grid_t *e)
{
    free_gridinfo_t(&e->info);
    free_projinfo_t(&e->proj);
    dimensionalloc_free(&e->dim);
    fieldinfoalloc_free(&e->field);
    attributealloc_free(&e->attr);
}

/** 
 * @brief EOS2 dimension mapping
 */
typedef struct {
    challoc data;
    challoc geo;
    int32 offset;
    int32 increment;
} dimmap_t;

void init_dimmap_t(dimmap_t *e)
{
    challoc_init(&e->data);
    challoc_init(&e->geo);
}

void free_dimmap_t(dimmap_t *e)
{
    challoc_free(&e->data);
    challoc_free(&e->geo);
}
DECLARE_VECTOR_ALLOCATOR_FREE(dimmapalloc, dimmap_t)

/** 
 * @brief EOS2 index mapping
 */
typedef struct {
    challoc geo;
    challoc data;
    int32alloc indices;
} indexmap_t;

void init_indexmap_t(indexmap_t *e)
{
    challoc_init(&e->geo);
    challoc_init(&e->data);
    int32alloc_init(&e->indices);
}

void free_indexmap_t(indexmap_t *e)
{
    challoc_free(&e->geo);
    challoc_free(&e->data);
    int32alloc_free(&e->indices);
}
DECLARE_VECTOR_ALLOCATOR_FREE(indexmapalloc, indexmap_t)

/** 
 * @brief one big datastructure that holds everything in one EOS2 swath
 */
typedef struct {
    challoc name;
    dimensionalloc dim;
    dimmapalloc dimmap;
    indexmapalloc index;
    fieldinfoalloc geofield;
    fieldinfoalloc datafield;
    attributealloc attr;
} swath_t;

void init_swath_t(swath_t *e)
{
    challoc_init(&e->name);
    dimensionalloc_init(&e->dim);
    dimmapalloc_init(&e->dimmap);
    indexmapalloc_init(&e->index);
    fieldinfoalloc_init(&e->geofield);
    fieldinfoalloc_init(&e->datafield);
    attributealloc_init(&e->attr);
}

void free_swath_t(swath_t *e)
{
    challoc_free(&e->name);
    dimensionalloc_free(&e->dim);
    dimmapalloc_free(&e->dimmap);
    indexmapalloc_free(&e->index);
    fieldinfoalloc_free(&e->geofield);
    fieldinfoalloc_free(&e->datafield);
    attributealloc_free(&e->attr);
}

/*
 * internally used data structure
 */

typedef struct {
    challoc name;
    int32 dimtype;
    int32 dimsize;
} hdf4dim_t;

void init_hdf4dim_t(hdf4dim_t *e)
{
    challoc_init(&e->name);
}

void free_hdf4dim_t(hdf4dim_t *e)
{
    challoc_free(&e->name);
}
DECLARE_VECTOR_ALLOCATOR_FREE(hdf4dimalloc, hdf4dim_t)

/** 
 * @brief this structure holds dimension's name and value
 * 'handled' indicates whether or not this dimension is already converted into an HDF5 dimscale.
 * 'value' is mostly empty as HDF-EOS2 dimension is mostly empty.
 */
typedef struct {
    challoc name;
    int handled;
    challoc value;
} dimvalue_t;

void init_dimvalue_t(dimvalue_t *e)
{
    challoc_init(&e->name);
    e->handled = 0;
    challoc_init(&e->value);
}

void free_dimvalue_t(dimvalue_t *e)
{
    challoc_free(&e->name);
    challoc_free(&e->value);
}
DECLARE_VECTOR_ALLOCATOR_FREE(dimvaluealloc, dimvalue_t)

/** 
 * @brief This hold additional information about a grid object
 * Some information is not retrieved from the file, but calculated.
 * That information is kept in this structure.
 */
typedef struct {
    int ydimmajor;
    challoc xdimname;
    challoc ydimname;
    int lonlat_converted;
    int orthogonal;
    dimvaluealloc dimvalues;
} grid_aux_t;

void init_grid_aux_t(grid_aux_t *e)
{
    e->ydimmajor = -1;
    challoc_init(&e->xdimname);
    challoc_init(&e->ydimname);
    e->lonlat_converted = 0;
    e->orthogonal = -1;
    dimvaluealloc_init(&e->dimvalues);
}

void free_grid_aux_t(grid_aux_t *e)
{
    challoc_free(&e->xdimname);
    challoc_free(&e->ydimname);
    dimvaluealloc_free(&e->dimvalues);
}

typedef struct {
    int empty;
} swath_aux_t;

void init_swath_aux_t(swath_aux_t *e)
{
}

void free_swath_aux_t(swath_aux_t *e)
{
}

DECLARE_VECTOR_ALLOCATOR(hobjreftalloc, hobj_ref_t)

/** 
 * @brief an element of mapping from source HDF5 dimensional scale to generated HDF5 dimensional scales
 * Due to dimension map, "Longitude" can introduce "Longitude_5:10_5:10", "Longitude_2:3_2:3" and so on.
 * To copy all attributes of "Longitude" to two generated dimensional scales, the following structure
 * keeps enough data to recover the mapping.
 *
 * If "Longitude" was defined in "Swath1" HDF-EOS2 name and "Longitude_5:10_5:10" and "Longitude_2:3_2:3"
 * are generated, there will be an instance of this structure like the following:
 *   swathname := "Swath1"
 *   geofieldname := "Longitude"
 *   source := reference to "Longitude"
 *   targets := [ reference to "Longitude_5:10_5:10", reference to "Longitude_2:3_2:3" ]
 *
 * @see findcreate_hdf4attrs
 * @see handle_pending_attribute
 * @see H4toH5eos_finalization
 */
typedef struct {
    challoc swathname;
    challoc geofieldname;
    hobj_ref_t source;
    hobjreftalloc targets;
} hdf4attrs_t;

void init_hdf4attrs_t(hdf4attrs_t *e)
{
    challoc_init(&e->swathname);
    challoc_init(&e->geofieldname);
    e->source = -1;
    hobjreftalloc_init(&e->targets);
}

void free_hdf4attrs_t(hdf4attrs_t *e)
{
    challoc_free(&e->swathname);
    challoc_free(&e->geofieldname);
    e->source = -1;
    hobjreftalloc_free(&e->targets);
}
DECLARE_VECTOR_ALLOCATOR_FREE(hdf4attrsalloc, hdf4attrs_t)

typedef enum {
    MANGLED_NONE,
    MANGLED_VGROUP,
    MANGLED_SDS,
    MANGLED_VDATA,
} mangledtype_t;

/** 
 * @brief When a dimension name is mangled, one instance of this structure is created.
 * This will be used to get the original dimension name.
 */
typedef struct {
    mangledtype_t kind;
    challoc parentpath;
    challoc origname;
    challoc mangledname;
} mangledname_t;

void init_mangledname_t(mangledname_t *e)
{
    e->kind = MANGLED_NONE;
    challoc_init(&e->parentpath);
    challoc_init(&e->origname);
    challoc_init(&e->mangledname);
}

void free_mangledname_t(mangledname_t *e)
{
    challoc_free(&e->parentpath);
    challoc_free(&e->origname);
    challoc_free(&e->mangledname);
}
DECLARE_VECTOR_ALLOCATOR_FREE(manglednamesalloc, mangledname_t)


/** 
 * @brief this structure keeps the most recently used grid and swath data
 */
typedef struct {
    challoc gridname;
    int32 gridfd;
    int32 gridid;
    grid_t grid;
    grid_aux_t grid_aux;
    challoc swathname;
    int32 swathfd;
    int32 swathid;
    swath_t swath;
    swath_aux_t swath_aux;
} eos2data_t;

static void init_eos2data_partial(eos2data_t *e, int grid)
{
    if (grid) {
        challoc_init(&e->gridname);
        e->gridfd = -1;
        e->gridid = -1;
        init_grid_t(&e->grid);
        init_grid_aux_t(&e->grid_aux);
    }
    else {
        challoc_init(&e->swathname);
        e->swathfd = -1;
        e->swathid = -1;
        init_swath_t(&e->swath);
        init_swath_aux_t(&e->swath_aux);
    }
}

void init_eos2data_t(eos2data_t *e)
{
    init_eos2data_partial(e, 1);
    init_eos2data_partial(e, 0);
}

static void free_eos2data_partial(eos2data_t *e, int grid)
{
    if (grid) {
        challoc_free(&e->gridname);
        if (e->gridid != -1) {
            GDdetach(e->gridid);
            e->gridid = -1;
        }
        if (e->gridfd != -1) {
            GDclose(e->gridfd);
            e->gridfd = -1;
        }
        free_grid_t(&e->grid);
        free_grid_aux_t(&e->grid_aux);
    }
    else {
        challoc_free(&e->swathname);
        if (e->swathid != -1) {
            SWdetach(e->swathid);
            e->swathid = -1;
        }
        if (e->swathfd != -1) {
            SWclose(e->swathfd);
            e->swathfd = -1;
        }
        free_swath_t(&e->swath);
        free_swath_aux_t(&e->swath_aux);
    }
}

void free_eos2data_t(eos2data_t *e)
{
    free_eos2data_partial(e, 1);
    free_eos2data_partial(e, 0);
}

static void reset_eos2data_grid(eos2data_t *e)
{
    free_eos2data_partial(e, 1);
    init_eos2data_partial(e, 1);
}

static void reset_eos2data_swath(eos2data_t *e)
{
    free_eos2data_partial(e, 0);
    init_eos2data_partial(e, 0);
}

#ifndef EAGER_DATA_FETCH
static int prepare_fieldinfo_lazydata(hid_t h4toh5id, int isgrid, fieldinfo_t *field)
{
    int succeeded = 0;

    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
        eos2data_t *eos2data = (eos2data_t *)dt->eos2data;

        if (field->lazydata) {
            H4TOH5ASSERT(field->lazydata->len == field->datalen);
        }
        else {
            int r = -1;
            if ((field->lazydata = malloc(sizeof(challoc))) == NULL) { H4TOH5ERROR_NOMEM1("lazydata"); break; }
            challoc_init(field->lazydata);
            if (!challoc_ready(field->lazydata, field->datalen)) { H4TOH5ERROR_NOMEM1("lazydata elem"); break; }
            if (isgrid)
                r = GDreadfield(eos2data->gridid, field->name.s, NULL, NULL, NULL, field->lazydata->s);
            else
                r = SWreadfield(eos2data->swathid, field->name.s, NULL, NULL, NULL, field->lazydata->s);
            if (r == -1) { H4TOH5ERROR_SET1(3, "readfield error"); break; }
            field->lazydata->len = field->datalen;
        }

        succeeded = 1;
    } while (0);
    return succeeded ? 0 : -1;
}

static int drop_fieldinfo_lazydata(hid_t h4toh5id, fieldinfo_t *field)
{
    if (field->lazydata) {
        challoc_free(field->lazydata);
        free(field->lazydata);
        field->lazydata = NULL;
    }
    return 0;
}
#endif


/*
 * read swath, grid information
 */

/** 
 * @brief parse a string with given separator
 * 
 * @param h4toh5id h4toh5 identifier
 * @param s source string
 * @param len length of @s
 * @param sep separator
 * @param[out] names vector of parsed lists
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int parse_list(hid_t h4toh5id, const char *s, int len, char sep, namelistalloc *names)
{
    int i, j, failinside = 0;

    for (i = j = 0; j <= len; ++j) {
        if ((j == len && len) || s[j] == sep) {
            namelist_t *elem;
            if (!namelistalloc_readyplus(names, 1)) { H4TOH5ERROR_NOMEM1("parse namelist_t"); failinside = 1; break; }
            elem = &names->s[names->len++];
            init_namelist_t(elem);
            {
    if (!challoc_copyb(&elem->name, s + i, j - i)) { H4TOH5ERROR_NOMEM1("parse name"); failinside = 1; break; }
            }
            i = j + 1;
            continue;
        }
    }
    return failinside ? FAIL : SUCCEED;
}

static int parse_namelist(hid_t h4toh5id, const char *fname, int32 (*inq)(const char *, char *, int32 *), namelistalloc *names)
{
    int32 bufsize;
    int numobjs;

    if ((numobjs = inq(fname, NULL, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "inquire namelist"); return -1; }
    if (numobjs > 0) {
        char *namelist;

        if ((namelist = (char *)malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("inquire namelist"); return -1; }
        if (inq(fname, namelist, &bufsize) == -1) { H4TOH5ERROR_SET1(3, "inquire namelist"); return -1; }
        parse_list(h4toh5id, namelist, bufsize, ',', names);
        H4TOH5ASSERT(names->len == numobjs);
        free(namelist);
    }
    return 0;
}

/** 
 * @brief get EOS2 the list of dimensions by using the given set of functions
 * 
 * @param h4toh5id h4toh5 identifier
 * @param id EOS2 id returned by GDattach or SWattach
 * @param entries GDnentries or SWnentries
 * @param inq GDinqdims or SWinqdims
 * @param[out] dims vector of dimension_t (pairs of name and size)
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_dimension(hid_t h4toh5id, int32 id, int32 (*entries)(int32, int32, int32 *), int32 (*inq)(int32, char *, int32 *), dimensionalloc *dims)
{
    char *namelist = NULL;
    int32 *dimsize = NULL;
    int succeeded = 0;

    do {
        int32 numdims, bufsize;

        if ((numdims = entries(id, HDFE_NENTDIM, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "dimension entries failure"); break; }
        if (numdims > 0) {
            int i, j, failinside = 0;

            if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("dimension namelist"); break; }
            if ((dimsize = malloc(numdims * sizeof(int32))) == NULL) { H4TOH5ERROR_NOMEM1("dimension size"); break; }
            if (inq(id, namelist, dimsize) == -1) { H4TOH5ERROR_SET1(3, "dimension inq failure"); break; }

            for (i = j = 0; j <= bufsize; ++j) {
                if ((j == bufsize && bufsize) || namelist[j] == ',') {
                    dimension_t *dim;
                    if (!dimensionalloc_readyplus(dims, 1)) { H4TOH5ERROR_NOMEM1("dimension_t"); failinside = 1; break; }
                    dim = &dims->s[dims->len++];
                    init_dimension_t(dim);
                    H4TOH5ASSERT(dims->len <= numdims)
                    {
                        if (!challoc_copyb(&dim->name, namelist + i, j - i)) { H4TOH5ERROR_NOMEM1("namelist"); failinside = 1; break; }
                        dim->dimsize = dimsize[dims->len - 1];
                    }
                    i = j + 1;
                    continue;
                }
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (namelist != NULL) free(namelist);
        if (dimsize != NULL) free(dimsize);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief get field info of EOS2 grid or swath by using given set of functions
 * 
 * @param h4toh5id h4toh5 identifier
 * @param id EOS2 id returned by GDattach or SWattach
 * @param entries GDnentries or SWnentries
 * @param inq GDinqfields, SWinqgeofields or SWinqdatafields
 * @param fldinfo GDfieldinfo or SWfieldinfo
 * @param readfld GDreadfield or SWreadfield
 * @param getfill GDgetfillvalue or SWgetfillvalue
 * @param geofield determine the second argument of @entries function
 * If geofield is nonzero, HDFE_NENTGFLD is used for the second argument of @entries function; otherwise, HDFE_NENTFLD is used
 * @param[out] fields vector of fieldinfo_t
 * 
 *
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_fieldinfo(hid_t h4toh5id, int32 id, int32 (*entries)(int32, int32, int32 *), int32 (*inq)(int32, char *, int32 *, int32 *), intn (*fldinfo)(int32, char *, int32 *, int32 *, int32 *, char *), intn (*readfld)(int32, char *, int32 *, int32 *, int32 *, VOIDP), intn (*getfill)(int32, char *, VOIDP), int geofield, fieldinfoalloc *fields)
{
    char *namelist = NULL;
    namelistalloc dimnames;
    int succeeded = 0;

    namelistalloc_init(&dimnames);
    do {
        int32 numfields, bufsize;

        if ((numfields = entries(id, geofield ? HDFE_NENTGFLD : HDFE_NENTDFLD, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo entries failure"); break; }
        if (numfields > 0) {
            int i, j, failinside = 0;

            if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("fieldinfo namelist"); break; }
            if (inq(id, namelist, NULL, NULL) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo inq failure"); break; }

            for (i = j = 0; j <= bufsize; ++j) {
                if ((j == bufsize && bufsize) || namelist[j] == ',') {
                    fieldinfo_t *field;
                    if (!fieldinfoalloc_readyplus(fields, 1)) { H4TOH5ERROR_NOMEM1("fieldinfo_t"); failinside = 1; break; }
                    field = &fields->s[fields->len++];
                    init_fieldinfo_t(field);
                    H4TOH5ASSERT(fields->len <= numfields)
                    {
                        int k, numelem = 0, failinside2 = 0;
                        /* XXX: This can be wrong assumption. As of 2008, I don't think there is any way to
                         * figure out how long dimlist[] should be. We contacted one HDF-EOS2 developer, and
                         * he recommended me to use UTLSTR_MAX_SIZE (512). Unfortunately, that macro is defined
                         * in the source code; so, this is invisible, and I hard-codede.
                         */
                        int32 dimsize[16]; /* XXX: 16? */
                        char dimlist[512];

                        if (!challoc_copyb(&field->name, namelist + i, j - i)) { H4TOH5ERROR_NOMEM1("namelist"); failinside = 1; break; }
                        if ((fldinfo(id, field->name.s, &field->rank, dimsize, &field->type, dimlist)) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo"); failinside = 1; break; }

                        namelistalloc_empty(&dimnames);
                        if (parse_list(h4toh5id, dimlist, strlen(dimlist), ',', &dimnames) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                        /* assert that rank is equal to number of names from comma-separated dimension list */
                        if (dimnames.len != field->rank) { H4TOH5ERROR_SET1(3, "rank != dimension name list"); failinside = 1; break; }
                        for (k = 0; k < dimnames.len; ++k) {
                            dimension_t *dim;
                            if (!dimensionalloc_readyplus(&field->dims, 1)) { H4TOH5ERROR_NOMEM1("dimension_t extension"); failinside2 = 1; break; }
                            dim = &field->dims.s[field->dims.len++];
                            init_dimension_t(dim);
                            {
                                if (!challoc_copy(&dim->name, &dimnames.s[k].name)) { H4TOH5ERROR_NOMEM1("dimension name copy"); failinside2 = 1; break; }
                                dim->dimsize = dimsize[k];
                            }
                        }
                        if (failinside2) { failinside = failinside2; break; }

                        for (k = 0, numelem = 1; k < field->dims.len; ++k)
                            numelem *= field->dims.s[k].dimsize;

#ifdef EAGER_DATA_FETCH
                        /* read data */
                        if (!challoc_ready(&field->data, numelem * DFKNTsize(field->type))) { H4TOH5ERROR_NOMEM1("fieldinfo data"); failinside = 1; break; }
                        if (readfld(id, field->name.s, NULL, NULL, NULL, field->data.s) == -1) { H4TOH5ERROR_SET1(3, "readfield failure"); failinside = 1; break; }
                        field->data.len = numelem * DFKNTsize(field->type);
#else
                        field->datalen = numelem * DFKNTsize(field->type);
#endif

                        /* filler may not exist */
                        if (!challoc_ready(&field->filler, DFKNTsize(field->type))) { H4TOH5ERROR_NOMEM1("fieldinfo filler"); failinside = 1; break; }
                        if (getfill(id, field->name.s, field->filler.s) != -1)
                            field->filler.len = DFKNTsize(field->type);
                    }
                    i = j + 1;
                    continue;
                }
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (namelist != NULL) free(namelist);
        namelistalloc_free(&dimnames);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief get a vector of attributes of EOS2 grid or swath by using given set of functions
 * 
 * @param h4toh5id h4toh5 identifier
 * @param id EOS2 id returned by GDattach or SWattach
 * @param inq GDinqattrs or SWinqattrs
 * @param attrinfo GDattrinfo or SWattrinfo
 * @param readattr GDreadattr or SWreadattr
 * @param[out] attrs vector of attributes
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_attribute(hid_t h4toh5id, int32 id, int32 (*inq)(int32, char *, int32 *), intn (*attrinfo)(int32, char *, int32 *, int32 *), intn (*readattr)(int32, char *, VOIDP), attributealloc *attrs)
{
    char *namelist = NULL;
    int succeeded = 0;

    do {
        int32 numattrs, bufsize;

        if ((numattrs = inq(id, NULL, &bufsize)) == -1) { H4TOH5ERROR_SET1(h4toh5id, "attribute inq failure"); break; }
        if (numattrs > 0) {
            int i, j, failinside = 0;

            if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("attribute namelist"); break; }
            if (inq(id, namelist, &bufsize) == -1) { H4TOH5ERROR_SET1(h4toh5id, "attribute inq failure (2)"); break; }

            for (i = j = 0; j <= bufsize; ++j) {
                if ((j == bufsize && bufsize) || namelist[j] == ',') {
                    attribute_t *attr;
                    if (!attributealloc_readyplus(attrs, 1)) { H4TOH5ERROR_NOMEM1("attribute_t"); failinside = 1; break; }
                    attr = &attrs->s[attrs->len++];
                    init_attribute_t(attr);
                    H4TOH5ASSERT(attrs->len <= numattrs)
                    {
                        int32 count;
                        if (!challoc_copyb(&attr->name, namelist + i, j - i)) { H4TOH5ERROR_NOMEM1("attribute name"); failinside = 1; break; }
                        if (attrinfo(id, attr->name.s, &attr->type, &count) == -1) { H4TOH5ERROR_SET1(h4toh5id, "attrinfo failure"); failinside = 1; break; }

                        if (!challoc_ready(&attr->value, count)) { H4TOH5ERROR_NOMEM1("attribute value"); failinside = 1; break; }
                        if (readattr(id, attr->name.s, attr->value.s) == -1) { H4TOH5ERROR_SET1(h4toh5id, "readattr failure"); failinside = 1; break; }
                        attr->value.len = count;
                    }
                    i = j + 1;
                    continue;
                }
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (namelist != NULL) free(namelist);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief get EOS2 grid information by using GDgridinfo API
 * 
 * @param h4toh5id h4toh5 identifier
 * @param gdid EOS2 id returned by GDattach
 * @param[out] info result
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_gridinfo(hid_t h4toh5id, int32 gdid, gridinfo_t *info)
{
    if (GDgridinfo(gdid, &info->xdim, &info->ydim, info->upleft, info->lowright) == -1) { H4TOH5ERROR_SET1(3, "GDgridinfo failure"); return FAIL; }
    return SUCCEED;
}

/** 
 * @brief detect if the given HDF-EOS2 Grid data used [XDim][YDim] style or [YDim][XDim] style
 * This function will check the major dimension and minor dimension by checking
 * all data fields defined in the HDF-EOS2 Grid data. If there is no data field,
 * it cannot detect the major order, and returns -1. If the order is not consistent,
 * this function returns -1.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param grid HDF-EOS2 Grid data
 * @param[out] ydimmajor 1 if the coordinate is like [YDim][XDim] in C convention
 * 
 * @return -1 if failed, 0 if successful
 */
static int detect_majordim(hid_t h4toh5id, const grid_t *grid, const grid_aux_t *aux, int *ydimmajor)
{
    /* *ydimmajor := 1 if (YDim, XDim) */
    /* *ydimmajor := 0 if (XDim, YDim) */

    int i, j, ym = -1;

    for (i = 0; i < grid->field.len; ++i) {
        const fieldinfo_t *field = &grid->field.s[i];
        int xdimindex = -1, ydimindex = -1, major;

        for (j = 0; j < field->dims.len; ++j) {
            const char *dimname = field->dims.s[j].name.s;
            if (strcmp(dimname, aux->xdimname.s) == 0) xdimindex = j;
            else if (strcmp(dimname, aux->ydimname.s) == 0) ydimindex = j;
        }
        /* HDF-EOS2 Reference states the following:
         *   The data fields are, of course, the most important part of the Grid. Data
         *   fields in a Grid data set are rectilinear arrays of two or more
         *   dimensions.
         * My understanding is that each field has at least two dimensions, and
         * I guess those two fundamental dimensions are XDim and YDim. So, I guessed
         * both xdimindex and ydimindex are equal to or greater than 0. However,
         * I found a file that has a field with the rank 1, and fixed the following code.
         */
        if (xdimindex == -1 || ydimindex == -1) continue;

        major = ydimindex < xdimindex ? 1 : 0;
        if (ym == -1) ym = major;
        else if (ym != major) {
            H4TOH5ERROR_SET1(3, "GRID DATA DOES NOT HAVE CONSISTENT XDim, YDim (or alternative dimensions) ORDER!!! You may want to specify -nc4 rather than -nc4strict");
            H4toH5error_suppress(h4toh5id, 1);
            return -1;
        }
    }

    if (ym == -1) {
        H4TOH5ERROR_SET1(3, "GRID DATA DOES NOT HAVE ANY FIELD THAT SHOWS XDim, YDim (or alternative dimensions) ORDER!!! You may want to specify -nc4 rather than -nc4strict");
        H4toH5error_suppress(h4toh5id, 1);
        return -1;
    }
    *ydimmajor = ym;
    return 0;
}

/** 
 * @brief generate longitudes and latitudes dimension scale from the given projection info
 * 
 * @param h4toh5id h4toh5 identifier
 * @param grid grid information from GDgridinfo API
 * @param[in, out] proj lons, lats fields are written
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int convert_proj(hid_t h4toh5id, const grid_t *grid, const grid_aux_t *aux, float64alloc *lons, float64alloc *lats)
{
    int32 *rows = NULL, *cols = NULL;
    gridinfo_t *g = (gridinfo_t *)&grid->info;
    projinfo_t *p = (projinfo_t *)&grid->proj;
    int succeeded = 0;

    do {
        int i, j, k;
        int32 numpoints = grid->info.xdim * grid->info.ydim;

        if ((rows = malloc(numpoints * sizeof(int32))) == NULL) { H4TOH5ERROR_NOMEM1("'rows'"); break; }
        if ((cols = malloc(numpoints * sizeof(int32))) == NULL) { H4TOH5ERROR_NOMEM1("'cols'"); break; }
        if (aux->ydimmajor == 1) {
            /* rows                         cols */
            /*   /- xdim -\             /- xdim -\ */
            /*   0 0 0 ... 0            0 1 2 ... x */
            /*   1 1 1 ... 1            0 1 2 ... x */
            /*           ...                            ... */
            /*   y y y ... y            0 1 2 ... x */
            for (k = j = 0; j < grid->info.ydim; ++j) {
                for (i = 0; i < grid->info.xdim; ++i) {
                    rows[k] = j;
                    cols[k] = i;
                    ++k;
                }
            }
        }
        else if (aux->ydimmajor == 0) {
            /* rows                         cols */
            /*   /- ydim -\             /- ydim -\ */
            /*   0 1 2 ... y            0 0 0 ... y */
            /*   0 1 2 ... y            1 1 1 ... y */
            /*           ...                            ... */
            /*   0 1 2 ... y            2 2 2 ... y */
            for (k = j = 0; j < grid->info.xdim; ++j) {
                for (i = 0; i < grid->info.ydim; ++i) {
                    rows[k] = i;
                    cols[k] = j;
                    ++k;
                }
            }
        }
        else {
            H4TOH5ERROR_SET1(3, "internal error"); break;
        }

        if (!float64alloc_ready(lons, numpoints)) { H4TOH5ERROR_NOMEM1("'lons'"); break; }
        if (!float64alloc_ready(lats, numpoints)) { H4TOH5ERROR_NOMEM1("'lats'"); break; }

        if (GDij2ll(grid->proj.proj, grid->proj.zone, p->param, grid->proj.sphere, grid->info.xdim, grid->info.ydim, g->upleft, g->lowright, numpoints, rows, cols, lons->s, lats->s, grid->proj.pix, grid->proj.origin) == -1) { H4TOH5ERROR_SET1(3, "GDij2ll failure"); break; }
        lons->len = numpoints;
        lats->len = numpoints;

        succeeded = 1;
    } while (0);

    if (1) {
        if (rows != NULL) free(rows);
        if (cols != NULL) free(cols);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief get projection information by using GDprojinfo API and generate longitudes and latitudes
 * 
 * @param h4toh5id h4toh5 identifier
 * @param gdid EOS2 id returned by GDattach
 * @param grid grid information from GDgridinfo API
 * @param[out] proj projection information
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_gridprojinfo(hid_t h4toh5id, int32 gdid, projinfo_t *proj)
{
    if (GDprojinfo(gdid, &proj->proj, &proj->zone, &proj->sphere, proj->param) == -1) { H4TOH5ERROR_SET1(3, "GDprojiunfo failure"); return FAIL; }
    if (GDpixreginfo(gdid, &proj->pix) == -1) { H4TOH5ERROR_SET1(3, "GDpixreginfo failure"); return FAIL; }
    if (GDorigininfo(gdid, &proj->origin) == -1) { H4TOH5ERROR_SET1(3, "GDorigininfo failure"); return FAIL; }
    return SUCCEED;
}

static int get_griddimension(hid_t h4toh5id, int32 gdid, dimensionalloc *dims)
{
    return get_dimension(h4toh5id, gdid, GDnentries, GDinqdims, dims);
}


static int get_gridfieldinfo(hid_t h4toh5id, int gdid, fieldinfoalloc *fields)
{
    return get_fieldinfo(h4toh5id, gdid, GDnentries, GDinqfields, GDfieldinfo, GDreadfield, GDgetfillvalue, 0, fields);
}

static int get_gridattribute(hid_t h4toh5id, int gdid, attributealloc *attrs)
{
    return get_attribute(h4toh5id, gdid, GDinqattrs, GDattrinfo, GDreadattr, attrs);
}

static int get_swathdimension(hid_t h4toh5id, int32 swid, dimensionalloc *dims)
{
    return get_dimension(h4toh5id, swid, SWnentries, SWinqdims, dims);
}

/** 
 * @brief get EOS2 swath dimension map
 * 
 * @param h4toh5id h4toh5 identifier
 * @param swid EOS2 id returned by SWattach
 * @param[out] maps vector of dimmap_t
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_swathdimmap(hid_t h4toh5id, int32 swid, dimmapalloc *maps)
{
    char *namelist = NULL;
    int32 *offset = NULL, *increment = NULL;
    namelistalloc name;
    int succeeded = 0;

    namelistalloc_init(&name);
    do {
        int32 nummaps, bufsize;

        if ((nummaps = SWnentries(swid, HDFE_NENTMAP, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "SWentries HDFE_NENTMAP failure"); break; }
        if (nummaps > 0) {
            int i, j, failinside = 0;

            if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("dimmap namelist"); break; }
            if ((offset = malloc(nummaps * sizeof(int32))) == NULL) { H4TOH5ERROR_NOMEM1("dimmap offset"); break; }
            if ((increment = malloc(nummaps * sizeof(int32))) == NULL) { H4TOH5ERROR_NOMEM1("dimmap increment"); break; }
            if (SWinqmaps(swid, namelist, offset, increment) == -1) { H4TOH5ERROR_SET1(3, "SWinqmaps failure"); break; }

            for (i = j = 0; j <= bufsize; ++j) {
                if ((j == bufsize && bufsize) || namelist[j] == ',') {
                    dimmap_t *map;
                    if (!dimmapalloc_readyplus(maps, 1)) { H4TOH5ERROR_NOMEM1("dimmap_t"); failinside = 1; break; }
                    map = &maps->s[maps->len++];
                    init_dimmap_t(map);
                    H4TOH5ASSERT(maps->len <= nummaps)
                    {
                        namelistalloc_empty(&name);
                        /* each dimension map consists of dimension name for geo field and dimension name for data field */
                        if (parse_list(h4toh5id, namelist + i, j - i, '/', &name) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                        if (name.len != 2) { H4TOH5ERROR_SET1(3, "# of dimmap name != 2"); failinside = 1; break; }
                        if (!challoc_copy(&map->geo, &name.s[0].name)) { H4TOH5ERROR_NOMEM1("dimmap geo"); failinside = 1; break; }
                        if (!challoc_copy(&map->data, &name.s[1].name)) { H4TOH5ERROR_NOMEM1("dimmap data"); failinside = 1; break; }
                        map->offset = offset[maps->len - 1];
                        map->increment = increment[maps->len - 1];
                    }
                    i = j + 1;
                    continue;
                }
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        namelistalloc_free(&name);
        if (increment != NULL) free(increment);
        if (offset != NULL) free(offset);
        if (namelist != NULL) free(namelist);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief get EOS2 swath index map
 * 
 * This function has not been tested.
 *
 * @param h4toh5id h4toh5 identifier
 * @param swid EOS2 id returned by SWattach
 * @param[out] indices vector of indexmap_t
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_swathindexmap(hid_t h4toh5id, int32 swid, indexmapalloc *indices)
{
    char *namelist = NULL;
    namelistalloc name;
    int succeeded = 0;

    namelistalloc_init(&name);
    do {
        int32 numindices, bufsize;

        if ((numindices = SWnentries(swid, HDFE_NENTIMAP, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "SWnentries HDFE_NENTIMAP failure"); break; }
        if (numindices > 0) {
            /* TODO: I have never seen any EOS2 files that have index map. */
            int i, j, failinside = 0;

            if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("indexmap namelist"); break; }
            if (SWinqidxmaps(swid, namelist, NULL) == -1) { H4TOH5ERROR_SET1(3, "SWinqdimmaps failure"); break; }

            for (i = j = 0; j <= bufsize; ++j) {
                if ((j == bufsize && bufsize) || namelist[j] == ',') {
                    indexmap_t *index;
                    if (!indexmapalloc_readyplus(indices, 1)) { H4TOH5ERROR_NOMEM1("indexmap_t"); failinside = 1; break; }
                    index = &indices->s[indices->len++];
                    init_indexmap_t(index);
                    H4TOH5ASSERT(indices->len <= numindices)
                    {
                        int32 dimsize;
                        namelistalloc_empty(&name);
                        if (parse_list(h4toh5id, namelist + i, j - i, '/', &name) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                        if (name.len != 2) { H4TOH5ERROR_SET1(3, "# of indexmap != 2"); failinside = 1; break; }
                        if (!challoc_copy(&index->geo, &name.s[0].name)) { H4TOH5ERROR_NOMEM1("indexmap geo"); failinside = 1; break; }
                        if (!challoc_copy(&index->data, &name.s[1].name)) { H4TOH5ERROR_NOMEM1("indexmap data"); failinside = 1; break; }

                        if ((dimsize = SWdiminfo(swid, index->geo.s)) == -1) { H4TOH5ERROR_SET1(3, "SWdiminfo failure"); failinside = 1; break; }
                        if (!int32alloc_ready(&index->indices, dimsize)) { H4TOH5ERROR_NOMEM1("indexmap data"); failinside = 1; break; }
                        if (SWidxmapinfo(swid, index->geo.s, index->data.s, index->indices.s) == -1) { H4TOH5ERROR_SET1(3, "SWidxmapinfo failure"); failinside = 1; break; }
                        index->indices.len = dimsize;
                    }
                    i = j + 1;
                    continue;
                }
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        namelistalloc_free(&name);
        if (namelist != NULL) free(namelist);
    }
    return succeeded ? SUCCEED : FAIL;
}

static int get_swathfieldinfo(hid_t h4toh5id, int32 swid, int geofield, fieldinfoalloc *fields)
{
    return get_fieldinfo(h4toh5id, swid, SWnentries, geofield ? SWinqgeofields : SWinqdatafields, SWfieldinfo, SWreadfield, SWgetfillvalue, geofield, fields);
}

static int get_swathattribute(hid_t h4toh5id, int32 swid, attributealloc *attrs)
{
    return get_attribute(h4toh5id, swid, SWinqattrs, SWattrinfo, SWreadattr, attrs);
}

static const mangledname_t * find_mangledname(hid_t h4toh5id, mangledtype_t kind, const char *parentpath, int parentpathlen, const char *mangledname, int manglednamelen)
{
    int i;
    h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
    const manglednamesalloc *manglednames = dt->manglednames;

    for (i = 0; i < manglednames->len; ++i) {
        const mangledname_t *mangled = &manglednames->s[i];
        if (mangled->kind != kind) continue;
        if (strlen(mangled->parentpath.s) != parentpathlen) continue;
        if (strncmp(mangled->parentpath.s, parentpath, parentpathlen)) continue;
        if (strlen(mangled->mangledname.s) != manglednamelen) continue;
        if (strncmp(mangled->mangledname.s, mangledname, manglednamelen)) continue;
        return mangled;
    }
    return NULL;
}

/** 
 * @brief it parses mangledfullpath and finds the name of the dimension that generated the mangled name
 */
static int get_orignameb(hid_t h4toh5id, mangledtype_t kind, const char *mangledfullpath, int pathlen, challoc *origname)
{
    int succeeded = 0;

    do {
        const mangledname_t *mangled;
        int i, j;

        for (i = -1, j = pathlen - 1; j >= 0; --j) {
            if (mangledfullpath[j] == '/') {
                if (j == pathlen - 1) continue;
                i = j;
                break;
            }
        }

        challoc_empty(origname);
        if (i != -1) {
            if ((mangled = find_mangledname(h4toh5id, kind, mangledfullpath, i, mangledfullpath + i + 1, pathlen - i - 1))) {
                if (!challoc_copy(origname, &mangled->origname)) { H4TOH5ERROR_NOMEM1("copy origname"); break; }
            }
        }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

static int get_origname(hid_t h4toh5id, mangledtype_t kind, const char *mangledfullpath, challoc *origname)
{
    return get_orignameb(h4toh5id, kind, mangledfullpath, strlen(mangledfullpath), origname);
}

/* EOS2 always puts every data field in "Data Fields" HDF4 vgroup */
/* This should be equal to H4toH5correct_name_netcdf4("Data Fields") */
const char *DATAFIELD_VGROUP = "Data_Fields";

/** 
 * @brief check if the given group name is expected type
 * 
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param kind expected group name
 * @param[out] iswanted 1 if groupname is expected one
 * 
 * @return SUCCEED
 */
static int check_eosfield(const char *groupname, const char *kind, int *iswanted)
{
    const char *target;

    *iswanted = 0;
    if (strlen(groupname) < strlen(kind) + 1)
        return SUCCEED;

    /* if groupname ends with "/Data Fields" */
    target = groupname + strlen(groupname) - strlen(kind);
    if (*(target - 1) != '/')
        return SUCCEED;
    if (strcmp(kind, target) != 0)
        return SUCCEED;

    *iswanted = 1;
    return SUCCEED;
}

/** 
 * @brief check if the given group name ends with "Data Fields"
 * 
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] isdatafield 1 if groupname is a data field
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_datafield(const char *groupname, int *isdatafield)
{
    return check_eosfield(groupname, DATAFIELD_VGROUP, isdatafield);
}

/* EOS2 always puts every geo field in "Geolocation Fields" HDF4 vgroup */
/* This should be equal to H4toH5correct_name_netcdf4("Geolocation Fields") */
const char *GEOFIELD_VGROUP = "Geolocation_Fields";

/** 
 * @brief check if the given group name ends with "Geolocation Fields"
 * 
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] isgeofield 1 if groupname is a HDF-EOS2 geo field
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_geofield(const char *groupname, int *isgeofield)
{
    return check_eosfield(groupname, GEOFIELD_VGROUP, isgeofield);
}

/* This should be equal to H4toH5correct_name_netcdf4("Grid Attributes") */
const char *GRIDATTR_VGROUP = "Grid_Attributes";

/** 
 * @brief check if the given group name ends with "Grid Attributes"
 * 
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] isattribute 1 if groupname is a HDF-EOS2 Grid attribute
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_gridattribute(const char *groupname, int *isattribute)
{
    return check_eosfield(groupname, GRIDATTR_VGROUP, isattribute);
}

/* This should be equal to H4toH5correct_name_netcdf4("Swath Attribute") */
const char *SWATHATTR_VGROUP = "Swath_Attributes";

/** 
 * @brief check if the given group name ends with "Swath Attributes"
 * 
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] isattribute 1 if groupname is a HDF-EOS2 Swath attribute
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_swathattribute(const char *groupname, int *isattribute)
{
    return check_eosfield(groupname, SWATHATTR_VGROUP, isattribute);
}

/** 
 * @brief divide the dimension name (HDF4 sds, @sdsdimname) into pure dimension name and EOS2 swath or grid name
 * 
 * EOS2 API uses HDF4 sds name for dimension scale by concatenating dimension name and EOS2 swath name.
 * e.g. DataTrack_lo:Swath1 is actual dimension scale name for Low_res_sst EOS2 swath field.
 * Here, DataTrack_lo is a dimension name and Swath1 is a EOS2 swath name.
 *
 * @param h4toh5id h4toh5 identifier
 * @param sdsdimname HDF4 sds name: assume that it looks like DataTrack_lo:Swath1
 * @param[out] puredimname dimension name; e.g. DataTrack_lo
 * @param[out] eosgroupname EOS2 swath or grid name; e.g. Swath1
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int extract_datafieldname(hid_t h4toh5id, const char *sdsdimname, challoc *puredimname, challoc *eosgroupname)
{
    int succeeded = 0;

    challoc_empty(puredimname);
    do {
        size_t i;

        for (i = 0; i < strlen(sdsdimname); ++i) {
            if (sdsdimname[i] == ':')
                break;
            if (!challoc_catb(puredimname, &sdsdimname[i], 1)) { H4TOH5ERROR_NOMEM1("puredimname"); break; }
        }
        if (i >= strlen(sdsdimname)) { H4TOH5ERROR_SET2(3, "cannot find ':'", sdsdimname); break; }
        if (!challoc_copys(eosgroupname, sdsdimname + i + 1)) { H4TOH5ERROR_NOMEM1("swath name"); break; }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

static int check_predefinedgroup(const char *str)
{
    if (strcmp(DATAFIELD_VGROUP, str) == 0) return 1;
    if (strcmp(GEOFIELD_VGROUP, str) == 0) return 1;
    if (strcmp(GRIDATTR_VGROUP, str) == 0) return 1;
    if (strcmp(SWATHATTR_VGROUP, str) == 0) return 1;
    return 0;
}

static int extract_eosgroup_name(hid_t h4toh5id, const char *h5groupname, int wantorigname, challoc *eosgroupname)
{
    challoc origpath;
    int succeeded = 0;

    challoc_init(&origpath);
    challoc_empty(eosgroupname);
    do {
        int i, j, len, handledinside = 0, failinside = 0;

        len = (int)strlen(h5groupname);
        for (i = -1, j = len - 1; j >= 0; --j) {
            if (h5groupname[j] == '/') {
                if (j == len - 1) continue;
                if (i != -1)
                    break;
                else {
                    i = j - 1;
                    if (j == 0 && !check_predefinedgroup(h5groupname + j)) {
                        if (wantorigname) {
                            if (get_origname(h4toh5id, MANGLED_VGROUP, h5groupname, eosgroupname) == FAIL) { H4TOH5ERROR_SET1(3, "origname"); failinside = 1; break; }
                        }
                        if (!(wantorigname && eosgroupname->s)) {
                            if (!challoc_copys(eosgroupname, h5groupname + 1)) { H4TOH5ERROR_NOMEM1("datagroup name"); failinside = 1; break; }
                        }
                        handledinside = 1;
                        break;
                    }
                }
            }
        }
        if (failinside) break;
        if (!handledinside) {
            if (i == -1 || j == -1) break;
            if (!(j < i)) break;
            if (wantorigname) {
                if (get_orignameb(h4toh5id, MANGLED_VGROUP, h5groupname, i + 1, eosgroupname) == FAIL) { H4TOH5ERROR_SET1(3, "origname"); break; }
            }
            if (!(wantorigname && eosgroupname->s)) {
                if (!challoc_copyb(eosgroupname, h5groupname + j + 1, i - j)) { H4TOH5ERROR_NOMEM1("datagroup name"); break; }
            }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&origpath);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief extract safe Swath name or Grid name from HDF5 group name
 * The given h5groupname should be either /swath/Data Fields or /swath
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] eosgroupname Swath name or Grid name
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int extract_eosgroup_safename(hid_t h4toh5id, const char *h5groupname, challoc *eosgroupname)
{
    return extract_eosgroup_name(h4toh5id, h5groupname, 0, eosgroupname);
}

static int extract_eosgroup_origname(hid_t h4toh5id, const char *h5groupname, challoc *eosgroupname)
{
    return extract_eosgroup_name(h4toh5id, h5groupname, 1, eosgroupname);
}

/** 
 * @brief extract a parent path
 * 
 * @param h4toh5id h4toh5 identifier 
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] parentgroup parent path
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int extract_parentpath(hid_t h4toh5id, const char *h5groupname, challoc *parentgroup)
{
    int succeeded = 0;

    challoc_empty(parentgroup);
    do {
        int j, len;

        len = (int)strlen(h5groupname);
        for (j = len - 1; j >= 0; --j) {
            if (h5groupname[j] == '/') {
                if (j == len - 1) continue;
                break;
            }
        }
        if (j == 0) break;
        if (!challoc_copyb(parentgroup, h5groupname, j)) { H4TOH5ERROR_NOMEM1("parent group"); break; }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief read HDF4 sds information and dimension by using HDF4 API
 * If eosgroupname is NULL, this function assumes the passed SDS object may not belong
 * to HDF-EOS2; so, HDF-EOS2-specific colon-separated dimension name is not assumed.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param sdsid HDF4 sds identifier
 * @param[out] sdsname HDF4 sds name
 * @param[in,out] eosgroupname EOS2 swath or grid name; e.g. Swath1
 * @param[out] hdf4dims list of dimension name that this HDF4 sds refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int read_sdsdim(hid_t h4toh5id, int32 sdsid, challoc *sdsname, challoc *eosgroupname, hdf4dimalloc *hdf4dims)
{
    challoc puredimname, anothereosgroupname;
    int succeeded = 0;

    challoc_init(&puredimname);
    challoc_init(&anothereosgroupname);
    do {
        char sdsn[H4H5_MAX_NC_NAME + 1];
        int32 rank, dimsizes[H4H5_MAX_VAR_DIMS], type, nattr;
        int i, failinside = 0;

        if (SDgetinfo(sdsid, sdsn, &rank, dimsizes, &type, &nattr) == FAIL) { H4TOH5ERROR_SET1(3, "SDgetinfo failure"); break; }
        for (i = 0; i < rank; ++i) {
            int32 dimscalesize, dimtype, dimnattr;
            char dimname[H4H5_MAX_NC_NAME + 1];
            int32 dimid;

            if ((dimid = SDgetdimid(sdsid, i)) == FAIL) { H4TOH5ERROR_SET1(3, "SDgetdimid failure"); failinside = 1; break; }
            if (SDdiminfo(dimid, dimname, &dimscalesize, &dimtype, &dimnattr) == FAIL) { H4TOH5ERROR_SET1(3, "SDdiminfo failure"); failinside = 1; break; }
            if (eosgroupname) {
                if (extract_datafieldname(h4toh5id, dimname, &puredimname, &anothereosgroupname) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                if (!eosgroupname->len) {
                    if (!challoc_copy(eosgroupname, &anothereosgroupname)) { H4TOH5ERROR_NOMEM1("eosgroupname copy"); failinside = 1; break; }
    }
                else if (strcmp(eosgroupname->s, anothereosgroupname.s)) { H4TOH5ERROR_SET1(3, "this sds has several swath(or grid) name"); failinside = 1; break; }
            }
            else {
                if (!challoc_copys(&puredimname, dimname)) { H4TOH5ERROR_NOMEM1("dimname"); failinside = 1; break; }
            }

            if (hdf4dims) {
                hdf4dim_t *hdf4dim;
                if (!hdf4dimalloc_readyplus(hdf4dims, 1)) { H4TOH5ERROR_NOMEM1("hdf4dim_t"); failinside = 1; break; }
                hdf4dim = &hdf4dims->s[hdf4dims->len++];
                init_hdf4dim_t(hdf4dim);
                if (!challoc_copys(&hdf4dim->name, puredimname.s)) { H4TOH5ERROR_NOMEM1("dimension name"); failinside = 1; break; }
                hdf4dim->dimtype = dimtype;
                hdf4dim->dimsize = dimscalesize;
            }
        }
        if (failinside) break;
        if (sdsname) {
            if (!challoc_copys(sdsname, sdsn)) { H4TOH5ERROR_NOMEM1("sdsname copy"); break; }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&anothereosgroupname);
        challoc_free(&puredimname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief read vdata information
 * Since vdata does not have dimension, it cannot give the same information as read_sdsdim() gives.
 * It does not give dimension types at all.
 * 
 * @param h4toh5id h4toh5id identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param vdataid HDF4 vdata identifier
 * @param[out] vdataname HDF4 vdata name
 * @param[out] eosgroupname EOS2 swath or grid name
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int read_vdatadim(hid_t h4toh5id, const char *h5groupname, int32 vdataid, challoc *vdataname, challoc *eosgroupname)
{
    int succeeded = 0;

    do {
        char vdatan[H4H5_MAX_NC_NAME + 1];

        if (VSinquire(vdataid, NULL, NULL, NULL, NULL, vdatan) == FAIL) { H4TOH5ERROR_SET1(3, "vsinqure"); break; }
        if (!challoc_copys(vdataname, vdatan)) { H4TOH5ERROR_NOMEM1("vdataname copy");; break; }

        if (extract_eosgroup_origname(h4toh5id, h5groupname, eosgroupname) == -1) { H4TOH5ERROR_SET0(3); break; }

        succeeded = 1;
    } while (0);

    if (1) {
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief return index of element corresponding to the given dimension name
 * 
 * @param hdf4dims list of dimension name that this HDF4 sds refers to
 * @param dimname dimension name
 * 
 * @return -1 if not found
 */
static int32 find_dimtype(const hdf4dimalloc *hdf4dims, const char *dimname)
{
    int i;

    for (i = 0; i < hdf4dims->len; ++i) {
        const hdf4dim_t *dim = &hdf4dims->s[i];
        if (strcmp(dim->name.s, dimname) == 0)
            return dim->dimtype;
    }
    return -1;
}

static dimvalue_t * find_dimvalue(hid_t h4toh5id, const dimvaluealloc *dimvalues, const char *name)
{
    int i;
    for (i = 0; i < dimvalues->len; ++i) {
        dimvalue_t *dimvalue = &dimvalues->s[i];
        if (strcmp(dimvalue->name.s, name) == 0)
            return dimvalue;
    }
    return NULL;
}

/** 
 * @brief try to create a string attribute
 * 
 * @param h4toh5id h4toh5 identifier
 * @param dataset HDF5 dataset that will have additional attribute
 * @param attrname attribute name
 * @param attrvalue attribute value
 * @param simple non-zero if simple dataspace should be used
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_attribute_string(hid_t h4toh5id, hid_t dataset, const char *attrname, const char *attrvalue, int simple)
{
    hid_t spaceid = FAIL;
    hid_t tid = FAIL;
    hid_t attrid = FAIL;
    int succeeded = 0;

    do {
        hsize_t dimsize = 1;

        /* if the attribute is there, do not touch */
        H5E_BEGIN_TRY {
            attrid = H5Aopen_name(dataset, attrname);
        } H5E_END_TRY;
        if (attrid == FAIL) {
            if (simple) {
                if ((spaceid = H5Screate_simple(1, &dimsize, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple failure"); break; }
                if ((tid = H5Tcopy(H5T_C_S1)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tcopy"); break; }
                if (H5Tset_size(tid, strlen(attrvalue)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_size"); break; }
            }
            else {
                if ((spaceid = H5Screate(H5S_SCALAR)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate failure"); break; }
                if ((tid = H5Tcopy(H5T_C_S1)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tcopy"); break; }
                if (H5Tset_size(tid, strlen(attrvalue) + 1) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_size"); break; }
                if (H5Tset_strpad(tid, H5T_STR_NULLTERM) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_strpad"); break; }
            }
            if ((attrid = H5Acreate_safe(h4toh5id, dataset, attrname, tid, spaceid, H5P_DEFAULT)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Acreate"); break; }
            if (H5Awrite(attrid, tid, attrvalue) == FAIL) { H4TOH5ERROR_SET1(3, "write dimension attr 'CLASS' fails"); break; }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (attrid != FAIL) H5Aclose(attrid);
        if (tid != FAIL) H5Tclose(tid);
        if (spaceid != FAIL) H5Sclose(spaceid);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief a wrapper of write_attribute_string with scalar space option
 * 
 * @param h4toh5id h4toh5 identifier
 * @param dataset HDF5 dataset that will have additional attribute
 * @param attrname attribute name
 * @param attrvalue attribute value
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_attribute_scalar(hid_t h4toh5id, hid_t dataset, const char *attrname, const char *attrvalue)
{
    return write_attribute_string(h4toh5id, dataset, attrname, attrvalue, 0);
}

static const char *NETCDF4_FILLVALUE = "_FillValue";

static int write_fillvalue_attribute(hid_t h4toh5id, hid_t dataset, hid_t datatype, hid_t memtype, int32 fieldtype, const challoc *filler)
{
    hid_t spaceid = FAIL;
    hid_t tid = FAIL;
    hid_t attrid = FAIL;
    int succeeded = 0;

    do {
        /* if the attribute is there, do not touch */
        H5E_BEGIN_TRY {
            attrid = H5Aopen_name(dataset, NETCDF4_FILLVALUE);
        } H5E_END_TRY;
        if (attrid == FAIL) {
            if (datatype == H5T_STRING) {
                if ((spaceid = H5Screate(H5S_SCALAR)) == FAIL) { H4TOH5ERROR_SET1(3, "create space"); break; }
                if ((tid = H5Tcopy(H5T_C_S1)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tcopy"); break; }
                if (H5Tset_size(tid, filler->len) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_size"); break; }
                if (H5Tset_strpad(tid, H5T_STR_NULLTERM) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_strpad"); break; }
                datatype = tid;
                memtype = tid;
            }
            else {
                hsize_t dimsize;
                int32 typesize;
                if ((typesize = DFKNTsize(fieldtype)) == 0) { H4TOH5ERROR_SET1(3, "datatype size"); break; }
                H4TOH5ASSERT(filler->len % typesize == 0);
                dimsize = filler->len / typesize;
                if ((spaceid = H5Screate_simple(1, &dimsize, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple failure"); break; }
            }
            if ((attrid = H5ACREATE(dataset, NETCDF4_FILLVALUE, datatype, spaceid, H5P_DEFAULT)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Acreate"); break; }
            if (H5Awrite(attrid, memtype, filler->s) == FAIL) { H4TOH5ERROR_SET1(3, "write dimension attr 'CLASS' fails"); break; }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (attrid != FAIL) H5Aclose(attrid);
        if (tid != FAIL) H5Tclose(tid);
        if (spaceid != FAIL) H5Sclose(spaceid);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief find or create mapping between source HDF5 dimensional scale to generated HDF5 dimensional scales
 * As explained in handle_pending_attribute() and H4toH5eos_finalization(), all attributes in "Longitude"
 * need to be copied to "Longitude_5:10_5:10". This function will create an entry into the list of mapping.
 * The content should be filled by the caller.
 * @see hdf4attrs_t
 * @see H4toH5eos_finalization
 * @see handle_pending_attribute
 * 
 * @param h4toh5id h4toh5 identifier
 * @param swath HDF-EOS2 Swath data to fetch its name
 * @param geofield HDF-EOS2 Geolocation field to fetch its name
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static hdf4attrs_t * findcreate_hdf4attrs(hid_t h4toh5id, const swath_t *swath, const fieldinfo_t *geofield)
{
    hdf4attrs_t *hdf4attr = NULL;
    int succeeded = 0;

    do {
        int i;
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
        hdf4attrsalloc *hdf4attrs = dt->created_datasets;

        H4TOH5ASSERT(hdf4attrs)

        for (i = 0; i < hdf4attrs->len; ++i) {
            hdf4attrs_t *attr = &hdf4attrs->s[i];
            if (strcmp(attr->swathname.s, swath->name.s)) continue;
            if (strcmp(attr->geofieldname.s, geofield->name.s)) continue;
            hdf4attr = attr;
            break;
        }

        if (!hdf4attr) {
            if (!hdf4attrsalloc_readyplus(hdf4attrs, 1)) { H4TOH5ERROR_NOMEM1("hdf4 attrs"); break; }
            hdf4attr = &hdf4attrs->s[hdf4attrs->len++];
            init_hdf4attrs_t(hdf4attr);

            if (!challoc_copy(&hdf4attr->swathname, &swath->name)) { H4TOH5ERROR_NOMEM1("hdf4 attrs name"); break; }
            if (!challoc_copy(&hdf4attr->geofieldname, &geofield->name)) { H4TOH5ERROR_NOMEM1("hdf4 attrs name"); break; }
        }

        succeeded = 1;
    } while (0);

    return succeeded ? hdf4attr : NULL;
}

/** 
 * @brief create or update an element of mapping from source HDF5 dimensional scale to generated HDF5 dimensional scales, and append one target
 * @see hdf4attrs_t
 * @see handle_pending_attribute
 * @see H4toH5eos_finalization
 * 
 * @param h4toh5id h4toh5 identifier
 * @param swath HDF-EOS2 Swath data to fetch its name
 * @param geofield HDF-EOS2 Geolocation field to fetch its name
 * @param ref reference to the target such as "Longitude:5_10:5_10"
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int add_pending_hdf4attrs(hid_t h4toh5id, const swath_t *swath, const fieldinfo_t *geofield, hobj_ref_t ref)
{
    int succeeded = 0;

    do {
        hdf4attrs_t *entry;
     
        if ((entry = findcreate_hdf4attrs(h4toh5id, swath, geofield)) == NULL) { H4TOH5ERROR_SET0(3); break; }

        if (!hobjreftalloc_readyplus(&entry->targets, 1)) { H4TOH5ERROR_NOMEM1("hdf4 attrs target"); break; }
        entry->targets.s[entry->targets.len++] = ref;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create or update an element of mapping from source HDF5 dimensional scale to generated HDF5 dimensional scales, and set the source
 * @see hdf4attrs_t
 * @see handle_pending_attribute
 * @see H4toH5eos_finalization
 * 
 * @param h4toh5id h4toh5 identifier
 * @param swath HDF-EOS2 Swath data to fetch its name
 * @param geofield HDF-EOS2 Geolocation field to fetch its name
 * @param ref reference to the source dimensional scale such as "Longitude"
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int fill_pending_hdf4attrs(hid_t h4toh5id, const swath_t *swath, const fieldinfo_t *geofield, hobj_ref_t ref)
{
    int succeeded = 0;

    do {
        hdf4attrs_t *entry;
     
        if ((entry = findcreate_hdf4attrs(h4toh5id, swath, geofield)) == NULL) { H4TOH5ERROR_SET0(3); break; }
        entry->source = ref;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create an HDF5 dataset corresponding to an HDF4 vdata
 * An HDF4 vdata may represent an HDF-EOS2 attribute or HDF-EOS2 one-dimensional field.
 * When H4toH5vdata_eosswath_dimscale() are H4toH5vdata_eosgrid_dimscale() are handling an
 * HDF-EOS2 one-dimensional field, they call this function to create the corresponding
 * HDF5 dataset.
 *
 * There is no corresponding function for HDF4 sds because creating an HDF5 dataset is
 * performed by the generic h4toh5 routine.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param field source HDF-EOS2 data field
 * @param vdataid HDF4 vdata id
 * @param isgrid whether or not this vdata belongs to a grid
 * @param[out] h5dataset HDF5 dataset identifier
 * @param[out] h5datasetref reference to h5dataset
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int create_vdata(hid_t h4toh5id, const char *h5groupname, const fieldinfo_t *field, int32 vdataid, int isgrid, hid_t *h5dataset, hobj_ref_t *h5datasetref)
{
    hid_t group = -1;
    hid_t spaceid = -1;
    hid_t dataset = -1;
    hid_t plistid = -1;
    hid_t tid = -1;
    hsize_talloc dimsizes;
    char *cvname = NULL;
    int succeeded = 0;

    hsize_talloc_init(&dimsizes);
    do {
        hid_t h5memtype, h5datatype, memtype, datatype;
        size_t h4memsize, h4size;
        int32 vdataref, numattrs, numfields;
        int groupflag = -1, handled, i, failinside = 0;
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);

        if (h4type_to_h5type(h4toh5id, field->type, &h5memtype, &h4memsize, &h4size, &h5datatype) == -1) { H4TOH5ERROR_SET0(3); break; }

        for (i = 0; i < field->dims.len; ++i) {
            if (!hsize_talloc_readyplus(&dimsizes, 1)) { H4TOH5ERROR_NOMEM1("dimsizes"); failinside = 1; break; }
            dimsizes.s[i] = field->dims.s[i].dimsize;
            dimsizes.len++;
        }
        if (failinside) break;

        if ((vdataref = VSQueryref(vdataid)) == FAIL) { H4TOH5ERROR_SET2(3, "obtain vdata ref", field->name.s); break; }
        if ((handled = lookup(h4toh5id, vdataref, VD_HASHSIZE, dt->vd_hashtab, &groupflag)) == -1) { H4TOH5ERROR_SET2(3, "lookup fail", field->name.s); break; }
        if ((cvname = get_h5datasetname(h4toh5id, h5groupname, field->name.s, vdataref, HDF4_VDATA, OBJECT_HASHSIZE, &groupflag)) == NULL) { H4TOH5ERROR_SET2(3, "dataset name", field->name.s); break; }
        if (lookup_name(h4toh5id, cvname, OBJECT_HASHSIZE, dt->name_hashtab) == 1) { H4TOH5ERROR_SET2(3, "object name has been used", cvname); break; }
        if (lookup_updatename(h4toh5id, cvname, OBJECT_HASHSIZE, dt->name_hashtab) == -1) { H4TOH5ERROR_SET2(3, "object name has been used", cvname); break; }
        if (!handled) {
            if (set_name(h4toh5id, vdataref, VD_HASHSIZE, dt->vd_hashtab, cvname, -1) == FAIL) { H4TOH5ERROR_SET2(3, "cannot set object name", cvname); break; }
        }
        else {
            H4TOH5ERROR_SET2(3, "vdata object is revisited", field->name.s); break;
        }

        if ((group = get_h5groupid(h5groupname, h4toh5id,0)) == FAIL) { H4TOH5ERROR_SET0(3); break; }


        if ((plistid = H5Pcreate(H5P_DATASET_CREATE)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Pcreate"); break; }
        if (h5datatype == H5T_STRING) {
            if (dimsizes.len != 1) { H4TOH5ERROR_SET2(3, "HDF-EOS2 stores multi-dimensional data as HDF4 vdata", field->name.s); break; }

            if ((spaceid = H5Screate_simple(1, dimsizes.s, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "create space"); break; }
            if ((tid = H5Tcopy(H5T_C_S1)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tcopy"); break; }
            if (field->filler.len) {
                if (H5Pset_fill_value(plistid, tid, field->filler.s) == FAIL) { H4TOH5ERROR_SET1(3, "H5Pset_fill_value"); break; }
            }
            datatype = tid;
            memtype = tid;
        }
        else {
            if (field->filler.len) {
                if (H5Pset_fill_value(plistid, h5memtype, field->filler.s) == FAIL) { H4TOH5ERROR_SET1(3, "H5Pset_fill_value"); break; }
            }
            if ((spaceid = H5Screate_simple(dimsizes.len, dimsizes.s, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple failure"); break; }
            datatype = h5datatype;
            memtype = h5memtype;
        }
        if ((dataset = H5DCREATE(group, cvname, datatype, spaceid, plistid)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Dcreate failure"); break; }
#ifdef EAGER_DATA_FETCH
        if (H5Dwrite(dataset, memtype, spaceid, spaceid, H5P_DEFAULT, field->data.s) == FAIL) { H4TOH5ERROR_SET1(3, "H5Dcreate failure"); break; }
#else
        {
            fieldinfo_t *mutablefield = (fieldinfo_t *)field;
            if (prepare_fieldinfo_lazydata(h4toh5id, isgrid, mutablefield) == -1) { H4TOH5ERROR_SET2(3, "read data", field->name.s); break; }
            if (H5Dwrite(dataset, memtype, spaceid, spaceid, H5P_DEFAULT, field->lazydata->s) == FAIL) { H4TOH5ERROR_SET1(3, "H5Dcreate failure"); break; }
            if (drop_fieldinfo_lazydata(h4toh5id, mutablefield) == -1) { H4TOH5ERROR_SET2(3, "drop data", field->name.s); break; }
        }
#endif

        if ((numattrs = VSfnattrs(vdataid, _HDF_VDATA)) == FAIL) { H4TOH5ERROR_SET1(3, "VSfnattrs fail"); break; }
        if (vdata_transattrs(h4toh5id, vdataid, dataset, numattrs, -1, NULL) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if ((numfields = VFnfields(vdataid)) == FAIL) { H4TOH5ERROR_SET1(3, "VFnfields fail"); break; }
        for (i = 0; i < numfields; ++i) {
            char *fieldname;
            if ((fieldname = VFfieldname(vdataid, i)) == NULL) { H4TOH5ERROR_SET1(3, "VFfieldname fail"); failinside = 1; break; }
            if ((numattrs = VSfnattrs(vdataid, i)) == FAIL) { H4TOH5ERROR_SET1(3, "VSfnattrs fail"); failinside = 1; break; }
            if (vdata_transattrs(h4toh5id, vdataid, dataset, numattrs, i, fieldname) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
        }
        if (failinside) break;

        if (write_attribute_scalar(h4toh5id, dataset, HDF4_OBJECT_TYPE, VDATALABEL) == -1) { H4TOH5ERROR_SET0(3); break; }
        if (write_attribute_scalar(h4toh5id, dataset, HDF4_OBJECT_NAME, field->name.s) == -1) { H4TOH5ERROR_SET0(3); break; }
        if (field->filler.len) {
            if (write_fillvalue_attribute(h4toh5id, dataset, h5datatype, h5memtype, field->type, &field->filler) == -1) { H4TOH5ERROR_SET0(3); break; }
        }

        if (H5Rcreate(h5datasetref, group, cvname, H5R_OBJECT, -1) == -1) { H4TOH5ERROR_SET1(3, "H5Rcreate fail"); break; }

        succeeded = 1;
    } while (0);

    if (succeeded) {
        *h5dataset = dataset;
    }
    else {
        if (dataset != -1) H5Dclose(dataset);
    }
    if (1) {
        if (cvname) free(cvname);
        hsize_talloc_free(&dimsizes);
        if (tid != -1) H5Tclose(tid);
        if (plistid != -1) H5Pclose(plistid);
        if (spaceid != -1) H5Sclose(spaceid);
        if (group != -1) H5Gclose(group);
    }
    return succeeded ? SUCCEED : FAIL;
}

static int augment_sds(hid_t h4toh5id, int32 sdsid, const char *h5groupname, const fieldinfo_t *field, hid_t h5dataset)
{
    int succeeded = 0;

    do {
        if (field->filler.len) {
            /* The given HDF-EOS2 file can have _FillValue attribute. I guess MERGE
             * option will change its behavior. If _FillValue is already annotated,
             * this routine should not do anything ; otherwise, sds_transattrs() will
             * complain.
             */
            int found = 0, i, failinside = 0;
            int32 attrnum = 0;

            {
    char sdsn[H4H5_MAX_NC_NAME + 1];
    int32 rank, dimsizes[H4H5_MAX_VAR_DIMS], type;

    if (SDgetinfo(sdsid, sdsn, &rank, dimsizes, &type, &attrnum) == FAIL) { H4TOH5ERROR_SET1(3, "SDgetinfo failure"); break; }
            }

            for (i = 0; i < attrnum; ++i) {
                char attrname[H4_MAX_NC_NAME + 1];
                int32 attrtype, attrcount;
                if (SDattrinfo(sdsid, i, attrname, &attrtype, &attrcount) == FAIL) { H4TOH5ERROR_SET1(3, "SDattrinfo"); failinside = 1; break; }
                if (strcmp(attrname, "_FillValue") == 0) {
                    found = 1;
                    break;
                }
            }
            if (failinside) break;

            if (!found) {
                hid_t h5memtype, h5datatype;
                size_t h4memsize, h4size;

                if (h4type_to_h5type(h4toh5id, field->type, &h5memtype, &h4memsize, &h4size, &h5datatype) == -1) { H4TOH5ERROR_SET0(3); break; }
                /* TODO: H5Pset_fill_value() should be called somehow, but H5Dcreate() was already called by
                 * the general routine before EOS2 handling function is invoked.
                 */
                if (write_fillvalue_attribute(h4toh5id, h5dataset, h5datatype, h5memtype, field->type, &field->filler) == -1) { H4TOH5ERROR_SET0(3); break; }
            }
        }

        succeeded = 1;
    } while (0);

    if (1) {
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief structure for write_dimscale()
 * All dimension-related functions will call write_dimscale() function which accepts
 * this structure. An HDF5 dataset can be either a netCDF4 pure dimension or a coordinate
 * variable according to the variable field and the dimension field.
 *
 * -------------------------------
 *  variable | dimension |
 * -------------------------------
 *          F        |       T       | case 1
 *          T        |       F       | case 2
 *          T        |       T       | case 3
 * -------------------------------
 *
 * "variable" field tells if the created HDF5 dataset will have data
 * "dimension" field tells if the created HDF5 dataset will be an HDF5 dimensional scale
 *
 * case 1: pure dimension
 *   The created HDF5 dataset will be an HDF5 dimensional scale which will have
 *   CLASS = "DIMENSION_SCALE" attribute. For netCDF4, an attribute NAME = "This is a netCDF dimension but not a netCDF variable."
 *   will be added, too. The HDF5 dataset will also have "REFERENCE_LIST" attribute.
 *   For example, "XDim" and "YDim" from HDF-EOS2 Grid data can be this case if they
 *   are not orthogonal.
 *
 * case 2: variable
 *   The created HDF5 dataset will not be an HDF5 dimensional scale. It will have actual values.
 *   Two dimensional array "lon" and "lat" generated by h4toh5 will be this case because netCDF4
 *   does not support 2d dimension.
 *
 * case 3: dimensional scale
 *   Like case 1, the created HDF5 dataset will be an HDF5 dimensional scale which whill
 *   have CLASS attribute. However, the created dataset will also have valid data which
 *   needs to suppress NAME = "This is a netCDF dimension but not a netCDF variable."
 *   If "XDim" and "YDim" are orthogonal in HDF-EOS2 Grid data, both "XDim" and "YDim"
 *   will be dimensional scale. Each of them needs to be a netCDF4 dimension and have
 *   actual values representing Longitude or Latitude.
 *
 * @see write_dimscale
 */
typedef struct {
    const char *name;
    const char *longname;
    const char *unit;
    const char *axis;
    int variable;
    int dimension;
} coordinate_t;

/** 
 * @brief returns predefined variable information for two-dimensional variables
 * This function will return either "lon" or "lat" which will be a two-dimensional
 * variable and used in create_2d_fakedimscale(). This will be case 2 in coordinate_t
 * description.
 * @see create_2d_fakedimscale
 * @see coordinate_t
 * 
 * @param grid HDF-EOS2 Grid data
 * @param x non-zero if this is about Longitude
 * 
 * @return predefined coordinate_t
 */
static const coordinate_t * get_fake_coordinate(const grid_t *grid, int x)
{
    static const coordinate_t lon = { "lon", "longitude", "degrees_east", NULL, 1, 0 };
    static const coordinate_t lat = { "lat", "latitude", "degrees_north", NULL, 1, 0 };

    const coordinate_t *ret = x ? &lon : &lat;
    H4TOH5ASSERT(ret)
    /* Section 4.1 of CF convention states the following: */
    /* Variables representing latitude (or longitude) must always explicitly include */
    /* the "units" attribute */
    H4TOH5ASSERT(ret->unit)
    return ret;
}

/** 
 * @brief returns predefined variable information for one-dimensional dimensional scale or dimension
 * This function will return "lon", "lat", "XDim" or "YDim" according to the orthogonal argument
 * and the x argument. If orthogonal, one-dimensional coordinate variable "lon" or "lat" will
 * be perfect. This is case 3 in coordinate_t description.
 *
 * Otherwise, netCDF4 does not support directly; so, pure dimension "XDim" or "YDim"
 * should be created, which was explained in the case 1 of coordinate_t document.
 * @see coordinate_t
 * 
 * @param grid HDF-EOS2 Grid data
 * @param orthogonal non-zero if XDim and YDim are orthogonal
 * @param x non-zero if this is about Longitude or XDim
 * 
 * @return predefined coordinate_t
 */
static const coordinate_t * get_coordinate(int orthogonal, int x)
{
    static const coordinate_t lon = { "lon", "longitude", "degrees_east", NULL, 1, 1 };
    static const coordinate_t lat = { "lat", "latitude", "degrees_north", NULL, 1, 1 };
    static const coordinate_t xdim = { "XDim", "x-coordinate in Cartesian system", NULL, "X", 0, 1 };
    static const coordinate_t ydim = { "YDim", "y-coordinate in Cartesian system", NULL, "Y", 0, 1 };

    const coordinate_t *ret = NULL;
    /* If the calculated lon and lat are orthogonal, it means lon and lat are independent. */
    /* For this case, XDim and YDim are actual dimensional scales; otherwise, they are just */
    /* like link between variables and 2-dimensional lon and lat. */
    if (orthogonal)
        ret = x ? &lon : &lat;
    else
        ret = x ? &xdim : &ydim;
    H4TOH5ASSERT(ret)
    return ret;
}

/** 
 * @brief fill coordinate_t structure for a pure dimension
 * This function will fill coord argument with the given dimension name, and
 * set flags to make write_dimscale create a pure dimension.
 * @see coordinate_t
 * @see write_dimscale
 * 
 * @param dimname dimension name
 * @param[out] coord coordinate_t specifying a pure dimension
 */
static void get_coordinate_from_dimension(const char *dimname, coordinate_t *coord)
{
    /* write_dimscale() can be used for both coordinate variables and normal dimensions. */
    /* This function wraps dimension name with coordinate_t so that write_dimscale() */
    /* can accept this dimension. */
    coord->name = dimname;
    coord->longname = NULL;
    coord->unit = NULL;
    coord->axis = NULL;
    coord->variable = 0;
    coord->dimension = 1;
}

static void get_coordinate_from_dimvalue(const grid_aux_t *aux, const char *dimname, coordinate_t *coord)
{
    if (strcmp(dimname, aux->xdimname.s) == 0)
        *coord = *get_coordinate(1, 1);
    else if (strcmp(dimname, aux->ydimname.s) == 0)
        *coord = *get_coordinate(1, 0);
    else {
        coord->name = dimname;
        coord->longname = NULL;
        coord->unit = NULL;
        coord->axis = NULL;
    }
    coord->variable = 1;
    coord->dimension = 1;
}

/** 
 * @brief fill coordinate_t structure for a variable
 * This function will fill coord argument with the given variable name, and
 * set flags to make write_dimscale create a variable.
 * @see coordinate_t
 * @see write_dimscale
 * 
 * @param varname variable name
 * @param[out] coord coordinate_t specifying a variable
 */
static void get_coordinate_from_variable(const char *varname, coordinate_t *coord)
{
    coord->name = varname;
    coord->longname = NULL;
    coord->unit = NULL;
    coord->axis = NULL;
    coord->variable = 1;
    coord->dimension = 0;
}

/** 
 * @brief creates attributes in an HDF5 dataset
 * @see coordinate_t
 * @see write_dimscale
 * 
 * @param h4toh5id h4toh5 identifier
 * @param dataset the target HDF5 dataset which can be either a normal HDF5 dataset or an HDF5 dimensional scale
 * @param coord flags to tell options
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_coordinate_attributes(hid_t h4toh5id, hid_t dataset, const coordinate_t *coord)
{
    int succeeded = 0;

    do {
        if (coord->dimension) {
            /* "lon" and "lat" for 2d dimscale are not actual dimensions */
            if (write_attribute_scalar(h4toh5id, dataset, HDF5_DIMENSION_SCALE_CLASS, "DIMENSION_SCALE") == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }
        if (!coord->variable) {
            /* "XDim" and "YDim" for 2d dimscale are not variables */
            if (write_attribute_scalar(h4toh5id, dataset, HDF5_DIMENSION_SCALE_NAME, "This is a netCDF dimension but not a netCDF variable.") == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }

        if (coord->longname) {
            if (write_attribute_scalar(h4toh5id, dataset, "long_name", coord->longname) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }
        if (coord->unit) {
            if (write_attribute_scalar(h4toh5id, dataset, "units", coord->unit) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }
        if (coord->axis) {
            if (write_attribute_scalar(h4toh5id, dataset, "axis", coord->axis) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }

        succeeded = 1;
    } while (0);
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief update string that will be used for "coordinates" attribute
 * The "coordinates" attribute will be used for data fields that refers to
 * two-dimensional dimensional scales such as "lon" and "lat". This function
 * will just append the name of dimensional scales.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param[in,out] attrvalue value of "coordinates" attribute
 * @param coord coordinate information
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int accumulate_grid_coordinates(hid_t h4toh5id, challoc *attrvalue, const coordinate_t *coord)
{
    char *nc4safename = NULL;
    int succeeded = 0;

    do {
        if ((nc4safename = correct_name(h4toh5id, coord->name)) == NULL) { H4TOH5ERROR_NOMEM1("coordinate name"); break; }

        if (attrvalue->len) {
            if (!challoc_append(attrvalue, " ")) { H4TOH5ERROR_SET1(3, "coord attr"); break; }
        }
        if (!challoc_cats(attrvalue, nc4safename)) { H4TOH5ERROR_SET1(3, "coord attr"); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        if (nc4safename) free(nc4safename);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief update string that will be used for "coordinates" attribute
 * The "coordinates" attribute will be used for two-dimensional dimensional scales
 * from HDF-EOS2 Swath data. This function is similar to accumulate_grid_coordinates,
 * but this function tries to keep Longitude -> Latitude order.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param[in,out] attrvalue value of "coordinates" attribute
 * @param coord coordinate information
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int accumulate_swath_coordinates(hid_t h4toh5id, challoc *attrvalue, const coordinate_t *coord)
{
    challoc tmp;
    char *nc4safename = NULL;
    int succeeded = 0;

    challoc_init(&tmp);
    do {
        int longitude = 0, latitude = 0;

        if ((nc4safename = correct_name(h4toh5id, coord->name)) == NULL) { H4TOH5ERROR_NOMEM1("coordinate name"); break; }

        /* Since Latitude and Longitude are geo fields and they may be stored in arbitrary order, */
        /* the calling sequence of this function does not guarantee Longitude / Latitude order */
        /* For this reason, this function should be smarter than accumulate_grid_coordinates() */
        longitude = strstr(coord->name, "Longitude") == coord->name;
        latitude = strstr(coord->name, "Latitude") == coord->name;
        if (longitude) {
            if (!challoc_copy(&tmp, attrvalue)) { H4TOH5ERROR_NOMEM1("coord attr"); break; }
            if (!challoc_copys(attrvalue, nc4safename)) { H4TOH5ERROR_NOMEM1("coord attr"); break; }
            if (tmp.len) {
                if (!challoc_append(attrvalue, " ")) { H4TOH5ERROR_NOMEM1("coord attr"); break; }
                if (!challoc_cat(attrvalue, &tmp)) { H4TOH5ERROR_NOMEM1("coord attr"); break; }
            }
        }
        else if (latitude) {
            if (attrvalue->len) {
                if (!challoc_append(attrvalue, " ")) { H4TOH5ERROR_NOMEM1("coord attr"); break; }
            }
            if (!challoc_cats(attrvalue, nc4safename)) { H4TOH5ERROR_NOMEM1("coord attr"); break; }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (nc4safename) free(nc4safename);
        challoc_free(&tmp);
    }
    return succeeded ? SUCCEED : FAIL;
}

/**
 * @brief generate fully-qualified coordinate name from HDF5 group name and coordinate name
 * One assumption of h4toh5 is that a dimensions is unique in one file.
 * This assumption is not always true in HDF-EOS2 files. Also, netCDF4 does not
 * have any problem in having two different dimensions which share the same name
 * To support this feature with smallest changes, the key for moddim_hashtab is
 * not coordinate name, but fully-qualified coordinate name:
 *   grid_name (or swath_name) : coordinate name.
 *
 * It turns out that this was not enough for non-HDF-EOS2 objects. Some
 * HDF-EOS2 files have non-HDF-EOS2 objects. To deal with the problem with
 * small changes, this function works differently if the given coordinate is
 * not originated from HDF-EOS2 objects.
 *   groupname : coordinate name.
 * All slashes in groupname are replaced by colons, but this doesn't make any
 * problem because extract_pure_coordpath() will search ':' from the end of the
 * string.
 *
 * @param h4toh5id h4toh5 identifier
 * @param iseos 1 if this is for EOS2 data
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param coordname coordinate name
 * @param[out] fqcoordname fully-qualified coordinate name
 */
static int make_fq_coordname(hid_t h4toh5id, const char *h5groupname, int iseos, const char *coordname, challoc *fqcoordname)
{
    challoc eosgroupname;
    int succeeded = 0;

    challoc_init(&eosgroupname);
    do {
        if (iseos) {
            if (extract_eosgroup_safename(h4toh5id, h5groupname, &eosgroupname) == -1) { H4TOH5ERROR_SET0(3); break; }
            if (!challoc_copy(fqcoordname, &eosgroupname)) { H4TOH5ERROR_NOMEM1("fq coordname"); break; }
        }
        else {
            int i;
            if (!challoc_copys(fqcoordname, h5groupname)) { H4TOH5ERROR_NOMEM1("fq coordname"); break; }
            for (i = 0; i < fqcoordname->len; ++i) {
                if (fqcoordname->s[i] == '/')
                    fqcoordname->s[i] = ':';
            }
        }
        if (!challoc_append(fqcoordname, ":")) { H4TOH5ERROR_NOMEM1("fq coordname"); break; }
        if (!challoc_cats(fqcoordname, coordname)) { H4TOH5ERROR_NOMEM1("fq coordname"); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&eosgroupname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/**
 * @brief extract the pure coordinate name with path from the fully-qualified coordinate name
 * This is the inverse of make_fq_coordname. When the fqcoordname is
 *  group1/group2/.../groupn/junk1:junk2:...:junkn:dimname
 * , this function copies into purecoordpath
 *  group1/group2/.../groupn/dimname.
 * Previously, 'iseos' flag made difference, but it doesn't anymore because only one colon is
 * possible.
 * @see make_fq_coordname
 *
 * @param h4toh5id h4toh5 identifier
 * @param fqcoordname fully-qualified coordinate name
 * @param[out] purecoordpath pure coordinate name with path
 */
static int extract_pure_coordpath(hid_t h4toh5id, const char *fqcoordname, int iseos, challoc *purecoordpath)
{
    int succeeded = 0;

    do {
        int len, i, j;
        
        len = strlen(fqcoordname);
        for (i = len - 1; i >= 0; --i) {
            if (fqcoordname[i] == '/')
                break;
        }
        ++i;
        if (!challoc_copyb(purecoordpath, fqcoordname, i)) { H4TOH5ERROR_NOMEM1("path"); break; }

        if (iseos) {
            for ( ; i < len; ++i) {
                if (fqcoordname[i] == ':')
                    break;
            }
            if (i == len) break;
            if (!challoc_cats(purecoordpath, fqcoordname + i + 1)) { H4TOH5ERROR_NOMEM1("pure coordname"); break; }
        }
        else {
            for (j = len - 1; j > i; --j) {
                if (fqcoordname[j] == ':')
                    break;
            }
            if (!challoc_cats(purecoordpath, fqcoordname + j + 1)) { H4TOH5ERROR_NOMEM1("pure coordname"); break; }
        }

        succeeded = 1;
    } while (0);
    return succeeded ? SUCCEED : FAIL;
}

static int create_dimension_link(hid_t h4toh5id, hid_t locid, const challoc *linked, const challoc *newname)
{
    int succeeded = 0;

    do {
        if (H5Glink(locid, H5G_LINK_HARD, linked->s, newname->s) < 0) { H4TOH5ERROR_SET3(3, "hardlink", linked->s, newname->s); break; }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

static int set_chunksize(hid_t h4toh5id, hid_t plist, const hsize_talloc *maxdimsizes)
{
    hsize_talloc chunksizes;
    int succeeded = 0;

    hsize_talloc_init(&chunksizes);
    do {
        hsize_t remains = H4H5_DEFAULT_NUMELEM_IN_CHUNK;
        int i, failinside = 0;

        for (i = 0; i < maxdimsizes->len; ++i) {
            if (!hsize_talloc_readyplus(&chunksizes, 1)) { H4TOH5ERROR_NOMEM1("chunksizes"); failinside = 1; break; }
            chunksizes.s[i] = maxdimsizes->s[i];
            chunksizes.len++;

            if (maxdimsizes->s[i] != H5S_UNLIMITED)
                remains /= maxdimsizes->s[i];
        }
        if (failinside) break;

        for (i = chunksizes.len - 1; i >= 0; --i) {
            if (chunksizes.s[i] == H5S_UNLIMITED) {
                chunksizes.s[i] = remains ? remains : 1;
                remains = 1;
            }
        }

        if (H5Pset_chunk(plist, chunksizes.len, chunksizes.s) < 0) { H4TOH5ERROR_SET1(3, "chunk size"); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        hsize_talloc_free(&chunksizes);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief creates an HDF5 dataset that will be a netCDF4 dimension or variable
 * This function is most general and the only routine that actually create an
 * HDF5 dataset and all other dimension-related functions rely on. According to
 * the coord argument, it can create either a netCDF4 dimension or a netCDF4 variable.
 *
 * A netCDF4 dimension can be a netCDF4 variable, which is called a coordinate
 * variable. I'll call a netCDF4 dimension that is not a netCDF4 variable as
 * pure dimension. The coordinate_t structure passed as coord argument will tell
 * if the HDF5 dataset this function will create is a coordinate variable or
 * a pure dimension. Either case, the HDF5 dataset this function will create is
 * a complete HDF5 dataset.
 *
 * Since this function is generic for all other cases, this function create
 * either a general HDF5 dataset or an HDF5 dimensional scale according to the
 * coord argument.
 * @see coordinate_t
 * 
 * @param h4toh5id h4toh5 identifier
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param coord coordinate information to specify name
 * @param dims list of dimensions that the created dimensional scale will refer to
 * @param datatype datatype for the HDF5 dataset that this function will create
 * @param memtype datatype of data argument that the created HDF5 dataset will contain
 * @param iseos 1 if this is called for HDF-EOS2 Grid or Swath dimension
 * @param data actual data written to the created HDF5 dataset
 * @param[out] created 1 if this function actually created the HDF5 dataset; 0 if this function just created a link to the existing one
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, const dimensionalloc *dims, hid_t datatype, hid_t memtype, int iseos, const void *data, int *created)
{
    hid_t h5spaceid = -1, h5createplist = -1, h5datasetid = -1;
    char *corrected = NULL, *dimpath = NULL, *dimorigname = NULL;
    challoc fqcoordname, purecoordpath, purelinkedpath;
    hsize_talloc dimsizes;
    hsize_talloc maxdimsizes;
    int succeeded = 0;

    if (created) *created = 0;
    challoc_init(&fqcoordname);
    challoc_init(&purecoordpath);
    challoc_init(&purelinkedpath);
    hsize_talloc_init(&dimsizes);
    hsize_talloc_init(&maxdimsizes);
    do {
        int handled, shared;
        hid_t h5swathid;
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);

        if ((h5swathid = get_h5groupid(groupname, h4toh5id,0)) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        /* Use fully-qualified coordinate name as the key of moddim_hashtab and name_hashtab.
         * However, this should not change the real name for HDF5 APIs. Before calling HDF5 APIs,
         * extract_pure_coordpath() should be called.
         */
        if ((corrected = correct_name(h4toh5id, coord->name)) == NULL) { H4TOH5ERROR_SET0(3); break; }
        if (make_fq_coordname(h4toh5id, groupname, iseos, corrected, &fqcoordname) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if ((dimpath = make_objname_yes(h4toh5id, fqcoordname.s, groupname)) == NULL) { H4TOH5ERROR_SET0(3); break; }
        if ((handled = modlookup_updatename(h4toh5id, fqcoordname.s, dimpath, DIM_HASHSIZE, dt->moddim_hashtab)) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if ((shared = lookup_updatename(h4toh5id, dimpath, OBJECT_HASHSIZE, dt->name_hashtab)) == FAIL) { H4TOH5ERROR_SET0(3); break; }

        /* The fully-qualified name should not be used for H5Glink() and H5Dcreate(). */
        if (extract_pure_coordpath(h4toh5id, dimpath, iseos, &purecoordpath) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (handled) {
            if (shared) {
            }
            else {
                dimorigname = get_modname2(h4toh5id, fqcoordname.s, DIM_HASHSIZE, dt->moddim_hashtab);
                if (extract_pure_coordpath(h4toh5id, dimorigname, iseos, &purelinkedpath) == FAIL) { H4TOH5ERROR_SET0(3); break; }
                if (create_dimension_link(h4toh5id, h5swathid, &purelinkedpath, &purecoordpath) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            }
        }
        else {
            int containunlimited = 0;
            int i, failinside = 0;
            for (i = 0; i < dims->len; ++i) {
                if (!hsize_talloc_readyplus(&dimsizes, 1)) { H4TOH5ERROR_NOMEM1("dimsizes"); failinside = 1; break; }
                dimsizes.s[i] = dims->s[i].dimsize;
                dimsizes.len++;
                if (!hsize_talloc_readyplus(&maxdimsizes, 1)) { H4TOH5ERROR_NOMEM1("maxdimsizes"); failinside = 1; break; }
                maxdimsizes.s[i] = dims->s[i].dimsize;
                if (maxdimsizes.s[i] == SD_UNLIMITED) {
                    maxdimsizes.s[i] = H5S_UNLIMITED;
                    containunlimited = 1;
                }
                maxdimsizes.len++;
            }
            if (failinside) break;

            if ((h5createplist = H5Pcreate(H5P_DATASET_CREATE)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Pcreate failure"); break; }
            if (containunlimited) {
                if (set_chunksize(h4toh5id, h5createplist, &maxdimsizes) == FAIL) { H4TOH5ERROR_SET0(3); break; }
                if ((h5spaceid = H5Screate_simple(dimsizes.len, dimsizes.s, maxdimsizes.s)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple failure"); break; }
            }
            else {
                if ((h5spaceid = H5Screate_simple(dimsizes.len, dimsizes.s, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple failure"); break; }
            }
            if ((h5datasetid = H5DCREATE(h5swathid, purecoordpath.s, datatype, h5spaceid, h5createplist)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Dcreate failure"); break; }
            if (data && coord->variable) {
                if (H5Dwrite(h5datasetid, memtype, h5spaceid, h5spaceid, H5P_DEFAULT, data) == FAIL) { H4TOH5ERROR_SET1(3, "H5Dwrite failure"); break; }
            }
            if (write_coordinate_attributes(h4toh5id, h5datasetid, coord) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (created) *created = 1;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&fqcoordname);
        challoc_free(&purecoordpath);
        challoc_free(&purelinkedpath);
        hsize_talloc_free(&dimsizes);
        hsize_talloc_free(&maxdimsizes);
        if (h5spaceid != -1) H5Sclose(h5spaceid);
        if (h5createplist != -1) H5Pclose(h5createplist);
        if (h5datasetid != -1) H5Dclose(h5datasetid);
        if (corrected != NULL) free(corrected);
        if (dimpath != NULL) free(dimpath);
        if (dimorigname != NULL) free(dimorigname);
    }
    return succeeded ? SUCCEED : FAIL;
}

static int write_eos_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, const dimensionalloc *dims, hid_t datatype, hid_t memtype, const void *data, int *created)
{
    return write_dimscale(h4toh5id, groupname, coord, dims, datatype, memtype, 1, data, created);
}

/** 
 * @brief check if the dimensional scale has been handled already
 * This function should not have any side-effects.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param coord coordinate information to specify name
 * @param[out] handled 1 if the specified dimensional scale is already handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int probe_eos_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, int *handled)
{
    char *corrected = NULL, *dimorigname = NULL;
    challoc fqcoordname;
    int succeeded = 0;

    challoc_init(&fqcoordname);
    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);

        /* Use fully-qualified coordinate name as the key of moddim_hashtab. */
        if ((corrected = correct_name(h4toh5id, coord->name)) == NULL) { H4TOH5ERROR_SET0(3); break; }
        if (make_fq_coordname(h4toh5id, groupname, 1, corrected, &fqcoordname) == FAIL) { H4TOH5ERROR_SET0(3); break; }

        dimorigname = get_modname2(h4toh5id, fqcoordname.s, DIM_HASHSIZE, dt->moddim_hashtab);
        *handled = dimorigname ? 1 : 0;

        succeeded = 1;
    } while (0);

    if (1) {
        if (corrected != NULL) free(corrected);
        if (dimorigname != NULL) free(dimorigname);
        challoc_free(&fqcoordname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief check if the dimensional scale has been handled already and get the reference
 * This function should not have any side-effects.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param iseos 1 if this is for EOS2 data
 * @param name dimension name without path or qualification
 * @param[out] refer created reference
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_ref_dimscale(hid_t h4toh5id, const char *groupname, int iseos, const char *name, hobj_ref_t *refer)
{
    char *corrected = NULL, *dimorigname = NULL;
    challoc fqcoordname;
    challoc purepath;
    hid_t group = -1;
    int succeeded = 0;

    challoc_init(&fqcoordname);
    challoc_init(&purepath);
    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);

        if ((group = get_h5groupid(groupname, h4toh5id,0)) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        /* Use fully-qualified coordinate name as the key of moddim_hashtab. */
        if ((corrected = correct_name(h4toh5id, name)) == NULL) { H4TOH5ERROR_SET0(3); break; }
        if (make_fq_coordname(h4toh5id, groupname, iseos, corrected, &fqcoordname) == FAIL) { H4TOH5ERROR_SET0(3); break; }

        dimorigname = get_modname2(h4toh5id, fqcoordname.s, DIM_HASHSIZE, dt->moddim_hashtab);
        if (!dimorigname) { H4TOH5ERROR_SET2(3, "cannot find dimension", fqcoordname.s); break; }

        if (extract_pure_coordpath(h4toh5id, dimorigname, iseos, &purepath) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (H5Rcreate(refer, group, purepath.s, H5R_OBJECT, -1) == -1) { H4TOH5ERROR_SET2(3, "H5Rcreate fail", purepath.s); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        if (group != -1) H5Gclose(group);
        if (corrected != NULL) free(corrected);
        if (dimorigname != NULL) free(dimorigname);
        challoc_free(&purepath);
        challoc_free(&fqcoordname);
    }
    return succeeded ? SUCCEED : FAIL;
}

static int get_ref_eos_dimscale(hid_t h4toh5id, const char *groupname, const char *name, hobj_ref_t *refer)
{
    return get_ref_dimscale(h4toh5id, groupname, 1, name, refer);
}

static int get_ref_noneos_dimscale(hid_t h4toh5id, const char *groupname, const char *name, hobj_ref_t *refer)
{
    return get_ref_dimscale(h4toh5id, groupname, 0, name, refer);
}

/** 
 * @brief a wrapper of write_dimscale() for one-dimensional case
 *
 * @param h4toh5id h4toh5 identifier
 * @param groupname HDF5 group name where the converted HDF5 dataset resides
 * @param coord coordinate information
 * @param dimsize the number of elements
 * @param datatype datatype for the HDF5 dataset that this function will create
 * @param memtype datatype of data argument that the created HDF5 dataset will contain
 * @param data actual data written to the created HDF5 dataset
 * @param[out] created 1 if this function actually created the HDF5 dataset; 0 if this function just created a link to the existing one
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_1d_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, int32 dimsize, hid_t datatype, hid_t memtype, int iseos, const void *data, int *created)
{
    dimension_t *d;
    dimensionalloc dims;
    int r = FAIL;
    int succeeded = 0;

    dimensionalloc_init(&dims);
    do {
        if (!dimensionalloc_ready(&dims, 1)) { H4TOH5ERROR_NOMEM1("dimension"); break; }
        d = &dims.s[dims.len++];
        init_dimension_t(d);

        d->dimsize = dimsize;
        r = write_dimscale(h4toh5id, groupname, coord, &dims, datatype, memtype, iseos, data, created);

        succeeded = 1;
    } while (0);

    if (1) {
        dimensionalloc_free(&dims);
    }
    return succeeded ? r : FAIL;
}

static int write_1d_eos_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, int32 dimsize, hid_t datatype, hid_t memtype, const void *data, int *created)
{
    return write_1d_dimscale(h4toh5id, groupname, coord, dimsize, datatype, memtype, 1, data, created);
}

static int write_1d_noneos_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, int32 dimsize, hid_t datatype, hid_t memtype, const void *data, int *created)
{
    return write_1d_dimscale(h4toh5id, groupname, coord, dimsize, datatype, memtype, 0, data, created);
}

/*
 * simple wrappers to avoid warnings
 */

static int32 GDopen_const(const char *filename, intn access)
{
    return GDopen((char *)filename, access);
}

static int32 GDattach_const(int32 fid, const char *gridname)
{
    return GDattach(fid, (char *)gridname);
}

static int32 SWopen_const(const char *filename, intn access)
{
    return SWopen((char *)filename, access);
}

static int32 GDinqgrid_const(const char *filename, char *namelist, int32 *bufsize)
{
    return GDinqgrid((char *)filename, namelist, bufsize);
}

static int32 SWinqswath_const(const char *filename, char *namelist, int32 *bufsize)
{
    return SWinqswath((char *)filename, namelist, bufsize);
}

static int32 PTinqpoint_const(const char *filename, char *namelist, int32 *bufsize)
{
    return PTinqpoint((char *)filename, namelist, bufsize);
}


/*
 * handle GRID dimscale
 */

/** 
 * @brief create a pure netCDF4 dimension which is not a netCDF4 variable
 * This will create an HDF5 dataset which will be treated as a netCDF4 dimension, but
 * not a netCDF4 variable. By netCDF4 convention, the created dimension will not have
 * any data. Also, it will have the following attribute:
 *   NAME = "This is a netCDF dimension but not a netCDF variable."
 *
 * @param h4toh5id h4toh5 identifier
 * @param group HDF5 group name where the converted HDF5 dataset resides
 * @param coord coordinate information to specify name and attributes of the dimension
 * @param dimsize number of elements in this dimension
 * @param[out] created 1 if this function actually created the HDF5 dataset; 0 if this function just created a link to the existing one
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_1d_empty_eos_dimscale(hid_t h4toh5id, const char *groupname, const coordinate_t *coord, int32 dimsize, int *created)
{
    H4TOH5ASSERT(!coord->variable)
    H4TOH5ASSERT(coord->dimension)
    return write_1d_eos_dimscale(h4toh5id, groupname, coord, dimsize, H5T_IEEE_F64BE, H5T_NATIVE_DOUBLE, NULL, created);
}

/** 
 * @brief check if this Longitude and Latitude values are orthgonal
 * The results of GDij2ll() function are Longitude and Latitude which are two-dimensional.
 * This function will check both Longitude and Latitude if they have the same values for
 * each row or column. If the same values are used for the whole row or column, this coordinate
 * can be regarded as orthogonal, and one-dimensional Longitude and Latitude dimensional scale
 * will be enough.
 * 
 * @param grid HDF-EOS2 Grid data
 * @param lon Longitude values
 * @param lat Latitude values
 * 
 * @return 1 if orthogonal
 */
static int check_ortho_dimscale(const grid_t *grid, const grid_aux_t *aux, const float64alloc *lon, const float64alloc *lat)
{
    int i, j;

    if (aux->ydimmajor) {
        /* check if longitude is like the following: */
        /*   /- xdim - \ */
        /*   1 2 3 ... x */
        /*   1 2 3 ... x */
        /*   1 2 3 ... x */
        /*          ... */
        /*   1 2 3 ... x */
        for (i = 0; i < grid->info.xdim; ++i) {
            double v = lon->s[i];
            for (j = 1; j < grid->info.ydim; ++j) {
                if (lon->s[j * grid->info.xdim + i] != v)
                    break;
            }
            if (j != grid->info.ydim)
                return 0;
        }

        /* check if latitude is like the following: */
        /*   /- xdim -\ */
        /*   1 1 1 ... 1 */
        /*   2 2 2 ... 2 */
        /*   3 3 3 ... 3 */
        /*          ... */
        /*   y y y ... y */
        for (i = 0; i < grid->info.ydim; ++i) {
            double v = lat->s[i * grid->info.xdim];
            for (j = 1; j < grid->info.xdim; ++j) {
                if (lat->s[i * grid->info.xdim + j] != v)
                    break;
            }
            if (j != grid->info.xdim)
                return 0;
        }
    }
    else {
        /* check if longitude is like the following: */
        /*   /- ydim -\ */
        /*   1 1 1 ... 1 */
        /*   2 2 2 ... 2 */
        /*   3 3 3 ... 3 */
        /*          ... */
        /*   x x x ... x */
        for (i = 0; i < grid->info.xdim; ++i) {
            double v = lon->s[i * grid->info.ydim];
            for (j = 1; j < grid->info.ydim; ++j) {
                if (lon->s[i * grid->info.ydim + j] != v)
                    break;
            }
            if (j != grid->info.ydim)
                return 0;
        }

        /* check if latitude is like the following: */
        /*   /- ydim - \ */
        /*   1 2 3 ... y */
        /*   1 2 3 ... y */
        /*   1 2 3 ... y */
        /*          ... */
        /*   1 2 3 ... y */

        for (i = 0; i < grid->info.ydim; ++i) {
            double v = lat->s[i];
            for (j = 1; j < grid->info.xdim; ++j) {
                if (lat->s[j * grid->info.ydim + i] != v)
                    break;
            }
            if (j != grid->info.xdim)
                return 0;
        }
    }
    return 1;
}

/** 
 * @brief create a fake a 2d HDF5 dimensional scale for HDF-EOS2 Grid
 * HDF-EOS2 Grid data may have two dimensional Longitude and Latitude. For
 * example, if the projection code of a Grid data is GCTP_UTM, "Longitude" and
 * "Latitude" are two-dimensional. According to the CF convention,
 * two-dimensional HDF5 dataset such as "lon" and "lat" should be created. This
 * function will create either "lon" or "lat" which will be specified by coord.
 *
 * Since the created HDF5 dataset is not an HDF5 dimensional scale by the specification,
 * it needs to refer to dimensions such as "XDim" and "YDim". This function will
 * attach them to the created HDF5 dataset.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param group HDF5 group name where the converted HDF5 dataset resides
 * @param coord coordinate information 
 * @param ydimmajor 1 if the coordinate is like [YDim][XDim] in C convention
 * @param xdim XDim dimension referred to by the HDF5 dataset created in this function
 * @param ydim YDim dimension refereed to by the HDF5 dataset created in this function
 * @param data actual data hold in the created HDF5 dataset
 * @param[out] created 1 if this function actually created the HDF5 dataset; 0 if this function just created a link to the existing one
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int create_2d_fakedimscale(hid_t h4toh5id, const char *h5groupname, const coordinate_t *coord, int ydimmajor, const dimension_t *xdim, const dimension_t *ydim, const float64alloc *data, int *created)
{
    dimensionalloc dims;
    int succeeded = 0;

    dimensionalloc_init(&dims);
    do {
        int i, failinside = 0;
        if (!dimensionalloc_ready(&dims, 2)) { H4TOH5ERROR_NOMEM1("dimension"); break; }
        for (i = 0; i < 2; ++i) {
            const dimension_t *d = ydimmajor ? (i == 0 ? ydim : xdim) : (i == 0 ? xdim : ydim);
            init_dimension_t(&dims.s[i]);
            if (!challoc_copy(&dims.s[i].name, &d->name)) { H4TOH5ERROR_NOMEM1("dim name"); failinside = 1; break; }
            dims.s[i].dimsize = d->dimsize;
        }
        if (failinside) break;
        dims.len = 2;

        H4TOH5ASSERT(dims.s[0].dimsize * dims.s[1].dimsize == data->len)

        if (write_eos_dimscale(h4toh5id, h5groupname, coord, &dims, H5T_IEEE_F64BE, H5T_NATIVE_DOUBLE, data->s, created) == -1) { H4TOH5ERROR_SET0(3); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        dimensionalloc_free(&dims);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief creates the DIMENSION_LIST attribute in the HDF5 dataset
 * This function creates DIMENSION_LIST attribute in the HDF5 dataset specified in h5dataset.
 * This attribute contains the reference to HDF5 dimensional scale given byy dimscales for each dimension.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5dataset HDF5 dataset that will have dimensional scales
 * @param dimscales references to dimension scales that the given HDF5 dataset refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_dimensionlist_attr(hid_t h4toh5id, hid_t h5dataset, const hobjreftalloc *dimscales)
{
    hvl_t *dimrefs = NULL;
    int deepfreecount = 0;
    hid_t spaceid = FAIL;
    hid_t tid = FAIL;
    hid_t attrid = FAIL;
    int succeeded = 0;

    do {
        if (dimscales->len) {
            hssize_t numelems;
            int i, failinside = 0;
            hsize_t spacedimsizes = dimscales->len;

            H5E_BEGIN_TRY {
                attrid = H5Aopen_name(h5dataset, HDF5_DIMENSION_LIST);
            } H5E_END_TRY;
            if (attrid != FAIL) {

                /* This routine may be called more than once for fake dimscales such as lon and lat.
                 * Check if previously written data are same as new input
                */
               if ((spaceid = H5Aget_space(attrid)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Aget_space"); break; }
               if ((tid = H5Aget_type(attrid)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Aget_type"); break; }
               if ((numelems = H5Sget_simple_extent_npoints(spaceid)) == FAIL) { H4TOH5ERROR_SET1(3, "get extent_npoints"); break; }
               H4TOH5ASSERT(numelems == dimscales->len)
               if ((dimrefs = malloc(dimscales->len * sizeof(hvl_t))) == NULL) { H4TOH5ERROR_NOMEM1("dim ref vl type"); break; }
               if (H5Aread(attrid, tid, dimrefs) == -1) { H4TOH5ERROR_SET1(3, "read dimension_list"); break; }
               for (i = 0; i < dimscales->len; ++i) {
                   H4TOH5ASSERT(dimrefs[i].len == 1)
                   H4TOH5ASSERT(*(hobj_ref_t *)dimrefs[i].p == dimscales->s[i])
               }
            }
            else {
                if ((spaceid = H5Screate_simple(1, &spacedimsizes, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple"); break; }
                /* I think the specification differs from the implementation. Both netCDF4 and H5DS
                 * implementation uses VLEN of hobj_ref_t as datatype, and I follow this.
                */
                if ((tid = H5Tvlen_create(H5T_STD_REF_OBJ)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tvlen_create failure"); break; }

                if ((dimrefs = malloc(dimscales->len * sizeof(hvl_t))) == NULL) { H4TOH5ERROR_NOMEM1("dim ref vl type"); break; }
                for (i = 0; i < dimscales->len; ++i) {
                    if ((dimrefs[i].p = malloc(sizeof(hobj_ref_t))) == NULL) { H4TOH5ERROR_NOMEM1("dim ref vl[i].p"); failinside = 1; break; }
                    ++deepfreecount;
                    *((hobj_ref_t *)dimrefs[i].p) = dimscales->s[i];
                    dimrefs[i].len = 1;
                }
                if (failinside) break;

                if ((attrid = H5ACREATE(h5dataset, HDF5_DIMENSION_LIST, tid, spaceid, H5P_DEFAULT)) == FAIL) { H4TOH5ERROR_SET1(3, "dataset attribute"); break; }
                if (H5Awrite(attrid, tid, dimrefs) == FAIL) { H4TOH5ERROR_SET1(3, "write dataset attribute"); break; }
            }
        }
        succeeded = 1;
    } while (0);

    if (1) {
        if (spaceid != FAIL) H5Sclose(spaceid);
        if (tid != FAIL) H5Tclose(tid);
        if (attrid != FAIL) H5Aclose(attrid);
        if (dimrefs != NULL) {
            while (deepfreecount--)
                free(dimrefs[deepfreecount].p);
            free(dimrefs);
        }
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
* @brief corresponding type of "Dataset Reference Type"
*/
typedef struct {
    hobj_ref_t ref;
    int32 index;
} reference_backpointer_t;

/** 
 * @brief creates or updates the REFERENCE_LIST attribute in the HDF5 dimensional scales
 * This function creates or updates the REFERNCE_LIST which is part of HDF5 Dimensional
 * Scale Specification. Each element of REFERENCE_LIST contains the reference number of
 * referrer and index.
 * @see attach_dimscales
 * 
 * @param h4toh5id h4toh5 identifier
 * @param dsid HDF5 dimensional scale identifier referred by the HDF5 dataset
 * @param datasetref reference to HDF5 dataset that refers to dsid
 * @param dimindex which index of the HDF5 dataset refers to the HDF5 dimensional scales
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_referencelist_attr(hid_t h4toh5id, hid_t dsid, hobj_ref_t datasetref, int dimindex)
{
    hid_t spaceid = FAIL;
    hid_t oldspaceid = FAIL;
    hid_t tid = FAIL;
    hid_t attrid = FAIL;
    reference_backpointer_t *refs = NULL;
    int succeeded = 0;

    do {
        hssize_t numelems;
        hsize_t dimsize;

        /* If "REFERENCE_LIST" is already there, one element should be appended. It seems that appending
         * is not supported. To handle this, append in the memory first, and then remove and recreate the attribute
         */ 
        H5E_BEGIN_TRY {
            attrid = H5Aopen_name(dsid, HDF5_REFERENCE_LIST);
        } H5E_END_TRY;
        if (attrid != FAIL) {
            /* REFERENCE_LIST exists in ds dataset */
            if ((tid = H5Aget_type(attrid)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Aget_type"); break; }
            if ((oldspaceid = H5Aget_space(attrid)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Aget_space"); break; }
            if ((numelems = H5Sget_simple_extent_npoints(oldspaceid)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Sget_simple_extent_npoints"); break; }
            numelems++;
            if ((refs = malloc((int)numelems * sizeof(reference_backpointer_t))) == NULL) { H4TOH5ERROR_NOMEM1("reference_backpointer_t"); break; }
            if (H5Aread(attrid, tid, refs) == FAIL) { H4TOH5ERROR_SET1(3, "H5Aread"); break; }
            if (H5Sclose(oldspaceid) == FAIL) { H4TOH5ERROR_SET1(3, "H5Sclose"); break; }
            oldspaceid = FAIL;
            if (H5Aclose(attrid) == FAIL) { H4TOH5ERROR_SET1(3, "H5Aclose"); break; }
            attrid = FAIL;
            if (H5Adelete(dsid, HDF5_REFERENCE_LIST) == FAIL) { H4TOH5ERROR_SET1(3, "H5Adelete"); break; }
        }
        else {
            numelems = 1;
            if ((refs = malloc((int)numelems * sizeof(reference_backpointer_t))) == NULL) { H4TOH5ERROR_NOMEM1("reference_backpointer_t 1"); break; }
        }
        refs[numelems - 1].ref = datasetref;
        refs[numelems - 1].index = dimindex;

        dimsize = numelems;
        if ((spaceid = H5Screate_simple(1, &dimsize, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple"); break; }
        if (tid == FAIL) {
            if ((tid = H5Tcreate(H5T_COMPOUND, sizeof(reference_backpointer_t))) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tcreate"); break; }
            if (H5Tinsert(tid, "dataset", HOFFSET(reference_backpointer_t, ref), H5T_STD_REF_OBJ) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tinsert DATASET"); break; }
            if (H5Tinsert(tid, "index", HOFFSET(reference_backpointer_t, index), H5T_NATIVE_INT) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tinsert INDEX"); break; }
        }

        if ((attrid = H5ACREATE(dsid, HDF5_REFERENCE_LIST, tid, spaceid, H5P_DEFAULT)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Acreate"); break; }
        if (H5Awrite(attrid, tid, refs) == FAIL) { H4TOH5ERROR_SET1(3, "write dimension scale data set attribute"); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        if (refs != NULL) free(refs);
        if (attrid != FAIL) H5Aclose(attrid);
        if (tid != FAIL) H5Tclose(tid);
        if (oldspaceid != FAIL) H5Sclose(oldspaceid);
        if (spaceid != FAIL) H5Sclose(spaceid);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief attach HDF5 dimensional scales to the given HDF5 dataset
 * To conform to HDF5 Dimensional Scale Specification, this function creates a
 * DIMENSION_LIST attribute in the HDF5 dataset specified by h5dataset. It also
 * creates or updates REFERENCE_LIST in the HDF5 dimensional scales given by dimscales.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5dataset HDF5 dataset that will have dimensional scales
 * @param h5datasetref reference to datasetname
 * @param dimscales references to dimension scales that the given HDF5 dataset refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int attach_dimscales(hid_t h4toh5id, hid_t h5dataset, hobj_ref_t h5datasetref, const hobjreftalloc *dimscales)
{
    int succeeded = 0;

    do {
        int i, failinside = 0;

        /* dataset: DIMENSION_LIST */
        if (write_dimensionlist_attr(h4toh5id, h5dataset, dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }

        /* dimscale dataset: REFERENCE_LIST; other trivial attributes are written in write_coordinate_attributes() */
        for (i = 0; i < dimscales->len; ++i) {
            hid_t dsid = -1;
            int succeeded2 = 0;

            do {
                if ((dsid = H5RDEREFERENCE(h5dataset, H5R_OBJECT, &dimscales->s[i])) == -1) { H4TOH5ERROR_SET1(3, "deference fail"); failinside = 1; break; }
                if (write_referencelist_attr(h4toh5id, dsid, h5datasetref, i)) { H4TOH5ERROR_SET0(3); failinside = 1; break; }

                succeeded2 = 1;
            } while (0);
            if (1) {
                if (dsid != -1) H5Dclose(dsid);
            }
            if (!succeeded2) break;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief attach HDF5 dimensional scales to the given HDF5 dataset
 * This function is wrapper of attach_dimscales.
 * @see attach_dimscales
 * 
 * @param h4toh5id h4toh5 identifier
 * @param group HDF5 group name where the converted HDF5 dataset resides
 * @param h5datasetref reference to datasetname
 * @param dimscales references to dimension scales that the given HDF5 dataset refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int attach_dimscales_byref(hid_t h4toh5id, hid_t group, hobj_ref_t h5datasetref, const hobjreftalloc *dimscales)
{
    hid_t dataset = -1;
    int r;
    int succeeded = 0;

    do {
        if ((dataset = H5RDEREFERENCE(group, H5R_OBJECT, &h5datasetref)) == -1) { H4TOH5ERROR_SET1(3, "DEREFERENCE fail"); break; }
        r = attach_dimscales(h4toh5id, dataset, h5datasetref, dimscales);

        succeeded = 1;
    } while (0);

    if (1) {
        if (dataset != -1) H5Dclose(dataset);
    }
    return succeeded ? r : FAIL;
}

static int count_dimref(hid_t h4toh5id, const grid_t *grid, const char *name)
{
    int count = 0;
    int i, j;

    for (i = 0; i < grid->field.len; ++i) {
        const fieldinfo_t *field = &grid->field.s[i];

        for (j = 0; j < field->dims.len; ++j) {
            const dimension_t *dim = &field->dims.s[j];

            if (strcmp(dim->name.s, name) == 0)
                ++count;
        }
    }
    return count;
}

/* XDim and YDim are predefined dimensions in grid, but sometimes, non-standard names are used.
 * This function checks if non-standard names are used, and return names.
 */
static int detect_xdimydim(hid_t h4toh5id, const grid_t *grid, challoc *xdimname, challoc *ydimname)
{
    int succeeded = 0;

    do {
        const char *stdxdim = "XDim";
        const char *stdydim = "YDim";
        int stdxdimcount, stdydimcount;

        stdxdimcount = count_dimref(h4toh5id, grid, stdxdim);
        stdydimcount = count_dimref(h4toh5id, grid, stdydim);

        if (stdxdimcount > 0 && stdydimcount > 0) {
            if (!challoc_copys(xdimname, stdxdim)) { H4TOH5ERROR_NOMEM1("std xdim"); break; }
            if (!challoc_copys(ydimname, stdydim)) { H4TOH5ERROR_NOMEM1("std ydim"); break; }
        }
        else {
            const char *altxdim = "nlon";
            const char *altydim = "nlat";
            int altxdimcount, altydimcount;

            altxdimcount = count_dimref(h4toh5id, grid, altxdim);
            altydimcount = count_dimref(h4toh5id, grid, altydim);

            if (altxdimcount == 0 || altydimcount == 0) {
                H4TOH5ERROR_SET1(3, "Two standard dimensions, XDim and YDim, and two alternative dimensions, nlon and nlat, are not associated.");
                break;
            }

            if (!challoc_copys(xdimname, altxdim)) { H4TOH5ERROR_NOMEM1("alternative xdim"); break; }
            if (!challoc_copys(ydimname, altydim)) { H4TOH5ERROR_NOMEM1("alternative ydim"); break; }
            if (H4toH5config_use_verbose_msg()) {
                printf("XDim and YDim are not associated with any fields. Instead, %s, %s are used.\n", xdimname->s, ydimname->s);
            }
        }

        succeeded = 1;
    } while (0);

    return succeeded ? 0 : -1;
}

/** 
 * @brief attach two-dimensional dimensional scales to a data field
 * According to the CF conventions, this function just puts an attribute "coordinates".
 * Some programs aware of CF conventions will recognize this attribute.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param grid all information in EOS2 grid
 * @param h5dataset HDF5 dataset identifier representing a data field
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int attach_grid_2d_lonlat_dimscales(hid_t h4toh5id, const grid_t *grid, hid_t h5dataset)
{
    challoc coordinates;
    int succeeded = 0;

    challoc_init(&coordinates);
    do {
        int i, failinside = 0;

        for (i = 0; i < 2; ++i) {
            const coordinate_t *coord = get_fake_coordinate(grid, i == 0);

            if (accumulate_grid_coordinates(h4toh5id, &coordinates, coord) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
        }
        if (failinside) break;

        if (write_attribute_scalar(h4toh5id, h5dataset, "coordinates", coordinates.s) == -1) { H4TOH5ERROR_SET1(3, "coordinates attribute"); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&coordinates);
    }
    return succeeded ? 0 : -1;
}

/** 
 * @brief read dimension values from the given list of dimensions
 * This function mostly does not read any values as HDF-EOS2 dimensions mostly do not have values.
 * For unusual files, however, this function can read values.
 */
static int read_dimvalues(hid_t h4toh5id, const grid_t *grid, int32 sdsid, const hdf4dimalloc *hdf4dims, dimvaluealloc *dimvalues)
{
    int succeeded = 0;

    do {
        int i, failinside = 0;

        for (i = 0; i < hdf4dims->len; ++i) {
            const hdf4dim_t *hdf4dim = &hdf4dims->s[i];
            dimvalue_t *dimvalue;
            int32 dimid;

            if (hdf4dim->dimtype == 0 || hdf4dim->dimtype == -1) continue;

            {
                const dimvalue_t *visited;
                visited = find_dimvalue(h4toh5id, dimvalues, hdf4dim->name.s);
                if (visited && visited->handled) continue;
            }

            if (!dimvaluealloc_readyplus(dimvalues, 1)) { H4TOH5ERROR_NOMEM1("dimvalue_t"); failinside = 1; break; }
            dimvalue = &dimvalues->s[dimvalues->len++];
            init_dimvalue_t(dimvalue);

            if (!challoc_copy(&dimvalue->name, &hdf4dim->name)) { H4TOH5ERROR_NOMEM1("dim name"); failinside = 1; break; }

            if ((dimid = SDgetdimid(sdsid, i)) == -1) { H4TOH5ERROR_SET1(3, "SDgetdimid"); failinside = 1; break; }
            if (!challoc_ready(&dimvalue->value, DFKNTsize(hdf4dim->dimtype) * hdf4dim->dimsize)) { H4TOH5ERROR_NOMEM1("dimscale"); failinside = 1; break; }
            if (SDgetdimscale(dimid, dimvalue->value.s) == FAIL) { H4TOH5ERROR_SET1(3, "SDgetdimscale"); failinside = 1; break; }
            dimvalue->value.len = DFKNTsize(hdf4dim->dimtype) * hdf4dim->dimsize;

            if (H4toH5config_use_verbose_msg()) {
                printf("An HDF-EOS2 Dimension '%s' has elements. (%d bytes)\n", dimvalue->name.s, dimvalue->value.len);
            }
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);
    return succeeded ? 0 : -1;
}

/** 
 * @brief read EOS2 grid data by using EOS2 API from the given HDF4 sds or vdata identifier
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param ishdf4sds 1 if the HDF4 object is sds
 * @param objectid HDF4 sds or vdata identifier
 * @param[out] datafieldindex index of data fields or geolocation field in this EOS2 swath
 * @param[out] grid all information in EOS2 grid
 * @param[out] aux auxiliary information for EOS2 grid
 * @param[out] hdf4dims list of dimension name that this data field refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int read_grid(hid_t h4toh5id, const char *h5groupname, int ishdf4sds, int32 objectid, int *datafieldindex, grid_t **grid, grid_aux_t **aux, hdf4dimalloc *hdf4dims)
{
    int32 eosfd = -1;
    int32 gdid = -1;
    challoc gridname, objectname;
    int succeeded = 0;

    challoc_init(&gridname);
    challoc_init(&objectname);
    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
        eos2data_t *eos2data = (eos2data_t *)dt->eos2data;
        int iseosfield, i;

        /* check if h5groupname ends with "/Data Fields" */
        if (check_datafield(h5groupname, &iseosfield) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (!iseosfield) break;

        if (ishdf4sds) {
            if (read_sdsdim(h4toh5id, objectid, &objectname, &gridname, hdf4dims) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }
        else {
            if (read_vdatadim(h4toh5id, h5groupname, objectid, &objectname, &gridname) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }

        if (!eos2data->gridname.s || strcmp(eos2data->gridname.s, gridname.s)) {
            reset_eos2data_grid(eos2data);

            /* read EOS2 grid data */
            if ((eosfd = GDopen_const(dt->hdf4filename, DFACC_READ)) == FAIL) break;
            if ((gdid = GDattach_const(eosfd, gridname.s)) == FAIL) break;

            if (get_gridinfo(h4toh5id, gdid, &eos2data->grid.info) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (get_gridprojinfo(h4toh5id, gdid, &eos2data->grid.proj) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (get_griddimension(h4toh5id, gdid, &eos2data->grid.dim) == -1) { H4TOH5ERROR_SET0(3); break; }
            if (get_gridfieldinfo(h4toh5id, gdid, &eos2data->grid.field) == -1) { H4TOH5ERROR_SET0(3); break; }

            if (detect_xdimydim(h4toh5id, &eos2data->grid, &eos2data->grid_aux.xdimname, &eos2data->grid_aux.ydimname) == -1) { H4TOH5ERROR_SET0(3); break; }

            if (detect_majordim(h4toh5id, &eos2data->grid, &eos2data->grid_aux, &eos2data->grid_aux.ydimmajor) == -1) { H4TOH5ERROR_SET0(3); break; }
            if (read_dimvalues(h4toh5id, &eos2data->grid, objectid, hdf4dims, &eos2data->grid_aux.dimvalues) == -1) { H4TOH5ERROR_SET0(3); break; }

            if (!challoc_copy(&eos2data->gridname, &gridname)) { H4TOH5ERROR_NOMEM1("swath name"); break; }
            eos2data->gridfd = eosfd;
            eos2data->gridid = gdid;
        }
        *grid = &eos2data->grid;
        *aux = &eos2data->grid_aux;

        *datafieldindex = -1;
        for (i = 0; i < eos2data->grid.field.len; ++i) {
            if (strcmp(eos2data->grid.field.s[i].name.s, objectname.s) == 0) {
                *datafieldindex = i;
                break;
            }
        }
        if (*datafieldindex == -1) break;

        succeeded = 1;
    } while (0);

    if (!succeeded) {
        if (gdid != -1) GDdetach(gdid);
        if (eosfd != -1) GDclose(eosfd);
    }
    if (1) {
        challoc_free(&gridname);
        challoc_free(&objectname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief
 * GDij2ll() reads lon/lat values assuming that they are always two-dimensional.
 * This function makes lon/lat values one-dimensional by removing unnecessary values.
 */
static int fill_striding(hid_t h4toh5id, const grid_t *grid, int ydimmajor, const float64alloc *lons, const float64alloc *lats, float64alloc *striding)
{
    int succeeded = 0;

    do {
        int i;

        if (ydimmajor == 1) {
            if (!float64alloc_ready(striding, grid->info.ydim)) { H4TOH5ERROR_NOMEM1("partial latitude"); break; }
            for (i = 0; i < grid->info.ydim; ++i)
                striding->s[i] = lats->s[i * grid->info.xdim];
            striding->len = (int)grid->info.ydim;
        }
        else if (ydimmajor == 0) {
            if (!float64alloc_ready(striding, grid->info.xdim)) { H4TOH5ERROR_NOMEM1("partial longitude"); break; }
            for (i = 0; i < grid->info.xdim; ++i)
                striding->s[i] = lons->s[i * grid->info.ydim];
            striding->len = (int)grid->info.xdim;
        }
        else {
            H4TOH5ERROR_SET1(3, "internal error"); break;
        }

        succeeded = 1;
    } while (0);

    return succeeded ? 0 : -1;
}

/** 
 * @brief read the values in explicit XDim and YDim, and verify if they are same as calculated lon/lat values
 * For more information, refer to the comments in write_grid_dimensions()
 * 
 * @param h4toh5id h4toh5 identifier
 * @param grid grid object
 * @param aux auxiliary information
 * @param xdimvalue actual values in explicit XDim
 * @param xdimtype type of explicit XDim
 * @param ydimvalue actual values in explicit YDim
 * @param ydimtype type of explicit YDim
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int verify_lonlat_dimvalue(hid_t h4toh5id, const grid_t *grid, const grid_aux_t *aux, const dimvalue_t *xdimvalue, int32 xdimtype, const dimvalue_t *ydimvalue, int32 ydimtype)
{
    float64alloc lons;
    float64alloc lats;
    float64alloc striding;
    int succeeded = 0;

    float64alloc_init(&lons);
    float64alloc_init(&lats);
    float64alloc_init(&striding);
    do {
        int i, j, failinside = 0;
        int orthogonal;

        if (convert_proj(h4toh5id, grid, aux, &lons, &lats) == -1) { H4TOH5ERROR_SET0(3); break; }
        orthogonal = check_ortho_dimscale(grid, aux, &lons, &lats);
        if (!orthogonal) { H4TOH5ERROR_SET1(3, "The file contains XDim and YDim values, but the projection requires two-dimensional longitude and latitude."); break; }
        if (fill_striding(h4toh5id, grid, aux->ydimmajor, &lons, &lats, &striding) == -1) { H4TOH5ERROR_SET0(3); break; }

        for (i = 0; i < 2; ++i) {
            const float64alloc *expected = i == 0 ? &lons : &lats;
            const dimvalue_t *actual = i == 0 ? xdimvalue : ydimvalue;

            if (aux->ydimmajor && i != 0) expected = &striding;
            if (!aux->ydimmajor && i == 0) expected = &striding;

            for (j = 0; j < (i == 0 ? grid->info.xdim : grid->info.ydim); ++j) {
#define LONLAT_TOLERANCE    0.1f
#define HANDLE_TYPE(tid, t)                         \
    case tid:                               \
    {                                   \
        float64 a;                              \
        const t *v = (const t *)&actual->value.s[j * sizeof(t)];    \
        a = *v;                             \
        a -= expected->s[j];                            \
        if (a > LONLAT_TOLERANCE || a < -LONLAT_TOLERANCE) {            \
            H4TOH5ERROR_SET2(3, "Suspicious dimension", i == 0 ? "XDim" : "YDim");  \
            failinside = 1;                         \
        }                                   \
        break;                              \
    }
    switch (i == 0 ? xdimtype : ydimtype) {
        HANDLE_TYPE(DFNT_FLOAT32, float32)
        HANDLE_TYPE(DFNT_FLOAT64, float64)
        HANDLE_TYPE(DFNT_INT8, int8)
        HANDLE_TYPE(DFNT_UINT8, uint16)
        HANDLE_TYPE(DFNT_INT16, int16)
        HANDLE_TYPE(DFNT_UINT16, uint16)
        HANDLE_TYPE(DFNT_INT32, int32)
        HANDLE_TYPE(DFNT_UINT32, uint32)
    }
#undef HANDLE_TYPE
    if (failinside) break;
            }
            if (failinside) break;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        float64alloc_free(&lons);
        float64alloc_free(&lats);
        float64alloc_free(&striding);
    }
    return succeeded ? 0 : -1;
}

/** 
 * @brief create dimensional scales corresponding to XDim and YDim in the HDF-EOS2 grid
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param grid all information in EOS2 grid
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_grid_lonlat_dimscale(hid_t h4toh5id, const char *h5groupname, const grid_t *grid, grid_aux_t *aux)
{
    float64alloc lons;
    float64alloc lats;
    float64alloc striding;
    dimension_t dims[2];
    hobjreftalloc dimscales;
    hid_t group = -1;
    challoc coordinates;
    int succeeded = 0;

    float64alloc_init(&lons);
    float64alloc_init(&lats);
    float64alloc_init(&striding);
    init_dimension_t(&dims[0]);
    init_dimension_t(&dims[1]);
    hobjreftalloc_init(&dimscales);
    challoc_init(&coordinates);
    do {
        int i, failinside = 0;
        hobj_ref_t drefx, drefy;

        if ((group = get_h5groupid(h5groupname, h4toh5id,0)) == -1) { H4TOH5ERROR_SET0(3); break; }

        if (convert_proj(h4toh5id, grid, aux, &lons, &lats) == -1) { H4TOH5ERROR_SET0(3); break; }
        aux->orthogonal = check_ortho_dimscale(grid, aux, &lons, &lats);
        if (fill_striding(h4toh5id, grid, aux->ydimmajor, &lons, &lats, &striding) == -1) { H4TOH5ERROR_SET0(3); break; }

        /* create two dimscales: XDim and YDim */
        for (i = 0; i < 2; ++i) {
            const void *dimdata = i == 0 ? lons.s : lats.s;
            hobj_ref_t *dref = i == 0 ? &drefx : &drefy;
            const coordinate_t *coord = get_coordinate(aux->orthogonal, i == 0);
            int created = 0;

            if (aux->ydimmajor && i != 0) dimdata = striding.s;
            if (!aux->ydimmajor && i == 0) dimdata = striding.s;

            if (!challoc_copys(&dims[i].name, coord->name)) { H4TOH5ERROR_NOMEM1("dim name"); failinside = 1; break; }
            dims[i].dimsize = i == 0 ? grid->info.xdim : grid->info.ydim;

            if (coord->variable) {
                if (write_1d_eos_dimscale(h4toh5id, h5groupname, coord, dims[i].dimsize, H5T_IEEE_F64BE, H5T_NATIVE_DOUBLE, dimdata, &created) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
            else {
                if (write_1d_empty_eos_dimscale(h4toh5id, h5groupname, coord, dims[i].dimsize, &created) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
            if (get_ref_eos_dimscale(h4toh5id, h5groupname, dims[i].name.s, dref) == -1) { H4TOH5ERROR_SET1(3, "create reference"); failinside = 1; break; }
            if (created && coord->variable) {
                if (!hobjreftalloc_ready(&dimscales, 1)) { H4TOH5ERROR_NOMEM1("dimscale ref"); failinside = 1; break; }
                dimscales.s[0] = *dref;
                dimscales.len = 1;
                if (attach_dimscales_byref(h4toh5id, group, *dref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
        }
        if (failinside) break;

        if (!aux->orthogonal) {
            /* if dimscale is not orthogonal, full data are needed */
            /* due to the limit of netCDF dimscale model, these data are treated as variables rather than dimensions */
            /* see http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.1/cf-conventions.html */
            hobjreftalloc_empty(&dimscales);
            for (i = 0; i < 2; ++i) {
                if (!hobjreftalloc_readyplus(&dimscales, 1)) { H4TOH5ERROR_NOMEM1("dimscale ref"); failinside = 1; break; }
                dimscales.s[i] = *(aux->ydimmajor ? (i == 0 ? &drefy : &drefx) : (i == 0 ? &drefx : &drefy));
                dimscales.len++;
            }
            if (failinside) break;

            for (i = 0; i < 2; ++i) {
                const coordinate_t *coord = get_fake_coordinate(grid, i == 0);
                const float64alloc *data = i == 0 ? &lons : &lats;
                int created = 0;

                if (create_2d_fakedimscale(h4toh5id, h5groupname, coord, aux->ydimmajor, &dims[0], &dims[1], data, &created) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                if (created) {
                    hobj_ref_t fakedimscaleref;
                    if (get_ref_eos_dimscale(h4toh5id, h5groupname, coord->name, &fakedimscaleref) == -1) { H4TOH5ERROR_SET1(3, "create fakedim ref"); failinside = 1; break; }
                    if (attach_dimscales_byref(h4toh5id, group, fakedimscaleref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                }
                if (accumulate_grid_coordinates(h4toh5id, &coordinates, coord) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&coordinates);
        if (group != -1) H5Gclose(group);
        hobjreftalloc_free(&dimscales);
        free_dimension_t(&dims[0]);
        free_dimension_t(&dims[1]);
        float64alloc_free(&striding);
        float64alloc_free(&lons);
        float64alloc_free(&lats);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create HDF5 dimensions that were attached to the given HDF-EOS2 Grid field.
 * This function creates HDF5 dimensions. Attaching will be done other functions, write_grid_dimscale().
 *
 * @param h4toh5id h4toh5 identifier
 * @param grid HDF-EOS2 Grid
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param hdf4dims mapping from HDF4 dimension name to its type
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_grid_dimensions(hid_t h4toh5id, const grid_t *grid, grid_aux_t *aux, const char *h5groupname, const hdf4dimalloc *hdf4dims)
{
    int succeeded = 0;

    do {
        const dimvalue_t *xdimhandled = NULL, *ydimhandled = NULL;
        int32 xdimtype = 0, ydimtype = 0;
        int i, failinside = 0;

        for (i = 0; i < grid->dim.len; ++i) {
            const dimension_t *dim = &grid->dim.s[i];
            int32 dimtype = find_dimtype(hdf4dims, dim->name.s);
            coordinate_t coord;

            if (dimtype == -1 || dimtype == 0) {
                /* Usually, XDim and YDim are implicit; so, this loop does not visit
                 * XDim or YDim. However, some files explicitly define XDim and YDim, or nlon and nlat.
                 * If they don't have any values, let write_grid_lonlat_dimscale() handle that.
                 * Otherwise, the else branch will convert this, and those values will be verified.
                 */
                if (strcmp(dim->name.s, aux->xdimname.s) == 0) continue;
                if (strcmp(dim->name.s, aux->ydimname.s) == 0) continue;

                get_coordinate_from_dimension(dim->name.s, &coord);
                if (write_1d_empty_eos_dimscale(h4toh5id, h5groupname, &coord, dim->dimsize, NULL) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
            else {
                dimvalue_t *dimvalue;
                hid_t h5memtype, h5datatype;
                size_t h4memsize, h4size;

                get_coordinate_from_dimvalue(aux, dim->name.s, &coord);
                if (h4type_to_h5type(h4toh5id, dimtype, &h5memtype, &h4memsize, &h4size, &h5datatype) == -1) { H4TOH5ERROR_SET0(3); break; }
             
                if ((dimvalue = find_dimvalue(h4toh5id, &aux->dimvalues, dim->name.s)) == NULL) { H4TOH5ERROR_SET1(3, "internal error"); failinside = 1; break; }
                if (write_1d_eos_dimscale(h4toh5id, h5groupname, &coord, dim->dimsize, h5datatype, h5memtype, dimvalue->value.s, NULL) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
                dimvalue->handled = 1;

                if (strcmp(dim->name.s, aux->xdimname.s) == 0) {
                    xdimhandled = dimvalue;
                    xdimtype = dimtype;
                }
                if (strcmp(dim->name.s, aux->ydimname.s) == 0) {
                    ydimhandled = dimvalue;
                    ydimtype = dimtype;
                }
            }
        }
        if (failinside) break;

        if (xdimhandled && ydimhandled) {
            if (verify_lonlat_dimvalue(h4toh5id, grid, aux, xdimhandled, xdimtype, ydimhandled, ydimtype) == -1) { H4TOH5ERROR_SET1(3, "The file contains XDim and YDim (or alternatives), but those values are suspicious."); break; }
            aux->lonlat_converted = 1;
        }
        else if (!xdimhandled && !ydimhandled) {
            if (!aux->lonlat_converted) {
                if (write_grid_lonlat_dimscale(h4toh5id, h5groupname, grid, aux) == -1) { H4TOH5ERROR_SET0(3); break; }
                aux->lonlat_converted = 1;
            }
        }
        else {
            H4TOH5ERROR_SET1(3, "XDim and YDim (or alternatives) are inconsitently filled."); break;
        }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief pick references to HDF5 dimensions that were attached to the given
 * HDF-EOS2 Swath field. This function does not create HDF5 dimensions, but
 * just assumes that write_grid_dimensions() is already called and this
 * function created all dimensions belonging to the Swath. Attaching will be
 * done other functions, write_grid_dimscale().
 * @see write_grid_dimensions
 * 
 * @param h4toh5id h4toh5 identifier
 * @param field HDF-EOS2 datafield or geolocation field to investigate
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param[out] dimscales list of references to dimensions created in this function
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int prepare_grid_dimensions(hid_t h4toh5id, const grid_t * grid, const grid_aux_t *aux, const fieldinfo_t *field, const char *h5groupname, int orthogonal, hobjreftalloc *dimscales, int *pxdimindex, int *pydimindex)
{
    int xdimindex = -1, ydimindex = -1;
    int succeeded = 0;

    do {
        int i, failinside = 0;

        if (!hobjreftalloc_ready(dimscales, field->dims.len)) { H4TOH5ERROR_NOMEM1("obj ref list"); break; }
        for (i = 0; i < field->dims.len; ++i)
            dimscales->s[dimscales->len++] = -1;

        for (i = 0; i < field->dims.len; ++i) {
            const dimension_t *dim = &field->dims.s[i];
            const char *dimname = dim->name.s;
            hobj_ref_t dimref;

            if (strcmp(dim->name.s, aux->xdimname.s) == 0 || strcmp(dim->name.s, aux->ydimname.s) == 0) {
                const coordinate_t *coord = get_coordinate(orthogonal, strcmp(dim->name.s, aux->xdimname.s) == 0);
                if (strcmp(dim->name.s, aux->xdimname.s) == 0) xdimindex = i;
                if (strcmp(dim->name.s, aux->ydimname.s) == 0) ydimindex = i;
                dimname = coord->name;
            }

            if (get_ref_eos_dimscale(h4toh5id, h5groupname, dimname, &dimref) == -1) { H4TOH5ERROR_SET2(3, "create reference", dim->name.s); failinside = 1; break; }
            dimscales->s[i] = dimref;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (succeeded) {
        *pxdimindex = xdimindex;
        *pydimindex = ydimindex;
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief for given HDF-EOS2 Grid, create HDF5 dimensional scales and attaches them to the HDF5 dataset
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param h5dataset HDF5 dataset identifier
 * @param h5datasetref reference to h5dataset
 * @param grid HDF-EOS2 Grid
 * @param aux auxiliary data for HDF-EOS2 Grid
 * @param datafieldindex index of datafield in the Grid
 * @param hdf4dims mapping from HDF4 dimension name to its type
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_grid_dimscale(hid_t h4toh5id, const char *h5groupname, hid_t h5dataset, hobj_ref_t h5datasetref, const grid_t *grid, grid_aux_t *aux, int datafieldindex, const hdf4dimalloc *hdf4dims)
{
    hobjreftalloc dimscales;
    int succeeded = 0;

    hobjreftalloc_init(&dimscales);
    do {
        const fieldinfo_t *field = &grid->field.s[datafieldindex];
        int xdimindex = -1, ydimindex = -1;

        if (write_grid_dimensions(h4toh5id, grid, aux, h5groupname, hdf4dims)) { H4TOH5ERROR_SET0(3); break; }
        if (prepare_grid_dimensions(h4toh5id, grid, aux, field, h5groupname, aux->orthogonal, &dimscales, &xdimindex, &ydimindex) == -1) { H4TOH5ERROR_SET0(3); break; }

        /* One real file has a field that refers to only XDim, not both of XDim and YDim.
         * I think this is against the specification, but the h4toh5 needs to tolerate that.
         */
        if (xdimindex != -1 && ydimindex != -1) {
            H4TOH5ASSERT((ydimindex < xdimindex ? 1 : 0) == aux->ydimmajor)
            if (!aux->orthogonal) {
                if (attach_grid_2d_lonlat_dimscales(h4toh5id, grid, h5dataset) == -1) { H4TOH5ERROR_SET0(3); break; }
            }
        }

        if (attach_dimscales(h4toh5id, h5dataset, h5datasetref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        hobjreftalloc_free(&dimscales);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief try to read HDF-EOS2 Grid data from the given HDF4 sds or vdata, and calls conversion function if it is Swath
 *
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param ishdf4sds 1 if the HDF4 object is sds
 * @param objectid HDF4 sds or vdata identifier
 * @param h5dataset HDF5 dataset identifier
 * @param h5datasetref reference to h5dataset
 * @param[out] handled 1 if this HDF4 object is EOS2 Grid datatype and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int generic_eosgrid_dimscale(hid_t h4toh5id, const char *h5groupname, int ishdf4sds, int32 objectid, hid_t h5dataset, hobj_ref_t h5datasetref, int *handled)
{
    hdf4dimalloc hdf4dims;
    int datasetcreated = 0;
    int succeeded = 0;

    *handled = 0;
    hdf4dimalloc_init(&hdf4dims);
    do {
        grid_t *grid;
        grid_aux_t *aux;
        int datafieldindex;

        if (read_grid(h4toh5id, h5groupname, ishdf4sds, objectid, &datafieldindex, &grid, &aux, &hdf4dims) == FAIL)
            H4toH5error_reset(h4toh5id);
        else {
            const fieldinfo_t *field = 0;
            if (datafieldindex == -1) break;
            field = &grid->field.s[datafieldindex];

            if (h5dataset == -1) {
                /* create an HDF5 dataset on-the-fly if HDF4 vdata is given */
                if (create_vdata(h4toh5id, h5groupname, field, objectid, 1, &h5dataset, &h5datasetref) == -1) { H4TOH5ERROR_SET0(3); break; }
                datasetcreated = 1;
            }
            else {
                if (augment_sds(h4toh5id, objectid, h5groupname, field, h5dataset) == -1) { H4TOH5ERROR_SET0(3); break; }
            }
            if (write_grid_dimscale(h4toh5id, h5groupname, h5dataset, h5datasetref, grid, aux, datafieldindex, &hdf4dims) == -1) { H4TOH5ERROR_SET0(3); break; }

            *handled = 1;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (datasetcreated && h5dataset != -1) H5Dclose(h5dataset);
        hdf4dimalloc_free(&hdf4dims);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief try to convert an HDF4 sds which may be an HDF-EOS2 Grid field into an HDF5 dimensional scale
 * 
 * @param h4toh5id h4toh5id
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param sdsid HDF4 sds identifier
 * @param h5dataset the converted HDF5 identifier corresponding to the HDF4 sds
 * @param h5datasetref reference to h5dataset
 * @param[out] handled 1 if the given HDF4 sds is an HDF-EOS2 attribute and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5sds_eosgrid_dimscale(hid_t h4toh5id, const char *h5groupname, int32 sdsid, hid_t h5dataset, hobj_ref_t h5datasetref, int *handled)
{
    return generic_eosgrid_dimscale(h4toh5id, h5groupname, 1, sdsid, h5dataset, h5datasetref, handled);
}

/** 
 * @brief try to convert an HDF4 vdata which may be an HDF-EOS2 Grid field into an HDF5 dimensional scale
 * 
 * @param h4toh5id h4toh5id
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param vdataid HDF4 vdata identifier
 * @param[out] handled 1 if the given HDF4 vdata is an HDF-EOS2 attribute and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5vdata_eosgrid_dimscale(hid_t h4toh5id, const char *h5groupname, int32 vdataid, int *handled)
{
    return generic_eosgrid_dimscale(h4toh5id, h5groupname, 0, vdataid, -1, -1, handled);
}


/*
 * handle SWATH dimscale
 */

/** 
 * @brief read EOS2 swath data by using EOS2 API from the given HDF4 sds or vdata identifier
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param ishdf4sds 1 if the HDF4 object is sds
 * @param objectid HDF4 sds or vdata identifier
 * @param geowanted 1 if the caller wants to read geofield rather than data field
 * @param[out] fieldindex index of data fields or geolocation field in this EOS2 swath
 * One EOS2 swath can have several Data fields, and datafieldindex indicates the index of the given objectid in EOS2 swath.
 * @param[out] swath all information in EOS2 swath
 * @param[out] aux auxiliary information for EOS2 swath
 * @param[out] hdf4dims list of dimension name that this data field refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int read_swath(hid_t h4toh5id, const char *h5groupname, int ishdf4sds, int32 objectid, int geowanted, int *fieldindex, swath_t **swath, swath_aux_t **aux, hdf4dimalloc *hdf4dims)
{
    int32 eosfd = -1;
    int32 swid = -1;
    challoc swathname, objectname;
    int succeeded = 0;

    challoc_init(&swathname);
    challoc_init(&objectname);
    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
        eos2data_t *eos2data = (eos2data_t *)dt->eos2data;
        int i;

        if (ishdf4sds) {
            if (read_sdsdim(h4toh5id, objectid, &objectname, &swathname, hdf4dims) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }
        else {
            if (read_vdatadim(h4toh5id, h5groupname, objectid, &objectname, &swathname) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        }

        if (!eos2data->swathname.s || strcmp(eos2data->swathname.s, swathname.s)) {
            reset_eos2data_swath(eos2data);

            /* read EOS2 swath data */
            if ((eosfd = SWopen_const(dt->hdf4filename, DFACC_READ)) == FAIL) break;
            if ((swid = SWattach(eosfd, swathname.s)) == FAIL) break;

            if (!challoc_copy(&eos2data->swath.name, &swathname)) { H4TOH5ERROR_NOMEM1("swath name"); break; }
            if (get_swathdimension(h4toh5id, swid, &eos2data->swath.dim) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (get_swathdimmap(h4toh5id, swid, &eos2data->swath.dimmap) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (get_swathindexmap(h4toh5id, swid, &eos2data->swath.index) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (get_swathfieldinfo(h4toh5id, swid, 1, &eos2data->swath.geofield) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (get_swathfieldinfo(h4toh5id, swid, 0, &eos2data->swath.datafield) == FAIL) { H4TOH5ERROR_SET0(3); break; }

            /* something for aux */

            if (!challoc_copy(&eos2data->swathname, &swathname)) { H4TOH5ERROR_NOMEM1("swath name"); break; }
            eos2data->swathfd = eosfd;
            eos2data->swathid = swid;
        }
        *swath = &eos2data->swath;
        *aux = &eos2data->swath_aux;

        /* find a data (or geo) field which has the same name as HDF4 sds name, and records the index */
        *fieldindex = -1;
        {
            const fieldinfoalloc *fields = geowanted ? &eos2data->swath.geofield : &eos2data->swath.datafield;
            for (i = 0; i < fields->len; ++i) {
                if (strcmp(fields->s[i].name.s, objectname.s) == 0) {
                    *fieldindex = i;
                    break;
                }
            }
        }
        if (*fieldindex == -1) break;

        /* Vdata does not have dimensions as SDS has, which makes the argument, hdf4dims, empty.
         * I assumed that this is safe because each field in Swath is multi-dimensional, and that
         * cannot be a Vdata. Recently, I found a swath data with one-dimensional field.
         * Since empty hdf4dims can make the caller skip all dimension map stuffs, the following
         * routine passes some dimensions as if they were fetched by HDF4 API.
         */
        if (!ishdf4sds) {
            int i, failinside = 0;
            const fieldinfo_t *field = geowanted ? &eos2data->swath.geofield.s[*fieldindex] : &eos2data->swath.datafield.s[*fieldindex];

            for (i = 0; i < field->dims.len; ++i) {
                const dimension_t *dim = &field->dims.s[i];
                hdf4dim_t *hdf4dim;
                if (!hdf4dimalloc_readyplus(hdf4dims, 1)) { H4TOH5ERROR_NOMEM1("hdf4dim_t"); failinside = 1; break; }
                hdf4dim = &hdf4dims->s[hdf4dims->len++];
                init_hdf4dim_t(hdf4dim);
                if (!challoc_copys(&hdf4dim->name, dim->name.s)) { H4TOH5ERROR_NOMEM1("dimension name"); failinside = 1; break; }
                hdf4dim->dimtype = -1;
                hdf4dim->dimsize = dim->dimsize;
            }
            if (failinside) break;
        }

        succeeded = 1;
    } while(0);

    if (!succeeded) {
        if (swid != -1) SWdetach(swid);
        if (eosfd != -1) SWclose(eosfd);
    }
    if (1) {
        challoc_free(&swathname);
        challoc_free(&objectname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create default dimension map (increment: 1, offset: 0)
 * If increment is 1 and offset is 0, it seems HDF-EOS2 does not
 * create dimension maps. To implement this program simply, this
 * function creates fake dimension maps.
 * @see remove_default_dimmap
 * 
 * @param h4toh5id h4toh5 identifier
 * @param[in,out] swath all information in EOS2 swath
 * @param hdf4dims list of dimension name that this data field refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int create_default_dimmap(hid_t h4toh5id, swath_t *swath, const hdf4dimalloc *hdf4dims)
{
    int succeeded = 0;

    do {
        int i, failinside = 0;

        for (i = 0; i < hdf4dims->len; ++i) {
            const hdf4dim_t *hdf4dim = &hdf4dims->s[i];
            dimmap_t *dimmap;
            if (!dimmapalloc_readyplus(&swath->dimmap, 1)) { H4TOH5ERROR_NOMEM1("dimmap_t"); failinside = 1; break; }
            dimmap = &swath->dimmap.s[swath->dimmap.len++];
            init_dimmap_t(dimmap);
            if (!challoc_copy(&dimmap->data, &hdf4dim->name)) { H4TOH5ERROR_NOMEM1("dimmap data"); failinside = 1; break; }
            if (!challoc_copy(&dimmap->geo, &hdf4dim->name)) { H4TOH5ERROR_NOMEM1("dimmap geo"); failinside = 1; break; }
            dimmap->offset = 0;
            dimmap->increment = 1;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief remove default dimension map (increment: 1, offset: 0) created by create_default_dimmap
 * Some dimension maps that don't exist in the original file are created by create_default_dimmap
 * to make this program simple. This function remove those additional dimension maps.
 * @see create_default_dimmap
 * 
 * @param h4toh5id h4toh5 identifier
 * @param[in,out] swath all information in EOS2 swath
 * @param hdf4dims list of dimension name that this data field refers to
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int remove_default_dimmap(hid_t h4toh5id, swath_t *swath, const hdf4dimalloc *hdf4dims)
{
    int succeeded = 0;

    do {
        int i, j;

        for (i = swath->dimmap.len - 1; i >= 0; --i) {
            dimmap_t *dimmap = &swath->dimmap.s[i];

            if (!(dimmap->offset == 0 && dimmap->increment == 1)) break;
            for (j = 0; j < hdf4dims->len; ++j) {
                const hdf4dim_t *hdf4dim = &hdf4dims->s[j];
                if (strcmp(dimmap->data.s, hdf4dim->name.s)) continue;
                if (strcmp(dimmap->geo.s, hdf4dim->name.s)) continue;
                break;
            }
            if (j == hdf4dims->len) break;

            free_dimmap_t(dimmap);
            swath->dimmap.len--;
        }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief an element of queue that keeps all possible combinations of dimension scales
 * @see write_swath_dimscale
 */
typedef struct {
    const fieldinfo_t *geofield;
    int datadimindex;
    int geodimindex;
    const dimmap_t *dimmap;
} swathdim_t;

void init_swathdim_t(swathdim_t *e)
{
    e->geofield = NULL;
    e->datadimindex = -1;
    e->geodimindex = -1;
    e->dimmap = NULL;
}

void free_swathdim_t(swathdim_t *e)
{
}
DECLARE_VECTOR_ALLOCATOR_FREE(swathdimalloc, swathdim_t)

/** 
 * @brief enqueue a tuple that can be combinated to make one concrete dimension scale
 * @see write_swath_dimscale
 * 
 * @param h4toh5id h4toh5 identifier
 * @param[in, out] queue queue
 * @param geofield first element of tuple
 * @param datadimindex second element of tuple
 * @param geodimindex third element of tuple
 * @param dimmap fourth element of tuple
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int enqueue_swathdim(hid_t h4toh5id, swathdimalloc *queue, const fieldinfo_t *geofield, int datadimindex, int geodimindex, const dimmap_t *dimmap)
{
    int succeeded = 0;

    do {
        swathdim_t *swathdim;
        if (!swathdimalloc_readyplus(queue, 1)) { H4TOH5ERROR_NOMEM1("swathdim_t"); break; }
        swathdim = &queue->s[queue->len++];
        init_swathdim_t(swathdim);

        swathdim->geofield = geofield;
        swathdim->datadimindex = datadimindex;
        swathdim->geodimindex = geodimindex;
        swathdim->dimmap = dimmap;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief a context used to generate all possible combinations of dimension scales
 * @see write_swath_dimscale
 */
typedef struct {
    /* handling HDF-EOS2 Swath data */
    const swath_t *swath;
    /* possible swathdim_t elements */
    const swathdimalloc *queue;
    /* handling HDF-EOS2 Swath datafield */
    const fieldinfo_t *datafield;
    /* HDF5 dimensions corresponding to dimensions that were attached to the datafield */
    const hobjreftalloc *dimscales;

    hid_t h4toh5id;
    const char *h5swathdatafieldpath;

    /* indices in the queue for each geolocation dimension */
    intalloc *queueindices;
    /* indices in the datafield for each geolocation dimension */
    intalloc *datadimindices;
    /* coordinates attribute value */
    challoc *coordinates;
} cartesian_product_context_t;

static void init_cartesian_product_context(cartesian_product_context_t *ctx, const swath_t *swath, const swathdimalloc *queue, const fieldinfo_t *datafield, hobjreftalloc *dimscales, hid_t h4toh5id, const char *h5swathdatafieldpath, intalloc *queueindices, intalloc *datadimindices, challoc *coordinates)
{
    ctx->swath = swath;
    ctx->queue = queue;
    ctx->datafield = datafield;
    ctx->dimscales = dimscales;
    ctx->h4toh5id = h4toh5id;
    ctx->h5swathdatafieldpath = h5swathdatafieldpath;
    ctx->queueindices = queueindices;
    ctx->datadimindices = datadimindices;
    ctx->coordinates = coordinates;
}

/** 
 * @brief from the given @queue, it generates all possible combinations.
 *
 * For example, suppose that @queue is { { Longitude, 0, DataTrack_lo }, { Longitude, 1, DataXTrack_lo } }.
 * Then, this function generates "Longitude" with [ DataTrack_lo, DataXTrack_lo ] dimension.
 *
 * @param queue queue
 * @param geodimindex index of geolocation dimension (depth of recursion)
 * @param handler function called when one concrete dimension scale is generated
 * @param ctx the context that will be passed for @handler
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int traverse_cartesian_product(const swathdimalloc *queue, int geodimindex, int (*handler)(const fieldinfo_t *geofield, const cartesian_product_context_t *ctx), const cartesian_product_context_t *ctx)
{
    int i;

    H4TOH5ASSERT(geodimindex <= ctx->queueindices->len)
    H4TOH5ASSERT(geodimindex <= ctx->datadimindices->len)

    for (i = 0; i < queue->len; ++i) {
        const swathdim_t *swathdim = &queue->s[i];
        const fieldinfo_t *geofield = queue->s[i].geofield;
        if (swathdim->geodimindex != geodimindex) continue;
        if (geodimindex > 0) {
            /* Only { Longitude, 0, DataTrack_lo } and { Longitude, 1, DataXTrack_lo } can be combined.
             * If the pair was "Longitude" and "Latitude", it is not a possible combination; skip it
             */
            const char *prevgeoname = queue->s[ctx->queueindices->s[geodimindex - 1]].geofield->name.s;
            const char *thisgeoname = geofield->name.s;
            if (strcmp(prevgeoname, thisgeoname)) continue;
        }

        ctx->queueindices->s[geodimindex] = i;
        ctx->queueindices->len = geodimindex + 1;
        ctx->datadimindices->s[geodimindex] = swathdim->datadimindex;
        ctx->datadimindices->len = geodimindex + 1;

        /* when one concrete dimension is completed, call @handler */
        if (geodimindex == geofield->dims.len - 1) {
            if (handler(queue->s[i].geofield, ctx) == FAIL) return FAIL;
        }
        /* go to next index */
        else if (traverse_cartesian_product(queue, geodimindex + 1, handler, ctx) == FAIL) return FAIL;
    }
    return SUCCEED;
}

/** 
 * @brief information for interpolation
 * one instance of this structure will contain all interpolation information
 * for one dimension of one point. If the rank is n, n instances
 * of this structure is required to interpolate one point.
 *
 * "leftpoint" and "rightpoint" tells indices of pivot points in the
 * geolocation field. "leftdist" and "rightdist" gives how far the interpolated
 * point is from "leftpoint" and "rightpoint" respectively. For example, if the
 * rank is 1, "leftpoint" is 5, "rightpoint" is 6, "leftdist" is 3 and
 * "rightdist" is 7, the interpolated value will be calculated from 5th element
 * and 6th element of the geo location field with the weight of 7 and 3.
 * @see generate_interpolated_geofield
 */
typedef struct {
    int leftpoint;
    int rightpoint;
    int leftdist;
    int rightdist;
} interpolateinfo_t;

DECLARE_VECTOR_ALLOCATOR(interpolatedinfoalloc, interpolateinfo_t)

typedef struct {
    float64 accumulated;
} interpolatecontext_t;

/** 
 * @brief calculate an interpolated value for each dimension, recursively
 * This function calculates the interpolated value for each dimension. To consider
 * all pivot points for all dimensions, it recursively calls itself for the next dimension.
 * If rank is n, it will investigate 2^n points.
 * 
 * @param geofield geo field
 * @param interpolateinfo information about two values that each axis should take care of (the number of elements is equal to rank)
 * @param dimindex index of dimension (depth of recursion)
 * @param[in,out] pivotindices indices of geo fields values for each dimension index
 * The problem is that geo field in EOS2 swath is 1 dimensional array, but we want to access it as it is n-dimensional array.
 * To treat that 1-dimensional array as n-dimensional array, assume that geo fields are stored as row-major order, and
 * pivotindices stores the indices for each dimension index. For example, assume that geo fields has two dimensions whose length
 * are 20 x 10. (Then, there should be 200 number of elements in geo fields.) If pivotindices has { 3, 4 }, this function will
 * access geofield[20 * 3 + 4].
 * @param[in,out] interpolatectx stores the accumulated value
 * @param ctx the context that will be passed for @handler
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int accumulate_pivots(const fieldinfo_t *geofield, const interpolatedinfoalloc *interpolateinfo, int dimindex, intalloc *pivotindices, interpolatecontext_t *interpolatectx, const cartesian_product_context_t *ctx)
{
    int succeeded = 0;

    do {
        int i, j, failinside = 0;
        hid_t h4toh5id = ctx->h4toh5id;

        /* two points are used for each dimension */
        for (i = 0; i < 2; ++i) {
            int point = i == 0 ? interpolateinfo->s[dimindex].leftpoint : interpolateinfo->s[dimindex].rightpoint;
            int distsum = interpolateinfo->s[dimindex].leftdist + interpolateinfo->s[dimindex].rightdist;
            float64 factor;

            pivotindices->s[dimindex] = point;
            factor = i == 1 ? interpolateinfo->s[dimindex].leftdist : interpolateinfo->s[dimindex].rightdist;
            factor /= distsum;

            if (dimindex < pivotindices->len - 1) {
    /* go to next dimension index */
    interpolatecontext_t nestedctx;
    nestedctx.accumulated = 0.0f;
    if (accumulate_pivots(geofield, interpolateinfo, dimindex + 1, pivotindices, &nestedctx, ctx) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
        interpolatectx->accumulated += factor * nestedctx.accumulated;
            }
            else {
    /* one of 2^n points */
    int dataindex;
    float64 value;

    /* treat as pivotindices->len dimensional row-major array, and calculate the index in 1-dimensional array */
    for (dataindex = j = 0; j < pivotindices->len - 1; ++j) {
        dataindex += pivotindices->s[j];
        dataindex *= geofield->dims.s[j + 1].dimsize;
    }
    dataindex += pivotindices->s[pivotindices->len - 1];

#ifdef EAGER_DATA_FETCH
#define HANDLE_TYPE(tid, t) case tid: { t *v; v = (t *)&geofield->data.s[dataindex * sizeof(t)]; value = *v; } break;
#else
#define HANDLE_TYPE(tid, t) case tid: { t *v; v = (t *)&geofield->lazydata->s[dataindex * sizeof(t)]; value = *v; } break;
#endif
    switch (geofield->type) {
        HANDLE_TYPE(DFNT_FLOAT32, float32)
        HANDLE_TYPE(DFNT_FLOAT64, float64)
        HANDLE_TYPE(DFNT_INT8, int8)
        HANDLE_TYPE(DFNT_UINT8, uint16)
        HANDLE_TYPE(DFNT_INT16, int16)
        HANDLE_TYPE(DFNT_UINT16, uint16)
        HANDLE_TYPE(DFNT_INT32, int32)
        HANDLE_TYPE(DFNT_UINT32, uint32)
        case DFNT_UCHAR8:
        case DFNT_CHAR8:
        default:
            { H4TOH5ERROR_SET1(3, "unexpected geofield type"); failinside = 1; break; }
            break;
    }
#undef HANDLE_TYPE
    if (failinside) break;
    interpolatectx->accumulated += factor * value;
            }
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief calculate one interpolated value, and append it to the vector of interpolated values
 * This function calls accumulate_pivots() to get the interpolated value, and appends it to
 * the interpolated argument.
 * 
 * @param geofield geo field
 * @param ctx the context that will be passed for @handler
 * @param interpolateinfo information about two values that each axis should take care of (the number of elements is equal to rank)
 * @param[in,out] interpolated vector of interpolated values
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int finish_one_point(const fieldinfo_t *geofield, const cartesian_product_context_t *ctx, const interpolatedinfoalloc *interpolateinfo, float64alloc *interpolated)
{
    intalloc pivotindices;
    int succeeded = 0;

    intalloc_init(&pivotindices);
    do {
        float64 *value;
        interpolatecontext_t interpolatectx;
        hid_t h4toh5id = ctx->h4toh5id;

        if (!float64alloc_readyplus(interpolated, 1)) { H4TOH5ERROR_NOMEM1("float64 for interpolated"); break; }
        value = &interpolated->s[interpolated->len++];

        if (0) {
            printf("    0: %d, %d (dist %d %d) ; ", interpolateinfo->s[0].leftpoint, interpolateinfo->s[0].rightpoint, interpolateinfo->s[0].leftdist, interpolateinfo->s[0].rightdist);
            printf("    1: %d, %d (dist %d %d)\n", interpolateinfo->s[1].leftpoint, interpolateinfo->s[1].rightpoint, interpolateinfo->s[1].leftdist, interpolateinfo->s[1].rightdist);
        }

        if (!intalloc_ready(&pivotindices, ctx->queueindices->len)) { H4TOH5ERROR_NOMEM1("pivotindices"); break; }
        pivotindices.len = ctx->queueindices->len;
        interpolatectx.accumulated = 0.0f;
        if (accumulate_pivots(geofield, interpolateinfo, 0, &pivotindices, &interpolatectx, ctx) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        *value = interpolatectx.accumulated;

        succeeded = 1;
    } while (0);

    if (1) {
        intalloc_free(&pivotindices);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief get the index in the geolocation field corresponding to the given the index in the data field when increment is positive
 * 
 * @param dataindex the index in the data field
 * @param dimmap dimension map giving offset and increment
 * 
 * @return the index in the geolocation field
 */
static int le_geoindex_from_dataindex(int dataindex, const dimmap_t *dimmap)
{
    int r = -1;
    H4TOH5ASSERT(dimmap->increment > 0)
    if (dimmap->offset >= 0)
        r = (dataindex - dimmap->offset) / dimmap->increment;
    else
        r = dataindex / dimmap->increment + -dimmap->offset;
    return r;
}

/** 
 * @brief get the index in the geolocation field corresponding to the given the index in the data field when increment is negative
 * 
 * @param dataindex the index in the data field
 * @param dimmap dimension map giving offset and increment
 * 
 * @return the index in the geolocation field
 */
static int geoindex_from_dataindex(int dataindex, const dimmap_t *dimmap)
{
    int r = -1;
    H4TOH5ASSERT(dimmap->increment < 0)
    if (dimmap->offset >= 0)
        r = (dataindex - dimmap->offset) * -dimmap->increment;
    else
        r = dataindex * -dimmap->increment + -dimmap->offset;
    return r;
}

/** 
 * @brief get the index in the data field corresponding to the given the index in the geolocation field
 * 
 * @param geoindex the index in the geolocation field
 * @param dimmap dimension map giving offset and increment
 * 
 * @return the index in the data field
 */
static int dataindex_from_geoindex(int geoindex, const dimmap_t *dimmap)
{
    int r = -1;
    H4TOH5ASSERT(dimmap->increment > 0)
    if (dimmap->offset >= 0)
        r = geoindex * dimmap->increment + dimmap->offset;
    else
        r = (geoindex + dimmap->offset) * dimmap->increment;
    return r;
}

/** 
 * @brief generate interpolated values for data fields
 * The dimension map defined in HDF-EOS2 Library User's Guide. Each dimension map has
 * four elements: dimension of data field, dimension of geo field, offset and increment.
 * If "increment" is positive (greater than 1), dimension of geo field has smaller number of
 * elements than dimension of data field. So, values in geo field are interpolated.
 * If "increment" is negative, dimension of data field has smaller number of elements.
 * For this case, some values from geo fields are dropped to create a new geo field that
 * has the same number of elements the data field has.
 *
 * Also, the meaning of "offset" is slightly different according to the "increment".
 * If "increment" is positive, "offset" tells where the first element of the geo field falls
 * into the data field. If "increment" is negative, howver, "offset" tells which element of
 * the geo field corresponds to the first element of the data field.
 *
 * This function iterates each point in the data field. For each point, this function also
 * collects a list of interpolate_t for each dimension. This element contains two pivot points
 * and distances from pivots for each dimension. After finishing collecting interpolate_t data
 * for all dimensions, it calls finish_one_point() which will append one interpolated point
 * corresponding to one point in the data field.
 * @see interpolateinfo_t
 * 
 * @param geofield geo field
 * @param geodimindex index of dimension (depth of recursion)
 * @param ctx the context that will be passed for @handler
 * @param[in,out] interpolateinfo information about two values that each axis should take care of (the number of elements is equal to rank)
 * @param[in,out] interpolated vector of interpolated values
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int generate_interpolated_geofield(const fieldinfo_t *geofield, int geodimindex, const cartesian_product_context_t *ctx, interpolatedinfoalloc *interpolateinfo, float64alloc *interpolated)
{
    int succeeded = 0;

    H4TOH5ASSERT_RANGE(geodimindex, 0, geofield->dims.len)
    H4TOH5ASSERT_RANGE(geodimindex, 0, ctx->queueindices->len)
    H4TOH5ASSERT_RANGE(ctx->queueindices->s[geodimindex], 0, ctx->queue->len)
    H4TOH5ASSERT_RANGE(geodimindex, 0, ctx->datadimindices->len)
    H4TOH5ASSERT_RANGE(ctx->datadimindices->s[geodimindex], 0, ctx->datafield->dims.len)

    do {
        const dimension_t *geodim = &geofield->dims.s[geodimindex];
        const dimmap_t *dimmap = ctx->queue->s[ctx->queueindices->s[geodimindex]].dimmap;
        const dimension_t *datadim = &ctx->datafield->dims.s[ctx->datadimindices->s[geodimindex]];
        interpolateinfo_t *info = &interpolateinfo->s[geodimindex];
        hid_t h4toh5id = ctx->h4toh5id;
        int i, failinside = 0;

        if (dimmap->increment == 0) { H4TOH5ERROR_SET1(3, "increment is equal to 0"); break; }
        for (i = 0; i < datadim->dimsize; ++i) {
            /* if "increment" is positive, dimension of geo field has smaller number of elements */
            if (dimmap->increment > 0) {
                int geoindex = le_geoindex_from_dataindex(i, dimmap);
                /* beyond the left boundary */
                if (geoindex <= 0) {
                    info->leftpoint = 0;
                    info->rightpoint = 1;
                    if (info->rightpoint < geodim->dimsize) {
                        info->leftdist = i - dataindex_from_geoindex(info->leftpoint, dimmap);
                        info->rightdist = dataindex_from_geoindex(info->rightpoint, dimmap) - i;
                    }
                    else {
                        info->rightpoint = 0;
                        info->leftdist = -1;
                        info->rightdist = -1;
                    }
                }
                /* beyound the right boundary */
                else if (geoindex >= geodim->dimsize - 1) {
                    info->rightpoint = geodim->dimsize - 1;
                    info->leftpoint = info->rightpoint - 1;
                    if (info->leftpoint >= 0) {
                        info->leftdist = i - dataindex_from_geoindex(info->leftpoint, dimmap);
                        info->rightdist = dataindex_from_geoindex(info->rightpoint, dimmap) - i;
                    }
                    else {
                        info->leftpoint = geodim->dimsize - 1;
                        info->leftdist = -1;
                        info->rightdist = -1;
                    }
                }
                /* middle */
                else {
                    info->leftpoint = geoindex;
                    info->rightpoint = info->leftpoint + 1;
                    info->leftdist = i - dataindex_from_geoindex(info->leftpoint, dimmap);
                    info->rightdist = dataindex_from_geoindex(info->rightpoint, dimmap) - i;
                }
            }
            /* if "increment" is negative, dimension of data field has smaller number of elements */
            else {
                int geoindex = geoindex_from_dataindex(i, dimmap);
                /* beyond the left boundary */
                if (geoindex <= 0) {
                    info->leftpoint = 0;
                    info->rightpoint = -dimmap->increment;
                    if (info->rightpoint < geodim->dimsize) {
                        info->leftdist = geoindex - info->leftpoint;
                        info->rightdist = info->rightpoint - geoindex;
                    }
                    else {
                        info->rightpoint = 0;
                        info->leftdist = -1;
                        info->rightdist = -1;
                    }
                }
                /* beyond the right boundary */
                else if (geoindex >= geodim->dimsize - 1) {
                    info->rightpoint = geodim->dimsize - 1;
                    info->leftpoint = info->rightpoint - -dimmap->increment;
                    if (info->leftpoint >= 0) {
                        info->leftdist = geoindex - info->leftpoint;
                        info->rightdist = info->rightpoint - geoindex;
                    }
                    else {
                        info->leftpoint = geodim->dimsize - 1;
                        info->leftdist = -1;
                        info->rightdist = -1;
                    }
                }
                /* middle */
                else {
                    info->leftpoint = geoindex;
                    info->rightpoint = geoindex;
                    info->leftdist = -1;
                    info->rightdist = -1;
                }
            }

            {
                if (info->leftpoint < 0 || info->rightpoint >= geodim->dimsize) {
                    H4TOH5ASSERT(0)
                }
                if (info->rightpoint > 0 && info->leftpoint < geodim->dimsize - 1) {
                    if (info->leftdist == -1 && info->rightdist == -1) {
                        H4TOH5ASSERT(info->leftpoint == info->rightpoint)
                    }
                    else if (dimmap->increment > 0) {
                        H4TOH5ASSERT(info->leftdist + info->rightdist == dimmap->increment)
                        H4TOH5ASSERT(info->leftpoint + 1 == info->rightpoint)
                    }
                    else {
                        H4TOH5ASSERT(info->leftdist + info->rightdist == -dimmap->increment)
                        H4TOH5ASSERT(info->leftpoint + -dimmap->increment == info->rightpoint)
                    }
                }
                else {
                    H4TOH5ASSERT(info->leftpoint == info->rightpoint)
                    H4TOH5ASSERT(info->leftdist == -1 && info->rightdist == -1)
                }
            }

            if (geodimindex < ctx->queueindices->len - 1) {
                /* go to next dimension */
                if (generate_interpolated_geofield(geofield, geodimindex + 1, ctx, interpolateinfo, interpolated) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
            else {
                /* append one interpolated value */
                if (finish_one_point(geofield, ctx, interpolateinfo, interpolated) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief mangle HDF5 dataset name from geofield and a set of pairs of <offset, increment>
 * 
 * @param geofield the original geolocation field
 * @param ctx the context that will be passed for @handler
 * @param[out] mangled mangled name
 *
 * If the name of geo field is "Longitude", and it is mapped by two dimension maps whose <offset, increment> are <5, 10>, <5, 10> repectively,
 * the mangled name becomes "Longitude_5:10_5:10".
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int mangle_dimscale_name(const fieldinfo_t *geofield, const cartesian_product_context_t *ctx, challoc *mangled)
{
    int succeeded = 0;

    do {
        hid_t h4toh5id = ctx->h4toh5id;
        int i, failinside = 0;

        if (!challoc_copy(mangled, &geofield->name)) { H4TOH5ERROR_NOMEM1("mangled"); break; }
        for (i = 0; i < ctx->queueindices->len; ++i) {
            const dimmap_t *dim = ctx->queue->s[ctx->queueindices->s[i]].dimmap;
            if (!challoc_catb(mangled, "_", 1)) { H4TOH5ERROR_NOMEM1("mangled"); failinside = 1; break; }
            if (!challoc_catlong(mangled, dim->offset, 0)) { H4TOH5ERROR_NOMEM1("mangled"); failinside = 1; break; }
            if (!challoc_catb(mangled, ":", 1)) { H4TOH5ERROR_NOMEM1("mangled"); failinside = 1; break; }
            if (!challoc_catlong(mangled, dim->increment, 0)) { H4TOH5ERROR_NOMEM1("mangled"); failinside = 1; break; }
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief check if the specified combination is exactly matched with defined dimensional scales
 * To write generic code, fake dimension map is generated at the beginning. Here, it prunes out
 * the fake dimension map so that only real dimension map introduces interpolated dimensional scales.
 * @see create_default_dimmap
 * 
 * @param geofield original geolocation field
 * @param ctx the context that holds the information which index points which dimension map
 * @param h5geofieldpath HDF5 group name where the geolocation group resides
 * @param[out] dimscalepath the absolute path for defined dimensional scales
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int try_defined_dimscale(const fieldinfo_t *geofield, const cartesian_product_context_t *ctx, const challoc *h5geofieldpath, challoc *dimscalepath)
{
    int succeeded = 0;

    do {
        hid_t h4toh5id = ctx->h4toh5id;
        int i, candidate = 1;

        for (i = 0; i < ctx->queueindices->len; ++i) {
            const dimmap_t *dim = ctx->queue->s[ctx->queueindices->s[i]].dimmap;
            if (strcmp(dim->geo.s, dim->data.s) == 0) {
                H4TOH5ASSERT(dim->offset == 0 && dim->increment == 1)
                continue;
            }
            candidate = 0;
            break;
        }

        challoc_empty(dimscalepath);
        if (candidate) {
            if (!challoc_copy(dimscalepath, h5geofieldpath)) { H4TOH5ERROR_NOMEM1("dimscale path"); break; }
            if (!challoc_cats(dimscalepath, "/")) { H4TOH5ERROR_NOMEM1("dimscale path"); break; }
            if (!challoc_cat(dimscalepath, &geofield->name)) { H4TOH5ERROR_NOMEM1("dimscale path"); break; }
        }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief replace "Data Fields" with "Geolocation Fields" if necessary
 * 
 * @param h4toh5id h4toh5 identifier
 * @param datafieldpath path that ends with "/Data Fields" or "/Geolocation Fields"
 * @param[out] geofieldpath result
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_h5swathgeofieldpath(hid_t h4toh5id, const char *datafieldpath, challoc *geofieldpath)
{
    int succeeded = 0;

    do {
        int geovglen, pathlen, i, succeedinside = 0;

        geovglen = strlen(GEOFIELD_VGROUP);
        pathlen = strlen(datafieldpath);

        for (i = 0; i < 2; ++i) {
            const char *group = i == 0 ? DATAFIELD_VGROUP : GEOFIELD_VGROUP;
            int vglen = (int)strlen(group);

            if (pathlen < vglen + 1) continue;
            if (datafieldpath[pathlen - vglen - 1] != '/') continue;
            if (!challoc_copyb(geofieldpath, datafieldpath, pathlen - vglen)) continue;
            if (!challoc_cats(geofieldpath, GEOFIELD_VGROUP)) continue;
            succeedinside = 1;
            break;
        }
        if (!succeedinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief extract Swath group from "Data Fields" or "Geolocation Fields" if necessary
 * 
 * @param h4toh5id h4toh5 identifier
 * @param datafieldpath path that ends with "/Data Fields" or "/Geolocation Fields"
 * @param[out] geofieldpath result
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int get_h5swathpath(hid_t h4toh5id, const char *datafieldpath, challoc *geofieldpath)
{
    int succeeded = 0;

    do {
        int geovglen, pathlen, i, succeedinside = 0;

        geovglen = strlen(GEOFIELD_VGROUP);
        pathlen = strlen(datafieldpath);

        for (i = 0; i < 2; ++i) {
            const char *group = i == 0 ? DATAFIELD_VGROUP : GEOFIELD_VGROUP;
            int vglen = (int)strlen(group);

            if (pathlen < vglen + 1) continue;
            if (datafieldpath[pathlen - vglen - 1] != '/') continue;
            if (!challoc_copyb(geofieldpath, datafieldpath, pathlen - vglen - 1)) continue;
            succeedinside = 1;
            break;
        }
        if (!succeedinside) break;

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief try to create HDF5 dimension scale dataset and write interpolated value if this dimension scale has not been handled yet
 * When one possible candidate of dimensional scale is assembled, this function is called.
 * This function first checks if this case is already handled. If not handled, it creates
 * an HDF5 dimensional scale and fills values with interpolated data.
 * 
 * @param geofield geo field
 * @param ctx the context that will be passed for @handler
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int handle_one_cartesian_product(const fieldinfo_t *geofield, const cartesian_product_context_t *ctx)
{
    challoc mangled;
    challoc h5geofieldpath;
    challoc h5swathpath;
    dimensionalloc dims;
    hobjreftalloc dimscales;
    interpolatedinfoalloc interpolateinfo;
    float64alloc interpolated;
    hid_t group = -1;
    int succeeded = 0;

    challoc_init(&mangled);
    challoc_init(&h5geofieldpath);
    challoc_init(&h5swathpath);
    dimensionalloc_init(&dims);
    hobjreftalloc_init(&dimscales);
    interpolatedinfoalloc_init(&interpolateinfo);
    float64alloc_init(&interpolated);
    do {
        hid_t h4toh5id = ctx->h4toh5id;
        const float64 *data = NULL;
        hid_t datatype = H5T_IEEE_F64BE;
        hid_t memtype = H5T_NATIVE_DOUBLE;
        coordinate_t coord;
        int handled = 0, created, i, failinside = 0;

        /* If h5groupname points datafield group rather than geolocation group, corrects it */
        /* because I want to keep dimensional scales inside of geolocation group. */
        if (get_h5swathgeofieldpath(ctx->h4toh5id, ctx->h5swathdatafieldpath, &h5geofieldpath) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (get_h5swathpath(ctx->h4toh5id, ctx->h5swathdatafieldpath, &h5swathpath) == FAIL) { H4TOH5ERROR_SET0(3); break; }

        /* check if this case is exactly matched with dimensional scales that were defined in the source file */
        if (try_defined_dimscale(geofield, ctx, &h5geofieldpath, &mangled) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (mangled.len > 0) {
            /* if matched, nothing needs to be done */
            get_coordinate_from_variable(geofield->name.s, &coord);
        }
        else {
            if ((group = get_h5groupid(h5geofieldpath.s, h4toh5id,0)) == -1) { H4TOH5ERROR_SET0(3); break; }

            /* Create dimensions that were attached to the geolocation field that */
            /* this combination originated from. */
            for (i = 0; i < ctx->queueindices->len; ++i) {
                const swathdim_t *swathdim = &ctx->queue->s[ctx->queueindices->s[i]];
                coordinate_t geocoord;
                const dimension_t *geodim = &geofield->dims.s[i];
                const dimension_t *datadim = &ctx->datafield->dims.s[ctx->datadimindices->s[i]];
                dimension_t *dim;

                H4TOH5ASSERT(strcmp(swathdim->dimmap->geo.s, geodim->name.s) == 0)
                H4TOH5ASSERT(strcmp(swathdim->dimmap->data.s, datadim->name.s) == 0)

                get_coordinate_from_dimension(geodim->name.s, &geocoord);
                if (write_1d_empty_eos_dimscale(h4toh5id, h5swathpath.s, &geocoord, geodim->dimsize, NULL) == FAIL) { H4TOH5ERROR_SET0(3); failinside = 1; break; }

                if (!dimensionalloc_readyplus(&dims, 1)) { H4TOH5ERROR_NOMEM1("dimensions"); failinside = 1; break; }
                dim = &dims.s[dims.len++];
                init_dimension_t(dim);
                if (!challoc_copy(&dim->name, &datadim->name)) { H4TOH5ERROR_NOMEM1("dim name"); failinside = 1; break; }
                dim->dimsize = datadim->dimsize;

                if (!hobjreftalloc_readyplus(&dimscales, 1)) { H4TOH5ERROR_NOMEM1("dimscales"); failinside = 1; break; }
                dimscales.s[dimscales.len++] = ctx->dimscales->s[swathdim->datadimindex];
            }
            if (failinside) break;

            if (mangle_dimscale_name(geofield, ctx, &mangled) == FAIL) { H4TOH5ERROR_SET0(3); break; }

            get_coordinate_from_variable(mangled.s, &coord);
            if (probe_eos_dimscale(ctx->h4toh5id, h5geofieldpath.s, &coord, &handled) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (!handled) {
                /* allocate buffer and fill interpolated values */
                if (!interpolatedinfoalloc_ready(&interpolateinfo, ctx->queueindices->len)) { H4TOH5ERROR_NOMEM1("datadimindices"); break; }
                interpolateinfo.len = ctx->queueindices->len;
                {
#ifndef EAGER_DATA_FETCH
                    fieldinfo_t *mutablefield = (fieldinfo_t *)geofield;
                    if (prepare_fieldinfo_lazydata(h4toh5id, 0, mutablefield) == -1) { H4TOH5ERROR_SET2(3, "read data", geofield->name.s); break; }
#endif
                    if (generate_interpolated_geofield(geofield, 0, ctx, &interpolateinfo, &interpolated) == FAIL) { H4TOH5ERROR_SET0(3); break; }
#ifndef EAGER_DATA_FETCH
                    if (drop_fieldinfo_lazydata(h4toh5id, mutablefield) == -1) { H4TOH5ERROR_SET2(3, "drop data", geofield->name.s); break; }
#endif
                }
                data = interpolated.s;
            }
            /* create the interpolated (or shrunken) HDF5 dimensional scale */
            if (write_eos_dimscale(ctx->h4toh5id, h5geofieldpath.s, &coord, &dims, datatype, memtype, data, &created) == FAIL) { H4TOH5ERROR_SET0(3); break; }
            if (created) {
                hobj_ref_t dimref;
                if (get_ref_eos_dimscale(h4toh5id, h5geofieldpath.s, coord.name, &dimref) == -1) { H4TOH5ERROR_SET2(3, "create reference", coord.name); break; }
                /* attach dimensions that were attached to the original geolocation field to the new dimensional scale; */
                /* e.g. dimensions such as geodim0 and geodim1 from Latitude will be attached to the Latitude_5:10_5:10. */
                if (attach_dimscales_byref(h4toh5id, group, dimref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }
                /* to copy all atttributes in the Latitude to Latitude_5:10_5:10, stores mapping */
                if (add_pending_hdf4attrs(h4toh5id, ctx->swath, geofield, dimref) == -1) { H4TOH5ERROR_SET0(3); break; }
            }
        }

        if (accumulate_swath_coordinates(h4toh5id, ctx->coordinates, &coord) == -1) { H4TOH5ERROR_SET0(3); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        if (group != -1) H5Gclose(group);
        float64alloc_free(&interpolated);
        interpolatedinfoalloc_free(&interpolateinfo);
        hobjreftalloc_free(&dimscales);
        dimensionalloc_free(&dims);
        challoc_free(&mangled);
        challoc_free(&h5geofieldpath);
        challoc_free(&h5swathpath);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create HDF5 dimensions that were attached to the given HDF-EOS2 Swath field.
 * This function creates HDF5 dimensions. Attaching will be done other functions, write_swath_dimscale().
 *
 * @param h4toh5id h4toh5 identifier
 * @param swath HDF-EOS2 Swath
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param hdf4dims mapping from HDF4 dimension name to its type
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_swath_dimensions(hid_t h4toh5id, const swath_t *swath, const char *h5groupname, const hdf4dimalloc *hdf4dims)
{
    hid_t swathgroup = -1;
    challoc h5swathpath;
    int succeeded = 0;

    challoc_init(&h5swathpath);
    do {
        int i, failinside = 0;

        /* If h5groupname points datafield group or geolocation group, corrects it
         * because I want to keep all dimensions and dimensional scales inside of swath group. */
        if (get_h5swathpath(h4toh5id, h5groupname, &h5swathpath) == -1) { H4TOH5ERROR_SET0(3); break; }
        if ((swathgroup = get_h5groupid(h5swathpath.s, h4toh5id,0)) == -1) { H4TOH5ERROR_SET0(3); break; }

        for (i = 0; i < swath->dim.len; ++i) {
            const dimension_t *dim = &swath->dim.s[i];
            int32 dimtype = find_dimtype(hdf4dims, dim->name.s);
            coordinate_t coord;

            if (!(dimtype == -1 || dimtype == 0)) { H4TOH5ERROR_SET1(3, "dimension has type"); failinside = 1; break; }

            get_coordinate_from_dimension(dim->name.s, &coord);
            if (write_1d_empty_eos_dimscale(h4toh5id, h5swathpath.s, &coord, dim->dimsize, NULL) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&h5swathpath);
        if (swathgroup != -1) H5Gclose(swathgroup);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief pick references to HDF5 dimensions that were attached to the given
 * HDF-EOS2 Swath field. This function does not create HDF5 dimensions, but
 * just assumes that write_swath_dimensions() is already called and this
 * function created all dimensions belonging to the Swath. Attaching will be
 * done other functions, write_swath_dimscale().
 * @see write_swath_dimensions
 * 
 * @param h4toh5id h4toh5 identifier
 * @param field HDF-EOS2 datafield or geolocation field to investigate
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param isgeofield 1 if fieldindex specifies HDF-EOS2 Swath geolocation field
 * @param[out] dimscales list of references to dimensions created in this function
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int prepare_swath_dimensions(hid_t h4toh5id, const fieldinfo_t *field, const char *h5groupname, int isgeofield, hobjreftalloc *dimscales)
{
    hid_t swathgroup = -1;
    challoc h5swathpath;
    int succeeded = 0;

    challoc_init(&h5swathpath);
    do {
        int i, failinside = 0;

        if (get_h5swathpath(h4toh5id, h5groupname, &h5swathpath) == -1) { H4TOH5ERROR_SET0(3); break; }
        if ((swathgroup = get_h5groupid(h5swathpath.s, h4toh5id,0)) == -1) { H4TOH5ERROR_SET0(3); break; }

        if (!hobjreftalloc_ready(dimscales, field->dims.len)) { H4TOH5ERROR_NOMEM1("obj ref list"); break; }
        for (i = 0; i < field->dims.len; ++i)
            dimscales->s[dimscales->len++] = -1;

        for (i = 0; i < field->dims.len; ++i) {
            const dimension_t *dim = &field->dims.s[i];
            hobj_ref_t dimref;

            if (get_ref_eos_dimscale(h4toh5id, h5swathpath.s, dim->name.s, &dimref) == -1) { H4TOH5ERROR_SET2(3, "create reference", dim->name.s); failinside = 1; break; }
            dimscales->s[i] = dimref;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&h5swathpath);
        if (swathgroup != -1) H5Gclose(swathgroup);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief for given HDF-EOS2 Swath, finds all possible combinations of dimension scales, create HDF5 dimensional scales, and attaches them to the HDF5 dataset
 * 
 * MOD10_L2.hdf file has one swath data (MOD_Swath_Snow) which contains several data fields, geo fields, dimensions and dimension maps.
 * This function is called for every data field and geo field.
 *
 * Suppose that
 *   - "Snow_Cover" data field is passed,
 *   - "Snow_Cover" defines two dimensions for each dimension: "Along_swath_lines_500m" and "Cross_swath_pixels_500m".
 *   - There are a dimension map from "Coarse_swath_lines_5km" -> "Along_swath_lines_500m", and
 *                           a dimension map from "Coarse_swath_pixels_5km" -> "Cross_swath_pixels_500m".
 *   - There are two geo fields: "Latitude" and "Longitude" use "Coarse_swath_lines_5km" and "Coarse_swath_pixels_5km".
 *
 * Then, the flow will be like the following:
 *   - Create dimensions for "Along_swath_lines_500m" and "Cross_swath_pixels_500m". (@see prepare_swath_dimensions)
 *   - Create dimensional scales for all possible combinations.
 * The second step is done by the following procedure.
 * datafield = Snow_Cover {
 *      queue init
 * 
 *      using dim[0] = Along_swath_lines_500m {
 *           matched dimmap [ Coarse_swath_lines_5km ] {
 *                  geofield [ Latitude ] {
 *                       queue <- Latitude : 5 : 10 : ? : ?
 *                  }
 *                  geofield [ Longitude ] {
 *                       queue <- Longitude : 5 : 10 : ? : ?
 *                  }
 *           }
 *      }
 * 
 *      using dim[1] = Cross_swath_pixels_500m {
 *           matched dimmap [ Coarse_swath_pixels_5km ] {
 *                  geofield [ Latitude ] {
 *                       queue <- Latitude : ? : ? : 5 : 10
 *                  }
 *                  geofield [ Longitude ] {
 *                       queue <- Longitude : ? : ? : 5 : 10
 *                  }
 *           }
 *      }
 * 
 *      queue == {
 *           Latitude : 5 : 10 : ? : ?,  Latitude : ? : ? : 5 : 10
 *           Longtidue : 5 : 10 : ? : ?,    Longtidue : ? : ? : 5 : 10
 *      }
 *
 *      cartesian product -> Latitude_5:10_5:10, Longitude_5:10_5:10
 * }
 *
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param h5dataset HDF5 dataset identifier
 * @param h5datasetref reference to h5dataset
 * @param swath HDF-EOS2 Swath
 * @param isgeofield 1 if fieldindex specifies HDF-EOS2 Swath geolocation field
 * @param fieldindex index of geolocation field or data field in the Swath
 * @param hdf4dims mapping from HDF4 dimension name to its type
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_swath_dimscale(hid_t h4toh5id, const char *h5groupname, hid_t h5dataset, hobj_ref_t h5datasetref, swath_t *swath, int isgeofield, int fieldindex, const hdf4dimalloc *hdf4dims)
{
    swathdimalloc queue;
    intalloc queueindices;
    intalloc datadimindices;
    hobjreftalloc dimscales;
    challoc coordinates;
    int succeeded = 0;

    swathdimalloc_init(&queue);
    intalloc_init(&queueindices);
    intalloc_init(&datadimindices);
    challoc_init(&coordinates);
    hobjreftalloc_init(&dimscales);
    do {
        const fieldinfo_t *field = isgeofield ? &swath->geofield.s[fieldindex] : &swath->datafield.s[fieldindex];
        int i, j, k, l, failinside_i = 0, failinside_j = 0, failinside_k = 0, failinside_l = 0;
        cartesian_product_context_t handlerctx;

        /* create all dimensions attached to this sds */
        if (write_swath_dimensions(h4toh5id, swath, h5groupname, hdf4dims)) { H4TOH5ERROR_SET0(3); break; }
        if (prepare_swath_dimensions(h4toh5id, field, h5groupname, isgeofield, &dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }

        if (isgeofield) {
            if (fill_pending_hdf4attrs(h4toh5id, swath, field, h5datasetref) == -1) { H4TOH5ERROR_SET0(3); break; }
        }
        else {
            if (create_default_dimmap(h4toh5id, swath, hdf4dims) == -1) { H4TOH5ERROR_SET0(3); break; }
            /* foreach dimension in this sds */
            for (i = 0; i < hdf4dims->len; ++i) {
                const hdf4dim_t *hdf4dim = &hdf4dims->s[i];
                /* foreach dimmap whose datafield is equal to one dimension in this sds */
                for (j = 0; j < swath->dimmap.len; ++j) {
                    const dimmap_t *dimmap = &swath->dimmap.s[j];
                    if (strcmp(hdf4dim->name.s, dimmap->data.s)) continue;
                    /* foreach geofield whose dimension contains the dimmap */
                    for (k = 0; k < swath->geofield.len; ++k) {
                        const fieldinfo_t *geofield = &swath->geofield.s[k];
                        /* foreach dimension in this geofield */
                        for (l = 0; l < geofield->dims.len; ++l) {
                            if (strcmp(dimmap->geo.s, geofield->dims.s[l].name.s)) continue;
                            if (enqueue_swathdim(h4toh5id, &queue, geofield, i, l, dimmap) == FAIL) { H4TOH5ERROR_SET0(3); failinside_l = 1; break; }
                        }
                        if (failinside_l) { H4TOH5ERROR_SET0(3); failinside_k = 1; break; }
                    }
                    if (failinside_k) { H4TOH5ERROR_SET0(3); failinside_j = 1; break; }
                }
                if (failinside_j) { H4TOH5ERROR_SET0(3); failinside_i = 1; break; }
            }
            if (failinside_i) break;

            if (queue.len) {
                if (!intalloc_ready(&queueindices, hdf4dims->len)) { H4TOH5ERROR_NOMEM1("queueindices"); break; }
                if (!intalloc_ready(&datadimindices, hdf4dims->len)) { H4TOH5ERROR_NOMEM1("datadimindices"); break; }

                /* traverse queue to try every possible combination */
                init_cartesian_product_context(&handlerctx, swath, &queue, field, &dimscales, h4toh5id, h5groupname, &queueindices, &datadimindices, &coordinates);
                if (traverse_cartesian_product(&queue, 0, handle_one_cartesian_product, &handlerctx) == -1) { H4TOH5ERROR_SET0(3); break; }
                if (coordinates.len > 0) {
                    /* if "Longitude" and "Latitude" are generated, this dataset needs this attribute to conform to CF convention */
                    if (write_attribute_scalar(h4toh5id, h5dataset, "coordinates", coordinates.s) == -1) { H4TOH5ERROR_SET0(3); break; }
                }
            }
            remove_default_dimmap(h4toh5id, swath, hdf4dims);
        }

        /* attach all dimensions attached to this sds to the created HDF5 dataset */
        if (attach_dimscales(h4toh5id, h5dataset, h5datasetref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        hobjreftalloc_free(&dimscales);
        challoc_free(&coordinates);
        intalloc_free(&queueindices);
        intalloc_free(&datadimindices);
        swathdimalloc_free(&queue);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief try to read HDF-EOS2 Swath data from the given HDF4 sds or vdata, and calls conversion function if it is Swath
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param ishdf4sds 1 if the HDF4 object is sds
 * @param objectid HDF4 sds or vdata identifier
 * @param h5dataset HDF5 dataset identifier
 * @param h5datasetref reference to h5dataset
 * @param[out] handled 1 if this HDF4 object is EOS2 Swath datatype and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int generic_eosswath_dimscale(hid_t h4toh5id, const char *h5groupname, int ishdf4sds, int32 objectid, hid_t h5dataset, hobj_ref_t h5datasetref, int *handled)
{
    hdf4dimalloc hdf4dims;
    int datasetcreated = 0;
    int succeeded = 0;

    *handled = 0;
    hdf4dimalloc_init(&hdf4dims);
    do {
        swath_t *swath;
        swath_aux_t *aux;
        int fieldindex, isgeofield;

        if (check_geofield(h5groupname, &isgeofield) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (read_swath(h4toh5id, h5groupname, ishdf4sds, objectid, isgeofield, &fieldindex, &swath, &aux, &hdf4dims) == FAIL)
            H4toH5error_reset(h4toh5id);
        else {
            const fieldinfo_t *field = 0;
            if (fieldindex == -1) break;
            field = isgeofield ? &swath->geofield.s[fieldindex] : &swath->datafield.s[fieldindex];

            if (h5dataset == -1) {
                /* create an HDF5 dataset on-the-fly if HDF4 vdata is given */
                if (create_vdata(h4toh5id, h5groupname, field, objectid, 0, &h5dataset, &h5datasetref) == -1) { H4TOH5ERROR_SET0(3); break; }
                datasetcreated = 1;
            }
            else {
                if (augment_sds(h4toh5id, objectid, h5groupname, field, h5dataset) == -1) { H4TOH5ERROR_SET0(3); break; }
            }
            if (write_swath_dimscale(h4toh5id, h5groupname, h5dataset, h5datasetref, swath, isgeofield, fieldindex, &hdf4dims) == -1) { H4TOH5ERROR_SET0(3); break; }

            *handled = 1;
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (datasetcreated && h5dataset != -1) H5Dclose(h5dataset);
        hdf4dimalloc_free(&hdf4dims);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief try to convert an HDF4 sds which may be an HDF-EOS2 geolocation field or data field into an HDF5 dimensional scale
 * 
 * @param h4toh5id h4toh5id
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param sdsid HDF4 sds identifier
 * @param h5dataset the converted HDF5 identifier corresponding to the HDF4 sds
 * @param h5datasetref reference to h5dataset
 * @param[out] handled 1 if the given HDF4 sds is an HDF-EOS2 attribute and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5sds_eosswath_dimscale(hid_t h4toh5id, const char *h5groupname, int32 sdsid, hid_t h5dataset, hobj_ref_t h5datasetref, int *handled)
{
    return generic_eosswath_dimscale(h4toh5id, h5groupname, 1, sdsid, h5dataset, h5datasetref, handled);
}

/** 
 * @brief try to convert an HDF4 vdata which may be an HDF-EOS2 geolocation field or data field into an HDF5 dimensional scale
 * 
 * @param h4toh5id h4toh5id
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param vdataid HDF4 vdata identifier
 * @param[out] handled 1 if the given HDF4 vdata is an HDF-EOS2 attribute and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5vdata_eosswath_dimscale(hid_t h4toh5id, const char *h5groupname, int32 vdataid, int *handled)
{
    return generic_eosswath_dimscale(h4toh5id, h5groupname, 0, vdataid, -1, -1, handled);
}

/** 
 * @brief read an HDF-EOS2 attribute by using HDF-EOS API
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param strname HDF4 vdata name which may be the name of an HDF-EOS2 attribute
 * @param isgrid true if this is HDF-EOS2 Grid datatype
 * @param[out] attrs list of HDF-EOS2 attributes
 * @param[out] attrindex index of corresponding attribute in attrs
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int read_attr(hid_t h4toh5id, const char *h5groupname, const char *strname, int isgrid, attributealloc *attrs, int *attrindex)
{
    int32 eosfd = -1;
    int32 strid = -1;
    challoc eosgroupname;
    int succeeded = 0;

    challoc_init(&eosgroupname);
    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
        int i;

        if (extract_eosgroup_origname(h4toh5id, h5groupname, &eosgroupname) == -1) { H4TOH5ERROR_SET0(3); break; }
        if ((eosfd = isgrid ? GDopen_const(dt->hdf4filename, DFACC_READ) : SWopen_const(dt->hdf4filename, DFACC_READ)) == -1) break;
        if ((strid = isgrid ? GDattach_const(eosfd, eosgroupname.s) : SWattach(eosfd, eosgroupname.s)) == -1) break;

        if ((isgrid ? get_gridattribute(h4toh5id, strid, attrs) : get_swathattribute(h4toh5id, strid, attrs)) == -1) { H4TOH5ERROR_SET0(3); break; }

        *attrindex = -1;
        for (i = 0; i < attrs->len; ++i) {
            if (strcmp(attrs->s[i].name.s, strname) == 0) {
                *attrindex = i;
                break;
            }
        }
        if (*attrindex == -1) break;

        succeeded = 1;
    } while (0);

    if (1) {
        if (strid != -1) isgrid ? GDdetach(strid) : SWdetach(strid);
        if (eosfd != -1) isgrid ? GDclose(eosfd) : SWclose(eosfd);
        challoc_free(&eosgroupname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief check if the given fieldname belongs to the grid specified by gdid
 * 
 * @param h4toh5id h4toh5 identifier
 * @param gdid grid identifier
 * @param fieldname the name of field that we're looking for
 * @param[out] exists non-zero if the field belongs to the grid
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_grid_field_existence(hid_t h4toh5id, int32 gdid, const char *fieldname, int *exists)
{
    char *namelist = NULL;
    int succeeded = 0;

    do {
        int32 numfields, bufsize;

        if ((numfields = GDnentries(gdid, HDFE_NENTDFLD, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo entries failure"); break; }
        if (numfields > 0) {
            int i, j;

            if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("fieldinfo namelist"); break; }
            if (GDinqfields(gdid, namelist, NULL, NULL) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo inq failure"); break; }

            for (i = j = 0; j <= bufsize; ++j) {
                if ((j == bufsize && bufsize) || namelist[j] == ',') {
                    if (strncmp(fieldname, namelist + i, j - i) == 0) {
                        *exists = 1;
                        break;
                    }
                    i = j + 1;
                    continue;
                }
            }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        if (namelist != NULL) free(namelist);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief checks if the given fieldname belongs to the swath specified by swid
 * 
 * @param h4toh5id h4toh5 identifier
 * @param swid swath identifier
 * @param fieldname the name of field that we're looking for
 * @param[out] exists non-zero if the field belongs to the swath
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_swath_field_existence(hid_t h4toh5id, int32 swid, const char *fieldname, int *exists)
{
    char *namelist = NULL;
    int succeeded = 0;

    do {
        int n, failinside = 0;
        int32 numfields, bufsize;

        for (n = 0; n < 2; ++n) {
            if ((numfields = SWnentries(swid, n == 0 ? HDFE_NENTDFLD : HDFE_NENTGFLD, &bufsize)) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo entries failure"); failinside = 1; break; }
            if (numfields > 0) {
                int i, j;

                if ((namelist = malloc(bufsize + 1)) == NULL) { H4TOH5ERROR_NOMEM1("fieldinfo namelist"); failinside = 1; break; }
                if ((n == 0 ? SWinqdatafields(swid, namelist, NULL, NULL) : SWinqgeofields(swid, namelist, NULL, NULL)) == -1) { H4TOH5ERROR_SET1(3, "fieldinfo inq failure"); failinside = 1; break; }

                for (i = j = 0; j <= bufsize; ++j) {
                    if ((j == bufsize && bufsize) || namelist[j] == ',') {
                        if (strncmp(fieldname, namelist + i, j - i) == 0) {
                            *exists = 1;
                            break;
                        }
                        i = j + 1;
                        continue;
                    }
                }
            }
            if (*exists) break;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        if (namelist != NULL) free(namelist);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief check if the given name is used for an HDF-EOS2 field
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param fieldname HDF4 vdata name which may be the name of an HDF-EOS2 attribute
 * @param isgrid true if this is HDF-EOS2 Grid datatype
 * @param[out] exists whether or not fieldname is defined
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int check_field_existence(hid_t h4toh5id, const char *h5groupname, const char *fieldname, int isgrid, int *exists)
{
    int32 eosfd = -1;
    int32 strid = -1;
    challoc eosgroupname;
    int succeeded = 0;

    *exists = 0;
    challoc_init(&eosgroupname);
    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);

        if (extract_eosgroup_origname(h4toh5id, h5groupname, &eosgroupname) == -1) { H4TOH5ERROR_SET0(3); break; }
        if ((eosfd = isgrid ? GDopen_const(dt->hdf4filename, DFACC_READ) : SWopen_const(dt->hdf4filename, DFACC_READ)) == -1) break;
        if ((strid = isgrid ? GDattach_const(eosfd, eosgroupname.s) : SWattach(eosfd, eosgroupname.s)) == -1) break;

        if ((isgrid ? check_grid_field_existence(h4toh5id, strid, fieldname, exists) : check_swath_field_existence(h4toh5id, strid, fieldname, exists)) == -1) { H4TOH5ERROR_SET0(3); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        if (strid != -1) isgrid ? GDdetach(strid) : SWdetach(strid);
        if (eosfd != -1) isgrid ? GDclose(eosfd) : SWclose(eosfd);
        challoc_free(&eosgroupname);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create an HDF5 attribute from an HDF-EOS2 attribute
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param attr HDF-EOS2 attribute
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int write_eosattribute(hid_t h4toh5id, const char *h5groupname, const attribute_t *attr)
{
    hid_t group = -1;
    hid_t spaceid = -1;
    hid_t attrid = -1;
    hid_t tid = -1;
    int succeeded = 0;

    do {
        int32 dimtype = attr->type;
        hid_t h5memtype, h5datatype;
        size_t h4memsize, h4size;
        int stringtype;

        if (h4type_to_h5type(h4toh5id, dimtype, &h5memtype, &h4memsize, &h4size, &h5datatype) == -1) { H4TOH5ERROR_SET0(3); break; }
        switch (dimtype) {
            case DFNT_UCHAR8:
            case DFNT_CHAR8:
                stringtype = 1;
                break;
            default:
                stringtype = 0;
                break;
        }

        if ((group = get_h5groupid(h5groupname, h4toh5id,0)) == -1) { H4TOH5ERROR_SET0(3); break; }
        if (stringtype) {
            if ((spaceid = H5Screate(H5S_SCALAR)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate failure"); break; }
            if ((tid = H5Tcopy(H5T_C_S1)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tcopy"); break; }
            if (H5Tset_size(tid, attr->value.len) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_size"); break; }
            if (H5Tset_strpad(tid, H5T_STR_NULLTERM) == FAIL) { H4TOH5ERROR_SET1(3, "H5Tset_strpad"); break; }
            h5datatype = tid;
            h5memtype = tid;
        }
        else {
            hsize_t dimsize = attr->value.len / h4size;
            if ((spaceid = H5Screate_simple(1, &dimsize, NULL)) == FAIL) { H4TOH5ERROR_SET1(3, "H5Screate_simple failure"); break; }
        }
        /* It seems slashes in an attribute name do not make any problem,
         * but I think this will be safer.
         */
        if ((attrid = H5Acreate_safe(h4toh5id, group, attr->name.s, h5datatype, spaceid, H5P_DEFAULT)) == -1) { H4TOH5ERROR_SET1(3, "vdata attribute"); break; }
        if (H5Awrite(attrid, h5memtype, attr->value.s) == -1) { H4TOH5ERROR_SET1(3, "vdata attribute data"); break; }

        succeeded = 1;
    } while (0);

    if (1) {
        if (tid != -1) H5Tclose(tid);
        if (attrid != -1) H5Aclose(attrid);
        if (spaceid != -1) H5Sclose(spaceid);
        if (group != -1) H5Gclose(group);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief try to convert HDF-EOS2 attribute
 * HDF-EOS2 stores attributes as HDF4 vdata. This function detects if the given HDF4 vdata is an
 * HDF-EOS2 attribute, and converts it to an HDF5 attribute and attaches it to an HDF5 group
 * corresponding to Grid or Swath structure.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param vdataname HDF4 vdata name
 * @param vdataid HDF4 vdata identifier
 * @param[out] handled 1 if the given HDF4 vdata is an HDF-EOS2 attribute and it was handled
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5vdata_eos_attribute(hid_t h4toh5id, const char *h5groupname, const char *vdataname, int32 vdataid, int *handled)
{
    attributealloc attrs;
    challoc parentgroup;
    int succeeded = 0;

    *handled = 0;
    attributealloc_init(&attrs);
    challoc_init(&parentgroup);
    do {
        int isgridattribute, isswathattribute, attrread = 0, attrindex;

        if (check_gridattribute(h5groupname, &isgridattribute) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (check_swathattribute(h5groupname, &isswathattribute) == FAIL) { H4TOH5ERROR_SET0(3); break; }

        if (isgridattribute)
            attrread = read_attr(h4toh5id, h5groupname, vdataname, 1, &attrs, &attrindex) == SUCCEED;
        else if (isswathattribute)
            attrread = read_attr(h4toh5id, h5groupname, vdataname, 0, &attrs, &attrindex) == SUCCEED;

        if (attrread && attrindex != -1) {
            /* it would be better to store attributes in Grid or Swath group rather than "Grid Attributes" group */
            if (extract_parentpath(h4toh5id, h5groupname, &parentgroup) == -1) { H4TOH5ERROR_SET0(3); break; }
            if (write_eosattribute(h4toh5id, parentgroup.s, &attrs.s[attrindex]) == -1) { H4TOH5ERROR_SET0(3); break; }
            *handled = 1;
        }
        else if (strncmp(vdataname, "_FV_", 4) == 0) {
            /* _FV_<fieldname> is a standard naming convention used by HDF-EOS2 */
            if (isgridattribute || isswathattribute) {
                int exists = 0;
                if (check_field_existence(h4toh5id, h5groupname, vdataname + 4, isgridattribute, &exists) == FAIL) { H4TOH5ERROR_SET0(3); break; }
                *handled = exists;
            }
        }

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&parentgroup);
        attributealloc_free(&attrs);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create fake dimensions for non-EOS2 SDS
 * NetCDF-4 requires that every single dimension of all variables is associated with netCDF-4 dimension.
 * To satisfy that requirement, this function creates netCDF-4 dimensions for HDF4 SDS that does not
 * belong to HDF-EOS2.
 *
 * For HDF-EOS2 objects, H4toH5sds_eosswath_dimscale() or H4toH5sds_eosgrid_dimscale() will
 * create dimensions.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param sdsid SDS identifier
 * @param h5dataset HDF5 dataset that will have dimensional scales
 * @param h5datasetref reference to datasetname
 * @param[out] handled 1 if the SDS got dimensions by this function
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5sds_noneos_fake_dimscale(hid_t h4toh5id, const char *h5groupname, int32 sdsid, hid_t h5dataset, hobj_ref_t h5datasetref, int *handled)
{
    challoc sdsname;
    hdf4dimalloc hdf4dims;
    hobjreftalloc dimscales;
    int succeeded = 0;

    *handled = 0;
    challoc_init(&sdsname);
    hdf4dimalloc_init(&hdf4dims);
    hobjreftalloc_init(&dimscales);
    do {
        int i, failinside = 0;

        if (read_sdsdim(h4toh5id, sdsid, &sdsname, NULL, &hdf4dims) == FAIL) { H4TOH5ERROR_SET0(3); break; }
        if (H4toH5config_use_debug_msg()) printf("trying fake dimensions for %s\n", sdsname.s);

        if (!hobjreftalloc_ready(&dimscales, hdf4dims.len)) { H4TOH5ERROR_NOMEM1("obj ref list"); break; }
        for (i = 0; i < hdf4dims.len; ++i)
            dimscales.s[dimscales.len++] = -1;

        for (i = 0; i < hdf4dims.len; ++i) {
            const hdf4dim_t *dim = &hdf4dims.s[i];
            coordinate_t coord;
            hid_t h5memtype, h5datatype;
            hobj_ref_t dimref;

            if (dim->dimtype == 0 || dim->dimtype == -1) {
                h5memtype = H5T_NATIVE_DOUBLE;
                h5datatype = H5T_IEEE_F64BE;
            }
            else {
                size_t h4memsize, h4size;
                if (h4type_to_h5type(h4toh5id, dim->dimtype, &h5memtype, &h4memsize, &h4size, &h5datatype) == -1) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
            }
            get_coordinate_from_dimension(dim->name.s, &coord);
            if (H4toH5config_use_debug_msg()) printf("  creating a dimension %s for %s\n", coord.name, sdsname.s);
            if (write_1d_noneos_dimscale(h4toh5id, h5groupname, &coord, dim->dimsize, h5datatype, h5memtype, NULL, NULL) == -1) {
                H4toH5error_reset(h4toh5id);
                H4TOH5ERROR_SET5(3, "Fake dimension '", dim->name.s, "' could not be created under '", h5groupname, "'. Maybe, a field and a dimension under the same group have the same name.");
                H4toH5error_suppress(h4toh5id, 1);
                failinside = 1;
                break;
            }
            if (get_ref_noneos_dimscale(h4toh5id, h5groupname, coord.name, &dimref) == -1) { H4TOH5ERROR_SET2(3, "create reference", dim->name.s); failinside = 1; break; }
            dimscales.s[i] = dimref;
        }
        if (failinside) break;

        if (H4toH5config_use_debug_msg()) printf("  attaching fake dimensions for %s\n", sdsname.s);
        if (attach_dimscales(h4toh5id, h5dataset, h5datasetref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }
        *handled = 1;

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&sdsname);
        hdf4dimalloc_free(&hdf4dims);
        hobjreftalloc_free(&dimscales);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief create fake dimensions for non-EOS2 Vdata
 * This is similar to H4toH5sds_noneos_fake_dimscale() except the fact that
 * this function is for Vdata, not SDS.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param h5groupname HDF5 group name where the converted HDF5 dataset resides
 * @param vdataid Vdata identifier
 * @param numrecord # of records; it will be treated as the number of elements
 * @param h5dataset HDF5 dataset that will have dimensional scales
 * @param h5datasetref reference to datasetname
 * @param[out] handled 1 if the SDS got dimensions by this function
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5vdata_noneos_fake_dimscale(hid_t h4toh5id, const char *h5groupname, int32 vdataid, int32 numrecord, hid_t h5dataset, hobj_ref_t h5datasetref, int *handled)
{
    hobjreftalloc dimscales;
    dimension_t dim;
    int succeeded = 0;

    *handled = 0;
    hobjreftalloc_init(&dimscales);
    init_dimension_t(&dim);
    do {
        if (!hobjreftalloc_ready(&dimscales, 1)) { H4TOH5ERROR_NOMEM1("obj ref list"); break; }
        dimscales.s[dimscales.len++] = -1;

        if (!challoc_copys(&dim.name, "fakedim_")) { H4TOH5ERROR_NOMEM1("fakedim name"); break; }
        if (!challoc_catlong(&dim.name, numrecord, 0)) { H4TOH5ERROR_NOMEM1("fakedim name"); break; }
        dim.dimsize = numrecord;

        {
            coordinate_t coord;
            get_coordinate_from_dimension(dim.name.s, &coord);
            if (H4toH5config_use_verbose_msg()) {
                printf("Creating a fake dimension %s[%d] under %s...\n", dim.name.s, (int)dim.dimsize, h5groupname);
            }
            if (write_1d_noneos_dimscale(h4toh5id, h5groupname, &coord, dim.dimsize, H5T_IEEE_F64BE, H5T_NATIVE_DOUBLE, NULL, NULL) == -1) { H4TOH5ERROR_SET4(3, "dimscale", dim.name.s, "under", h5groupname); break; }
            if (get_ref_noneos_dimscale(h4toh5id, h5groupname, coord.name, &dimscales.s[0]) == -1) { H4TOH5ERROR_SET2(3, "create reference", dim.name.s); break; }
        }

        if (attach_dimscales(h4toh5id, h5dataset, h5datasetref, &dimscales) == -1) { H4TOH5ERROR_SET0(3); break; }
        *handled = 1;

        succeeded = 1;
    } while (0);

    if (1) {
        free_dimension_t(&dim);
        hobjreftalloc_free(&dimscales);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief returns number of EOS Grid structures
 * 
 * @param filename HDF-EOS2 file name
 * 
 * @return number of Grid structures
 */
int H4toH5eos_num_grid(const char *filename)
{
    int32 bufsize;

    return (int)GDinqgrid_const(filename, NULL, &bufsize);
}

/** 
 * @brief returns number of EOS Swath structures
 * 
 * @param filename HDF-EOS2 file name
 * 
 * @return number of Swath structures
 */
int H4toH5eos_num_swath(const char *filename)
{
    int32 bufsize;

    return (int)SWinqswath_const(filename, NULL, &bufsize);
}

/** 
 * @brief returns number of EOS Point structures
 * 
 * @param filename HDF-EOS2 file name
 * 
 * @return number of Point structures
 */
int H4toH5eos_num_point(const char *filename)
{
    int32 bufsize;

    return (int)PTinqpoint_const(filename, NULL, &bufsize);
}

/** 
 * @brief test if the given file has any EOS data set that does not have any field
 * If an EOS data set does not have any field, H4toH5_sds() cannot be reached; so,
 * H4toH5all_dimscale() and none of functions in this file can be called. The conversion
 * will succeed, but the converted file will lose dimension information because
 * it didn't have a chance to call any HDF-EOS2 API. To prevent losing information,
 * h4toh5 explicitly rejects those files.
 * 
 * @param filename HDF-EOS2 file name
 * 
 * @return FAIL if an HDF-EOS2 data set without a field exists
 */
int H4toH5eos_test_emptydataset(const char *filename)
{
    namelistalloc datasetnames;
    int succeeded = 0;

    namelistalloc_init(&datasetnames);
    do {
        int32 eosfd, datasetid, bufsize, numfields;
        int i, failinside_i = 0, succeeded2;

        /* grid */
        succeeded2 = 0;
        do {
            namelistalloc_empty(&datasetnames);
            if (parse_namelist(-1, filename, GDinqgrid_const, &datasetnames) == -1) break;

            if ((eosfd = GDopen_const(filename, DFACC_READ)) == -1) break;
            for (i = 0; i < datasetnames.len; ++i) {
                if ((datasetid = GDattach_const(eosfd, datasetnames.s[i].name.s)) == -1) { failinside_i = 1; break; }
                if ((numfields = GDnentries(datasetid, HDFE_NENTDFLD, &bufsize)) == -1) { GDdetach(datasetid);failinside_i = 1; break; }
                if (numfields == 0) { GDdetach(datasetid); failinside_i = 1; break; }
                GDdetach(datasetid);
            }
            GDclose(eosfd);
            if (failinside_i) break;

            succeeded2 = 1;
        } while (0);
        if (!succeeded2) break;

        /* swath */
        succeeded2 = 0;
        do {
            namelistalloc_empty(&datasetnames);
            if (parse_namelist(-1, filename, SWinqswath_const, &datasetnames) == -1) break;

            if ((eosfd = SWopen_const(filename, DFACC_READ)) == -1) break;
            for (i = 0; i < datasetnames.len; ++i) {
                if ((datasetid = SWattach(eosfd, datasetnames.s[i].name.s)) == -1) { failinside_i = 1; break; }
                if ((numfields = SWnentries(datasetid, HDFE_NENTGFLD, &bufsize)) == -1) { SWdetach(datasetid);failinside_i = 1; break; }
                if ((numfields = SWnentries(datasetid, HDFE_NENTDFLD, &bufsize)) == -1) { SWdetach(datasetid);failinside_i = 1; break; }
                if (numfields == 0) {  SWdetach(datasetid);failinside_i = 1; break; }
                SWdetach(datasetid);
            }
            SWclose(eosfd);
            if (failinside_i) break;

            succeeded2 = 1;
        } while (0);
        if (!succeeded2) break;

        succeeded = 1;
    } while (0);

    if (1) {
        namelistalloc_free(&datasetnames);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief initialize HDF-EOS2 conversion
 * This function initializes HDF-EOS2 conversion. If this function is not called,
 * HDF-EOS2 conversion terminates with runtime error like Segmentation fault under UNIX
 * or access violation.
 * 
 * @param h4toh5id h4toh5 identifier
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5eos_initialization(hid_t h4toh5id)
{
    hdf4attrsalloc *attrs = 0;
    manglednamesalloc *manglednames = 0;
    eos2data_t *eos2data = 0;
    int succeeded = 0;

    h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
    attrs = dt->created_datasets;
    manglednames = dt->manglednames;
    eos2data = dt->eos2data;
    do {
        if (attrs)
            hdf4attrsalloc_empty(attrs);
        else {
            attrs = malloc(sizeof(hdf4attrsalloc));
            if (!attrs) { H4TOH5ERROR_NOMEM1("created dataset list"); break; }
            hdf4attrsalloc_init(attrs);
        }

        if (manglednames)
            manglednamesalloc_empty(manglednames);
        else {
            manglednames = malloc(sizeof(manglednamesalloc));
            if (!manglednames) { H4TOH5ERROR_NOMEM1("created mangledname list"); break; }
            manglednamesalloc_init(manglednames);
        }

        if (eos2data) {
            free_eos2data_t(eos2data);
            init_eos2data_t(eos2data);
        }
        else {
            eos2data = malloc(sizeof(eos2data_t));
            if (!eos2data) { H4TOH5ERROR_NOMEM1("eos2 data"); break; }
            init_eos2data_t(eos2data);
        }

        succeeded = 1;
    } while (0);

    if (succeeded) {
        /* the following structure will be used to copy attributes in H4toH5eos_finalization(). */
        if (!dt->created_datasets) dt->created_datasets = attrs;
        if (!dt->manglednames) dt->manglednames = manglednames;
        if (!dt->eos2data) dt->eos2data = eos2data;
    }
    else {
        if (attrs) {
            hdf4attrsalloc_free(attrs);
            free(attrs);
        }
        if (manglednames) {
            manglednamesalloc_free(manglednames);
            free(manglednames);
        }
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief copy attributes in source HDF5 dataset to another HDF5 dataset
 * Copy attributes from the source HDF5 dataset specified by sourceid to the target
 * HDF5 dataset specified by targetid.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param sourceid the source HDF5 dataset
 * @param targetid the target HDF5 dataset
 *
 * @return FAIL if failed, SUCCEED if successful
 */
static int copy_attributes(hid_t h4toh5id, hid_t sourceid, hid_t targetid)
{
    challoc attrname;
    challoc attrvalue;
    int succeeded = 0;

    challoc_init(&attrname);
    challoc_init(&attrvalue);
    do {
        int numattr, i, failinside = 0;
        
        if ((numattr = H5Aget_num_attrs(sourceid)) < 0) { H4TOH5ERROR_SET1(3, "num attrs"); break; }
        for (i = 0; i < numattr; ++i) {
            ssize_t namelen;
            hssize_t npoints;
            size_t typesize;
            hid_t sourceattrid = -1;
            hid_t targetattrid = -1;
            hid_t spaceid = -1;
            hid_t datatype = -1;
            int succeeded2 = 0;

            do {
                if ((sourceattrid = H5Aopen_by_idx(sourceid, ".", H5_INDEX_CRT_ORDER, H5_ITER_INC, i, H5P_DEFAULT, H5P_DEFAULT)) == -1) { H4TOH5ERROR_SET1(3, "attr open"); break; }
                if ((datatype = H5Aget_type(sourceattrid)) == -1) { H4TOH5ERROR_SET1(3, "attr gettype"); break; }
                if ((spaceid = H5Aget_space(sourceattrid)) == -1) { H4TOH5ERROR_SET1(3, "attr getspace"); break; }
                if ((namelen = H5Aget_name(sourceattrid, 0, NULL)) < 0) { H4TOH5ERROR_SET1(3, "attr getname"); break; }
                if (!challoc_ready(&attrname, namelen)) { H4TOH5ERROR_NOMEM1("attr name"); break; }
                if (H5Aget_name(sourceattrid, namelen + 1, attrname.s) < 0) { H4TOH5ERROR_SET1(3, "attr getname"); break; }
                attrname.len = namelen;

                /* DIMENSION_LIST is always written */
                if (strcmp(HDF5_DIMENSION_LIST, attrname.s)) {
                    if ((targetattrid = H5ACREATE(targetid, attrname.s, datatype, spaceid, H5P_DEFAULT)) == -1) { H4TOH5ERROR_SET1(3, "attr create"); break; }

                    if ((npoints = H5Sget_simple_extent_npoints(spaceid)) == 0) { H4TOH5ERROR_SET1(3, "attr npoints"); break; }
                    if ((typesize = H5Tget_size(datatype)) == 0) { H4TOH5ERROR_SET1(3, "attr typesize"); break; }
                    if (!challoc_ready(&attrvalue, (int)(typesize * npoints))) { H4TOH5ERROR_NOMEM1("attr value"); break; }
                    attrvalue.len = (int)(typesize * npoints);

                    if (H5Aread(sourceattrid, datatype, attrvalue.s) == -1) { H4TOH5ERROR_SET1(3, "attr read"); break; }
                    if (H5Awrite(targetattrid, datatype, attrvalue.s) == -1) { H4TOH5ERROR_SET1(3, "attr write"); break; }
                }

                succeeded2 = 1;
            } while (0);

            if (1) {
                if (datatype != -1) H5Tclose(datatype);
                if (spaceid != -1) H5Sclose(spaceid);
                if (sourceattrid != -1) H5Aclose(sourceattrid);
                if (targetattrid != -1) H5Aclose(targetattrid);
            }
            if (!succeeded2) { H4TOH5ERROR_SET0(3); failinside = 1; break; }
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&attrname);
        challoc_free(&attrvalue);
    }

    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief traverse the mapping between source HDF5 dimensional scale to generated HDF5 dimensional scales
 * HDF-EOS2 conversion routines accumulate the mapping from "Latitude" to "Latitude:1_2:1_2" if "Latitude:1_2:1_2"
 * was generated by HDF-EOS2 conversion routine. For netCDF4, the generated dimensional scales need to have the same
 * attributes as the source "Latitude" dimensional scale has. This function copies all attributes from "Latitude"
 * to "Latitude:1_2:1_2" and so on.
 * @see H4toH5eos_finalization
 *
 * @param h4toh5id h4toh5 identifier
 * @param hdf4attrs mapping from source HDF5 dimensional scale to generated HDF5 dimensional scales
 * @param hdf5fileid HDF5 file id 
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
static int handle_pending_attribute(hid_t h4toh5id, const hdf4attrsalloc *hdf4attrs, hid_t hdf5fileid)
{
    hid_t sourceid = -1;
    hid_t targetid = -1;
    int succeeded = 0;

    do {
        int i, j, failinside = 0, failinside2 = 0;

        for (i = 0; i < hdf4attrs->len; ++i) {
            const hdf4attrs_t *attr = &hdf4attrs->s[i];
            if ((sourceid = H5RDEREFERENCE(hdf5fileid, H5R_OBJECT, &attr->source)) == -1) { H4TOH5ERROR_SET1(3, "attr source"); failinside = 1; break; }

            for (j = 0; j < attr->targets.len; ++j) {
                if ((targetid = H5RDEREFERENCE(hdf5fileid, H5R_OBJECT, &attr->targets.s[j])) == -1) { H4TOH5ERROR_SET1(3, "attr target"); failinside2 = 1; break; }
                if (copy_attributes(h4toh5id, sourceid, targetid) == -1) { H4TOH5ERROR_SET0(3); failinside2 = 1; break; }
                H5Dclose(targetid); targetid = -1;
            }
            if (failinside2) { H4TOH5ERROR_SET0(3); failinside = 1; break; }

            H5Dclose(sourceid); sourceid = -1;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        if (sourceid != -1) H5Dclose(sourceid);
        if (targetid != -1) H5Dclose(targetid);
    }
    return succeeded ? SUCCEED : FAIL;
}

/** 
 * @brief finalize HDF-EOS2 conversion
 * This function copies attributes for each generated dimensional scale.
 * For example, "Latitude:1_2:1_2" is generated if a dimensional map is defined.
 * HDF-EOS2 routines accumulate mapping from "Latitude" to "Latitude:1_2:1_2" while
 * handling conversion. With this information, this function copies all attributes
 * from "Latitude" to "Latitude:1_2:1_2".
 *
 * Copying is handled at the last stage because the traversing order cannot be
 * predicted.  For example, "Latitude:1_2:1_2" can be created before "Latitude"
 * is read. When handling "Latitude:1_2:1_2", no information can be fetched
 * from "Latitude". Reading attributes from "Latitude" on-the-fly is not hard
 * to implement because attributes per HDF4 dataset is not supported by
 * HDF-EOS2.
 * 
 * @param h4toh5id h4toh5 identifier
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int H4toH5eos_finalization(hid_t h4toh5id)
{
    int succeeded = 0;

    do {
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);

        if (dt->created_datasets) {
            hdf4attrsalloc *attrs = dt->created_datasets;
            dt->created_datasets = 0;

            if (handle_pending_attribute(h4toh5id, attrs, dt->file5_id) == -1) { H4TOH5ERROR_SET0(3); break; }

            hdf4attrsalloc_free(attrs);
            free(attrs);
        }
        if (dt->manglednames) {
            manglednamesalloc *manglednames = dt->manglednames;
            dt->manglednames = 0;

            manglednamesalloc_free(manglednames);
            free(manglednames);
        }
        if (dt->eos2data) {
            eos2data_t *eos2data = dt->eos2data;
            dt->eos2data = 0;

            free_eos2data_t(eos2data);
            free(eos2data);
        }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

static int add_mangledname(hid_t h4toh5id, mangledtype_t kind, const char *origname, const char *mangledfullpath)
{
    /*
        Caller gives the following:
            origname: sds/with/slash
            mangledfullpath: /vgroup_without_slash/sds_with_slash

        Put the following into the list
            parentpath: /vgroup_without_slash
            origname: sds/with/slash
            mangledname: sds_with_slash
     */
    int succeeded = 0;

    do {
        int orignamelen;
        int mangledfullpathlen;
        h4toh5id_t *dt = H4TOH5I_object(h4toh5id);
        manglednamesalloc *manglednames = dt->manglednames;

        orignamelen = strlen(origname);
        mangledfullpathlen = strlen(mangledfullpath);

        if (orignamelen < mangledfullpathlen && strcmp(origname, mangledfullpath + mangledfullpathlen - orignamelen)) {
            mangledname_t *mangled;
            if (!manglednamesalloc_readyplus(manglednames, 1)) { H4TOH5ERROR_NOMEM1("mangled list"); break; }
            mangled = &manglednames->s[manglednames->len++];
            init_mangledname_t(mangled);

            mangled->kind = kind;
            if (!challoc_copyb(&mangled->parentpath, mangledfullpath, mangledfullpathlen - orignamelen - 1)) { H4TOH5ERROR_NOMEM1("mangled parentpath"); break; }
            if (!challoc_copys(&mangled->origname, origname)) { H4TOH5ERROR_NOMEM1("mangled origname"); break; }
            if (!challoc_copys(&mangled->mangledname, mangledfullpath + mangledfullpathlen - orignamelen)) { H4TOH5ERROR_NOMEM1("mangled name"); break; }
        }

        succeeded = 1;
    } while (0);

    return succeeded ? SUCCEED : FAIL;
}

int H4toH5eos_add_mangled_vgroupname(hid_t h4toh5id, const char *vgroupname, const char *mangledfullpath)
{
    return add_mangledname(h4toh5id, MANGLED_VGROUP, vgroupname, mangledfullpath);
}

int H4toH5eos_add_mangled_vdataname(hid_t h4toh5id, const char *vdataname, const char *mangledfullpath)
{
    /* return add_mangledname(h4toh5id, MANGLED_VDATA, vdataname, mangledfullpath); */
    return 0;
}

int H4toH5eos_add_mangled_sdsname(hid_t h4toh5id, const char *sdsname, const char *mangledfullpath)
{
    /* return add_mangledname(h4toh5id, MANGLED_SDS, sdsname, mangledfullpath); */
    return 0;
}

char * H4toH5correct_name_netcdf4(hid_t h4toh5id, const char *oldname)
{
    /* The comment of NC_check_name(), a part of netCDF-4 library, states that
     * the acceptable name is ([a-zA-Z0-9_]|{UTF8})([^\x00-\x1F\x7F/]|{UTF8})* .
     * Since this function is used by nc_def_var(), nc_put_att() and so on, I think
     * this is forced by every object.
     * I think the first character should not be digit because that may make CDL unhappy.
     * I also think UTF8 part is not necessary.
     * Here's the rule:
     *   Replace forbidden letter with '_'
     *   Prepend '_' if the name starts with digits
     */
    char *newname = NULL;
    challoc buffer;
    int succeeded = 0;

    challoc_init(&buffer);
    do {
        const char *p;
        int failinside = 0;

        if (!oldname) { H4TOH5ERROR_SET1(3, "empty name was passed"); break; }
#define IS_DIGIT(c)  ('0' <= (c) && (c) <= '9')
#define IS_ALPHA(c)  (('a' <= (c) && (c) <= 'z') || ('A' <= (c) && (c) <= 'Z'))
#define IS_OTHER(c)  ((c) == '_')

        for (p = oldname; *p; ++p) {
            if (buffer.len == 0) {
                if (IS_DIGIT(*p)) {
                    if (!challoc_append(&buffer, "_")) { H4TOH5ERROR_NOMEM1("prepend _"); failinside = 1; break; }
                }
            }

            if (IS_DIGIT(*p) || IS_ALPHA(*p) || IS_OTHER(*p)) {
                if (!challoc_append(&buffer, p)) { H4TOH5ERROR_NOMEM1("copy a letter"); failinside = 1; break; }
            }
            else {
                if (!challoc_append(&buffer, "_")) { H4TOH5ERROR_NOMEM1("copy _"); failinside = 1; break; }
            }
        }
        if (failinside) break;

        if ((newname = malloc(buffer.len + 1)) == NULL) { H4TOH5ERROR_NOMEM1("clone buffer"); break; }
        strcpy(newname, buffer.s);

        succeeded = 1;
    } while (0);

    if (1) {
        challoc_free(&buffer);
    }
    if (!succeeded) {
        if (!newname) free(newname);
    }
    return succeeded ? newname : NULL;
}
#endif

/** 
 * @brief translate an attribute with splitting if necessary
 * I noticed some HDF-EOS2 files have very long StructMetadata, and
 * HDF5 1.6 does not hold that long attribute and raises an error.
 * To avoid this error and not to lose any information, this function
 * splits the long attribute into several pieces of attributes.
 * 
 * @param h4toh5id h4toh5 identifier
 * @param enclosing enclosing HDF5 dataset that will contain this attribute
 * @param attrname attribute name
 * @param aspace dataspace of the attribute
 * @param data actual data of the attribute
 * @param datalen byte count of 'data'
 * 
 * @return FAIL if failed, SUCCEED if successful
 */
int transattrs_split(hid_t h4toh5id, hid_t enclosing, const char *attrname, hid_t aspace, const char *data, int datalen)
{
    hid_t existing = -1;
    hid_t atype = -1;
    hid_t aid = -1;
    challoc fabricatedname;
    int succeeded = 0;

    challoc_init(&fabricatedname);
    do {
        int i =0;
	int id =0;
	int failinside = 0;
        size_t selection[2];
        static const int splitsize = 60 * 1024;

        /* Check whether or not the original name has been used. This name can be incidentally
         * taken if another attribute is very long. For example, there are two attributes, 'attr'
         * and 'attr_001' in the same dataset. If 'attr' was visited first and it is too long,
         * 'attr' could be split into 'attr_001', 'attr_002' and so on. When 'attr_001' is visited
         * later, H5A_create('attr_001') will fail because this name was already used while handling
         * 'attr'. The following checks if this is the case. If it is, the conversion terminates.
         */
        H5E_BEGIN_TRY {
            existing = H5Aopen_name(enclosing, attrname);
        } H5E_END_TRY;
        if (existing >= 0) {
            H4TOH5ERROR_SET2(3, attrname, "was already handled. This can happen when a very long attribute that has very similar name exists.");
            break;
        }

        for (i = 1, id = 0; ; ++i) {
            if (!challoc_copys(&fabricatedname, attrname)) { H4TOH5ERROR_NOMEM1("attr name"); failinside = 1; break; }
            if (!challoc_cats(&fabricatedname, "_")) { H4TOH5ERROR_NOMEM1("attr name"); failinside = 1; break; }
            if (!challoc_catulong(&fabricatedname, i, 3)) { H4TOH5ERROR_NOMEM1("attr name #"); failinside = 1; break; }

            selection[0] = id * splitsize;
            selection[1] = (id + 1) * splitsize;
            if (selection[1] > datalen) selection[1] = datalen;
            if ((atype = mkstr(h4toh5id, selection[1] - selection[0], H5T_STR_NULLTERM)) < 0) { H4TOH5ERROR_SET2(3, "fail to create a type for", fabricatedname.s); failinside = 1; break; }

            /* 'attr_001' could be already taken, if this attribute is 'attr', and
             * an attribute 'attr_001' exists in the source file. This should be tolerated.
             */
            H5E_BEGIN_TRY {
                aid = H5Acreate_safe(h4toh5id, enclosing, fabricatedname.s, atype, aspace, H5P_DEFAULT);
            } H5E_END_TRY;
            if (aid < 0) {
                if (atype >= 0) { H5Tclose(atype); atype = -1; }
                continue;
            }
            if (H5Awrite(aid, atype, data + selection[0]) < 0) { H4TOH5ERROR_SET2(3, "fail to write to attribute", fabricatedname.s); failinside = 1; break; }

            if (H5Aclose(aid) < 0) { H4TOH5ERROR_SET2(3, "fail to close attribute", fabricatedname.s); failinside = 1; break; }
            aid = -1;
            if (H5Tclose(atype) < 0) { H4TOH5ERROR_SET2(3, "fail to close type", fabricatedname.s); failinside = 1; break; }
            atype = -1;

            if (H4toH5config_use_verbose_msg()) {
                printf("A part [%u - %u) of %s was written to %s.\n", (int)selection[0], (int)selection[1], attrname, fabricatedname.s);
            }

            if (selection[1] >= datalen) break;
            ++id;
        }
        if (failinside) break;

        succeeded = 1;
    } while (0);

    if (1) {
        if (existing >= 0) H5Aclose(existing);
        challoc_free(&fabricatedname);
        if (atype >= 0) H5Tclose(atype);
        if (aid >= 0) H5Aclose(aid);
    }
    return succeeded ? 0 : -1;
}

/* vim:set ts=8 sw=2 sts=2 cindent: */
