/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "hdf.h"
#include "hfile_priv.h"

#define MAXBUFF 8192

dd_t *desc_buf;

intn debug   = FALSE, /* Debugging is off by default */
    ddblocks = FALSE, /* DD block dumping is off by default */
#ifdef DISKBLOCK_DEBUG
    diskblock_check = FALSE,     /* check disk-block boundaries */
#endif                           /* DISKBLOCK_DEBUG */
    sort                = TRUE,  /* Sorting is on by default */
    longout             = FALSE, /* short output by default */
    labels              = FALSE, /* no label info by default */
    special             = FALSE, /* no special info by default */
    groups              = FALSE; /* no group info by default */
static intn v_init_done = FALSE; /* whether the Vset interface has been */
/* initialized for this file */
uint16 only_tag = DFTAG_NULL; /* by default print info about all tags */

char *file_name; /* name of current file being listed */

int  compare(const void *, const void *);
int  main(int, char *a[]);
void lprint(int32, dd_t *, int);
void print_item(int32, dd_t *, intn);
void printfilever(int32 file_id);
void usage(char *argv[]);
int  dumpDD(void);

int
compare(const void *aa, const void *bb)
{
    const dd_t *a = (const dd_t *)aa;
    const dd_t *b = (const dd_t *)bb;

    if (a->tag > b->tag)
        return (1);
    if (a->tag < b->tag)
        return (-1);
    if (a->ref > b->ref)
        return (1);
    if (a->ref < b->ref)
        return (-1);
    return (0);
}

void
print_item(int32 fid, dd_t *desc_list, intn n)
{
    intn  status;
    int32 len;
    char *name, *label_str;

    printf("\tRef no %6d\t%8d bytes\n", (int)desc_list[n].ref, (int)desc_list[n].length);

    /* print out labels and annotations if desired */
    if (labels) { /* read in all of the labels */
        len = DFANgetlablen(file_name, desc_list[n].tag, desc_list[n].ref);
        if (len != FAIL) {
            label_str      = (char *)malloc((uint32)len + 1);
            status         = DFANgetlabel(file_name, desc_list[n].tag, desc_list[n].ref, label_str, len + 1);
            label_str[len] = '\0';
            if (status == FAIL)
                printf("\t  Unable to read label\n");
            else
                printf("\t  Label: %s\n", label_str);
            free(label_str);
        }

        /* read in all of the annotations */
        len = DFANgetdesclen(file_name, desc_list[n].tag, desc_list[n].ref);
        if (len != FAIL) {
            label_str      = (char *)malloc((uint32)len + 1);
            status         = DFANgetdesc(file_name, desc_list[n].tag, desc_list[n].ref, label_str, len + 1);
            label_str[len] = '\0';
            if (status == FAIL)
                printf("\t  Unable to read description\n");
            else
                printf("\t  Description: %s\n", label_str);
            free(label_str);
        }
    }

    if ((special) && (SPECIALTAG(desc_list[n].tag))) { /* print out special info if desired */
        sp_info_block_t info;
        int32           aid, ret;

        aid = Hstartread(fid, desc_list[n].tag, desc_list[n].ref);
        if (aid == FAIL)
            return;

        ret = HDget_special_info(aid, &info);
        if ((ret == FAIL) || (info.key == FAIL))
            return;

        switch (info.key) {
            case SPECIAL_LINKED:
                printf("\tLinked Block: first %ld standard %ld per unit %ld\n", (long)info.first_len,
                       (long)info.block_len, (long)info.nblocks);
                break;

            case SPECIAL_EXT:
                printf("\tExternal File: path %s  offset %ld\n", info.path, (long)info.offset);
                break;

            case SPECIAL_COMP:
                printf("\tCompressed Element: compression type: %s  modeling type %s\n",
                       (info.comp_type == COMP_CODE_NONE
                            ? "None"
                            : (info.comp_type == COMP_CODE_RLE
                                   ? "Run-Length"
                                   : (info.comp_type == COMP_CODE_NBIT
                                          ? "N-Bit"
                                          : (info.comp_type == COMP_CODE_SKPHUFF
                                                 ? "Skipping Huffman"
                                                 : (info.comp_type == COMP_CODE_DEFLATE ? "Deflated"
                                                                                        : "Unknown"))))),
                       (info.model_type == COMP_MODEL_STDIO ? "Standard" : "Unknown"));
                break;

            case SPECIAL_CHUNKED:
                printf("\tChunked Element: \n \tlogical size: %ld\n \tnumber of dimensions: %ld \n",
                       (long)info.chunk_size, (long)info.ndims);
                printf("\tarray of chunk lengths for each dimension:");
                for (int i = 0; i < info.ndims; i++)
                    printf("\t %ld", (long)info.cdims[i]);
                printf("\n");
                free(info.cdims);
                break;

            default:
                printf("\tDo not understand special element type %d\n", info.key);
                break;
        }
        Hendaccess(aid);
    }

    if ((groups) && (desc_list[n].tag == DFTAG_RIG || desc_list[n].tag == DFTAG_SDG ||
                     desc_list[n].tag == DFTAG_NDG || desc_list[n].tag == DFTAG_VG)) { /* print groups */
        if (desc_list[n].tag != DFTAG_VG) { /* groups other than Vgroups */
            int32 GroupID;
            DFdi  elmt;

            printf("\tContents:\n");
            if ((GroupID = DFdiread(fid, desc_list[n].tag, desc_list[n].ref)) >= 0) {
                while (!DFdiget(GroupID, &elmt.tag, &elmt.ref)) {
                    name = (char *)HDgettagsname(elmt.tag);
                    if (!name)
                        printf("\t\t%-30s: (tag=%6d) ref=%d\n", "Unknown Tag", elmt.tag, elmt.ref);
                    else {
                        printf("\t\t%-30s: (tag=%6d) ref=%d\n", name, elmt.tag, elmt.ref);
                        free(name);
                    } /* end else */
                }     /* end while */
            }         /* end if */
            else
                printf("\t\tNone!\n");
        }      /* end if */
        else { /* dump Vgroup tag/refs */
            int32  ntagrefs;
            int32  vkey;
            int32 *tag_arr, *ref_arr;

            if (v_init_done == FALSE) { /* init the V routines */
                v_init_done = TRUE;
                Vinitialize(fid);
            } /* end if */
            if ((vkey = Vattach(fid, (int32)desc_list[n].ref, "r")) != FAIL) {
                ntagrefs = Vntagrefs(vkey);
                printf("\tContents: %d items\n", (int)ntagrefs);
                if (ntagrefs > 0) {
                    tag_arr = (int32 *)malloc(sizeof(int32) * (size_t)ntagrefs);
                    ref_arr = (int32 *)malloc(sizeof(int32) * (size_t)ntagrefs);
                    if (tag_arr == NULL || ref_arr == NULL) {
                        free(tag_arr);
                        free(ref_arr);
                    }
                    else {
                        if (Vgettagrefs(vkey, tag_arr, ref_arr, ntagrefs) != FAIL) {
                            for (int i = 0; i < ntagrefs; i++) {
                                name = (char *)HDgettagsname((uint16)tag_arr[i]);
                                if (!name)
                                    printf("\t\t%-30s: (tag=%6d) ref=%d\n", "Unknown Tag", (int)tag_arr[i],
                                           (int)ref_arr[i]);
                                else {
                                    printf("\t\t%-30s: (tag=%6d) ref=%d\n", name, (int)tag_arr[i],
                                           (int)ref_arr[i]);
                                    free(name);
                                } /* end else */
                            }     /* end for */
                        }         /* end if */
                        free(tag_arr);
                        free(ref_arr);
                    } /* end else */
                }     /* end if */
                Vdetach(vkey);
            } /* end if */
        }     /* end else */
    }         /* dumping groups */
} /* print_item */

void
lprint(int32 fid, dd_t *desc_tmp, int num)
{
    intn   j = 0, empty = 0;
    uint16 prev = 0;
    char  *name;

    while (j < num) {
        if (desc_tmp[j].tag == DFTAG_NULL) {
            empty++;
            j++;
            continue; /* don't print anything now */
        }

        /*
         * skip this tag if the user only wants to see some tags and
         *  this is not one of them
         */
        if (only_tag != DFTAG_NULL && only_tag != desc_tmp[j].tag) {
            j++;
            continue;
        }

        /*
        ** Find and print text description of this tag
        */
        name = (char *)HDgettagsname(desc_tmp[j].tag);
        if (!name)
            printf("\n%-30s: (tag %d)\n", "Unknown Tag", desc_tmp[j].tag);
        else {
            printf("\n%-30s: (tag %d)\n", name, desc_tmp[j].tag);
            free(name);
        }

        /*
        ** Print out reference number information
        */
        prev = desc_tmp[j].tag;
        if (longout) {
            while (desc_tmp[j].tag == prev && j < num) {
                print_item(fid, desc_tmp, j);
                j++;
            }
        }
        else {
            printf("\tRef nos:");
            while (desc_tmp[j].tag == (uint16)prev && j < num) {
                printf(" %d", desc_tmp[j].ref);
                j++;
            }
        }
    }

    if (empty)
        printf("\nEmpty (tag %d): %d slots\n", DFTAG_NULL, empty);
}

/* print the library version of the file */
void
printfilever(int32 file_id)
{
    uint32 major, minor, release;
    char   string[LIBVSTR_LEN + 1];

    if (Hgetfileversion(file_id, &major, &minor, &release, string) == SUCCEED) {
        string[LIBVSTR_LEN] = '\0'; /* make it a null terminated string */
        printf("File library version: ");
        printf("Major= %u, Minor=%u, Release=%u\nString=%s\n", (unsigned)major, (unsigned)minor,
               (unsigned)release, string);
    }
    else
        printf("(Does not have library version information)\n");
}

/* print command usage */
void
usage(char *argv[])
{
    printf("%s,  %s\n\n", argv[0], LIBVER_STRING);
#ifdef DISKBLOCK_DEBUG
    printf("%s [-o] [-l] [-d] [-v] [-g] [-s] [-h] [-x] [-t #] fn ....\n", argv[0]);
#else  /* DISKBLOCK_DEBUG */
    printf("%s [-o] [-l] [-d] [-v] [-g] [-s] [-h] [-t #] fn ....\n", argv[0]);
#endif /* DISKBLOCK_DEBUG */
    printf("        This program displays information about the");
    printf(" data elements in\n");
    printf("        HDF file.\n");
    printf("    -d: offset & length info of each element in the file\n");
    printf("    -o: Ordered - display in reference number order\n");
    printf("    -l: Long format - display more information\n");
    printf("    -v: Verbose format - display text of annotations and labels.\n");
    printf("        (Verbose format automatically puts you in Long format).\n");
    printf("    -t #: List only information about a specific type of tag.\n");
    printf("          For example '%s -t 700 foo.hdf' \n", argv[0]);
    printf("          will list information only about Scientific Data\n");
    printf("          Groups.\n");
    printf("    -s: Give detailed descriptions of \"special elements\"\n");
    printf("    -g: List items in groups\n");
    printf("    -h: Dump DD Block information\n");
#ifdef DISKBLOCK_DEBUG
    printf("    -x: Check disk block boundaries (also dumps DD blocks)\n");
#endif /* DISKBLOCK_DEBUG */
}

/* dump the DD blocks */
int
dumpDD(void)
{
    hdf_file_t file_id; /* stdio file ID */
    int32      next_block = MAGICLEN;
    uint8     *ddbuf;                    /* buffer to store the DD information */
    uint8      buf[NDDS_SZ + OFFSET_SZ]; /* buffer to hold DD block info */
    uint8     *b;
    int16      n_dds;    /* number of DDs in the current block */
    uint16     tag, ref; /* DD tag & ref */
    int32      off, len; /* DD offset & length */
    intn       l;        /* local counting variable */

    file_id = HI_OPEN(file_name, DFACC_READ);
    if (OPENERR(file_id)) {
        printf("Error opening file: %s\n", file_name);
        return (FAIL);
    } /* end if */
    while (next_block != 0) {
        if (HI_SEEK(file_id, next_block) == FAIL) {
            printf("Error seeking in file: %s\n", file_name);
            return (FAIL);
        } /* end if */
        if (HI_READ(file_id, buf, NDDS_SZ + OFFSET_SZ) == FAIL) {
            printf("Error reading in file: %s\n", file_name);
            return (FAIL);
        } /* end if */
        printf("current block: %ld,", (long)next_block);
        b = buf;
        INT16DECODE(b, n_dds);
        INT32DECODE(b, next_block);
        printf(" size of block: %ld, number of DDs:%d, next block: %ld\n",
               (long)(NDDS_SZ + OFFSET_SZ + (n_dds * DD_SZ)), (int)n_dds, (long)next_block);

        ddbuf = (uint8 *)malloc(n_dds * DD_SZ);
        if (HI_READ(file_id, ddbuf, n_dds * DD_SZ) == FAIL) {
            printf("Error reading in file: %s\n", file_name);
            return (FAIL);
        } /* end if */
        b = ddbuf;
        for (l = 0; l < n_dds; l++) {
#ifdef DISKBLOCK_DEBUG
            uint8 block_head[DISKBLOCK_HSIZE];
            uint8 block_tail[DISKBLOCK_TSIZE];
#endif /* DISKBLOCK_DEBUG */

            UINT16DECODE(b, tag);
            UINT16DECODE(b, ref);
            INT32DECODE(b, off);
            INT32DECODE(b, len);
            printf("\t[%5d] tag=%5u ref=%5u offset=%10ld length=%10ld\n", (int)l, (unsigned)tag,
                   (unsigned)ref, (long)off, (long)len);
#ifdef DISKBLOCK_DEBUG
            if ((tag != DFTAG_NULL && tag != DFTAG_FREE) &&
                (len != INVALID_LENGTH && off != INVALID_OFFSET)) {
                if (HI_SEEK(file_id, off - DISKBLOCK_HSIZE) == FAIL) {
                    printf("Error seeking in file: %s\n", file_name);
                    return (FAIL);
                } /* end if */
                if (HI_READ(file_id, block_head, DISKBLOCK_HSIZE) == FAIL) {
                    printf("Error reading in file: %s\n", file_name);
                    return (FAIL);
                } /* end if */
                if (memcmp(block_head, diskblock_header, DISKBLOCK_HSIZE) != 0) {
                    intn k;

                    printf("Header wrong for disk block!\n");
                    for (k = 0; k < DISKBLOCK_HSIZE; k++) {
                        printf("\tbyte: %d, current: %x, correct: %x\n", k, (unsigned)block_head[k],
                               (unsigned)diskblock_header[k]);
                    } /* end for */
                }     /* end if */
                if (HI_SEEK(file_id, off + len) == FAIL) {
                    printf("Error seeking in file: %s\n", file_name);
                    return (FAIL);
                } /* end if */
                if (HI_READ(file_id, block_tail, DISKBLOCK_TSIZE) == FAIL) {
                    printf("Error reading in file: %s\n", file_name);
                    return (FAIL);
                } /* end if */
                if (memcmp(block_tail, diskblock_tail, DISKBLOCK_TSIZE) != 0) {
                    intn k;

                    printf("Tail wrong for disk block!\n");
                    for (k = 0; k < DISKBLOCK_HSIZE; k++) {
                        printf("\tbyte: %d, current: %x, correct: %x\n", k, (unsigned)block_tail[k],
                               (unsigned)diskblock_tail[k]);
                    } /* end for */
                }     /* end if */
            }         /* end if */
#endif                /* DISKBLOCK_DEBUG */
        }             /* end for */
        free(ddbuf);
    } /* end while */
    HI_CLOSE(file_id);
    return (SUCCEED);
}

int
main(int argc, char *argv[])
{
    int32 fid, aid;
    int   i = 1, j, n, status;

    while ((i < argc) && (argv[i][0] == '-')) {
        switch (argv[i][1]) {
            case 'o': /* give non ordered output */
                sort = FALSE;
                break;

            case 'd': /* go into debugging mode */
                debug = TRUE;
                break;

            case 'h': /* dump the dd blocks */
                ddblocks = TRUE;
                break;

#ifdef DISKBLOCK_DEBUG
            case 'x': /* check the disk-block boundaries (implies dumping dd blocks) */
                diskblock_check = TRUE;
                ddblocks        = TRUE;
                break;
#endif /* DISKBLOCK_DEBUG */

            case 'v': /* print labels for elements */
                labels  = TRUE;
                longout = TRUE;
                break;

            case 'l': /* give long output */
                longout = TRUE;
                break;

            case 's': /* give info on special elements */
                special = TRUE;
                longout = TRUE;
                break;

            case 'g': /* give info on groups */
                groups  = TRUE;
                longout = TRUE;
                break;

            case 't':
                if (argv[i][2] != '\0')
                    only_tag = (uint16)atoi(&(argv[i][2]));
                else
                    only_tag = (uint16)atoi(&(argv[++i][0]));
                break;

            default:
                printf("Unknown option : -%c\n", argv[1][1]);
                break;
        }
        i++;
    }

    /*
     * If a file name has not been supplied print the usage message
     */
    if (i == argc) {
        usage(argv);
        exit(1);
    }

    desc_buf = (dd_t *)calloc(MAXBUFF, sizeof(dd_t));

    while (i < argc) {
        file_name = argv[i];
        printf("%s:\n", file_name);

        /* Dump the DD blocks, if asked */
        if (ddblocks == TRUE) {
            if (dumpDD() == FAIL) {
                i++;
                continue;
            }
        } /* end if */

        fid = Hopen(file_name, DFACC_READ, -1);
        if (fid == FAIL) {
            if (HEvalue(1) == DFE_NOTDFFILE) {
                printf("\tNot an HDF file.\n");
                i++;
                continue;
            }
            else {
                HEprint(stderr, 0);
            }
        }

        printfilever(fid);

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

        status = SUCCEED;
        for (n = 0; (n < MAXBUFF) && (status != FAIL); n++) {
            Hinquire(aid, NULL, &desc_buf[n].tag, &desc_buf[n].ref, &desc_buf[n].length, &desc_buf[n].offset,
                     NULL, NULL, NULL);
            status = Hnextread(aid, DFTAG_WILDCARD, DFREF_WILDCARD, DF_CURRENT);
        }

        if (debug) {
            printf("\n");
            for (j = 0; j < n; j++) {
                printf("%6d) tag %6d ref %6d ", j, desc_buf[j].tag, desc_buf[j].ref);
                printf(" offset %10d length %10d\n", (int)desc_buf[j].offset, (int)desc_buf[j].length);
            }
        }

        if (sort)
            qsort((char *)desc_buf, (size_t)n, sizeof(dd_t), compare);

        v_init_done = FALSE;

        lprint(fid, desc_buf, n);

        if (v_init_done == TRUE)
            Vfinish(fid);

        if (Hendaccess(aid) == FAIL)
            HEprint(stderr, 0);

        if (Hclose(fid) == FAIL)
            HEprint(stderr, 0);

        i++;
        printf("\n");

        if (n >= MAXBUFF)
            fprintf(stderr, "Warning:  File may have more DD's than hdfls can display\n");
    }

    free(desc_buf);

    return (0);
}
