/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * 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 <assert.h>
#include <stdlib.h>
#include <string.h>

#include "hdf.h"
#include "mfhdf.h"
#include "hrepack.h"
#include "hrepack_gr.h"
#include "hrepack_utils.h"
#include "hrepack_an.h"
#include "hrepack_parse.h"
#include "hrepack_opttable.h"

/*-------------------------------------------------------------------------
 * Function: copy_gr
 *
 * Purpose: copy a GR from input file to output file and compress it
 *  using options
 *
 * Return: 0 ok, -1 not ok
 *-------------------------------------------------------------------------
 */
int
copy_gr(int32 infile_id, int32 outfile_id, int32 gr_in, int32 gr_out, int32 tag, /* tag of input GR */
        int32      ref,                                                          /* ref of input GR */
        int32      vgroup_id_out_par,                                            /* output parent group ID */
        char      *path_name, /* absolute path for input group name */
        options_t *options, list_table_t *list_tbl)
{
    int32         ri_id;          /* raster image identifier */
    int32         ri_out = FAIL;  /* raster image identifier */
    int32         ri_index;       /* index of a image */
    int32         dimsizes[2];    /* dimensions of an image */
    int32         n_comps;        /* number of components an image contains */
    int32         interlace_mode; /* interlace mode of an image */
    int32         dtype;          /* number type of an image */
    int32         n_attrs;        /* number of attributes belong to an image */
    int32         gr_ref;         /* reference number of the output data set */
    int32         pal_id;         /* palette identifier */
    int32         pal_out;        /* palette identifier */
    int32         pal_ref;
    int32         r_num_entries;
    int32         r_data_type;
    int32         r_ncomp;
    int32         r_interlace_mode;
    char          gr_name[H4_MAX_GR_NAME];
    char         *path = NULL;
    int           info;           /* temporary int compression information */
    int           szip_mode;      /* szip mode, EC, NN */
    comp_coder_t  comp_type;      /* compression type requested  */
    comp_coder_t  comp_type_in;   /* compression type original  */
    comp_info     c_info;         /* compression information requested  */
    comp_info     c_info_in;      /* compression information original  */
    HDF_CHUNK_DEF chunk_def;      /* chunk definition */
    HDF_CHUNK_DEF chunk_def_in;   /* chunk definition original */
    int32         chunk_flags;    /* chunk flags */
    int32         chunk_flags_in; /* chunk flags original*/
    int           i, j, ret = 0, rank = 2, have_info, stat;
    int           has_pal = 0;
    int32         start[2], /* read start */
        edges[2],           /* read edges */
        numtype,            /* number type */
        eltsz,              /* element size */
        nelms,              /* number of elements */
        data_size;
    void       *buf = NULL;
    uint8       pal_data[256 * 3];
    int         can_compress = 1; /* flag to tell if a compression is supported */
    const char *pal_path     = "palette";

    ri_index = GRreftoindex(gr_in, (uint16)ref);
    ri_id    = GRselect(gr_in, ri_index);

    if (GRgetiminfo(ri_id, gr_name, &n_comps, &dtype, &interlace_mode, dimsizes, &n_attrs) == FAIL) {
        printf("Could not information for GR\n");
        GRendaccess(ri_id);
        return -1;
    }

    /* initialize path */
    path = get_path(path_name, gr_name);

    /* add object to table */
    list_table_add(list_tbl, tag, ref, path);

    /*-------------------------------------------------------------------------
     * get the original compression/chunk information from the object
     *-------------------------------------------------------------------------
     */

    comp_type_in = COMP_CODE_NONE; /* reset variables before retrieving information */
    memset(&c_info_in, 0, sizeof(comp_info));
    stat = GRgetcompinfo(ri_id, &comp_type_in, &c_info_in);
    if (stat == FAIL && comp_type_in > 0) {
        printf("Could not get compress information for GR <%s>\n", path);
        GRendaccess(ri_id);
        return -1;
    }

    /* get chunk lengths */
    if (GRgetchunkinfo(ri_id, &chunk_def_in, &chunk_flags_in) == FAIL) {
        printf("Could not get chunk information for GR <%s>\n", path);
        GRendaccess(ri_id);
        return -1;
    }

    /* retrieve the compress information if so */
    if ((HDF_CHUNK | HDF_COMP) == chunk_flags_in) {
        chunk_def_in.comp.comp_type = comp_type_in;
        switch (comp_type_in) {
            case COMP_CODE_NONE:
                break;
            case COMP_CODE_SZIP:
#ifdef H4_GR_SZIP
                chunk_def_in.comp.comp_type  = COMP_CODE_SZIP;
                chunk_def_in.comp.cinfo.szip = c_info_in.szip;
#endif
                chunk_def_in.comp.comp_type = COMP_CODE_NONE;
                printf("Warning: SZIP compression not supported for GR\n");
                break;
            case COMP_CODE_RLE:
                chunk_def_in.comp.comp_type = COMP_CODE_RLE;
                break;
            case COMP_CODE_SKPHUFF:
                chunk_def_in.comp.comp_type     = COMP_CODE_SKPHUFF;
                chunk_def_in.comp.cinfo.skphuff = c_info_in.skphuff;
                break;
            case COMP_CODE_DEFLATE:
                chunk_def_in.comp.comp_type     = COMP_CODE_DEFLATE;
                chunk_def_in.comp.cinfo.deflate = c_info_in.deflate;
                break;
            case COMP_CODE_JPEG:
                chunk_def_in.comp.comp_type  = COMP_CODE_JPEG;
                chunk_def_in.comp.cinfo.jpeg = c_info_in.jpeg;
                break;
            default:
                printf("Error: Unrecognized compression code <%d> in <%s>\n", comp_type_in, path);
                break;
        };
    }

    /*-------------------------------------------------------------------------
     * set the default values to the ones read from the object
     *-------------------------------------------------------------------------
     */

    /*-------------------------------------------------------------------------
     * compression
     *-------------------------------------------------------------------------
     */

    comp_type = comp_type_in;

    switch (comp_type_in) {
        default:
            break;
        case COMP_CODE_SZIP:
#ifdef H4_GR_SZIP
            info = c_info_in.szip.pixels_per_block;
#ifdef H4_HAVE_LIBSZ
            if (c_info_in.szip.options_mask & SZ_EC_OPTION_MASK) {
                szip_mode = EC_MODE;
            }
            else if (c_info_in.szip.options_mask & SZ_NN_OPTION_MASK) {
                szip_mode = NN_MODE;
            }
#else
            szip_mode = 0; /* irrelevant */
#endif
#endif
            szip_mode = 0; /* irrelevant */
            break;
        case COMP_CODE_RLE:
            break;
        case COMP_CODE_SKPHUFF:
            info = c_info_in.skphuff.skp_size;
            break;
        case COMP_CODE_DEFLATE:
            info = c_info_in.deflate.level;
            break;
        case COMP_CODE_JPEG:
            /* JPEG's quality factor was not saved to the file and 75 is
               recommended by http://www.faqs.org/faqs/jpeg-faq/part1 - BMR 1/2009*/
            info                   = 75; /* a constant should be defined somewhere in the library */
            c_info_in.jpeg.quality = 75;
            break;
    };

    /*-------------------------------------------------------------------------
     * chunking
     *-------------------------------------------------------------------------
     */

    chunk_flags = chunk_flags_in;

    if ((HDF_CHUNK) == chunk_flags) {
        for (i = 0; i < rank; i++)
            chunk_def.chunk_lengths[i] = chunk_def_in.chunk_lengths[i];
    }
    else if ((HDF_CHUNK | HDF_COMP) == chunk_flags) {
        for (i = 0; i < rank; i++) {
            chunk_def.chunk_lengths[i]      = chunk_def_in.chunk_lengths[i];
            chunk_def.comp.chunk_lengths[i] = chunk_def_in.chunk_lengths[i];
        }
        chunk_def.comp.comp_type = comp_type_in;
        switch (comp_type_in) {
            case COMP_CODE_NONE:
                break;
            case COMP_CODE_SZIP:
#ifdef H4_GR_SZIP
                chunk_def.comp.comp_type  = COMP_CODE_SZIP;
                chunk_def.comp.cinfo.szip = c_info_in.szip;
#endif
                chunk_def.comp.comp_type = COMP_CODE_NONE;
                printf("Warning: SZIP not supported for GR\n");
                break;
            case COMP_CODE_RLE:
                chunk_def.comp.comp_type = COMP_CODE_RLE;
                break;
            case COMP_CODE_SKPHUFF:
                chunk_def.comp.comp_type     = COMP_CODE_SKPHUFF;
                chunk_def.comp.cinfo.skphuff = c_info_in.skphuff;
                break;
            case COMP_CODE_DEFLATE:
                chunk_def.comp.comp_type     = COMP_CODE_DEFLATE;
                chunk_def.comp.cinfo.deflate = c_info_in.deflate;
                break;
            case COMP_CODE_JPEG:
                chunk_def.comp.comp_type  = COMP_CODE_JPEG;
                chunk_def.comp.cinfo.jpeg = c_info_in.jpeg;
                break;
            default:
                printf("Error: Unrecognized compression code <%d> in <%s>\n", comp_type_in, path);
                break;
        };
    }

    /*-------------------------------------------------------------------------
     * get the compression/chunk information of this object from the table
     * translate to usable information
     * this is done ONLY for the second trip inspection
     *-------------------------------------------------------------------------
     */

    /* check inspection mode */
    have_info = 0;
    if (options->trip > 0) {
        have_info = options_get_info(options,      /* global options */
                                     &chunk_flags, /* chunk flags OUT */
                                     &chunk_def,   /* chunk definition OUT */
                                     &info,        /* compression information OUT */
                                     &szip_mode,   /* compression information OUT */
                                     &comp_type,   /* compression type OUT  */
                                     rank,         /* rank of object IN */
                                     path,         /* path of object IN */
                                     n_comps,      /* number of GR image planes (for SZIP), IN */
                                     dimsizes,     /* dimensions (for SZIP), IN */
                                     dtype         /* numeric type ( for SZIP), IN */
        );
        if (have_info == FAIL)
            goto out;
    } /* check inspection mode */

    /*-------------------------------------------------------------------------
     * check for data size before printing
     *-------------------------------------------------------------------------
     */

    /* compute the number of the bytes for each value. */
    numtype = dtype & DFNT_MASK;
    eltsz   = DFKNTsize(numtype | DFNT_NATIVE);

    /* set edges of GR */
    nelms = 1;
    for (j = 0; j < rank; j++) {
        nelms *= dimsizes[j];
        edges[j] = dimsizes[j];
        start[j] = 0;
    }

    data_size = dimsizes[0] * dimsizes[1] * n_comps * eltsz;

    /*-------------------------------------------------------------------------
     * check for objects too small
     *-------------------------------------------------------------------------
     */
    if (have_info && options->trip > 0 && nelms * eltsz < options->threshold) {
        /* reset to the original values . we don't want to uncompress if it was */
        chunk_flags = chunk_flags_in;
        comp_type   = comp_type_in;
        if (options->verbose) {
            printf("Warning: object size smaller than %d bytes. Not compressing <%s>\n", options->threshold,
                   path);
        }
    }

    /*-------------------------------------------------------------------------
     * print the PATH, COMP and CHUNK information
     *-------------------------------------------------------------------------
     */

    if (options->verbose) {
        int pr_comp_type = 0;
        int pr_chunk_flags;

        if (options->trip == 0)
            pr_chunk_flags = chunk_flags_in;
        else
            pr_chunk_flags = chunk_flags;

        if (comp_type > 0) {
            pr_comp_type = comp_type;
        }
        else {
            if (pr_chunk_flags == (HDF_CHUNK | HDF_COMP)) {
                pr_comp_type = chunk_def.comp.comp_type;
            }
        }
        printf(PFORMAT, (pr_chunk_flags > 0) ? "chunk" : "",      /*chunk information*/
               (pr_comp_type > 0) ? get_scomp(pr_comp_type) : "", /*compression information*/
               "",                                                /*compression ratio */
               path);                                             /*name*/
    }

    /*-------------------------------------------------------------------------
     * if we are in first trip inspection mode, exit, after printing the information
     *-------------------------------------------------------------------------
     */

    /* check inspection mode */
    if (options->trip == 0) {
        if (GRendaccess(ri_id) == FAIL) {
            printf("Could not close GR <%s>\n", path);
            free(path);
            return -1;
        }
        free(path);
        return 0;
    }
    /*-------------------------------------------------------------------------
     * read gr and create new one
     *-------------------------------------------------------------------------
     */

    /* alloc */
    if ((buf = (void *)malloc((size_t)data_size)) == NULL) {
        printf("Failed to allocate %d elements of size %d\n", nelms, eltsz);
        GRendaccess(ri_id);
        free(path);
        return -1;
    }

    /* set the interlace for reading  */
    if (GRreqimageil(ri_id, interlace_mode) == FAIL) {
        printf("Could not set interlace for GR <%s>\n", path);
        GRendaccess(ri_id);
        free(path);
        return -1;
    }

    /* read data */
    if (GRreadimage(ri_id, start, NULL, edges, buf) == FAIL) {
        printf("Could not read GR <%s>\n", path);
        GRendaccess(ri_id);
        free(path);
        return -1;
    }

    /* create output GR */
    if ((ri_out = GRcreate(gr_out, gr_name, n_comps, dtype, interlace_mode, dimsizes)) == FAIL) {
        printf("Failed to create new GR <%s>\n", path);
        ret = -1;
        goto out;
    }

    /*-------------------------------------------------------------------------
     * set chunk
     *
     * Chunked                  -> flags = HDF_CHUNK
     * Chunked and compressed   -> flags = HDF_CHUNK | HDF_COMP
     * Non-chunked              -> flags = HDF_NONE
     *-------------------------------------------------------------------------
     */

    /* set chunk */
    if ((chunk_flags == HDF_CHUNK) || (chunk_flags == (HDF_CHUNK | HDF_COMP))) {
        if (GRsetchunk(ri_out, chunk_def, chunk_flags) == FAIL) {
            printf("Error: Failed to set chunk dimensions for <%s>\n", path);
            ret = -1;
            goto out;
        }
    }

    /*-------------------------------------------------------------------------
     * set compression
     *
     * COMP_CODE_RLE       -> simple RLE encoding
     * COMP_CODE_SKPHUFF   -> Skipping huffman encoding
     * COMP_CODE_DEFLATE   -> gzip 'deflate' encoding
     *-------------------------------------------------------------------------
     */

    /* use compress without chunk-in */
    else if (chunk_flags == HDF_NONE && comp_type > COMP_CODE_NONE) {
        if (have_info && options->trip > 0 && nelms * eltsz < options->threshold) {
            /* reset to the original values . we don't want to uncompress if it was */
            comp_type = COMP_CODE_NONE;
            if (options->verbose) {
                printf("Warning: object size smaller than %d bytes. Not compressing <%s>\n",
                       options->threshold, path);
            }
        }
        else {
            /* setup compression factors */
            switch (comp_type) {
                case COMP_CODE_SZIP:
#ifdef H4_GR_SZIP
                    /* not supported */
                    if (set_szip(info, szip_mode, &c_info) == FAIL) {
                        comp_type = COMP_CODE_NONE;
                    }
#endif
                    printf("Warning: SZIP not supported for GR\n");
                    can_compress = 0;
                    break;
                case COMP_CODE_RLE:
                    break;
                case COMP_CODE_SKPHUFF:
                    c_info.skphuff.skp_size = info;
                    break;
                case COMP_CODE_DEFLATE:
                    c_info.deflate.level = info;
                    break;
                case COMP_CODE_JPEG:
                    c_info.jpeg.quality        = info;
                    c_info.jpeg.force_baseline = 1;
                    break;
                default:
                    printf("Error: Unrecognized compression code %d\n", comp_type);
                    can_compress = 0;
            }

            if (can_compress) {
                if (GRsetcompress(ri_out, comp_type, &c_info) == FAIL) {
                    printf("Error: Failed to set compression for <%s>\n", path);
                    ret = -1;
                    goto out;
                }
            } /* can_compress */
        }
    }

    /* write the data */
    if (GRwriteimage(ri_out, start, NULL, edges, buf) == FAIL) {
        printf("Failed to write to new GR <%s>\n", path);
        ret = -1;
        goto out;
    }

    /*-------------------------------------------------------------------------
     * copy attributes
     *-------------------------------------------------------------------------
     */

    if (copy_gr_attrs(ri_id, ri_out, n_attrs, options) == FAIL) {
        ret = -1;
        goto out;
    }

    /*-------------------------------------------------------------------------
     * check for palette
     *-------------------------------------------------------------------------
     */

    pal_id = GRgetlutid(ri_id, 0);
    GRgetlutinfo(pal_id, &r_ncomp, &r_data_type, &r_interlace_mode, &r_num_entries);

    /*check if there is palette data */
    has_pal = ((r_ncomp == 0) || (r_interlace_mode < 0) || (r_num_entries == 0)) ? 0 : 1;

    if (has_pal == 1) {
        GRreqlutil(ri_id, r_interlace_mode);
        if (GRreadlut(pal_id, pal_data) == FAIL) {
            printf("Failed to get palette data for <%s>\n", path);
        }

        if ((pal_ref = GRluttoref(pal_id)) == FAIL) {
            printf("Failed to get palette ref for <%s>\n", path);
        }
        /* add palette to table; we want to later check for lone palettes */
        list_table_add(list_tbl, DFTAG_IP8, pal_ref, pal_path);

        /* Get the id for the new palette */
        if ((pal_out = GRgetlutid(ri_out, 0)) == FAIL) {
            printf("Failed to get palette ID for <%s>\n", path);
        }

        /* Write the palette to file. */
        if (GRwritelut(pal_out, r_ncomp, r_data_type, r_interlace_mode, r_num_entries, (void *)pal_data) ==
            FAIL) {
            printf("Failed to write palette for <%s>\n", path);
        }
    } /* has_pal==1 */

    /* obtain the reference number of the new SDS using its identifier */
    if ((gr_ref = GRidtoref(ri_out)) == FAIL) {
        printf("Failed to get new GR reference in <%s>\n", path);
    }

    /*-------------------------------------------------------------------------
     * add GR to group, if needed
     *-------------------------------------------------------------------------
     */
    if (vgroup_id_out_par) {
        /* add the GR to the vgroup. the tag DFTAG_RIG is used */
        if (Vaddtagref(vgroup_id_out_par, TAG_GRP_IMAGE, gr_ref) == FAIL) {
            printf("Failed to add new GR to group <%s>\n", path);
        }
    }

    /*-------------------------------------------------------------------------
     * copy ANs
     *-------------------------------------------------------------------------
     */

    if (copy_an(infile_id, outfile_id, ref, DFTAG_RIG, gr_ref, DFTAG_RIG, path, options) < 0) {
        ret = -1;
        goto out;
    }
    if (copy_an(infile_id, outfile_id, ref, DFTAG_RI, gr_ref, DFTAG_RI, path, options) < 0) {
        ret = -1;
        goto out;
    }

out:

    /* terminate access to the GRs */
    if (GRendaccess(ri_id) == FAIL) {
        if (path)
            printf("Failed to close SDS <%s>\n", path);
        else
            printf("Failed to close SDS\n");
    }
    if (GRendaccess(ri_out) == FAIL) {
        if (path)
            printf("Failed to close SDS <%s>\n", path);
        else
            printf("Failed to close SDS\n");
    }

    free(path);
    free(buf);

    return ret;
}

/*-------------------------------------------------------------------------
 * Function: copy_gr_attrs
 *
 * Purpose: copy GR attributes from input file to output file
 *
 * Return: 1, for success, -1 for error
 *-------------------------------------------------------------------------
 */
int
copy_gr_attrs(int32 ri_id, int32 ri_out, int32 nattrs, options_t *options)
{
    int32 dtype, /* SDS data type */
        numtype, /* number type */
        eltsz,   /* element size */
        nelms;   /* number of elements */
    char  attr_name[H4_MAX_NC_NAME];
    void *attr_buf = NULL;
    int   i;

    (void)options;

    /* loop through attributes in input GR */
    for (i = 0; i < nattrs; i++) {
        if (GRattrinfo(ri_id, i, attr_name, &dtype, &nelms) == FAIL) {
            printf("Cannot get information for attribute number %d\n", i);
            return -1;
        }
        /* compute the number of the bytes for each value. */
        numtype = dtype & DFNT_MASK;
        eltsz   = DFKNTsize(numtype | DFNT_NATIVE);
        if ((attr_buf = (void *)malloc((size_t)(nelms * eltsz))) == NULL) {
            printf("Error allocating %d values of size %d for attribute %s", nelms, numtype, attr_name);
            return -1;
        }
        /* read attributes from input GR */
        if (GRgetattr(ri_id, i, attr_buf) == FAIL) {
            printf("Cannot read attribute %s\n", attr_name);
            return -1;
        }
        /* put attributes into output GR */
        if (GRsetattr(ri_out, attr_name, dtype, nelms, attr_buf) == FAIL) {
            printf("Cannot write attribute %s\n", attr_name);
            return -1;
        }

        free(attr_buf);
    }

    return 1;
}
