#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "hdf5.h"
#include "DataSignals.h"

/*
void *chkmalloc(size_t l){
	void *p = malloc(l);
	printf("malloc(%i) = %p\n", (int)l, p);
	return p;		
}

void chkfree(void *p){
	printf("free(%p)\n", p);
	free(p);
}
#define malloc(l) chkmalloc(l)
#define free(p) chkfree(p)
*/

/** Fetches a given JPF signal from HDF5 disk cache
 * Allocates memory for t and data
 *
 */
int getJPFFromCache(const char *cacheRoot,
			int pulse, const char *subsys, const char *signal,
			void **t, int *nt,
			void **data ){
	char *path;
	int file_id, dataspace_id, dataset_id, type_id, status;
	int rank, i, ret;
	hsize_t *dims;
 	if(t) *t = NULL;
	if(data)*data = NULL;
	int *tmpData;

	path = makeCacheFileName(cacheRoot, "jet", "jpf", NULL, pulse, subsys, signal, 0);
	if(!path){
		ret = -ENOMEM;
		goto jpfFromCache_done;
	}

	/* Open an existing file. */
	file_id = H5Fopen(path, H5F_ACC_RDWR, H5P_DEFAULT);
	if(file_id <= 0){
		fprintf(stderr, "Unable to open HDF5 file.\n");
		ret = -EIO;
		goto jpfFromCache_done;
	}

	/* we have to read the data even if data==NULL, to check dims and type etc */
	ret = readHD5DataSpace(file_id, "/data", &rank, &dims, data);
	if( ret ) goto jpfFromCache_done;

	if(rank == 1){
		*nt = (int)dims[0];
		free(dims);
		if(t){
			ret = readHD5DataSpace(file_id, "/coord0", &rank, &dims, t);
			if( ret ) goto jpfFromCache_done;
			if( rank != 1 || ((int)dims[0]) != *nt){
				fprintf(stderr, "/coord0 has %iD with length of first dim as %i. Expecting 1D of length %i.\n", rank, (int)dims[0], *nt);
				ret = -EINVAL;
				goto jpfFromCache_done;			
			}
			free(dims);
		}
	} else{
		fprintf(stderr, "/data is %iD, expecting only 1D for JPF.\n", rank);
		ret = -EINVAL;
		goto jpfFromCache_done;
	}
	
	ret = 0;

jpfFromCache_done:
	if(file_id > 0){
		/* Close the file. */	
		status = H5Fclose(file_id);
		if(status) fprintf(stderr, "Error %i closing HDF5 file\n", status);
	}

	if(ret){
		if(path) fprintf(stderr, "Error reading HDF5 file '%s'.\n", path);

		if(data){ if(*data){ free(*data); } *data = NULL; }
		if(t){ if(*t){ free(*t); } *t = NULL; }

		nt = 0;
	}
	if(path) free(path);

	return ret;
}


/** Fetches a given PPF signal from HDF5 disk cache
 * Allocates memory for x, t, data, comment and units.
 *
 */
int getPPFFromCache(const char *cacheRoot,
			const char *uid, int pulse, const char *dda, const char *dtype, int seq, 
			void **t, int *nt,
			void **x, int *nx, 
			void **data,			
			char **comment,
			char **units,
			int *userStatusFlag,
			int *systemStatusFlag ){
	char *path;
	int file_id, dataspace_id, dataset_id, type_id, status;
	int rank, i, ret;
	hsize_t *dims;
 	if(t) *t = NULL;
	if(x) *x = NULL;
	if(data)*data = NULL;
	if(comment) *comment = NULL;
	if(units) *units = NULL;
	int *tmpData;

	path = makeCacheFileName(cacheRoot, "jet", "ppf", uid, pulse, dda, dtype, seq);
	if(!path){
		ret = -ENOMEM;
		goto ppfFromCache_done;
	}

	/* Open an existing file. */
	file_id = H5Fopen(path, H5F_ACC_RDWR, H5P_DEFAULT);
	if(file_id <= 0){
		fprintf(stderr, "Unable to open HDF5 file.\n");
		ret = -EIO;
		goto ppfFromCache_done;
	}

	/* we ahve to read the data even if data==NULL, to check dims for x,t and type etc */
	ret = readHD5DataSpace(file_id, "/data", &rank, &dims, data);
	if( ret ) goto ppfFromCache_done;

	if(rank == 1){
		*nx = 0;
		*nt = (int)dims[0];
		free(dims);
		if(t){
			ret = readHD5DataSpace(file_id, "/coord0", &rank, &dims, t);
			if( ret ) goto ppfFromCache_done;
			if( rank != 1 || ((int)dims[0]) != *nt){
				fprintf(stderr, "/coord0 has %iD with length of first dim as %i. Expecting 1D of length %i.\n", rank, (int)dims[0], *nt);
				ret = -EINVAL;
				goto ppfFromCache_done;			
			}
			free(dims);
		}
	}else if(rank == 2){
		*nx = (int)dims[0];
		*nt = (int)dims[1];
		free(dims);
		if(x){
			ret = readHD5DataSpace(file_id, "/coord0", &rank, &dims, x);
			if( ret ) goto ppfFromCache_done;
			if( rank != 1 || dims[0] != *nx){
				fprintf(stderr, "/coord0 has %iD with length of first dim as %i. Expecting 1D of length %i.\n", rank, (int)dims[0], *nx);
				ret = -EINVAL;
				goto ppfFromCache_done;			
			}
			free(dims);
		}
		if(t){
			ret = readHD5DataSpace(file_id, "/coord1", &rank, &dims, t);
			if( ret ) goto ppfFromCache_done;
			if( rank != 1 || ((int)dims[0]) != *nt){
				fprintf(stderr, "/coord0 has %iD with length of first dim as %i. Expecting 1D of length %i.\n", rank, (int)dims[0], *nt);
				ret = -EINVAL;
				goto ppfFromCache_done;			
			}
			free(dims);
		}
	}else{
		fprintf(stderr, "/data is %iD, expecting 1D or 2D.\n", rank);
		ret = -EINVAL;
		goto ppfFromCache_done;
	}

	if(comment){
		ret = readHD5DataSpace(file_id, "/Comment", &rank, &dims, (void **)comment);
		if( ret ) goto ppfFromCache_done;
		if( rank != 1 || ((int)dims[0]) != 1){
			fprintf(stderr, "WARNING: /Comment is %iD with first dim as %i. Expecting 1D length 1\n", rank, (int)dims[0]);
			free(*comment);
			*comment = NULL;
		}
		free(dims);
	}

	if(units){
		ret = readHD5DataSpace(file_id, "/Units", &rank, &dims, (void **)units);
		if( ret ) goto ppfFromCache_done;
		if( rank != 1 || ((int)dims[0]) != 1){
			fprintf(stderr, "/Units is %iD with first dim as %i. Expecting 1D length 1\n", rank, (int)dims[0]);
			free(*units);
			*units = NULL;
		}
		free(dims);
	}

	ret = readHD5DataSpace(file_id, "/UserStatus", &rank, &dims, (void **)&tmpData);
	if( ret ) goto ppfFromCache_done;
	if( rank != 1 || ((int)dims[0]) != 1)
		fprintf(stderr, "/UserStatus is %iD with first dim as %i. Expecting 1D length 1\n", rank, (int)dims[0]);
	else
		*userStatusFlag = *tmpData;
	free(dims);
	free(tmpData);
	tmpData = NULL;

	ret = readHD5DataSpace(file_id, "/SystemStatus", &rank, &dims, (void **)&tmpData);
	if( ret ) goto ppfFromCache_done;
	if( rank != 1 || ((int)dims[0]) != 1)
		fprintf(stderr, "/SystemStatus is %iD with first dim as %i. Expecting 1D length 1\n", rank, (int)dims[0]);
	else
		*systemStatusFlag = *tmpData;
	free(dims);
	free(tmpData);
	tmpData = NULL;

	ret = 0;

ppfFromCache_done:
	if(file_id > 0){
		/* Close the file. */	
		status = H5Fclose(file_id);
		if(status) fprintf(stderr, "Error %i closing HDF5 file\n", status);
	}

	if(ret){
		if(path) fprintf(stderr, "Error reading HDF5 file '%s'.\n", path);

		if(data){ if(*data){ free(*data); } *data = NULL; }
		if(x){ if(*x){ free(*x); } *x = NULL; }
		if(t){ if(*t){ free(*t); } *t = NULL; }
		if(comment){ if(*comment){ free(*comment); } *comment = NULL; }
		if(units){ if(*units){ free(*units); } *units = NULL; }

		nt = 0; nx = 0;
		*userStatusFlag = 0; *systemStatusFlag = 0;
	}
	if(path) free(path);

	return ret;
}

int readHD5DataSpace(int file_id, const char *name, int *rankPtr, hsize_t **dimsPtr, void **dataPtr){
	int dataspace_id, dataset_id, type_id, status;
	int rank, i, ret;
	hsize_t *dims = NULL;
	long totalEntries, totalBytes;
	int type_class, type_size;
	void *data = NULL;

	/* Open an existing dataset. */
	dataset_id = H5Dopen(file_id, name);
	if(dataset_id <= 0){
		fprintf(stderr, "Unable to open HDF5 dataset '%s'\n", name);
		goto rh5dspace_done;
		return -EIO;
	}

	dataspace_id = H5Dget_space(dataset_id);
	if(dataspace_id <= 0){
		fprintf(stderr, "Unable to get HDF5 dataspace from '%s'\n", name);
		ret = -EIO;	
		goto rh5dspace_done;
	}

	rank = H5Sget_simple_extent_ndims(dataspace_id);

	dims = (hsize_t *)malloc(rank * sizeof(hsize_t));
	if(!dims){
		fprintf(stderr, "Unable to allocatespace for dims of dataset '%s'\n", name);
		ret = -ENOMEM;	
		goto rh5dspace_done;
	}
	H5Sget_simple_extent_dims(dataspace_id, dims, NULL);

	type_id = H5Dget_type(dataset_id);	
	totalBytes = H5Tget_size(type_id);
	for(i=0;i<rank;i++)
		totalBytes *= (int)dims[i];

	data = malloc(totalBytes);
	if(!data){
		fprintf(stderr, "Unable to allocate %li bytes to read HDF5 dataset '%s'\n", totalBytes, name);
		ret = -ENOMEM;	
		goto rh5dspace_done;
	}
	
	status = H5Dread(dataset_id, type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, data);	
	if(status){
		fprintf(stderr, "Error reading data from HDF5 dataset '%s'", name);
		ret = -EIO;
		goto rh5dspace_done;
	}

	ret = 0;
	
rh5dspace_done:
	if(dataset_id > 0){
		/* Close the dataset. */
		status = H5Dclose(dataset_id);
		if(status) fprintf(stderr, "Error %i closing HDF5 dataset\n", status);
	}

	if(ret){
		if(data) free(data);
		if(dims) free(dims);
		if(rankPtr) *rankPtr = 0;
		if(dimsPtr) *dimsPtr = NULL;
		if(dataPtr) *dataPtr = NULL;
	}else{
		if(rankPtr) *rankPtr = rank;
		if(dimsPtr) *dimsPtr = dims; else free(dims);
		if(dataPtr) *dataPtr = data; else free(data);
	}
	return ret;
}

int strncatAndFixup(char *dest, const char *src, int maxLen) {
	char *dstPtr = dest;
	const char *srcPtr = src;

	while(*srcPtr){
		if(*srcPtr == '<'){
			if(dstPtr >= (dest + maxLen - 4))
				break;
			*dstPtr++ = '_';
			*dstPtr++ = 'l';
			*dstPtr++ = 't';
			*dstPtr++ = '_';
		}else if(*srcPtr == '>'){
			if(dstPtr >= (dest + maxLen - 4))
				break;
			*dstPtr++ = '_';
			*dstPtr++ = 'g';
			*dstPtr++ = 't';
			*dstPtr++ = '_';
		}else
			*dstPtr++ = *srcPtr;

		*srcPtr++;

		if(dstPtr >= (dest + maxLen - 1))
			break;
	};

	*dstPtr = 0x00;

	return (dstPtr - dest);
}

/** Make up the fileName for a signal in the cache */
char *makeCacheFileName(const char *cacheRoot, const char *machine, const char *sigType, const char *uid, int pulse, const char *path1, const char *path2, int seq){

	int maxLen, pos, i;
	char *fileName;
	
	maxLen = strlen(cacheRoot) + strlen(sigType) + strlen(path1) + 
			strlen(path2) + ((uid != NULL) ? strlen(uid) : 0) + 64;
	fileName = (char *)malloc( maxLen );
	if(!fileName) {
		fprintf(stderr, "Unable to alloc %i bytes for cache fileName.\n", maxLen);
		return NULL;
	}

	pos = snprintf(fileName, maxLen, "%s/%s/%i/%s", cacheRoot, machine, pulse, sigType);

	if (uid != NULL && strlen(uid) > 0 && strcasecmp(uid, "JETPPF") != 0 )
		pos += snprintf(fileName+pos, maxLen-pos, "-%s", uid);

	strncat(fileName+pos, "/", maxLen-pos); pos++;
	pos += strncatAndFixup(fileName+pos, path1, maxLen-pos);
	strncat(fileName+pos, "/", maxLen-pos); pos++;
	pos += strncatAndFixup(fileName+pos, path2, maxLen-pos);

	if (seq > 0)
		pos += snprintf(fileName+pos, maxLen-pos, "#%i", seq);	

	snprintf(fileName+pos, maxLen-pos, ".hdf");	

	//lower case everything except the cacheRoot
	for(i=strlen(cacheRoot);i<pos;i++)
		if( isupper(fileName[i]) )
			fileName[i] = tolower(fileName[i]);

	return fileName;
}
