/*****************************************************
 * \brief Read a natural block of raster band data
 *****************************************************/
CPLErr PostGISRasterTileRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
                                               CPL_UNUSED int nBlockYOff,
                                               void * pImage)
{
    CPLString osCommand;
    PGresult * poResult = NULL;
    int nWKBLength = 0;

    int nPixelSize = GDALGetDataTypeSize(eDataType)/8;

    PostGISRasterTileDataset * poRTDS =
        (PostGISRasterTileDataset *)poDS;
        
    // Get by PKID
    if (poRTDS->poRDS->pszPrimaryKeyName) {
        osCommand.Printf("select st_band(%s, %d) from %s.%s where "
            "%s = '%s'", poRTDS->poRDS->pszColumn, nBand, poRTDS->poRDS->pszSchema, poRTDS->poRDS->pszTable,
            poRTDS->poRDS->pszPrimaryKeyName, poRTDS->pszPKID);

    }
    
    // Get by upperleft
    else {
        osCommand.Printf("select st_band(%s, %d) from %s.%s where "
            "abs(ST_UpperLeftX(%s) - %.8f) < 1e-8 and abs(ST_UpperLeftY(%s) - %.8f) < 1e-8", 
            poRTDS->poRDS->pszColumn, nBand, poRTDS->poRDS->pszSchema, poRTDS->poRDS->pszTable, poRTDS->poRDS->pszColumn, 
            poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X], poRTDS->poRDS->pszColumn, 
            poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y]);
        
    }
    
    poResult = PQexec(poRTDS->poRDS->poConn, osCommand.c_str());
    
#ifdef DEBUG_QUERY
    CPLDebug("PostGIS_Raster", "PostGISRasterTileRasterBand::IReadBlock(): "
             "Query = \"%s\" --> number of rows = %d",
             osCommand.c_str(), poResult ? PQntuples(poResult) : 0 );
#endif

    if (poResult == NULL || 
        PQresultStatus(poResult) != PGRES_TUPLES_OK ||
        PQntuples(poResult) <= 0) {
            
        if (poResult)
            PQclear(poResult);
            
        ReportError(CE_Failure, CPLE_AppDefined,
            "Error getting block of data (upperpixel = %f, %f)",
                poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X], 
                poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y]);
            
        return CE_Failure;
    }


    // TODO: Check this
    if (bIsOffline) {
        CPLError(CE_Failure, CPLE_AppDefined, "This raster has outdb "
            "storage. This feature isn't still available");
        
        PQclear(poResult);    
        return CE_Failure;
    }
    
    /* Copy only data size, without payload */
    int nExpectedDataSize = 
        nBlockXSize * nBlockYSize * nPixelSize;
        
    GByte * pbyData = CPLHexToBinary(PQgetvalue(poResult, 0, 0), 
        &nWKBLength);
    int nExpectedWKBLength = RASTER_HEADER_SIZE + BAND_SIZE(nPixelSize, nExpectedDataSize);
    CPLErr eRet = CE_None;
    if( nWKBLength != nExpectedWKBLength )
    {
        CPLDebug("PostGIS_Raster", "nWKBLength=%d, nExpectedWKBLength=%d", nWKBLength, nExpectedWKBLength );
        eRet = CE_Failure;
    }
    else
    {
        GByte * pbyDataToRead = 
        (GByte*)GET_BAND_DATA(pbyData,1, nPixelSize, 
            nExpectedDataSize);

        // Do byte-swapping if necessary */
        int bIsLittleEndian = (pbyData[0] == 1);
#ifdef CPL_LSB
        int bSwap = !bIsLittleEndian;
#else
        int bSwap = bIsLittleEndian;
#endif
        if( bSwap && nPixelSize > 1 )
        {
            GDALSwapWords( pbyDataToRead, nPixelSize,
                           nBlockXSize * nBlockYSize,
                           nPixelSize );
        }

        memcpy(pImage, pbyDataToRead, nExpectedDataSize);
    }

    CPLFree(pbyData);
    PQclear(poResult);

    return eRet;
}
/*****************************************************
 * \brief Read a natural block of raster band data
 *****************************************************/
CPLErr PostGISRasterRasterBand::IReadBlock(int nBlockXOff,
        int nBlockYOff, void * pImage)
{
    PGresult * poResult = NULL;
    CPLString osCommand;
    int nXOff = 0;
    int nYOff = 0;
    int nNaturalBlockXSize = 0;
    int nNaturalBlockYSize = 0;
    double adfProjWin[8];
    PostGISRasterDataset * poRDS = (PostGISRasterDataset *)poDS;

    int nPixelSize = GDALGetDataTypeSize(eDataType) / 8;

    // Construct a polygon to intersect with
    GetBlockSize(&nNaturalBlockXSize, &nNaturalBlockYSize);

    nXOff = nBlockXOff * nNaturalBlockXSize;
    nYOff = nBlockYOff * nNaturalBlockYSize;

    poRDS->PolygonFromCoords(nXOff, nYOff, nXOff + nNaturalBlockXSize, nYOff + nNaturalBlockYSize, adfProjWin);

    // Raise the query
    if (poRDS->pszWhere == NULL) {
        osCommand.Printf("SELECT st_band(%s, %d) FROM %s.%s "
            "WHERE st_intersects(%s, ST_PolygonFromText"
            "('POLYGON((%.17f %.17f, %.17f %.17f, %.17f %.17f, %.17f "
            "%.17f, %.17f %.17f))', %d))", pszColumn, nBand, pszSchema,
            pszTable, pszColumn, adfProjWin[0], adfProjWin[1],
            adfProjWin[2], adfProjWin[3],  adfProjWin[4], adfProjWin[5],
            adfProjWin[6], adfProjWin[7], adfProjWin[0], adfProjWin[1],
            poRDS->nSrid);
    }

    else {
        osCommand.Printf("SELECT st_band(%s, %d) FROM %s.%s WHERE (%s) "
            "AND st_intersects(%s, ST_PolygonFromText"
            "('POLYGON((%.17f %.17f, %.17f %.17f, %.17f %.17f, %.17f "
            "%.17f, %.17f %.17f))', %d))", pszColumn, nBand, pszSchema,
            pszTable, poRDS->pszWhere, pszColumn, adfProjWin[0],
            adfProjWin[1], adfProjWin[2], adfProjWin[3], adfProjWin[4],
            adfProjWin[5], adfProjWin[6], adfProjWin[7], adfProjWin[0],
            adfProjWin[1], poRDS->nSrid);
    }

#ifdef DEBUG_QUERY
    CPLDebug("PostGIS_Raster",
        "PostGISRasterRasterBand::IReadBlock(): Query = %s",
            osCommand.c_str());
#endif

    poResult = PQexec(poRDS->poConn, osCommand.c_str());
    if (poResult == NULL ||
        PQresultStatus(poResult) != PGRES_TUPLES_OK ||
        PQntuples(poResult) < 0) {

        if (poResult)
            PQclear(poResult);

        ReportError(CE_Failure, CPLE_AppDefined,
            "Error retrieving raster data FROM database");

        CPLDebug("PostGIS_Raster",
            "PostGISRasterRasterBand::IRasterIO(): %s",
                PQerrorMessage(poRDS->poConn));

        return CE_Failure;
    }

    /**
     * No data. Return the buffer filled with nodata values
     **/
    else if (PQntuples(poResult) == 0) {
        PQclear(poResult);

        CPLDebug("PostGIS_Raster",
            "PostGISRasterRasterBand::IRasterIO(): Null block");

        NullBlock(pImage);

        return CE_None;
    }


    /**
     * Ok, we get the data. Only data size, without payload
     *
     * TODO: Check byte order
     **/
    int nExpectedDataSize = nNaturalBlockXSize * nNaturalBlockYSize *
        nPixelSize;

    int nWKBLength = 0;

    GByte * pbyData = CPLHexToBinary(PQgetvalue(poResult, 0, 0),
        &nWKBLength);

    char * pbyDataToRead = (char*)GET_BAND_DATA(pbyData,nBand,
            nPixelSize, nExpectedDataSize);

    memcpy(pImage, pbyDataToRead, nExpectedDataSize * sizeof(char));

    CPLDebug("PostGIS_Raster", "IReadBlock: Copied %d bytes FROM block "
            "(%d, %d) to %p", nExpectedDataSize, nBlockXOff,
            nBlockYOff, pImage);

    CPLFree(pbyData);
    PQclear(poResult);

    return CE_None;
}
/*****************************************************
 * \brief Read a natural block of raster band data
 *****************************************************/
CPLErr PostGISRasterTileRasterBand::IReadBlock(int /*nBlockXOff*/,
                                               int /*nBlockYOff*/,
                                               void * pImage)
{
    CPLString osCommand;
    PGresult * poResult = nullptr;
    int nWKBLength = 0;

    const int nPixelSize = GDALGetDataTypeSizeBytes(eDataType);

    PostGISRasterTileDataset * poRTDS =
        cpl::down_cast<PostGISRasterTileDataset *>(poDS);

    const double dfTileUpperLeftX = poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X];
    const double dfTileUpperLeftY = poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y];
    const double dfTileResX = poRTDS->adfGeoTransform[1];
    const double dfTileResY = poRTDS->adfGeoTransform[5];
    const int nTileXSize = nBlockXSize;
    const int nTileYSize = nBlockYSize;

    CPLString osRasterToFetch;
    osRasterToFetch.Printf("ST_Band(%s, %d)",
                           poRTDS->poRDS->pszColumn, nBand);
    // We don't honour CLIENT_SIDE_IF_POSSIBLE since it would be likely too
    // costly in that context.
    if( poRTDS->poRDS->eOutDBResolution != OutDBResolution::CLIENT_SIDE )
    {
        osRasterToFetch = "encode(ST_AsBinary(" + osRasterToFetch + ",TRUE),'hex')";
    }

    osCommand.Printf("SELECT %s FROM %s.%s WHERE ",
        osRasterToFetch.c_str(), poRTDS->poRDS->pszSchema, poRTDS->poRDS->pszTable);

    // Get by PKID
    if (poRTDS->poRDS->pszPrimaryKeyName)
    {
        osCommand += CPLSPrintf("%s = '%s'",
                        poRTDS->poRDS->pszPrimaryKeyName, poRTDS->pszPKID);
    }

    // Get by upperleft
    else {
        osCommand += CPLSPrintf(
            "abs(ST_UpperLeftX(%s) - %.8f) < 1e-8 and abs(ST_UpperLeftY(%s) - %.8f) < 1e-8",
            poRTDS->poRDS->pszColumn,
            dfTileUpperLeftX,
            poRTDS->poRDS->pszColumn,
            dfTileUpperLeftY);
    }

    poResult = PQexec(poRTDS->poRDS->poConn, osCommand.c_str());

#ifdef DEBUG_QUERY
    CPLDebug("PostGIS_Raster", "PostGISRasterTileRasterBand::IReadBlock(): "
             "Query = \"%s\" --> number of rows = %d",
             osCommand.c_str(), poResult ? PQntuples(poResult) : 0 );
#endif

    if (poResult == nullptr ||
        PQresultStatus(poResult) != PGRES_TUPLES_OK ||
        PQntuples(poResult) <= 0) {

        CPLString osError;
        if( PQresultStatus(poResult) == PGRES_FATAL_ERROR )
        {
            const char *pszError = PQerrorMessage( poRTDS->poRDS->poConn );
            if( pszError )
                osError = pszError;
        }
        if (poResult)
            PQclear(poResult);

        ReportError(CE_Failure, CPLE_AppDefined,
            "Error getting block of data (upperpixel = %f, %f): %s",
                dfTileUpperLeftX,
                dfTileUpperLeftY,
                osError.c_str());

        return CE_Failure;
    }

    /* Copy only data size, without payload */
    int nExpectedDataSize =
        nBlockXSize * nBlockYSize * nPixelSize;

    struct CPLFreer { void operator() (GByte* x) const { CPLFree(x); } };
    std::unique_ptr<GByte, CPLFreer> pbyDataAutoFreed(
        CPLHexToBinary(PQgetvalue(poResult, 0, 0), &nWKBLength));
    GByte* pbyData = pbyDataAutoFreed.get();
    PQclear(poResult);

    const int nMinimumWKBLength = RASTER_HEADER_SIZE + BAND_SIZE(1, nPixelSize);
    if( nWKBLength < nMinimumWKBLength )
    {
        CPLDebug("PostGIS_Raster", "nWKBLength=%d. too short. Expected at least %d",
                 nWKBLength, nMinimumWKBLength );
        return CE_Failure;
    }

    // Is it indb-raster ?
    if( (pbyData[RASTER_HEADER_SIZE] & 0x80) == 0 )
    {
        int nExpectedWKBLength = RASTER_HEADER_SIZE +
                                 BAND_SIZE(nPixelSize, nExpectedDataSize);
        if( nWKBLength != nExpectedWKBLength )
        {
            CPLDebug("PostGIS_Raster",
                     "nWKBLength=%d, nExpectedWKBLength=%d",
                     nWKBLength, nExpectedWKBLength );
            return CE_Failure;
        }

        GByte * pbyDataToRead = GET_BAND_DATA(pbyData,1,
                                              nPixelSize,nExpectedDataSize);

        // Do byte-swapping if necessary */
        const bool bIsLittleEndian = (pbyData[0] == 1);
#ifdef CPL_LSB
        const bool bSwap = !bIsLittleEndian;
#else
        const bool bSwap = bIsLittleEndian;
#endif

        if( bSwap && nPixelSize > 1 )
        {
            GDALSwapWords( pbyDataToRead, nPixelSize,
                           nBlockXSize * nBlockYSize,
                           nPixelSize );
        }

        memcpy(pImage, pbyDataToRead, nExpectedDataSize);
    }
    else
    {
        int nCurOffset = RASTER_HEADER_SIZE;
        if( !poRTDS->poRDS->LoadOutdbRaster(nCurOffset, eDataType, nBand,
                                             pbyData,
                                             nWKBLength,
                                             pImage,
                                             dfTileUpperLeftX,
                                             dfTileUpperLeftY,
                                             dfTileResX,
                                             dfTileResY,
                                             nTileXSize,
                                             nTileYSize) )
        {
            return CE_Failure;
        }
    }


    return CE_None;
}