#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <stdio.h>
#include "GDALAccessor.h"

using namespace std;
void
GDALAccessor::init (void * poly)
{
    return;
}
void
GDALAccessor::finalize ()
{
    if (FileObject != NULL)
    {
        std::cout << "GDAL close: " << Filename << std::endl;
	GDALClose ((GDALDatasetH) FileObject);
    }
    if (!(Data == NULL))
    {
	delete[] Data;
    }
}
void
GDALAccessor::init (string filename, string accessMode, int sizeV, int Bands,
		    int LineWidth)
{
    init (filename, accessMode,sizeV);
}
void
GDALAccessor::init (string filename, string accessMode, int sizeV)
{
    setAccessMode (accessMode);

    Filename = filename;
    openFile (Filename, AccessMode, &FileObject);
    LineWidth = FileObject->GetRasterXSize ();
    NumberOfLines = FileObject->GetRasterYSize ();
    DataType = (GDALDataType) sizeV;
    Bands = FileObject->GetRasterCount ();
    SizeV = GDALGetDataTypeSize (DataType) / 8; //the function returns # bits
    FileSize = LineWidth * NumberOfLines * Bands * SizeV;
}

void
GDALAccessor::rewindAccessor ()
{
    LastPosition = 0;
    EofFlag = 0;
}
int
GDALAccessor::getFileLength ()
{
    return NumberOfLines;
}
void
GDALAccessor::createFile (int numberOfLines)
{
    //TODO
}

void
GDALAccessor::openFile (string filename, string accessMode, GDALDataset ** fd)
{

    if (accessMode == "read" || accessMode == "READ")
    {
        std::cout << "GDAL open (R): " << filename << std::endl;
        (*fd) = (GDALDataset *) GDALOpenShared (filename.c_str (), GA_ReadOnly);
        if ((*fd) == NULL)
        {
            string errMsg = "Cannot open the file " + filename + " in "
                            + accessMode + " mode.";
            throw runtime_error(errMsg);
            // ERR_MESSAGE;
        }
    }
    else
    {
        string errMsg = "Error. Only read mode is available and not " + accessMode + " mode.";
        throw runtime_error(errMsg);
        // ERR_MESSAGE
        // ;
    }

}
//The IORaster can read all the bands at once but the data is read one band at the time.
//This means that one has to know the interleaved scheme and reassemble the data into a stream.
//Just assume that is one band image or that the user created the appropriate vrt file to read all at once
void
GDALAccessor::getStream (char * dataLine, int & numEl)
{
    //NOTE: arguments 4 and 5 (nXSize and nYSize) are one based

    int ypos0 = LastPosition / LineWidth;
    int xpos0 = LastPosition % LineWidth;
    LastPosition += numEl;
    int ypos1 = (LastPosition - std::streampos(1)) / LineWidth;
    if (LastPosition * SizeV >= FileSize)
    {
	numEl -= LastPosition % LineWidth;
	LastPosition = 0;
	ypos1 = NumberOfLines - 1;

    }

    char buf[SizeV * (ypos1 - ypos0 + 1) * LineWidth];
    FileObject->RasterIO (GF_Read, 0, ypos0, LineWidth, ypos1 - ypos0 + 1, buf,
			  LineWidth, ypos1 - ypos0 + 1, DataType, 1, NULL, 0, 0,
			  0, NULL);
    for (int i = 0; i < numEl; ++i)
    {
	for (int j = 0; j < SizeV; ++j)
	{
	    dataLine[i * SizeV + j] = buf[xpos0 * SizeV + i * SizeV + j];
	}
    }
}

void
GDALAccessor::getStreamAtPos (char * dataLine, int & pos, int & numEl)
{
    if (pos * SizeV >= FileSize)
    {
	numEl = 0;
    }
    else
    {
	//put pos in  npos since it changes and pos is by reference.
	//should not have passed by reference since it is not modified
	int npos = pos;
	int ypos0 = npos / LineWidth;
	int xpos0 = npos % LineWidth;
	npos += numEl;
	int ypos1 = (npos - 1) / LineWidth;
	if (npos * SizeV >= FileSize)
	{
	    numEl -= npos % LineWidth;
	    ypos1 = NumberOfLines - 1;
	}
	char buf[SizeV * (ypos1 - ypos0 + 1) * LineWidth];
	FileObject->RasterIO (GF_Read, 0, ypos0, LineWidth, ypos1 - ypos0 + 1,
			      buf, LineWidth, ypos1 - ypos0 + 1, DataType, 1,
			      NULL, 0, 0, 0, NULL);
	for (int i = 0; i < numEl; ++i)
	{
	    for (int j = 0; j < SizeV; ++j)
	    {
		dataLine[i * SizeV + j] = buf[xpos0 * SizeV + i * SizeV + j];
	    }
	}
    }
}
void
GDALAccessor::setData (char * buf, int row, int col, int numEl)
{
//TO DO once we start with new formats
    return;
}

void
GDALAccessor::setDataBand (char* buf, int row, int col, int numEl, int band)
{
//TO DO once we start with new formats
    return;
}

//Since GDAL RasterIO returns the data in BSQ (band sequential) no matter what the underlying scheme is
//we don't need a reader for each interleaved scheme
void
GDALAccessor::getData (char * buf, int row, int col, int & numEl)
{
    int ypos0 = row;
    int xpos0 = col;
    int ypos1 = ypos0 + (xpos0 + numEl - 1) / LineWidth;
    
//make sure we don't go over
    if (ypos1 >= NumberOfLines)
    {
	ypos1 = NumberOfLines - 1;
	//adjust number of elements read
	numEl -= (xpos0 + numEl - 1) % LineWidth;
    EofFlag = -1;
    }

//B. Riel: 05/19/17: additional check for ypos0 to prevent negative allocation size
    if (ypos0 > ypos1)
    {
    ypos0 = ypos1;
    }

//get every band at once. Read enough line to fit all the data. GDAL read one band after the other
//i.e. band sequential scheme
    char dataLine[SizeV * (ypos1 - ypos0 + 1) * LineWidth * Bands];
    CPLErr err = FileObject->RasterIO (GF_Read, 0, ypos0, LineWidth, ypos1 - ypos0 + 1,
			  dataLine, LineWidth, ypos1 - ypos0 + 1, DataType,
			  Bands, NULL, 0, 0, 0, NULL);

    for (int i = 0; i < numEl; ++i)
    {
	for (int j = 0; j < Bands; ++j)
	{

	    for (int k = 0; k < SizeV; ++k)
	    {

		buf[i * Bands * SizeV + j * SizeV + k] = dataLine[xpos0 * SizeV
			+ i * SizeV
			+ j * SizeV * (ypos1 - ypos0 + 1) * LineWidth + k];
	    }
	}
    }
}
//Similarly as above the RasterIO already returns the band. Just put it into the buffer
void
GDALAccessor::getDataBand (char * buf, int row, int col, int &numEl, int band)
{
    GDALRasterBand *poBand;
//NOTE GDAL band counting is 1 based
    poBand = FileObject->GetRasterBand (band + 1);
    int ypos0 = row;
    int xpos0 = col;
    int ypos1 = ypos0 + (xpos0 + numEl - 1) / LineWidth;
//make sure we don't go over
    if (ypos1 >= NumberOfLines)
    {
	ypos1 = NumberOfLines - 1;
	//adjust number of elements read
	numEl -= (xpos0 + numEl - 1) % LineWidth;
        EofFlag = -1;
    }
//get every band at once. Read enough line to fit all the data. GDAL read one band after the other
//i.e. band sequential scheme
    char dataLine[SizeV * (ypos1 - ypos0 + 1) * LineWidth];
    poBand->RasterIO (GF_Read, 0, ypos0, LineWidth, ypos1 - ypos0 + 1, dataLine,
		      LineWidth, ypos1 - ypos0 + 1, DataType, 0, 0);

    for (int i = 0; i < numEl; ++i)
    {
	for (int k = 0; k < SizeV; ++k)
	{
	    buf[i * SizeV + k] = dataLine[xpos0 * SizeV + i * SizeV + k];
	}

    }
}
void
GDALAccessor::setStream (char * dataLine, int numEl)
{

}
void
GDALAccessor::setStreamAtPos (char * dataLine, int & pos, int & numEl)
{

}

