#include "H5HLprivate2.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

/*
 * Extending datasets in WATCH.h5 generated by h5watchgentest.c
 */
#define DSET_ONE "DSET_ONE"
#define DSET_TWO "DSET_TWO"
#define DSET_CMPD "DSET_CMPD"
#define DSET_CMPD_ESC "DSET_CMPD_ESC"
#define DSET_CMPD_TWO "DSET_CMPD_TWO"
#define DSET_ALLOC_LATE "DSET_ALLOC_LATE"
#define DSET_ALLOC_EARLY "DSET_ALLOC_EARLY"

/* The message sent by this process (extend_dset) to the test script to start "h5watch" */
#define WRITER_MESSAGE  "writer_message"
/* The message received from the test script to start extending dataset */
#define READER_MESSAGE  "reader_message"
/* Message timeout in seconds */
#define MESSAGE_TIMEOUT 300

/* Size of data buffer */
#define TEST_BUF_SIZE 100

/* 
 * Test variations (incremental) for one-dimensional dataset:
 * 	Varies from 10->13->12->12->1->3
 */
#define ONE_NTESTS	5
int one_tests[ONE_NTESTS] = {3, -1, 0, -11, 2};

/* 
 * Test variations (incremental) for two-dimensional dataset:
 *	Varies from {4,10}->{6,12}->{8,1}->{10,1}->
 *	       	      	    {3,3}->{2,2}->{1,2}->
 *		            {1,4}->{1,3}->{1,3}
 */
#define TWO_NTESTS  9
int two_tests[TWO_NTESTS][2] = { {2, 2}, {2, -11}, {2, 0},
                                 {-7, 2}, {-1, -1}, {-1, 0},
                                 {0, 2}, {0, -1}, {0, 0}   
			       };

static int extend_dset_two(const char *file, char *dname);
static int extend_dset_one(const char *file, char *dname);
void send_message(const char *file);
static int wait_message(const char *file);


/* Data structures for datasets with compound data type */
typedef struct sub22_t {
    unsigned int a;
    unsigned int b;
    unsigned int c;
} sub22_t;

typedef struct sub2_t {
    unsigned int a;
    sub22_t b;
    unsigned int c;
} sub2_t;

typedef struct sub4_t {
    unsigned int a;
    unsigned int b;
} sub4_t;

typedef struct set_t {
    unsigned int field1;
    sub2_t field2;
    double field3;
    sub4_t field4;
} set_t;

/*
 * To send a message by creating the file.
 */
void
send_message(const char *file)
{
    FILE *id;

    id = fopen(file, "w+");
    fclose(id);
} /* send_message() */

/*
 *
 * Repeatedly check for the message file.
 * It will stop when the file exists or exceeds the timeout limit.
 */
static int
wait_message(const char *file)
{
    FILE *id;           /* File pointer */
    time_t t0, t1;      /* Time info */

    /* Start timer */
    time(&t0);

    /* Wait for message from test script to start work */
    while((id = fopen(file, "r")) == NULL) {
        /* Get current time */
        time(&t1);
        /*
         * Determine time difference--
         *   if waiting too long for the message, then it is
         *   unlikely the message will get sent, then fail rather
         *   than loop forever.
         */
        if(difftime(t1, t0) > MESSAGE_TIMEOUT)
            goto done;
    }

    fclose(id);
    unlink(file);
    return(SUCCEED);

done:
    return(FAIL);
} /* wait_message() */

/*
 ***********************************************************************
 *
 * Extending a two-dimensional dataset:
 *		  	dims[0]         dims[1]
 *                      -------         -------
 *      case #1:        increase        increase
 *      case #2:        increase        decrease
 *      case #3:        increase        same
 *      case #4:        decrease        increase
 *      case #5:        decrease        decrease        (no action)
 *      case #6:        decrease        same            (no action)
 *      case #7:        same            increase
 *      case #8:        same            decrease        (no action)
 *      case #9:        same            same            (no action)
 *
 *	two_tests[TWO_NTESTS][2] = { {2,2}, {2,-11}, {2,0},
 *                                   {-7,2}, {-1,-1}, {-1,0},
 *                                   {0,2}, {0,-1}, {0,0} }
 *	varies from {4,10}->{6,12}->{8,1}->{10,1}->
 *	       	      	    {3,3}->{2,2}->{1,2}->
 *		            {1,4}->{1,3}->{1,3}
 ***********************************************************************
 */
static int
extend_dset_two(const char *file, char *dname)
{
    hid_t fid = -1;		/* file id */
    hid_t fapl = -1;		/* file access property list id */
    hid_t did; 		/* dataset id */
    hid_t sid; 		/* dataspace id */
    hid_t dtid;		/* dataset's datatype id */
    int ndims;		/* # of dimension sizes */
    unsigned i, j;	/* local index variable */
    hsize_t ext_dims[2];	/* new dimension sizes after extension */
    hsize_t cur_dims[2];	/* current dimension sizes */
    size_t dtype_size;	/* size of the dataset's datatype */
    unsigned num_elmts; /* number of elements in the dataset */
    int ibuf[TEST_BUF_SIZE];   	/* buffer for storing retrieved elements (integer) */
    set_t cbuf[TEST_BUF_SIZE];	/* buffer for storing retrieved elemnets (compound) */

    /* Create a copy of file access property list */
    if((fapl = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        return -1;

    /* Set to use the latest library format */
    if(H5Pset_libver_bounds(fapl, H5F_LIBVER_LATEST, H5F_LIBVER_LATEST) < 0)
        return -1;

    /* Open the file and dataset with SWMR write */
    if((fid = H5Fopen(file, H5F_ACC_RDWR|H5F_ACC_SWMR_WRITE, fapl)) < 0)
	goto done;

    if((did = H5Dopen2(fid, dname, H5P_DEFAULT)) < 0)
	goto done;

    /* Send message to the test script to start "h5watch" */
    send_message(WRITER_MESSAGE);

    if((sid = H5Dget_space(did)) < 0)
	goto done;

    if((ndims = H5Sget_simple_extent_ndims(sid)) < 0)
	goto done;

    /* Get the size of the dataset's datatype */
    if((dtype_size = H5LDget_dset_type_size(did, NULL)) == 0)
	goto done;

    /* Get the dataset's data type */
    if((dtid = H5Tget_native_type(H5Dget_type(did), H5T_DIR_DEFAULT)) < 0)
	goto done;

    /* Wait for message from the test script to start extending dataset */
    if(wait_message(READER_MESSAGE) < 0)
        goto done;

    /* Loop through different variations of extending the dataset */
    for(i = 0; i < TWO_NTESTS; i++) {

	/* sleep to emulate about 2 seconds of application operation */
	sleep(2);

	/* Get current dimension sizes */ 
        if(H5LDget_dset_dims(did, cur_dims) < 0)
	    goto done;

	/* Set up the new extended dimension sizes  */
        ext_dims[0] = cur_dims[0] + (hsize_t)two_tests[i][0];
        ext_dims[1] = cur_dims[1] + (hsize_t)two_tests[i][1];

	/* Extend the dataset */
	if(H5Dset_extent(did, ext_dims) < 0)
	    goto done;

	num_elmts = 1;
	for(j = 0; j < (unsigned)ndims; j++)
	    num_elmts *= (unsigned)ext_dims[j];
	
	/* Compound type */
	if(!HDstrcmp(dname, DSET_CMPD_TWO)) {

	    HDmemset(cbuf, 0, sizeof(cbuf));
	    for(j = 0; j < num_elmts; j++) {
		cbuf[j].field1 = i + 1;
		cbuf[j].field2.a = i + 1;
		cbuf[j].field2.c = i + 1;
		cbuf[j].field2.b.a = i + 1;
		cbuf[j].field2.b.b = i + 1;
		cbuf[j].field2.b.c = i + 1;
		cbuf[j].field3 = i + 1;
		cbuf[j].field4.a = i + 1;
		cbuf[j].field4.b = i + 1;
	    }

	    /* Write to the dataset */
	    if(H5Dwrite(did, dtid, H5S_ALL, H5S_ALL, H5P_DEFAULT, cbuf) < 0)
		goto done;
	} else { /* Integer type */
	    HDmemset(ibuf, 0, sizeof(ibuf));
	    for(j = 0; j < num_elmts; j++)
		ibuf[j] = (int)(i + 1);

	    /* Write to the dataset */
	    if(H5Dwrite(did, dtid, H5S_ALL, H5S_ALL, H5P_DEFAULT, ibuf) < 0)
		goto done;
	}

	if(H5Dflush(did) < 0)
	    goto done;

    } /* end for TWO_NTESTS */

    /* Closing */
    if(H5Tclose(dtid) < 0) goto done;
    if(H5Dclose(did) < 0) goto done;
    if(H5Pclose(fapl) < 0) goto done;
    if(H5Fclose(fid) < 0) goto done;

    return(SUCCEED);

done:
    H5E_BEGIN_TRY
        H5Tclose(dtid);
        H5Dclose(did);
	H5Pclose(fapl);
	H5Fclose(fid);
    H5E_END_TRY

    return(FAIL);

} /* extend_dset_two() */

/*
 ***********************************************************************
 *
 * Extending a one-dimensional dataset
 *	Test cases: 
 *		#1: increase 
 *		#2: decrease
 *		#3: same
 *		#4: decrease
 *		#5: increase
 *
 * 	one_tests[ONE_NTESTS] = {3, -1, 0, -11, 2}
 * 	varies from 10->13->12->12->1->3
 *
 ***********************************************************************
 */
static int
extend_dset_one(const char *file, char *dname)
{
    hid_t fid = -1;	/* file id */
    hid_t fapl = -1;	/* file access property list id */
    hid_t did; 		/* dataset id */
    hid_t dtid;		/* dataset's datatype id */
    hid_t sid;		/* dataspace id */
    hid_t mid;		/* memory space id */
    unsigned i, j;	/* local index variable */
    int ibuf[TEST_BUF_SIZE];   	/* buffer for storing retrieved elements (integer) */
    set_t cbuf[TEST_BUF_SIZE];	/* buffer for storing retrieved elemnets (compound) */
    hsize_t cur_dims[1];	/* current dimension sizes */
    hsize_t ext_dims[1];	/* new dimension sizes after extension */
    hsize_t offset[1];	/* starting offsets of appended data */
    hsize_t count[1];	/* dimension sizes of appended data */
    size_t dtype_size;	/* size of the dataset's datatype */

    /* Create a copy of file access property list */
    if((fapl = H5Pcreate(H5P_FILE_ACCESS)) < 0)
        return -1;
    /* Set to use the latest library format */
    if(H5Pset_libver_bounds(fapl, H5F_LIBVER_LATEST, H5F_LIBVER_LATEST) < 0)
        return -1;

    /* Open the file and dataset with SWMR write */
    if((fid = H5Fopen(file, H5F_ACC_RDWR|H5F_ACC_SWMR_WRITE, fapl)) < 0)
	goto done;

    /* Send message to the test script to start "h5watch" */
    send_message(WRITER_MESSAGE);

    if((did = H5Dopen2(fid, dname, H5P_DEFAULT)) < 0)
	goto done;

    /* Get size of the dataset's datatype */
    if((dtype_size = H5LDget_dset_type_size(did, NULL)) == 0)
	goto done;

    /* Get dataset's datatype */
    if((dtid = H5Tget_native_type(H5Dget_type(did), H5T_DIR_DEFAULT)) < 0)
	goto done;

    /* Wait for message from the test script to start extending dataset */
    wait_message(READER_MESSAGE);

    /* Loop through different variations of extending the dataset */
    for(i = 0; i < ONE_NTESTS; i++) {

	/* sleep to emulate about 2 seconds of application operation */
	sleep(2);

	/* Get current dimension sizes */
	if(H5LDget_dset_dims(did, cur_dims) < 0)
	    goto done;

	/* Set up the new extended dimension sizes  */
	ext_dims[0] = cur_dims[0] + (hsize_t)one_tests[i];

	/* Extend the dataset */
	if(H5Dset_extent(did, ext_dims) < 0)
	    goto done;

	/* Write to the new appended region of the dataset */
	if(one_tests[i] > 0) {

	    /* Select the extended region */
	    offset[0] = cur_dims[0];
	    count[0] = (hsize_t)one_tests[i];
	    if((sid = H5Dget_space(did)) < 0)
		goto done;
	    if(H5Sselect_hyperslab(sid, H5S_SELECT_SET, offset, NULL, count, NULL) < 0)
		goto done;

	    /* Set up memory space and get dataset's datatype */
	    if((mid = H5Screate_simple(1, count, NULL)) < 0)
		goto done;

	    /* Initialize data for the extended region of the dataset */
	    /* Compound type */
	    if(!HDstrcmp(dname, DSET_CMPD) || !HDstrcmp(dname, DSET_CMPD_ESC)) {
		HDmemset(cbuf, 0, sizeof(cbuf));
		for(j = 0; j < (unsigned)one_tests[i]; j++) {
		    cbuf[j].field1 = j + 1;
		    cbuf[j].field2.a = j + 2;
		    cbuf[j].field2.b.a = j + 2;
		    cbuf[j].field2.b.b = j + 2;
		    cbuf[j].field2.b.c = j + 2;
		    cbuf[j].field2.c = j + 2;

		    cbuf[j].field3 = j + 3;

		    cbuf[j].field4.a = j + 4;
		    cbuf[j].field4.b = j + 4;
		} /* end for */

		/* Write to the extended region of the dataset */
		if(H5Dwrite(did, dtid, mid, sid, H5P_DEFAULT, cbuf) < 0)
		    goto done;
	    } else { /* Integer type */
		for(j = 0; j < (unsigned)one_tests[i]; j++)
		    ibuf[j] = (int)j;

		/* Write to the extended region of the dataset */
		if(H5Dwrite(did, dtid, mid, sid, H5P_DEFAULT, ibuf) < 0)
		    goto done;
	    }

	    /* Closing */
	    if(H5Sclose(sid) < 0) goto done;
	    if(H5Sclose(mid) < 0) goto done;
	} /* end if */

	if(H5Dflush(did) < 0)
	    goto done;

    } /* end for ONE_NTESTS */

    /* Closing */
    if(H5Tclose(dtid) < 0) goto done;
    if(H5Dclose(did) < 0) goto done;
    if(H5Pclose(fapl) < 0) goto done;
    if(H5Fclose(fid) < 0) goto done;

    return(SUCCEED);

done:
    H5E_BEGIN_TRY
	H5Sclose(sid);
	H5Sclose(mid);
        H5Tclose(dtid);
        H5Dclose(did);
	H5Pclose(fapl);
	H5Fclose(fid);
    H5E_END_TRY

    return(FAIL);
} /* extend_dset_one() */

/* Usage: extend_dset xx.h5 dname */
int
main(int argc, const char *argv[])
{
    char *dname = NULL;
    char *fname = NULL;

    if(argc != 3) {
	fprintf(stderr, "Should have file name and dataset name to be extended...\n");
	goto done;
    }

    /* Get the dataset name to be extended */
    fname = strdup(argv[1]);
    dname = strdup(argv[2]);

    if(!HDstrcmp(dname, DSET_CMPD) || !HDstrcmp(dname, DSET_CMPD_ESC)) {
	if(extend_dset_one(fname, dname) < 0) 
	    goto done;
    } else if(!HDstrcmp(dname, DSET_ONE) || 
	    !HDstrcmp(dname, DSET_ALLOC_LATE) || 
	    !HDstrcmp(dname, DSET_ALLOC_EARLY)) {
	if(extend_dset_one(fname, dname) < 0) 
	    goto done;
    } else if(!HDstrcmp(dname, DSET_TWO) || !HDstrcmp(dname, DSET_CMPD_TWO)) {
	if(extend_dset_two(fname, dname) < 0) 
	    goto done;
    } else {
	fprintf(stdout, "Dataset cannot be extended...\n");
	goto done;
    }
    exit(EXIT_SUCCESS);

done:
    if(dname) HDfree(dname);
    if(fname) HDfree(fname);
    exit(EXIT_FAILURE);
} /* main() */
