/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF.  The full HDF 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, or in https://support.hdfgroup.org/ftp/HDF/releases/.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/******************************************************************************
 * he - HDF editor
 *
 * This program has a long history, starting with its creation as a tool to
 * help HDF developers work at a low-level on HDF files.  It has evolved into
 * user-level program, though it is still designed for small editing tasks on
 * HDF files.
 *
 * he allows sophisticated HDF users to manipulate the elements in an HDF file.
 * These manipulations include selecting groups and showing information about
 * them, dumping them to the output, writing them to new files, deleting them,
 * inserting them, replacing, say, the palette of an r8 group, and editing the
 * text labels and descriptions of any element.
 *
 * he will NOT allow the user to *arbitrarily* modify binary data in the file
 * or any element, though it allows modification of tag and reference numbers
 * within strict constraints.  The user should not attempt to alter individual
 * bytes.  It is acceptable, however, to replace an element with another of
 * the same type.
 *
 * he can be used both interactively or in "batch" mode.  Here is a sample
 * batch program:
 *
 *      #!/bin/csh -f
 *      set file=$1
 *      shift
 *      he -batch $file -nobackup << EOF
 *      info -all -group $*
 *      close
 *      quit
 *      EOF
 *
 * This makes use of C-shell variable substitution to pass a filename to he,
 * invokes he, and then lists out on separate lines the commands to give once
 * he is running.  The $* trailing the info command is also C-shell variable
 * substitution.
 *
 * List of commands:       annotate   dump    if      open    putr8    unalias
 *                         close      getr8   info    prev    revert   wait
 *                         delete     help    next    put     select   write
 *                         display
 *
 * Predicates are of the form TAG = 3 IMAGE_SIZE < 1000  LABEL = "abc"
 * Type <command> -help for usage of <command>.  DO NOT type the command
 * without arguments and expect help.  Some commands delete objects and do not
 * need any arguments.  If you are learning to use he, try it on an expendable
 * file.
 *****************************************************************************/
/* ------ he.c ------- main() main HDF interfacing routines */

#include "hdf_priv.h"
#include "he.h"

#include <stdio.h>
#ifdef H4_HAVE_UNISTD_H
#include <unistd.h>
#endif

#if defined H4_HAVE_WIN32_API && !defined __MINGW32__
typedef int                               pid_t;
#endif

/* the return status of last command executed */
int he_status = HE_OK;

/* is this on the console or remote terminals?
   this should eventually be detected automatically */
int he_remote = YES;

/* is this batch mode or interactive? */
extern int he_batch;

int       he_currDesc;
int       he_numDesc;
int       he_numGrp;
int       he_backup;
char     *he_file = NULL;
DFdesc   *he_desc = NULL;
HE_GROUP *he_grp  = NULL;

int
main(int argc, char *argv[])
{
    int   backup   = YES; /* Backup files when opening? */
    char *fileName = NULL;

    for (int i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            switch (findOpt(argv[i] + 1)) {
                case HE_HELP:
                    printf("he [<file>] [-nobackup] [-batch]\n");
                    help();
                    quit(0);
                    break;
                case HE_BATCH:
                    he_batch = YES;
                    break;
                case HE_REMOTE:
                    he_remote = YES;
                    break;
                case HE_NOBACKUP:
                    backup = NO;
                    break;
                case HE_BACKUP:
                    backup = YES;
                    break;
                case HE_NOTFOUND:
                    unkOpt(argv[i]);
                    quit(1); /* does not return */
                    break;
                case HE_AMBIG:
                    ambigOpt(argv[i]);
                    quit(1);
                    break;
                default:
                    irrOpt(argv[i]);
                    quit(1); /* does not return */
                    break;
            }
        }
        else {
            /* Must be a filename */
            if (!fileName)
                fileName = argv[i];
            else
                fprintf(stderr, "Single file only. %s not open.\n", argv[i]);
        }
    }

    if (NULL == (he_desc = (DFdesc *)calloc(HE_DESC_SZ, sizeof(DFdesc))))
        goto error;
    if (NULL == (he_grp = (HE_GROUP *)calloc(HE_DESC_SZ, sizeof(HE_GROUP))))
        goto error;

    /* if there is a file name given in the command line, open it */

    if (fileName)
        he_status = openFile(fileName, backup);

    /* read, execute loop */
    cmdLoop();

    if (fileOpen())
        closeFile(YES); /* close with keep */
    quit(EXIT_SUCCESS);
    return EXIT_SUCCESS;

error:
    free(he_desc);
    free(he_grp);
    quit(EXIT_FAILURE);
    return EXIT_FAILURE;
}

/* cmdLoop -- read commands and execute them */
void
cmdLoop(void)
{
    HE_CMD *cmd;

    for (cmd = getCmd(); cmd; cmd = getCmd()) {
        if (cmd->func)
            he_status = (*cmd->func)(cmd);
        else {
            fprintf(stderr, "Unknown command: %s.\n", cmd->argv[0]);
            he_status = HE_FAIL;
        }
        deleteCmd(cmd);
    }
}

int32
getElement(int desc, char **pdata)
{
    int32 length;
    int32 fid;

    length = he_desc[desc].length;

    /* alloc memory to read the element in */
    *pdata = (char *)malloc(length);
    if (*pdata == NULL)
        return FAIL;

    /* read in the element and check for error */
    if ((fid = Hopen(he_file, DFACC_READ, 0)) == -1) {
        HEprint(stderr, 0);
        return FAIL;
    }
    if (Hgetelement(fid, he_desc[desc].tag, he_desc[desc].ref, (unsigned char *)(*pdata)) < 0) {
        free(*pdata);
        fprintf(stderr, "Cannot read element.\n");
        return FAIL;
    }
    Hclose(fid);
    return length;
}

/* the tmp directory, currently set for unix */
#define TDIR "/tmp/"

int
getTmpName(char **pname)
{
    int        length;
    static int count = 0;
    char       s[32];

    int pid = getpid();
    (void)sprintf(s, "%she%d.%d", TDIR, pid, count);
    count++;

    length = (int)strlen(s);
    if (length <= 0)
        return FAIL;

    *pname = (char *)malloc(length + 1);
    strcpy(*pname, s);

    return length;
}

int
writeToFile(char *file, char *data, int32 length)
{
    FILE *fd = NULL;
    int   written;

    fd = fopen(file, "w");
    if (fd == NULL)
        goto error;

    written = (int)fwrite(data, sizeof(char), (size_t)length, fd);
    if (written != length) {
        fprintf(stderr, "Error in write.\n");
        goto error;
    }
    fclose(fd);

    return HE_OK;

error:
    if (fd != NULL)
        fclose(fd);
    return FAIL;
}

int
removeFile(char *file)
{
    return remove((const char *)file);
}

/* is a file currently opened */
int
fileOpen(void)
{
    return (he_file != NULL);
}

char *
backupName(const char *file)
{
    return catStr(file, "$hdfed$");
}

int
backupFile(char *file)
{
    char *back = NULL; /* backup file name */
    int   f    = -1;

    back = backupName(file);
    f    = copyFile(file, back);
    free(back);

    return f;
}

int
copyFile(char *from, char *to)
{
    int   num_read;
    char  buf[HE_BUF_SZ]; /* copying buffer */
    FILE *fp  = NULL;
    FILE *bfp = NULL;

    /* open the hdf file for backing up */
    if ((fp = fopen(from, "r")) == NULL) {
        fprintf(stderr, "Unable to open file: <%s>\n", from);
        goto error;
    }
    if ((bfp = fopen(to, "w")) == NULL) {
        fclose(fp);
        fprintf(stderr, "Unable to open backup file.\n");
        goto error;
    }
    /* copy the contents from hdf file to backup file */
    while ((num_read = (int)fread(buf, 1, HE_BUF_SZ, fp)) > 0)
        fwrite(buf, 1, (size_t)num_read, bfp);

    fclose(fp);
    fclose(bfp);

    return HE_OK;

error:

    if (fp != NULL)
        fclose(fp);
    if (bfp != NULL)
        fclose(bfp);

    return FAIL;
}

int
updateDesc(void)
{
    int32 fid;
    int32 groupID;
    int32 aid, status;
    int   i;

    if ((fid = Hopen(he_file, DFACC_READ, 0)) == 0) {
        printf("failed opening\n");
        HEprint(stdout, 0);
        return FAIL;
    }

    aid = Hstartread(fid, DFTAG_WILDCARD, DFREF_WILDCARD);
    if (aid == FAIL) {
        HEprint(stderr, 0);
        return FAIL;
    }

    status = SUCCEED;
    for (i = 0; (i < HE_DESC_SZ) && (status != FAIL); i++) {
        Hinquire(aid, NULL, &he_desc[i].tag, &he_desc[i].ref, &he_desc[i].length, &he_desc[i].offset, NULL,
                 (int16 *)NULL, (int16 *)NULL);
        status = Hnextread(aid, DFTAG_WILDCARD, DFREF_WILDCARD, DF_CURRENT);
    }
    he_numDesc = i;

    /* get information about the groups */
    he_numGrp = 0;
    for (i = 0; i < he_numDesc; i++) {
        if (isGrp(he_desc[i].tag)) {
            he_grp[he_numGrp].desc = i;
            /*                he_grp[he_numGrp].size = (int) (he_desc[i].length / sizeof(tag_ref));
                            he_grp[he_numGrp].ddList = (tag_ref_ptr) malloc(he_desc[i].length);
            */
            he_grp[he_numGrp].size   = (int)(he_desc[i].length / 4);
            he_grp[he_numGrp].ddList = (tag_ref_ptr)malloc(he_grp[he_numGrp].size * sizeof(tag_ref));

            if (!he_grp[he_numGrp].ddList) {
                fprintf(stderr, "Out of memory. Closing file.\n");
                closeFile(1); /* keep the backup */
                return FAIL;
            }
            groupID = DFdiread(fid, he_desc[i].tag, he_desc[i].ref);
            if (groupID < 0) {
                HEprint(stderr, 0);
                return FAIL;
            }
            for (int j = 0; j < he_grp[he_numGrp].size; j++)
                DFdiget(groupID, &he_grp[he_numGrp].ddList[j].tag, &he_grp[he_numGrp].ddList[j].ref);

            he_numGrp++;
        }
    }
    Hendaccess(aid);
    Hclose(fid);
    return SUCCEED;
}

int
initFile(char *file)
{
    free(he_file);
    he_file = copyStr(file);

    if (updateDesc() < 0)
        return FAIL;

    /* if there are groups in this file, go to the first group tag */
    /* otherwise, just go to the first element */
    if (he_numGrp > 0)
        he_currDesc = he_grp[0].desc;
    else
        he_currDesc = 0;

    return resetPred();
}

int
closeFile(int keep)
{
    char *back = NULL;

    if (!fileOpen()) {
        fprintf(stderr, "No open file to close.\n");
        return FAIL;
    }
    /* free some dynamic storages */
    if (he_backup && !keep) {
        back = backupName(he_file);
        (void)removeFile(back);
        free(back);
    }
    free(he_file);
    he_file = NULL;
    for (int i = 0; i < he_numGrp; i++)
        free(he_grp[i].ddList);

    return HE_OK;
}

int
getR8(int xdim, int ydim, char *image, char *pal, int compress)
{
    FILE *fp = NULL;
    int32 length;
    char *buf = NULL;

    if (!fileOpen()) {
        noFile();
        goto error;
    }
    if (pal)
        if (setPal(pal) < 0)
            /* Error already signalled by setPal */
            goto error;

    length = xdim * ydim;
    buf    = (char *)malloc(length);

    if ((fp = fopen(image, "r")) == NULL) {
        fprintf(stderr, "Error opening image file: %s.\n", image);
        goto error;
    }
    if (fread(buf, (size_t)xdim, (size_t)ydim, fp) < (size_t)ydim) {
        fprintf(stderr, "Error reading image file: %s.\n", image);
        goto error;
    }

    if (DFR8addimage(he_file, buf, (int32)xdim, (int32)ydim, (uint16)compress) < 0) {
        HEprint(stderr, 0);
        goto error;
    }

    if (updateDesc() < 0)
        goto error;

    fclose(fp);
    free(buf);

    return HE_OK;

error:

    if (fp != NULL)
        fclose(fp);
    free(buf);

    return FAIL;
}

int
setPal(char *pal)
{
    FILE *fp = NULL;
    char  reds[HE_COLOR_SZ], greens[HE_COLOR_SZ], blues[HE_COLOR_SZ];
    char  palette[HE_PALETTE_SZ];
    char *p;

    if ((fp = fopen(pal, "r")) == NULL) {
        fprintf(stderr, "Error opening palette file: %s.\n", pal);
        goto error;
    }
    if (fread(reds, 1, HE_COLOR_SZ, fp) < HE_COLOR_SZ || fread(greens, 1, HE_COLOR_SZ, fp) < HE_COLOR_SZ ||
        fread(blues, 1, HE_COLOR_SZ, fp) < HE_COLOR_SZ) {
        fprintf(stderr, "Error reading palette file: %s.\n", pal);
        goto error;
    }

    /* convert sun palette to hdf palette */
    p = palette;
    for (int i = 0; i < HE_COLOR_SZ; i++) {
        *p++ = reds[i];
        *p++ = greens[i];
        *p++ = blues[i];
    }

    if (DFR8setpalette((uint8 *)palette) < 0) {
        fputs("Error setting palette.\n", stderr);
        goto error;
    }

    fclose(fp);

    return HE_OK;

error:

    if (fp != NULL)
        fclose(fp);
    return FAIL;
}

int
findDesc(tag_ref_ptr dd)
{
    for (int i = 0; i < he_numDesc; i++)
        if ((he_desc[i].tag == dd->tag) && (he_desc[i].ref == dd->ref))
            return i;

    return FAIL;
}

int
desc2Grp(int desc)
{
    for (int i = 0; i < he_numGrp; i++)
        if (he_grp[i].desc == desc)
            return i;

    NOT_REACHED();
    return FAIL;
}

int
hasReference(int desc)
{
    for (int i = 0; i < he_numGrp; i++)
        for (int j = 0; j < he_grp[i].size; j++)
            if (he_grp[i].ddList[j].tag == he_desc[desc].tag && he_grp[i].ddList[j].ref == he_desc[desc].ref)
                return YES;
    return NO;
}

int
deleteDesc(int desc)
{
    int32 fid = -1;

    if ((fid = Hopen(he_file, DFACC_WRITE, 0)) == -1) {
        HEprint(stderr, 0);
        goto error;
    }

    if (Hdeldd(fid, he_desc[desc].tag, he_desc[desc].ref) == FAIL) {
        HEprint(stderr, 0);
        goto error;
    }
    return Hclose(fid);

error:
    if (fid != -1)
        if (FAIL == Hclose(fid))
            HEprint(stderr, 0);
    return FAIL;
}

int
getCurrRig(int32 *pXdim, int32 *pYdim, char **pPalette, char **pRaster)
{
    int ispal;

    goTo(he_currDesc);

    if (DFR8getdims(he_file, pXdim, pYdim, &ispal) < 0) {
        fprintf(stderr, "Error getting image group.\n");
        HEprint(stderr, 0);
        return FAIL;
    }
    if (ispal)
        *pPalette = (char *)malloc(HE_PALETTE_SZ);
    else
        *pPalette = (char *)NULL;
    *pRaster = (char *)malloc((size_t)(*pXdim) * (size_t)(*pYdim));

    if (DFR8getimage(he_file, (unsigned char *)*pRaster, *pXdim, *pYdim, (unsigned char *)*pPalette) ==
        FAIL) {
        fprintf(stderr, "Error getting image group.\n");
        HEprint(stderr, 0);
        return FAIL;
    }

    return HE_OK;
}

int
putWithTempl(char *template, int n1, int n2, int n3, char *data, int length, int verbose)
{
    char *file = NULL;
    int   ret  = FAIL;

    convertTemplate(template, n1, n2, n3, &file);
    if (verbose)
        printf("Writing to file: %s\n", file);
    ret = writeToFile(file, data, length);
    free(file);

    return ret;
}

int
revert(void)
{
    char *back = NULL;

    back = backupName(he_file);
    if (copyFile(back, he_file) < 0)
        return FAIL;
    return initFile(he_file);
}

int
writeElt(char *file, uint16 ref, int elt)
{
    int         ret;
    char       *data;
    int32       eltLength;
    char       *p;
    uint16      rank;
    int         i;
    uint16      ntTag;
    uint16      ntRef;
    tag_ref_ptr ntDesc;
    int         nt;

    eltLength = getElement(elt, &data);
    if (eltLength <= 0) {
        fprintf(stderr, "Cannot get element: tag %d ref %d.\n", he_desc[elt].tag, he_desc[elt].ref);
        return FAIL;
    }
    /* special case */

    if (he_desc[elt].tag == DFTAG_SDD) {

        /* lots of hack here */
        /* assume that the number types are of the same ref as this elt */
        p = data;

        /* get the rank */
        /* NOTE: UINT16READ and UINT16WRITE advances p */
        UINT16DECODE(p, rank);
        /* move to the NT of the data */
        p += (rank * 4);
        UINT16DECODE(p, ntTag);
        UINT16DECODE(p, ntRef);

        /* set up to write the number type element */
        ntDesc      = (tag_ref_ptr)malloc(sizeof(tag_ref));
        ntDesc->tag = ntTag;
        ntDesc->ref = ntRef;
        nt          = findDesc(ntDesc);
        free(ntDesc);
        writeElt(file, ref, nt);

        p -= 2;
        UINT16ENCODE(p, ref);
        /* do the NT of scales */
        for (i = 0; (uint16)i < rank; i++) {
            p += 2;
            UINT16ENCODE(p, ref);
        }
    }

    ret = putElement(file, he_desc[elt].tag, ref, data, eltLength);
    free(data);
    return ret;
}

int
putElement(char *file, uint16 tag, uint16 ref, char *data, int32 len)
{
    int32 ret;
    int32 fid = -1;

    if ((fid = Hopen(file, DFACC_READ | DFACC_WRITE, 0)) == FAIL)
        /* a little tricky here */
        if (HEvalue(0) != DFE_FNF || (fid = Hopen(file, DFACC_ALL, 0)) == FAIL) {
            HEprint(stderr, 0);
            goto error;
        }
    if ((ret = Hputelement(fid, tag, ref, (unsigned char *)data, len)) < 0) {
        HEprint(stderr, 0);
        goto error;
    }
    return Hclose(fid);

error:
    if (fid != -1)
        if (FAIL == Hclose(fid))
            HEprint(stderr, 0);

    return FAIL;
}

int
writeGrp(char *file)
{
    uint16 ref = DFREF_NONE;
    int    grp;
    int    elt;
    int    ret;
    int32  fid;
    int32  gid;

    getNewRef(file, &ref);

    grp = currGrpNo;
    gid = DFdisetup(he_grp[grp].size);
    for (int i = 0; i < he_grp[grp].size; i++) {
        elt = findDesc(he_grp[grp].ddList + i);
        if (elt >= 0)
            writeElt(file, ref, elt);

        /* Update the group dd list */
        DFdiput(gid, he_grp[grp].ddList[i].tag, ref);
    }

    /* Do the group now */

    if ((fid = Hopen(file, DFACC_READ | DFACC_WRITE, 0)) == FAIL) {
        HEprint(stderr, 0);
        return FAIL;
    }
    if ((ret = DFdiwrite(fid, gid, currTag, ref)) < 0) {
        HEprint(stderr, 0);
        return ret;
    }
    return Hclose(fid);
}

int
getNewRef(char *file, uint16 *pRef)
{
    int32 fid;

    if ((fid = Hopen(file, DFACC_READ | DFACC_WRITE, 0)) == FAIL) {
        /* a little tricky here */
        if (HEvalue(0) != DFE_FNF || (fid = Hopen(file, DFACC_ALL, 0)) == FAIL) {
            HEprint(stderr, 0);
            return FAIL;
        }
    }

    *pRef = Hnewref(fid);
    return Hclose(fid);
}

int
writeAnnot(char *file, uint16 tag, uint16 ref)
{
    char  *data;
    int32  eltLength;
    int    tmp;
    char  *p;
    uint16 newRef;

    while (tag == 0) {
        printf("Attach to what tag? (> 0)");
        scanf("%d", &tmp);
        tag = (uint16)tmp;
    }

    while (ref == 0) {
        printf("Attach to what ref? (> 0)");
        scanf("%d", &tmp);
        ref = (uint16)tmp;
    }

    eltLength = getElement(he_currDesc, &data);
    if (eltLength <= 0) {
        fprintf(stderr, "Cannot get element: tag %d ref %d.\n", he_desc[he_currDesc].tag,
                he_desc[he_currDesc].ref);
        return FAIL;
    }

    p = data;

    /*
     *  This is really ugly...
     */
    UINT16ENCODE(p, tag);
    UINT16ENCODE(p, ref);

    if (getNewRef(file, &newRef) < 0) {
        fprintf(stderr, "Error getting new ref number.\n");
        return FAIL;
    }

    return putElement(file, he_desc[he_currDesc].tag, newRef, data, eltLength);
}

int32
getAnn(int ann, uint16 tag, uint16 ref, char **pBuf)
{
    int32 len;

    if (ann == HE_LABEL) {
        len = DFANgetlablen(he_file, tag, ref);
        if (len > 0) {
            *pBuf = (char *)malloc((size_t)(len + 1));
            DFANgetlabel(he_file, tag, ref, *pBuf, len + 1);
        }
        else
            *pBuf = NULL;
    }
    else {
        len = DFANgetdesclen(he_file, tag, ref);
        if (len > 0) {
            *pBuf = (char *)malloc((size_t)len);
            DFANgetdesc(he_file, tag, ref, *pBuf, len);
        }
        else
            *pBuf = NULL;
    }
    return len;
}

int
putAnn(int ann, uint16 tag, uint16 ref, char *buf, int32 len)
{
    int ret;

    if (ann == HE_LABEL)
        ret = DFANputlabel(he_file, tag, ref, buf);
    else
        ret = DFANputdesc(he_file, tag, ref, buf, len);
    if (ret < 0)
        HEprint(stderr, 0);

    return ret;
}

int32
readFromFile(char *file, char **pBuf)
{
    FILE *fp = NULL;
    int32 soFar;
    int32 bufLen;
    int32 num_read;

    fp = fopen(file, "r");
    if (fp == NULL)
        goto error;

    soFar  = 0;
    bufLen = 0;
    for (num_read = HE_BUF_SZ; num_read == HE_BUF_SZ; soFar += num_read) {
        bufLen += HE_BUF_SZ;
        if (bufLen == HE_BUF_SZ)
            *pBuf = (char *)malloc(bufLen);
        else
            *pBuf = (char *)realloc(*pBuf, bufLen);
        if (*pBuf == NULL)
            goto error;

        num_read = (int32)fread((*pBuf) + soFar, 1, HE_BUF_SZ, fp);
    }
    *pBuf          = (char *)realloc(*pBuf, soFar + 1);
    (*pBuf)[soFar] = '\0';
    fclose(fp);
    return soFar;

error:
    if (fp != NULL)
        fclose(fp);
    return FAIL;
}

/* ---- table for operators -------- */
struct {
    const char *str;
    int         key;
} he_optTab[] = {
    {"readonly", HE_RDONLY},
    {"all", HE_ALL},
    {"backup", HE_BACKUP},
    {"batch", HE_BATCH},
    {"help", HE_HELP},
    {"longout", HE_LONGOUT},
    {"nobackup", HE_NOBACKUP},
    {"remote", HE_REMOTE},
    {"verbose", HE_VERBOSE},
    {"position", HE_POSITION},
    {"expansion", HE_EXPANSION},
    {"large", HE_LARGE},
    {"offset", HE_OFFSET},
    {"ascii", HE_ASCII},
    {"octal", HE_OCTAL},
    {"hexadecimal", HE_HEX},
    {"decimal", HE_DECIMAL},
    {"float", HE_FLOAT},
    {"dimensions", HE_DIMS},
    {"image", HE_IMAGE},
    {"palette", HE_PALETTE},
    {"raster", HE_RASTER},
    {"rle", HE_RLE},
    {"compress", HE_RLE},
    {"imcomp", HE_IMCOMP},
    {"group", HE_DOGROUP},
    {"file", HE_FILE},
    {"keep", HE_KEEP},
    {"length", HE_LENGTH},
    {"attachto", HE_ATTACHTO},
    {"label", HE_LABEL},
    {"descriptor", HE_DESCRIPTOR},
    {"editor", HE_EDITOR},
    {"byte", HE_BYTE},
    {"short", HE_SHORT},
    {"double", HE_DOUBLE},
    {"ushort", HE_USHORT},
    {"udecimal", HE_UDECIMAL},
    {"raw", HE_RAW},
};

int
findOpt(char *word)
{
    unsigned len;
    int      found = -1;

    len = strlen(word);

    for (int i = 0; i < sizeof(he_optTab) / sizeof(he_optTab[0]); i++)
        if (!strncmp(he_optTab[i].str, word, len)) {
            /* exact match */
            if (strlen(he_optTab[i].str) == len)
                return he_optTab[i].key;

            if (found < 0)
                found = (int)i;
            else
                return HE_AMBIG;
        }

    if (found < 0)
        return HE_NOTFOUND;

    return he_optTab[found].key;
}

char *
catStr(const char *s, const char *s1)
{
    char *t = NULL;

    t = (char *)malloc(strlen(s) + strlen(s1) + 1);
    strcpy(t, s);
    strcat(t, s1);
    return t;
}

char *
copyStr(char *s)
{
    char *t = NULL;

    t = (char *)malloc(strlen(s) + 1);
    strcpy(t, s);
    return t;
}

int
isGrp(uint16 tag)
{
    switch (tag) {
        case DFTAG_RIG:
        case DFTAG_SDG:
        case DFTAG_NDG:
            /* and other group tags */
            return 1;
        default:
            return 0;
    }
}

int
HEquit(HE_CMD *cmd)
{
    if (cmd->argc > 1) {
        puts("quit");
        puts("\tQuits this application.");
        return HE_OK;
    }
    return quit(0);
}

int
quit(int status)
{
    free(he_desc);
    free(he_grp);

    if (fileOpen()) {
        if (closeFile(0) < 0)
            return HE_FAIL;
    }
    exit(status);
}

int
HEhelp(HE_CMD *dummy)
{
    (void)dummy;

    help();
    return HE_OK;
}

void
help(void)
{
    /* print some help information */
    printf("hdfed allows sophisticated HDF users to manipulate the elements in");
    printf(" an HDF file.\nThese manipulations include selecting groups and ");
    printf("showing information about\nthem, dumping them to the output, ");
    printf("writing them to new files, deleting them,\ninserting them, ");
    printf("replacing, say, the palette of an r8 group, and editing the\n");
    printf("text labels and descriptions of any element.\n\n");

    printf("hdfed will NOT allow the user to *arbitrarily* modify binary data ");
    printf("in the file or\nany element, though it allows modification of tag ");
    printf("and reference numbers within\nstrict constraints.  The user should");
    printf(" not attempt to alter individual bytes.  It\nis acceptable,");
    printf(" however, to replace an element with another of the same type.\n\n");

    printf("hdfed can be used both interactively or in 'batch' mode.  See the ");
    printf("\"HDF Calling\nInterfaces and Utilities\" manual section on hdfed ");
    printf("for an example.\n\n");

    printf("List of commands:");
    printf("\tannotate   dump    if      open    putr8    unalias\n");
    printf("\t\t\tclose      getr8   info    prev    revert   wait\n");
    printf("\t\t\tdelete     help    next    put     select   write\n");

    printf("\nPredicates for 'if' 'select' 'next' and 'prev' are of the form\n");
    printf("\ttag = 3 ref = 2 image_size < 1000 label = \"abc\"\n");
    printf("Type <command> -help for usage of <command>.  DO NOT type the");
    printf(" command without\narguments and expect help.  Some commands delete");
    printf(" objects and do not need any\narguments.  If you are learning to ");
    printf("use hdfed, try it on an expendable file.\n");
}

int
HEwait(HE_CMD *cmd)
{
    int c;

    if (cmd->argv[1][0] == '-' && findOpt(cmd->argv[1] + 1) == HE_HELP) {
        printf("wait [<message>]\n");
        printf("\tPrints message and then wait for user to hit return\n");
        return HE_OK;
    }

    printf("%s\nPress return to continue.", cmd->argv[1]);
    do
        c = getchar();
    while (c != '\n');

    return HE_OK;
}

void
deleteCmd(HE_CMD *cmd)
{
    if (cmd == NULL)
        return;
    if (cmd->next != NULL)
        deleteCmd(cmd->next);
    if (cmd->sub != NULL)
        deleteCmd(cmd->sub);
    for (int i = 0; i < cmd->argc; i++)
        free(cmd->argv[i]);
    free(cmd);
}

/* -------------- routines to manipulate templates --------------------- */

#define TEMPLATE_CHAR1 '#'
#define TEMPLATE_CHAR2 '@'
#define TEMPLATE_CHAR3 '%'

void
convertTemplate(char *template, int n1, int n2, int n3, char **pname)
{
    char  s1[20], s2[20], s3[20];
    char *t;

    sprintf(s1, "%1d", n1);
    sprintf(s2, "%1d", n2);
    sprintf(s3, "%1d", n3);

    *pname = t = (char *)malloc(strlen(template) + 61);

    while (*template)
        switch (*template) {
            case TEMPLATE_CHAR1:
                fillTemplate(&template, &t, s1, TEMPLATE_CHAR1);
                break;
            case TEMPLATE_CHAR2:
                fillTemplate(&template, &t, s2, TEMPLATE_CHAR2);
                break;
            case TEMPLATE_CHAR3:
                fillTemplate(&template, &t, s3, TEMPLATE_CHAR3);
                break;
            default:
                *t++ = *template ++;
        }
    *t = '\0';
}

void
fillTemplate(char **template, char **pout, char *s, char templateChar)
{
    int templateLen, sLen;

    /* count length of template to replace */
    for (templateLen = 0; **template == templateChar; (*template)++, templateLen++)
        ;
    sLen = (int)strlen(s);

    /* fill with zero's if the space reserved in template is
       longer than the length of s */
    for (; templateLen > sLen; templateLen--)
        *(*pout)++ = '0';

    while (*s)
        *(*pout)++ = *s++;
}

/* end of he-main.c */
