/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * 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 COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://support.hdfgroup.org/ftp/HDF5/releases.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*****************************************************************************
   FILE
   ttypes.cpp - HDF5 C++ testing the general datatype functionality

 ***************************************************************************/
#ifdef OLD_HEADER_FILENAME
#include <iostream.h>
#else
#include <iostream>
#endif
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

/*
 * Offset from alinged memory returned by malloc().  This can be used to test
 * that type conversions handle non-aligned buffers correctly.
 */
#define ALIGNMENT        1

/*
 * Define if you want to test alignment code on a machine that doesn't
 * normally require alignment. When set, all native datatypes must be aligned
 * on a byte boundary equal to the data size.
 */
#if 0
#define TEST_ALIGNMENT

/* Alignment test stuff */
#ifdef TEST_ALIGNMENT
#define H5T_FRIEND
#include "H5Tpkg.h"
#endif
#define SET_ALIGNMENT(TYPE,VAL) \
    H5T_NATIVE_##TYPE##_ALIGN_g=MAX(H5T_NATIVE_##TYPE##_ALIGN_g, VAL)
#endif
 /* #include "H5Tpkg.h"
 */ 

const char *FILENAME[] = {
    "dtypes1.h5",
    "dtypes2.h5",
    "dtypes3.h5",
    NULL
};

/*
 * Count up or down depending on whether the machine is big endian or little
 * endian.  If local variable `endian' is H5T_ORDER_BE then the result will
 * be I, otherwise the result will be Z-(I+1).
 */
#define ENDIAN(Z,I)        (H5T_ORDER_BE==endian?(I):(Z)-((I)+1))


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_classes
 *
 * Purpose      Test type classes
 *
 * Return       None.
 *
 * Programmer   Binh-Minh Ribler (using C version)
 *              January, 2007
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static void test_classes()
{
    SUBTEST("PredType::getClass()");
    try {
        // maybe later, int curr_nerrors = GetTestNumErrs();

        // PredType::NATIVE_INT should be in H5T_INTEGER class
        H5T_class_t tcls = PredType::NATIVE_INT.getClass();
        if (H5T_INTEGER!=tcls) {
            puts("    Invalid type class for H5T_NATIVE_INT");
        }

        // PredType::NATIVE_DOUBLE should be in H5T_FLOAT class
        tcls = PredType::NATIVE_DOUBLE.getClass();
        if (H5T_FLOAT!=tcls) {
        verify_val(tcls, H5T_FLOAT, "test_class: invalid type class for NATIVE_DOUBLE -", __LINE__, __FILE__);
        }
        PASSED();
    }   // end of try block
    catch (Exception& E)
    {
        issue_fail_msg("test_classes", __LINE__, __FILE__, E.getCDetailMsg());
    }
}

/*-------------------------------------------------------------------------
 * Function:    test_copy
 *
 * Purpose      Test datatype copy functionality
 *
 * Return       Success: 0
 *
 *              Failure: number of errors
 *
 * Programmer   Binh-Minh Ribler (using C version)
 *              January, 2007
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static void test_copy()
{

    SUBTEST("DataType::copy() and DataType::operator=");
    try {
        // Test copying from a predefined datatype using DataType::operator=
        DataType assigned_type;
        assigned_type = PredType::NATIVE_SHORT;

        // Test copying a predefined type using DataType::copy
        DataType copied_type;
        copied_type.copy (PredType::STD_B8LE);

        // Test copying a user-defined type using DataType::operator=
        DataType assigned_usertype;
        assigned_usertype = copied_type;

        // Test copying from a user-defined datatype using DataType::copy
        DataType copied_usertype;
        copied_usertype.copy(copied_type);

        // Test copying a user-defined int type using DataType::operator=
        IntType orig_int(PredType::STD_B8LE);
        DataType generic_type;
        generic_type = orig_int;

        // Test copying an integer predefined type
        IntType new_int_type(PredType::STD_B8LE);

        // Test copying an int predefined type using DataType::operator=
        IntType another_int_type;
        another_int_type = new_int_type;

        PASSED();
    }
    catch (Exception& E)
    {
        issue_fail_msg("test_copy", __LINE__, __FILE__, E.getCDetailMsg());
    }
}


/*-------------------------------------------------------------------------
 * Function:    test_query
 *
 * Purpose      Tests query functions of compound and enumeration types.
 *
 * Return       Success: 0
 *
 *              Failure: number of errors
 *
 * Programmer   Binh-Minh Ribler (use C version)
 *              January, 2007
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */

const H5std_string CompT_NAME("Compound_type");
const H5std_string EnumT_NAME("Enum_type");

static void test_query()
{
    typedef struct {
        int    a;
        float  b;
        long   c;
        double d;
    } src_typ_t;
    short        enum_val;

    // Output message about test being performed
    SUBTEST("Query functions of compound and enumeration types");
    try
    {
        // Create File
        H5File file(FILENAME[2], H5F_ACC_TRUNC);

        // Create a compound datatype
        CompType tid1(sizeof(src_typ_t));

        tid1.insertMember("a", HOFFSET(src_typ_t, a), PredType::NATIVE_INT);
        tid1.insertMember("b", HOFFSET(src_typ_t, b), PredType::NATIVE_FLOAT);
        tid1.insertMember("c", HOFFSET(src_typ_t, c), PredType::NATIVE_LONG);
        tid1.insertMember("d", HOFFSET(src_typ_t, d), PredType::NATIVE_DOUBLE);

        // Create a enumerate datatype
        EnumType tid2(sizeof(short));

        tid2.insert("RED", (enum_val=0,&enum_val));
        tid2.insert("GREEN", (enum_val=1,&enum_val));
        tid2.insert("BLUE", (enum_val=2,&enum_val));
        tid2.insert("ORANGE", (enum_val=3,&enum_val));
        tid2.insert("YELLOW", (enum_val=4,&enum_val));

        // Query member number and member index by name, for compound type
        int nmembs = tid1.getNmembers();
        verify_val(nmembs, 4, "CompType::getNmembers()", __LINE__, __FILE__);

        int index = tid1.getMemberIndex("c");
        verify_val(index, 2, "CompType::getMemberIndex()", __LINE__, __FILE__);

        // Query member number and member index by name, for enumeration type.
        nmembs = tid2.getNmembers();
        verify_val(nmembs, 5, "EnumType::getNmembers()", __LINE__, __FILE__);

        index = tid2.getMemberIndex("ORANGE");
        verify_val(index, 3, "EnumType::getMemberIndex()", __LINE__, __FILE__);

        // Commit compound datatype, and test getting the datatype creation
        // prop list, then close it
        tid1.commit(file, CompT_NAME);
        PropList tcpl = tid1.getCreatePlist();
        if (!IdComponent::isValid(tcpl.getId()))
        {
            // Throw an invalid action exception
            throw InvalidActionException("H5Object::createAttribute", "Datatype creation property list is not valid");
        }
        tcpl.close();
        tid1.close();

        // Commit enumeration datatype, and test getting the datatype creation
        // prop list, then close it
        tid2.commit(file, EnumT_NAME);
        tcpl = tid2.getCreatePlist();
        if (!IdComponent::isValid(tcpl.getId()))
        {
            // Throw an invalid action exception
            throw InvalidActionException("H5Object::createAttribute", "Datatype creation property list is not valid");
        }
        tcpl.close();
        tid2.close();

        // Open the datatypes for query.  Testing both ways

        tid1 = file.openCompType(CompT_NAME);
        tid1.close();
        tid2 = file.openEnumType(EnumT_NAME);
        tid2.close();

        CompType comptype(file, CompT_NAME);
        EnumType enumtype(file, EnumT_NAME);

        // Query member number and member index by name, for compound type
        nmembs = comptype.getNmembers();
        verify_val(nmembs, 4, "CompType::getNmembers()", __LINE__, __FILE__);
        index = comptype.getMemberIndex("c");
        verify_val(index, 2, "CompType::getMemberIndex()", __LINE__, __FILE__);

        // Query member number and member index by name, for enumeration type
        nmembs = enumtype.getNmembers();
        verify_val(nmembs, 5, "EnumType::getNmembers()", __LINE__, __FILE__);
        index = enumtype.getMemberIndex("ORANGE");
        verify_val(index, 3, "EnumType::getMemberIndex()", __LINE__, __FILE__);

        // Close datatypes and file
        comptype.close();
        enumtype.close();
        file.close();

        // Try truncating the file to make sure reference counting is good.
        // If any references to ids of the accessed types are left unterminated,
        // the truncating will fail, because the file will not be closed in
        // the file.close() above.
        H5File file1(FILENAME[2], H5F_ACC_TRUNC);

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


/*-------------------------------------------------------------------------
 * Function:    test_transient
 *
 * Purpose      Tests transient datatypes.
 *
 * Return       Success: 0
 *
 *              Failure: number of errors
 *
 * Programmer   Binh-Minh Ribler (use C version)
 *              January, 2007
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
const char* filename1 = "dtypes1.h5";
static void test_transient ()
{
    static hsize_t        ds_size[2] = {10, 20};

    SUBTEST("Transient datatypes");
    try {

        // Create the file and the dataspace.
        H5File file(filename1, H5F_ACC_TRUNC);
        DataSpace space(2, ds_size, ds_size);

        // Copying a predefined type results in a modifiable copy
        IntType type(PredType::NATIVE_INT);
        type.setPrecision(256);

        // It should not be possible to create an attribute for a transient type
        try {
            Attribute attr(type.createAttribute("attr1", PredType::NATIVE_INT, space));
            // Should FAIL but didn't, so throw an invalid action exception
            throw InvalidActionException("H5Object::createAttribute", "Attempted to commit a predefined datatype.");
        } catch (AttributeIException& err) {}  // do nothing, failure expected

        // Create a dataset from a transient datatype
        // type.close(); - put trace in H5Tclose to make sure it's closed
        type.copy(PredType::NATIVE_INT);
        DataSet dset(file.createDataSet("dset1", type, space));

        // The type returned from a dataset should not be modifiable
        IntType itype(dset);
        try {
            itype.setPrecision(256);

            // Should FAIL but didn't, so throw an invalid action exception
            throw InvalidActionException("PredType::setPrecision", "Dataset datatypes should not be modifiable!");
        } catch (DataTypeIException& err) {}
        itype.close();

        // Get a copy of the dataset's datatype by applying DataType::copy()
        // to the dataset. The resulted datatype should be modifiable.
        itype.copy(dset);
        itype.setPrecision(256);
        itype.close();

        // Close the dataset and reopen it, testing that its type is still
        // read-only.  (Note that a copy of it is modifiable.)
        dset.close();
        dset = file.openDataSet("dset1");

        // Close objects and file.
        dset.close();
        file.close();
        type.close();
        space.close();
        PASSED();
    }   // end of try block
    catch (Exception& E)
    {
        issue_fail_msg("test_transient", __LINE__, __FILE__, E.getCDetailMsg());
    }
}   // test_transient


/*-------------------------------------------------------------------------
 * Function:    test_named
 *
 * Purpose      Tests named datatypes.
 *
 * Return       Success: 0
 *
 *              Failure: number of errors
 *
 * Programmer   Binh-Minh Ribler (use C version)
 *              January, 2007
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
const H5std_string filename2("dtypes2.h5");
static void test_named ()
{
    static hsize_t ds_size[2] = {10, 20};
    hsize_t i;
    unsigned attr_data[10][20];
    DataType *ds_type = NULL;

    SUBTEST("Named datatypes");
    try {
        // Create the file.
        H5File file(filename2, H5F_ACC_TRUNC);

        // Create a simple dataspace.
        DataSpace space(2, ds_size, ds_size);

        // Predefined types cannot be committed.
        try {
            PredType nativeint(PredType::NATIVE_INT);
            nativeint.commit(file, "test_named_1 (should not exist)");

            // Should FAIL but didn't, so throw an invalid action exception
            throw InvalidActionException("PredType::commit", "Attempted to commit a predefined datatype.");
        } catch (DataTypeIException& err) {}

        // Copy a predefined datatype and commit the copy.
        IntType itype(PredType::NATIVE_INT);
        itype.commit(file, "native-int");

        // Test commit passing in const H5File& for prototype with const
        try
        {
            // Create random char type
            IntType atype(PredType::NATIVE_UCHAR);

            // Creating group, declared as const
            const Group const_grp = file.createGroup("GR as loc");

            // Commit type passing in const group; compilation would fail if
            // no matching prototype
            atype.commit(const_grp, "random uchar");
        }   // end of try block
        catch (Exception& E)
        {
            issue_fail_msg("test_named", __LINE__, __FILE__, "Commit at const group");
        }

        // Check that it is committed.
        if (itype.committed() == false)
            cerr << "IntType::committed() returned false" << endl;

        // We should not be able to modify a type after it has been committed.
        try {
            itype.setPrecision(256);        // attempt an invalid action...

            // Should FAIL but didn't, so throw an invalid action exception
            throw InvalidActionException("IntType::setPrecision", "Attempted to modify a committed datatype.");
        } catch (DataTypeIException& err) {}

        // We should not be able to re-commit a committed type
        try {
            itype.commit(file, "test_named_2 (should not exist)");

            // Should FAIL but didn't, so throw an invalid action exception
            throw InvalidActionException("IntType::commit", "Attempted to re-commit a committed datatype.");
        } catch (DataTypeIException& err) {} // do nothing, failure expected

        // It should be possible to define an attribute for the named type
        Attribute attr1 = itype.createAttribute("attr1", PredType::NATIVE_UCHAR, space);
        for (i=0; i<ds_size[0]*ds_size[1]; i++)
            attr_data[0][i] = (int)i;/*tricky*/
        attr1.write(PredType::NATIVE_UINT, attr_data);
        attr1.close();

        // Copying a committed type should result in a transient type which is
        // not locked.
        IntType trans_type;
        trans_type.copy(itype);
        bool iscommitted = trans_type.committed();
        verify_val(iscommitted, 0, "DataType::committed() - Copying a named type should result in a transient type!", __LINE__, __FILE__);
        trans_type.setPrecision(256);
        trans_type.close();

        // Close the committed type and reopen it.  It should be a named type.
        itype.close();
        itype = file.openIntType("native-int");
        iscommitted = itype.committed();
        if (!iscommitted)
            throw InvalidActionException("IntType::committed()", "Opened named types should be named types!");

        // Create a dataset that uses the named type, then get the dataset's
        // datatype and make sure it's a named type.
        DataSet dset = file.createDataSet("dset1", itype, space);
        ds_type = new DataType(dset.getDataType());
        iscommitted = ds_type->committed();
        if (!iscommitted)
            throw InvalidActionException("IntType::committed()", "Dataset type should be named type!");
        dset.close();
        ds_type->close();
        delete ds_type;

        // Reopen the dataset and its type, then make sure the type is
        // a named type.
        dset = file.openDataSet("dset1");
        ds_type = new DataType(dset.getDataType());
        iscommitted = ds_type->committed();
        if (!iscommitted)
            throw InvalidActionException("IntType::committed()", "Dataset type should be named type!");

        // Close the dataset and create another with the type returned from
        // the first dataset.
        dset.close();
        dset = file.createDataSet("dset2", *ds_type, space);
        ds_type->close();
        dset.close();
        delete ds_type;

        // Reopen the second dataset and make sure the type is shared
        dset = file.openDataSet("dset2");
        ds_type = new DataType(dset.getDataType());
        iscommitted = ds_type->committed();
        if (!iscommitted)
            throw InvalidActionException("DataType::iscommitted()", "Dataset type should be named type!");
        ds_type->close();

        // Get the dataset datatype by applying DataType::copy() to the
        // dataset. The resulted datatype should be modifiable.
        IntType copied_type;
        copied_type.copy(dset);
        copied_type.setPrecision(256);
        copied_type.close();

        // Clean up
        dset.close();
        itype.close();
        space.close();
        file.close();
        PASSED();
    }   // end of try block
    catch (Exception& E)
    {
        issue_fail_msg("test_named", __LINE__, __FILE__, E.getCDetailMsg());
    }

    if(ds_type)
        delete ds_type;
}   // test_named


/****************************************************************
**
**  test_types(): Main datatypes testing routine.
**
****************************************************************/
extern "C"
void test_types()
{
    // Output message about test being performed
    MESSAGE(5, ("Testing Generic Data Types\n"));

    // Test basic datatypes
    test_classes();
    test_copy();
    test_query();
    test_transient();
    test_named();

}   // test_types()


/*-------------------------------------------------------------------------
 * Function:    cleanup_types
 *
 * Purpose      Cleanup temporary test files
 *
 * Return       none
 *
 * Programmer   Quincey Koziol
 *              September 10, 1999
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
extern "C"
void cleanup_types()
{
    for (int i = 0; i < 3; i++)
        HDremove(FILENAME[i]);
}  // cleanup_types
