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

/*****************************************************************************
   FILE
   tarray.cpp - HDF5 C++ testing the array datatype functionality

 ***************************************************************************/
#include <iostream>
using std::cerr;
using std::endl;

#include <string>
#include "H5Cpp.h" // C++ API header file
using namespace H5;

#include "h5test.h"
#include "h5cpputil.h" // C++ utilility header file

const H5std_string FILENAME("tarray.h5");
const H5std_string ARRAYTYPE_NAME("/Array type 1");
const int          SPACE1_RANK = 1;
const hsize_t      SPACE1_DIM1 = 4;
const int          ARRAY1_RANK = 1;
const hsize_t      ARRAY1_DIM1 = 4;

typedef enum flt_t { FLT_FLOAT, FLT_DOUBLE, FLT_LDOUBLE, FLT_OTHER } flt_t;

typedef enum int_t {
    INT_CHAR,
    INT_UCHAR,
    INT_SHORT,
    INT_USHORT,
    INT_INT,
    INT_UINT,
    INT_LONG,
    INT_ULONG,
    INT_LLONG,
    INT_ULLONG,
    INT_OTHER
} int_t;

/*-------------------------------------------------------------------------
 * Function:    test_array_compound_array
 *
 * Purpose      Tests 1-D array of compound datatypes (with array fields)
 *
 * Return       None
 *-------------------------------------------------------------------------
 */
static void
test_array_compound_array()
{
    SUBTEST("ArrayType::getArrayNDims & ArrayType::getArrayDims");
    typedef struct { // Typedef for compound datatype */
        int   i;
        float f[ARRAY1_DIM1];
    } s1_t;
    s1_t        wdata[SPACE1_DIM1][ARRAY1_DIM1]; // Information to write
    s1_t        rdata[SPACE1_DIM1][ARRAY1_DIM1]; // Information read in
    hsize_t     sdims1[] = {SPACE1_DIM1};
    hsize_t     tdims1[] = {ARRAY1_DIM1};
    int         nmemb;            // Number of compound members
    int         ii;               // counting variables
    hsize_t     idxi, idxj, idxk; // dimension indicing variables
    H5T_class_t mclass;           // Datatype class for field

    // Initialize array data to write
    for (idxi = 0; idxi < SPACE1_DIM1; idxi++)
        for (idxj = 0; idxj < ARRAY1_DIM1; idxj++) {
            wdata[idxi][idxj].i = static_cast<int>(idxi * 10 + idxj);
            for (idxk = 0; idxk < ARRAY1_DIM1; idxk++) {
                float temp = static_cast<float>(idxi) * 10.0F + static_cast<float>(idxj) * 2.5F +
                             static_cast<float>(idxk);
                wdata[idxi][idxj].f[idxk] = temp;
            }
        } // end for

    try {
        // Create File
        H5File file1(FILENAME, H5F_ACC_TRUNC);

        // Create dataspace for datasets
        DataSpace space(SPACE1_RANK, sdims1, NULL);

        /*
         * Create an array datatype of compounds, arrtype.  Each compound
         * datatype, comptype, contains an integer and an array of floats,
         * arrfltype.
         */

        // Create a compound datatype
        CompType comptype(sizeof(s1_t));

        // Insert integer field
        comptype.insertMember("i", HOFFSET(s1_t, i), PredType::NATIVE_INT);

        // Create an array of floats datatype
        ArrayType arrfltype(PredType::NATIVE_FLOAT, ARRAY1_RANK, tdims1);

        // Insert float array field
        comptype.insertMember("f", HOFFSET(s1_t, f), arrfltype);

        // Close array of floats field datatype
        arrfltype.close();

        // Create an array datatype of the compound datatype
        ArrayType arrtype(comptype, ARRAY1_RANK, tdims1);

        // Close compound datatype comptype
        comptype.close();

        // Create a dataset
        DataSet dataset = file1.createDataSet("Dataset1", arrtype, space);

        // Write dataset to disk
        dataset.write(wdata, arrtype);

        // Test opening ArrayType with opening constructor (Dec 2016)

        // Commit the arrtype to give it a name
        arrtype.commit(file1, ARRAYTYPE_NAME);

        // Close it, then re-open with the opening constructor
        arrtype.close();
        ArrayType named_type(file1, ARRAYTYPE_NAME);

        // Get and verify the type's name
        H5std_string type_name = named_type.getObjName();
        verify_val(type_name, ARRAYTYPE_NAME, "DataType::getObjName tests constructor", __LINE__, __FILE__);
        named_type.close();

        // Close all
        dataset.close();
        space.close();
        file1.close();

        // Re-open file
        file1.openFile(FILENAME, H5F_ACC_RDONLY);

        // Open the dataset
        dataset = file1.openDataSet("Dataset1");

        /*
         * Check the datatype array of compounds
         */

        // Verify that it is an array of compounds
        DataType dstype = dataset.getDataType();
        mclass          = dstype.getClass();
        verify_val(mclass == H5T_ARRAY, true, "f2_type.getClass", __LINE__, __FILE__);

        dstype.close();

        // Get the array datatype to check
        ArrayType atype_check = dataset.getArrayType();

        // Check the array rank
        int ndims = atype_check.getArrayNDims();
        verify_val(ndims, ARRAY1_RANK, "atype_check.getArrayNDims", __LINE__, __FILE__);

        // Get the array dimensions
        hsize_t rdims1[H5S_MAX_RANK];
        atype_check.getArrayDims(rdims1);

        // Check the array dimensions
        for (ii = 0; ii < ndims; ii++)
            if (rdims1[ii] != tdims1[ii]) {
                TestErrPrintf("Array dimension information doesn't match!, rdims1[%d]=%" PRIuHSIZE
                              ", tdims1[%d]=%" PRIuHSIZE "\n",
                              ii, rdims1[ii], ii, tdims1[ii]);
                continue;
            } // end if

        // Test ArrayType::ArrayType(const hid_t existing_id)
        ArrayType new_arrtype(atype_check.getId());

        // Check the array rank
        ndims = new_arrtype.getArrayNDims();
        verify_val(ndims, ARRAY1_RANK, "new_arrtype.getArrayNDims", __LINE__, __FILE__);

        // Get the array dimensions
        new_arrtype.getArrayDims(rdims1);

        // Check the array dimensions
        for (ii = 0; ii < ndims; ii++)
            if (rdims1[ii] != tdims1[ii]) {
                TestErrPrintf("Array dimension information doesn't match!, rdims1[%d]=%" PRIuHSIZE
                              ", tdims1[%d]=%" PRIuHSIZE "\n",
                              ii, rdims1[ii], ii, tdims1[ii]);
                continue;
            } // end if

        /*
         * Check the compound datatype and the array of floats datatype
         * in the compound.
         */
        // Get the compound datatype, which is the base datatype of the
        // array datatype atype_check.
        DataType base_type = atype_check.getSuper();
        mclass             = base_type.getClass();
        verify_val(mclass == H5T_COMPOUND, true, "atype_check.getClass", __LINE__, __FILE__);

        // Verify the compound datatype info
        CompType ctype_check(base_type.getId());
        base_type.close();

        // Check the number of members
        nmemb = ctype_check.getNmembers();
        verify_val(nmemb, 2, "ctype_check.getNmembers", __LINE__, __FILE__);

        // Check the 2nd field's name
        H5std_string field2_name = ctype_check.getMemberName(1);
        if (strcmp(field2_name.c_str(), "f") != 0)
            TestErrPrintf("Compound field name doesn't match!, field2_name=%s\n", field2_name.c_str());

        // Get the 2nd field's datatype
        DataType f2_type = ctype_check.getMemberDataType(1);

        // Get the 2nd field's class, this 2nd field should have an array type
        mclass = f2_type.getClass();
        verify_val(mclass == H5T_ARRAY, true, "f2_type.getClass", __LINE__, __FILE__);
        f2_type.close();

        // Get the 2nd field, array of floats datatype, to check
        ArrayType f2_atype_check = ctype_check.getMemberArrayType(1);

        // Check the array rank
        ndims = f2_atype_check.getArrayNDims();
        verify_val(ndims, ARRAY1_RANK, "f2_atype_check.getArrayNDims", __LINE__, __FILE__);

        // Get the array dimensions
        memset(rdims1, 0, sizeof(rdims1));
        f2_atype_check.getArrayDims(rdims1);

        // Check the array dimensions
        for (ii = 0; ii < ndims; ii++)
            if (rdims1[ii] != tdims1[ii]) {
                TestErrPrintf("Array dimension information doesn't match!, rdims1[%d]=%" PRIuHSIZE
                              ", tdims1[%d]=%" PRIuHSIZE "\n",
                              ii, rdims1[ii], ii, tdims1[ii]);
                continue;
            } // end if

        // Close done datatypes
        f2_atype_check.close();
        ctype_check.close();

        // Read dataset from disk
        dataset.read(rdata, atype_check);

        // Compare data read in
        for (idxi = 0; idxi < SPACE1_DIM1; idxi++) {
            for (idxj = 0; idxj < ARRAY1_DIM1; idxj++) {
                if (wdata[idxi][idxj].i != rdata[idxi][idxj].i) {
                    TestErrPrintf("Array data information doesn't match!, wdata[%" PRIuHSIZE "][%" PRIuHSIZE
                                  "].i=%d, "
                                  "rdata[%" PRIuHSIZE "][%" PRIuHSIZE "].i=%d\n",
                                  idxi, idxj, wdata[idxi][idxj].i, idxi, idxj, rdata[idxi][idxj].i);
                    continue;
                } // end if
            }     // end for
        }         // end for

        // Close all
        atype_check.close();
        dataset.close();
        file1.close();
        PASSED();
    } // end of try block
    catch (Exception &E) {
        issue_fail_msg("test_array_compound_array", __LINE__, __FILE__, E.getCDetailMsg());
    }

} // end test_array_compound_array()

/*
 * Helper routine to demonstrate the issue in HDFFV-9562
 */
static H5::DataType
getArr()
{
    hsize_t *dims = new hsize_t;
    *dims         = 5;
    H5::ArrayType ret;
    ret = H5::ArrayType(H5::PredType::NATIVE_INT, 1, dims);
    delete dims;
    return ret;
}

/*-------------------------------------------------------------------------
 * Function:    test_array_assignment
 *
 * Purpose      Tests the operator=
 *
 * Return       None
 *
 * Description:
 *              Used user's sample code in HDFFV-9562
 *-------------------------------------------------------------------------
 */
static void
test_array_assignment()
{
    hsize_t sdims1[] = {SPACE1_DIM1};
    SUBTEST("ArrayType::operator=");

    try {
        // Create File
        H5File file1(FILENAME, H5F_ACC_TRUNC);

        // Create dataspace for datasets
        DataSpace space(SPACE1_RANK, sdims1, NULL);

        /*
         * Create an array datatype of compounds, arrtype.  Each compound
         * datatype, comptype, contains an integer and an array of floats,
         * arrfltype.
         */

        // Create a compound datatype
        CompType comptype(static_cast<size_t>(24));

        // Insert integer field
        comptype.insertMember("i", 0, PredType::NATIVE_INT);

        // Insert float array field
        comptype.insertMember("a", 4, getArr());

        // Create a dataset
        DataSet dataset = file1.createDataSet("Dataset1", comptype, space);

        // Close all
        dataset.close();
        comptype.close();
        space.close();
        file1.close();

        PASSED();
    } // end of try block
    catch (Exception &E) {
        issue_fail_msg("test_array_assignment", __LINE__, __FILE__, E.getCDetailMsg());
    }
} // end test_array_assignment()

/*-------------------------------------------------------------------------
 * Function:    test_array_info
 *
 * Purpose      Tests getting array information using the const methods.
 *
 * Return       None
 *-------------------------------------------------------------------------
 */
static void
test_array_info()
{
    SUBTEST("ArrayType Const Methods");
    typedef struct { // Typedef for compound datatype */
        int   i;
        float f[ARRAY1_DIM1];
    } s1_t;
    s1_t        wdata[SPACE1_DIM1][ARRAY1_DIM1]; // Information to write
    hsize_t     sdims1[] = {SPACE1_DIM1};
    hsize_t     tdims1[] = {ARRAY1_DIM1};
    int         ii;               // counting variables
    hsize_t     idxi, idxj, idxk; // dimension indicing variables
    H5T_class_t mclass;           // Datatype class for field

    // Initialize array data to write
    for (idxi = 0; idxi < SPACE1_DIM1; idxi++)
        for (idxj = 0; idxj < ARRAY1_DIM1; idxj++) {
            wdata[idxi][idxj].i = static_cast<int>(idxi * 10 + idxj);
            for (idxk = 0; idxk < ARRAY1_DIM1; idxk++) {
                float temp = static_cast<float>(idxi) * 10.0F + static_cast<float>(idxj) * 2.5F +
                             static_cast<float>(idxk);
                wdata[idxi][idxj].f[idxk] = temp;
            }
        }

    try {
        // Create File
        H5File file1(FILENAME, H5F_ACC_TRUNC);

        // Create dataspace for datasets
        DataSpace space(SPACE1_RANK, sdims1, NULL);

        /*
         * Create some array datatypes, then close the file.
         */

        // Create an array of floats datatype
        ArrayType arrfltype(PredType::NATIVE_FLOAT, ARRAY1_RANK, tdims1);

        // Create an array datatype of the compound datatype
        ArrayType arrtype(PredType::NATIVE_UINT, ARRAY1_RANK, tdims1);

        // Create a dataset
        DataSet dataset = file1.createDataSet("Dataset1", arrtype, space);

        // Write dataset to disk
        dataset.write(wdata, arrtype);

        // Close array of floats field datatype
        arrfltype.close();

        // Close all
        dataset.close();
        arrtype.close();
        space.close();
        file1.close();

        // Re-open file
        file1.openFile(FILENAME, H5F_ACC_RDONLY);

        // Open the dataset
        dataset = file1.openDataSet("Dataset1");

        /*
         * Check the datatype array of compounds
         */

        // Verify that it is an array of compounds
        DataType dstype = dataset.getDataType();
        mclass          = dstype.getClass();
        verify_val(mclass == H5T_ARRAY, true, "f2_type.getClass", __LINE__, __FILE__);

        dstype.close();

        { // Let atype_check go out of scope
            // Get the array datatype, declared as const
            const ArrayType atype_check = dataset.getArrayType();

            // Check the array rank with the const method
            int ndims = atype_check.getArrayNDims();
            verify_val(ndims, ARRAY1_RANK, "atype_check.getArrayNDims", __LINE__, __FILE__);

            // Get the array dimensions with the const method
            hsize_t rdims1[H5S_MAX_RANK];
            atype_check.getArrayDims(rdims1);

            // Check the array dimensions
            for (ii = 0; ii < ndims; ii++)
                if (rdims1[ii] != tdims1[ii]) {
                    TestErrPrintf("Array dimension information doesn't match!, rdims1[%d]=%" PRIuHSIZE
                                  ", tdims1[%d]=%" PRIuHSIZE "\n",
                                  ii, rdims1[ii], ii, tdims1[ii]);
                    continue;
                }
        }

        // Close all
        dataset.close();
        file1.close();
        PASSED();
    } // end of try block
    catch (Exception &E) {
        issue_fail_msg("test_array_info", __LINE__, __FILE__, E.getCDetailMsg());
    }

} // end test_array_info()

/*-------------------------------------------------------------------------
 * Function:    test_array
 *
 * Purpose      Main datatypes testing routine
 *
 * Return       None
 *-------------------------------------------------------------------------
 */
extern "C" void
test_array(void *params)
{
    (void)params;

    // Output message about test being performed
    MESSAGE(5, ("Testing Array Datatypes\n"));

    // Test array of compounds with array field
    test_array_compound_array();

    // Test operator= (HDFFV-9562)
    test_array_assignment();

    // Test const functions (HDFFV-9725)
    test_array_info();

} // test_array()

/*-------------------------------------------------------------------------
 * Function:    cleanup_array
 *
 * Purpose      Cleanup temporary test files
 *
 * Return       None
 *-------------------------------------------------------------------------
 */
extern "C" void
cleanup_array(void *params)
{
    (void)params;

    if (GetTestCleanup()) {
        HDremove(FILENAME.c_str());
    }
} // cleanup_array
