#ifndef _BLASR_HDF_BUFFERED_HDF_2D_ARRAY_HPP_
#define _BLASR_HDF_BUFFERED_HDF_2D_ARRAY_HPP_

#include <cstdlib>
#include <iostream>
#include <string>

#include "H5Cpp.h"

#include "Types.h"
#include "HDFConfig.hpp"
#include "HDFData.hpp"
#include "HDFGroup.hpp"
#include "HDFWriteBuffer.hpp"

/*
 *
 * Implementation of a 2-D array for IO from an HDF array.
 * This is templated, but specialized for a few data types, so that 
 * the HDF data types do not need to be specified by somebody when reading.
 *
 * Currently no support exists for reading non-contiguous blocks of data, and
 * the main intended use is to read in increments of rows.

 int main(int argc, char* argv[]) {
	if (argc < 1) {
		cout << "usage: testHDFReading hdfFile" << endl;
		exit(0);
	}

	std::string hdfFileName = argv[1];
	
	H5File hdfFile;
	hdfFile.openFile(hdfFileName.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
	BufferedHDF2DArray<uint16_t> xyArray;
	xyArray.Initialize(hdfFile, "PulseData/BaseCalls/ZMW/HoleXY");
	int curX = 0;
	xyArray.Read(curX, curX + 1, 0, 2, holeXY);

	or, to read a row:
	xyArray.Read(curX, curX+1, holeXY);

 *
 */
template<typename T>
class BufferedHDF2DArray : public HDFData, public HDFWriteBuffer<T> {

private:
    hsize_t   nDims;
    hsize_t   *dimSize;
    int       maxDims;
    int       rowLength, colLength;

public:

    BufferedHDF2DArray(H5::CommonFG *_container, std::string _datasetName); 

    BufferedHDF2DArray();

    unsigned int GetNRows();

    unsigned int GetNCols(); 

    void Close(); 

    ~BufferedHDF2DArray(); 

    int InitializeForReading(HDFGroup &group, std::string datasetName);

    /*
     * Initialize HDF2D for reading.  No write buffer initialization is
     * required.  The assumption is that the dataspace is in two
     * dimensions, and this exits without grace if it is not. 
     */
    int Initialize(HDFGroup &group, std::string datasetName, unsigned int _rowLength=0, 
        int _bufferSize=0, bool createIfMissing=true); 

    int size(); 

    /*
     * Read rows in the range (startX, endX] in to dest.
     */

    void Read(int startX, int endX, H5::DataType typeID, T*dest); 

    void Read(int startX, int endX, T*dest); 

    /*
     * This is the non-specialized definition.  Since this should only
     * operate on specialized types, report an error and bail.
     */
    void Read(int startX, int endX, int startY, int endY, T* dest); 

    void Read(int startX, int endX, int startY, int endY, H5::DataType typeID, 
        T *dest); 

    void Create(H5::CommonFG *_container, std::string _datasetName, unsigned int _rowLength); 

    void TypedCreate(H5::DataSpace &fileSpace, H5::DSetCreatPropList &cparms); 
    
    // Append
    void TypedWriteRow(const T*, const H5::DataSpace &memoryDataSpace, 
        const H5::DataSpace &fileDataSpace); 

    /*
     * This code is copied directly form BufferedHDFArray.  I'm not sure
     * how to set up the objects nicely to share the code between the
     * two since the Flush() function is different.  There probably is a
     * design pattern or simply better way to engineer this, but for now
     * it's 15 lines of code.
     */
    void WriteRow(const T *data, int dataLength, int destRow=-1); 

    void Flush(int destRow = -1); 
};

UInt GetDatasetNDim(H5::CommonFG &parentGroup, std::string datasetName);

#define DECLARE_TYPED_WRITE_ROW(T, Pred) template<>\
void BufferedHDF2DArray<T>::TypedWriteRow(const T *data, \
    const H5::DataSpace &memorySpace, const H5::DataSpace &fileSpace) ;


DECLARE_TYPED_WRITE_ROW(int, H5::PredType::NATIVE_INT)
DECLARE_TYPED_WRITE_ROW(unsigned int, H5::PredType::NATIVE_UINT)
DECLARE_TYPED_WRITE_ROW(unsigned char, H5::PredType::NATIVE_UINT8)
DECLARE_TYPED_WRITE_ROW(uint16_t, H5::PredType::NATIVE_UINT16)
DECLARE_TYPED_WRITE_ROW(int16_t, H5::PredType::NATIVE_INT16)
DECLARE_TYPED_WRITE_ROW(float, H5::PredType::NATIVE_FLOAT)


#define DECLARE_TYPED_READ_ROW(T, Pred) template<>\
void BufferedHDF2DArray<T>::Read(int startX, int endX, int startY, int endY, T* dest);


DECLARE_TYPED_READ_ROW(int, H5::PredType::NATIVE_INT)
DECLARE_TYPED_READ_ROW(unsigned int, H5::PredType::NATIVE_UINT)
DECLARE_TYPED_READ_ROW(char, H5::PredType::NATIVE_INT8)
DECLARE_TYPED_READ_ROW(unsigned char, H5::PredType::NATIVE_UINT8)
DECLARE_TYPED_READ_ROW(uint16_t, H5::PredType::NATIVE_UINT16)
DECLARE_TYPED_READ_ROW(int16_t, H5::PredType::NATIVE_INT16)
DECLARE_TYPED_READ_ROW(float, H5::PredType::NATIVE_FLOAT)

#define DECLARE_TYPED_CREATE_ROW(T, Pred)template<>\
void BufferedHDF2DArray<T>::TypedCreate(H5::DataSpace &fileSpace, \
    H5::DSetCreatPropList &cparms) ;

DECLARE_TYPED_CREATE_ROW(int, H5::PredType::NATIVE_INT)
DECLARE_TYPED_CREATE_ROW(unsigned int, H5::PredType::NATIVE_UINT)
DECLARE_TYPED_CREATE_ROW(char, H5::PredType::NATIVE_INT8)
DECLARE_TYPED_CREATE_ROW(unsigned char, H5::PredType::NATIVE_UINT8)
DECLARE_TYPED_CREATE_ROW(uint16_t, H5::PredType::NATIVE_UINT16)
DECLARE_TYPED_CREATE_ROW(int16_t, H5::PredType::NATIVE_INT16)
DECLARE_TYPED_CREATE_ROW(float, H5::PredType::NATIVE_FLOAT)

#include "BufferedHDF2DArrayImpl.hpp"

#endif
