/* 
 * $Id$
 *
 * WKTRaster - Raster Types for PostGIS
 * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage
 *
 * Copyright (C) 2009  Sandro Santilli <strk@keybit.net>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <math.h>
#include <stdio.h>  /* for printf (default message handler) */
#include <stdarg.h> /* for va_list, va_start etc */
#include <string.h> /* for memcpy */
#include <assert.h>

#include "rt_api.h"

/* Define this to debug low-level RASTER API activity */
/* Alternative, use ./configure --enable-rtapi-debug */
/*#define RT_API_DEBUG*/

#define RT_WARN_ON_TRUNCATION

/*--- Utilities -------------------------------------------------*/

static void
swap_char(uint8_t *a, uint8_t *b)
{
        uint8_t c;

        c = *a;
        *a=*b;
        *b=c;
}

static void
flip_endian_16(uint8_t *d)
{
        swap_char(d, d+1);
}

static void
flip_endian_32(uint8_t *d)
{
        swap_char(d, d+3);
        swap_char(d+1, d+2);
}

static void
flip_endian_64(uint8_t *d)
{
        swap_char(d+7, d);
        swap_char(d+6, d+1);
        swap_char(d+5, d+2);
        swap_char(d+4, d+3);
}


/*- rt_context -------------------------------------------------------*/

static void
default_error_handler(const char *fmt, ...)
{
    va_list ap;

    static const char *label = "ERROR: ";
    char newfmt[1024];
    snprintf(newfmt, 1024, "%s%s\n", label, fmt);
    newfmt[1023] = '\0';

    va_start(ap, fmt);

    vprintf(newfmt, ap);

    va_end(ap);
}

static void
default_warning_handler(const char *fmt, ...)
{
    va_list ap;

    static const char *label = "WARNING: ";
    char newfmt[1024];
    snprintf(newfmt, 1024, "%s%s\n", label, fmt);
    newfmt[1023] = '\0';

    va_start(ap, fmt);

    vprintf(newfmt, ap);

    va_end(ap);
}

static void
default_info_handler(const char *fmt, ...)
{
    va_list ap;

    static const char *label = "INFO: ";
    char newfmt[1024];
    snprintf(newfmt, 1024, "%s%s\n", label, fmt);
    newfmt[1023] = '\0';

    va_start(ap, fmt);

    vprintf(newfmt, ap);

    va_end(ap);
}



struct rt_context_t {
    rt_allocator alloc;
    rt_reallocator realloc;
    rt_deallocator dealloc;
    rt_message_handler err;
    rt_message_handler warn;
    rt_message_handler info;
};

rt_context
rt_context_new(rt_allocator allocator, rt_reallocator reallocator,
               rt_deallocator deallocator)
{
    rt_context ret;

    if ( ! allocator ) allocator = malloc;
    if ( ! reallocator ) reallocator = realloc;
    if ( ! deallocator ) deallocator = free;

    ret = (rt_context)allocator(sizeof(struct rt_context_t));
    if ( ! ret ) {
        default_error_handler("Out of virtual memory creating an rt_context");
        return 0;
    }
#ifdef RT_API_DEBUG
    default_info_handler("Created rt_context @ %p", ret);
#endif
    ret->alloc = allocator;
    ret->realloc = reallocator;
    ret->dealloc = deallocator;
    ret->err = default_error_handler;
    ret->warn = default_warning_handler;
    ret->info = default_info_handler;
    return ret;
}

void
rt_context_set_message_handlers(rt_context ctx,
        rt_message_handler error_handler,
        rt_message_handler warning_handler,
        rt_message_handler info_handler)
{
    ctx->err = error_handler;
    ctx->warn = warning_handler;
    ctx->info = info_handler;
}

void
rt_context_destroy(rt_context ctx)
{
#ifdef RT_API_DEBUG
    ctx->info("Destroying rt_context @ %p", ctx);
#endif
    ctx->dealloc(ctx);
}

/*- rt_pixeltype -----------------------------------------------------*/

int
rt_pixtype_size(rt_context ctx, rt_pixtype pixtype)
{
    switch (pixtype)
    {
        default:
            ctx->err("Unknown pixeltype %d", pixtype);
            return -1;
        case PT_1BB:
        case PT_2BUI:
        case PT_4BUI:
        case PT_8BSI:
        case PT_8BUI:
            return 1;
        case PT_16BSI:
        case PT_16BUI:
        case PT_16BF:
            return 2;
        case PT_32BSI:
        case PT_32BUI:
        case PT_32BF:
            return 4;
        case PT_64BF:
            return 8;
    }

}

int
rt_pixtype_alignment(rt_context ctx, rt_pixtype pixtype)
{
	return rt_pixtype_size(ctx, pixtype);
}

const char*
rt_pixtype_name(rt_context ctx, rt_pixtype pixtype)
{
    switch (pixtype)
    {
        default:
            ctx->err("Unknown pixeltype %d", pixtype);
            return "Unknown";
        case PT_1BB:
            return "1BB";
        case PT_2BUI:
            return "2BUI";
        case PT_4BUI:
            return "4BUI";
        case PT_8BSI:
            return "8BSI";
        case PT_8BUI:
            return "8BUI";
        case PT_16BSI:
            return "16BSI";
        case PT_16BUI:
            return "16BUI";
        case PT_32BSI:
            return "32BSI";
        case PT_32BUI:
            return "32BUI";
        case PT_16BF:
            return "16BF";
        case PT_32BF:
            return "32BF";
        case PT_64BF:
            return "64BF";
    }
}

/*- rt_band ----------------------------------------------------------*/

struct rt_extband_t {
    uint8_t bandNum;
    char* path; /* externally owned ? */
};

struct rt_band_t {

    rt_pixtype pixtype; 
    int offline;
    uint16_t width; 
    uint16_t height;
    double nodataval; /* int will be converted ... */
    int ownsData;
    union {
        void* mem; /* actual data, externally owned */
        struct rt_extband_t offline;
    } data;

};

rt_band
rt_band_new_inline(rt_context ctx, uint16_t width, uint16_t height,
                   rt_pixtype pixtype, double nodataval, uint8_t* data)
{
    rt_band band;
    band = ctx->alloc(sizeof(struct rt_band_t));
    if ( ! band )
    {
        ctx->err("Out of memory allocating rt_band");
        return 0;
    }

#ifdef RT_API_DEBUG
    ctx->info("Created rt_band @ %p with pixtype %s",
        band, rt_pixtype_name(ctx, pixtype));
#endif

    band->pixtype = pixtype;
    band->offline = 0;
    band->width = width;
    band->height = height;
    band->nodataval = nodataval;
    band->data.mem = data;
    band->ownsData = 0;

    return band;
}

int
rt_band_is_offline(rt_context ctx, rt_band band)
{
    return band->offline;
}

void
rt_band_destroy(rt_context ctx, rt_band band)
{
#ifdef RT_API_DEBUG
    ctx->info("Destroying rt_band @ %p", band);
#endif

    /* band->data content is externally owned */
    ctx->dealloc(band);
}

const char*
rt_band_get_ext_path(rt_context ctx, rt_band band)
{
    if ( ! band->offline ) {
        ctx->warn("rt_band_get_ext_path: non-offline band doesn't have "
                  "an associated path");
        return 0;
    }
    return band->data.offline.path;
}

uint8_t
rt_band_get_ext_band_num(rt_context ctx, rt_band band)
{
    if ( ! band->offline ) {
        ctx->warn("rt_band_get_ext_path: non-offline band doesn't have "
                  "an associated band number");
        return 0;
    }
    return band->data.offline.bandNum;
}

void *
rt_band_get_data(rt_context ctx, rt_band band)
{
    if ( band->offline )
    {
        ctx->warn("rt_band_get_data: "
                  "offline band doesn't have associated data");
        return 0;
    }
    return band->data.mem;
}

rt_pixtype
rt_band_get_pixtype(rt_context ctx, rt_band band)
{
    return band->pixtype;
}

uint16_t
rt_band_get_width(rt_context ctx, rt_band band)
{
    return band->width;
}

uint16_t
rt_band_get_height(rt_context ctx, rt_band band)
{
    return band->height;
}

#ifdef OPTIMIZE_SPACE
/*
 * Set given number of bits of the given byte,
 * starting from given bitOffset (from the first) 
 * to the given value.
 *
 * Examples:
 *   char ch;
 *   ch=0; setBits(&ch, 1, 1, 0) -> ch==8
 *   ch=0; setBits(&ch, 3, 2, 1) -> ch==96 (0x60)
 *
 * Note that number of bits set must be <= 8-bitOffset
 *
 */
static void
setBits(char* ch, double val, int bits, int bitOffset)
{
	char mask = 0xFF >> (8-bits);
    char ival = val;

	assert(8-bitOffset >= bits);

#ifdef RT_API_DEBUG
	printf("ival:%d bits:%d mask:%hhx bitoffset:%d\n",
		ival, bits, mask, bitOffset);
#endif

	/* clear all but significant bits from ival */
	ival &= mask;
#ifdef RT_WARN_ON_TRUNCATION
	if ( ival != val ) {
		ctx->warn("Pixel value for %d-bits band got truncated"
		" from %g to %hhu\n", bits, val, ival);
	}
#endif /* RT_WARN_ON_TRUNCATION */

#ifdef RT_API_DEBUG
	printf(" cleared ival:%hhx\n", ival);
#endif

	/* Shift ival so the significant bits start at
	 * the first bit */
	ival <<= (8-bitOffset-bits);

#ifdef RT_API_DEBUG
	printf(" ival shifted:%hhx\n", ival);
	printf("  ch:%hhx\n", *ch);
#endif

	/* clear first bits of target */
	*ch  &= ~(mask << (8-bits-bitOffset));

#ifdef RT_API_DEBUG
	printf("  ch cleared:%hhx\n", *ch);
#endif

	/* Set the first bit of target */
	*ch |= ival;

#ifdef RT_API_DEBUG
	printf("  ch ored:%hhx\n", *ch);
#endif

}
#endif /* OPTIMIZE_SPACE */

int
rt_band_set_nodata(rt_context ctx, rt_band band, double val)
{
    rt_pixtype pixtype = band->pixtype;

    /* return -1 on out of range */
    switch (pixtype)
    {
        default:
            ctx->err("Unknown pixeltype %d", pixtype);
            return -1;
        case PT_1BB:
        {
            uint8_t v = val;
            v &= 1;
            band->nodataval = v;
            break;
        }
        case PT_2BUI:
        {
            uint8_t v = val;
            v &= 3;
            band->nodataval = v;
            break;
        }
        case PT_4BUI:
        {
            uint8_t v = val;
            v &= 15;
            band->nodataval = v;
            break;
        }
        case PT_8BSI:
        {
            int8_t v = val;
            band->nodataval = v;
            break;
        }

        case PT_8BUI:
        {
            uint8_t v = val;
            band->nodataval = v;
            break;
        }

        case PT_16BSI:
        {
            int16_t v = val;
            band->nodataval = v;
            break;
        }

        case PT_16BUI:
        {
            uint16_t v = val;
            band->nodataval = v;
            break;
        }

        case PT_32BSI:
        {
            int32_t v = val;
            band->nodataval = v;
            break;
        }

        case PT_32BUI:
        {
            uint32_t v = val;
            band->nodataval = v;
            break;
        }

        case PT_16BF:
            ctx->err("16BF pixel type not supported yet");
            return -1;
            break;

        case PT_32BF:
        {
            float v = val;
            band->nodataval = v;
            break;
        }

        case PT_64BF:
            band->nodataval = val;
            break;
    }

    if ( fabs(band->nodataval-val) > 0.0001 )
    {
#ifdef RT_WARN_ON_TRUNCATION
        ctx->warn("Nodata value for %s band got truncated"
                  " from %g to %g",
                  rt_pixtype_name(ctx, pixtype),
                  val, band->nodataval);
#endif
        return -1;
    }

    return 0;
}

int
rt_band_set_pixel(rt_context ctx, rt_band band, uint16_t x, uint16_t y,
        double val)
{
    rt_pixtype pixtype = band->pixtype;
    unsigned char* data;
    uint32_t offset;
    double checkval;

	if ( x >= band->width || y >= band->height )
	{
		ctx->err("Coordinates ouf of range");
		return -1;
	}

    if ( band->offline )
    {
		ctx->err("rt_band_set_pixel not implemented for OFFDB bands");
        return -1;
    }

    data = rt_band_get_data(ctx, band);
	offset = x+(y*band->width); 

    switch (pixtype)
    {
        default:
        {
            ctx->err("Unknown pixeltype %d", pixtype);
            return -1;
        }

        case PT_1BB:
        {
            data[offset] = (int)val&0x01;
            checkval = data[offset];
            break;
        }

        case PT_2BUI:
        {
            data[offset] = (int)val&0x03;
            checkval = data[offset];
            break;
        }

        case PT_4BUI:
        {
            data[offset] = (int)val&0x0F;
            checkval = data[offset];
            break;
        }

        case PT_8BSI:
        {
            data[offset] = (int8_t)val;
            checkval = (int8_t)data[offset];
            break;
        }

        case PT_8BUI:
        {
            data[offset] = val;
            checkval = data[offset];
            break;
        }

        case PT_16BSI:
        {
            int16_t *ptr = (int16_t*)data; /* we assume correct alignment */
            ptr[offset] = val;
            checkval = (int16_t)ptr[offset];
            break;
        }

        case PT_16BUI:
        {
            uint16_t *ptr = (uint16_t*)data; /* we assume correct alignment */
            ptr[offset] = val;
            checkval = ptr[offset];
            break;
        }

        case PT_16BF:
        {
            ctx->err("16BF pixel type not supported yet");
            return -1;
        }

        case PT_32BSI:
        {
            int32_t *ptr = (int32_t*)data; /* we assume correct alignment */
            ptr[offset] = val;
            checkval = (int32_t)ptr[offset];
            break;
        }

        case PT_32BUI:
        {
            uint32_t *ptr = (uint32_t*)data; /* we assume correct alignment */
            ptr[offset] = val;
            checkval = ptr[offset];
            break;
        }

        case PT_32BF:
        {
            float *ptr = (float*)data; /* we assume correct alignment */
            ptr[offset] = val;
            checkval = ptr[offset];
            break;
        }
        
        case PT_64BF:
        {
            double *ptr = (double*)data; /* we assume correct alignment */
            ptr[offset] = val;
            checkval = ptr[offset];
            break;
        }
    }

    /* Overflow checking */
    if ( fabs(checkval-val) > 0.0001 )
    {
#ifdef RT_WARN_ON_TRUNCATION
        ctx->warn("Pixel value for %s band got truncated"
                  " from %g to %u",
                  rt_pixtype_name(ctx, band->pixtype),
                  val, checkval);
#endif /* RT_WARN_ON_TRUNCATION */
        return -1;
    }

    return 0;
}

double
rt_band_get_pixel(rt_context ctx, rt_band band, uint16_t x, uint16_t y)
{
    rt_pixtype pixtype = band->pixtype;
    uint8_t* data;
    uint32_t offset;

	if ( x >= band->width || y >= band->height )
	{
		ctx->err("Coordinates ouf of range");
		return 0;
	}

    if ( band->offline )
    {
		ctx->err("rt_band_get_pixel not implemented for OFFDB bands");
        return 0;
    }

    data = rt_band_get_data(ctx, band);
	offset = x+(y*band->width); /* +1 for the nodata value */

    switch (pixtype)
    {
        default:
        {
            ctx->err("Unknown pixeltype %d", pixtype);
            return 0;
        }

        case PT_1BB:
#ifdef OPTIMIZE_SPACE
        {
            int byteOffset = offset/8;
            int bitOffset = offset%8;
            data += byteOffset;

            /* Bit to set is bitOffset into data */
            return getBits(data, val, 1, bitOffset);
        }
#endif

        case PT_2BUI:
#ifdef OPTIMIZE_SPACE
        {
            int byteOffset = offset/4;
            int bitOffset = offset%4;
            data += byteOffset;

            /* Bits to set start at bitOffset into data */
            return getBits(data, val, 2, bitOffset);
        }
#endif

        case PT_4BUI:
#ifdef OPTIMIZE_SPACE
        {
            int byteOffset = offset/2;
            int bitOffset = offset%2;
            data += byteOffset;

            /* Bits to set start at bitOffset into data */
            return getBits(data, val, 2, bitOffset);
        }
#endif

        case PT_8BSI:
        {
            int8_t val = data[offset];
            return val;
        }

        case PT_8BUI:
        {
            uint8_t val = data[offset];
            return val;
        }

        case PT_16BSI:
        {
            int16_t *ptr = (int16_t*)data; /* we assume correct alignment */
            return ptr[offset];
        }

        case PT_16BUI:
        {
            uint16_t *ptr = (uint16_t*)data; /* we assume correct alignment */
            return ptr[offset];
        }

        case PT_16BF:
        {
            ctx->err("16BF pixel type not supported yet");
            return 0;
        }

        case PT_32BSI:
        {
            int32_t *ptr = (int32_t*)data; /* we assume correct alignment */
            return ptr[offset];
        }

        case PT_32BUI:
        {
            uint32_t *ptr = (uint32_t*)data; /* we assume correct alignment */
            return ptr[offset];
        }

        case PT_32BF:
        {
            float *ptr = (float*)data; /* we assume correct alignment */
            return ptr[offset];
        }
        
        case PT_64BF:
        {
            double *ptr = (double*)data; /* we assume correct alignment */
            return ptr[offset];
        }
    }
}

double
rt_band_get_nodata(rt_context ctx, rt_band band)
{
    return band->nodataval;
}


/*- rt_raster --------------------------------------------------------*/

struct rt_raster_serialized_t {

    /*---[ 8 byte boundary ]---{ */
    uint32_t size;    /* required by postgresql: 4 bytes */
    uint16_t version; /* format version (this is version 0): 2 bytes */
    uint16_t numBands; /* Number of bands: 2 bytes */

    /* }---[ 8 byte boundary ]---{ */
    double scaleX; /* pixel width: 8 bytes */

    /* }---[ 8 byte boundary ]---{ */
    double scaleY; /* pixel height: 8 bytes */

    /* }---[ 8 byte boundary ]---{ */
    double ipX; /* insertion point X: 8 bytes */

    /* }---[ 8 byte boundary ]---{ */
    double ipY; /* insertion point Y: 8 bytes */

    /* }---[ 8 byte boundary ]---{ */
    double skewX;  /* rotation about the X axis: 8 bytes */

    /* }---[ 8 byte boundary ]---{ */
    double skewY;  /* rotation about the Y axis: 8 bytes */

    /* }---[ 8 byte boundary ]--- */
    int32_t srid; /* Spatial reference id: 4 bytes */
    uint16_t width;  /* pixel columns: 2 bytes */
    uint16_t height; /* pixel rows: 2 bytes */
};

/* NOTE: the initial part of this structure matches the layout
 *       of data in the serialized form version 0, starting 
 *       from the numBands element
 */
struct rt_raster_t {

    uint32_t size;
    uint16_t version;

    /* Number of bands, all share the same dimension
     * and georeference */
    uint16_t numBands; 

    /* Georeference (in projection units) */
    double scaleX; /* pixel width */
    double scaleY; /* pixel height */
    double ipX;    /* geo x ordinate of the middle of upper-left pixel */
    double ipY;    /* geo y ordinate of the middle of bottom-right pixel */
    double skewX;  /* rotation about the X axis*/
    double skewY;  /* rotation about the Y axis */

    int32_t srid; /* spatial reference id */
    uint16_t width;  /* pixel columns - max 65535 */
    uint16_t height; /* pixel rows - max 65535 */
    rt_band *bands; /* actual bands */

};

rt_raster
rt_raster_new(rt_context ctx, uint16_t width, uint16_t height)
{
    rt_raster ret;

    assert(ctx);
    assert(ctx->alloc);

    ret = (rt_raster)ctx->alloc(sizeof(struct rt_raster_t));
    if ( ! ret ) {
        ctx->err("Out of virtual memory creating an rt_raster");
        return 0;
    }

#ifdef RT_API_DEBUG
    ctx->info("Created rt_raster @ %p", ret);
#endif

    assert(ret);

    ret->width = width;

    ret->height = width;
    ret->scaleX = 1;
    ret->scaleY = 1;
    ret->ipX = 0.5;
    ret->ipY = 0.5;
    ret->skewX = 0.0;
    ret->skewY = 0.0;
    ret->srid = -1;

    ret->numBands = 0;
    ret->bands = 0;


    return ret;
}

void
rt_raster_destroy(rt_context ctx, rt_raster raster)
{
#ifdef RT_API_DEBUG
    ctx->info("Destroying rt_raster @ %p", raster);
#endif
    if ( raster->bands )
    {
        ctx->dealloc(raster->bands);
    }
    ctx->dealloc(raster);
}

uint16_t
rt_raster_get_width(rt_context ctx, rt_raster raster)
{
    return raster->width;
}

uint16_t
rt_raster_get_height(rt_context ctx, rt_raster raster)
{
    return raster->height;
}

void
rt_raster_set_pixel_sizes(rt_context ctx, rt_raster raster,
                          double scaleX, double scaleY)
{
    raster->scaleX = scaleX;
    raster->scaleY = scaleY;
}


double
rt_raster_get_pixel_width(rt_context ctx, rt_raster raster)
{
    return raster->scaleX;
}

double
rt_raster_get_pixel_height(rt_context ctx, rt_raster raster)
{
    return raster->scaleY;
}

void
rt_raster_set_rotations(rt_context ctx, rt_raster raster,
                          double skewX, double skewY)
{
    raster->skewX = skewX;
    raster->skewY = skewY;
}

double
rt_raster_get_x_rotation(rt_context ctx, rt_raster raster)
{
    return raster->skewX;
}

double
rt_raster_get_y_rotation(rt_context ctx, rt_raster raster)
{
    return raster->skewY;
}

void
rt_raster_set_offsets(rt_context ctx, rt_raster raster, double x, double y)
{
    raster->ipX = x;
    raster->ipY = y;
}

double
rt_raster_get_x_offset(rt_context ctx, rt_raster raster)
{
    return raster->ipX;
}

double
rt_raster_get_y_offset(rt_context ctx, rt_raster raster)
{
    return raster->ipY;
}

int32_t 
rt_raster_get_srid(rt_context ctx, rt_raster raster)
{
    return raster->srid;
}

void
rt_raster_set_srid(rt_context ctx, rt_raster raster, int32_t srid)
{
    raster->srid = srid;
}

int
rt_raster_get_num_bands(rt_context ctx, rt_raster raster)
{
    return raster->numBands;
}

rt_band
rt_raster_get_band(rt_context ctx, rt_raster raster, int n)
{
    if ( n >= raster->numBands ) return 0;
    return raster->bands[n];
}

int32_t
rt_raster_add_band(rt_context ctx, rt_raster raster, rt_band band)
{
    rt_band *oldbands;

#ifdef RT_API_DEBUG
    ctx->info("Adding band %p to raster %p", band, raster);
#endif

    if ( band->width != raster->width )
    {
        ctx->err("Can't add a %dx%d band to a %dx%d raster",
            band->width, band->height, raster->width, raster->height);
        return -1;
    }

    oldbands = raster->bands;

#ifdef RT_API_DEBUG
    ctx->info("Oldbands at %p", oldbands);
#endif

    raster->bands = (rt_band*)ctx->realloc(raster->bands, 
        sizeof(rt_band)*(raster->numBands+1)
    );

#ifdef RT_API_DEBUG
    ctx->info("realloc returned %p", raster->bands);
#endif

    if ( ! raster->bands ) {
        ctx->err("Out of virtual memory "
            "reallocating band pointers");
        raster->bands = oldbands;
        return -1;
    }

    raster->bands[raster->numBands] = band;

    return raster->numBands++;
}

void
rt_raster_cell_to_geopoint(rt_context ctx, rt_raster raster,
        double x, double y,
        double* x1, double* y1)
{
    *x1 = raster->scaleX*x + raster->skewX*y + raster->ipX;
    *y1 = raster->scaleY*y + raster->skewY*x + raster->ipY;

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_cell_to_geopoint(%g,%g)", x, y);
    ctx->info(" ipx/y:%g/%g", raster->ipX, raster->ipY);
    ctx->info("cell_to_geopoint: ipX:%g, ipY:%g, %g,%g -> %g,%g",
        raster->ipX, raster->ipY, x, y, *x1, *y1);
#endif
}

LWPOLY*
rt_raster_get_envelope(rt_context ctx, rt_raster raster)
{
    POINTARRAY **rings;
    POINTARRAY *pts;
    LWPOLY* ret;
    POINT4D p4d;

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_get_envelope: raster is %dx%d",
        raster->width, raster->height);
#endif
    if ( (!raster->width)  || (!raster->height) )
    {
        return 0;
    }
    
    rings = ctx->alloc(sizeof(POINTARRAY*));
    if ( ! rings )
    {
        ctx->err("Out of memory [%s:%d]", __FILE__, __LINE__);
        return 0;
    }
    rings[0] = ptarray_construct(0, 0, 5);
    /* TODO: handle error on ptarray construction */
    pts = rings[0];

    /* Upper-left corner (first and last points) */
    rt_raster_cell_to_geopoint(ctx, raster,
            -0.5, -0.5,
            &p4d.x, &p4d.y);
    setPoint4d(pts, 0, &p4d);
    setPoint4d(pts, 4, &p4d); /* needed for closing it? */

    /* Upper-right corner (we go clockwise) */
    rt_raster_cell_to_geopoint(ctx, raster,
            raster->width-0.5, -0.5,
            &p4d.x, &p4d.y);
    setPoint4d(pts, 1, &p4d);

    /* Lower-right corner */
    rt_raster_cell_to_geopoint(ctx, raster,
            raster->width-0.5, raster->height-0.5,
            &p4d.x, &p4d.y);
    setPoint4d(pts, 2, &p4d);

    /* Lower-left corner */
    rt_raster_cell_to_geopoint(ctx, raster,
            -0.5, raster->height-0.5,
            &p4d.x, &p4d.y);
    setPoint4d(pts, 3, &p4d);

    ret = lwpoly_construct(raster->srid, 0, 1, rings);

    return ret;
}

/*--------- WKB I/O ---------------------------------------------------*/

static uint8_t
isMachineLittleEndian(void)
{
    static int endian_check_int = 1; /* dont modify this!!! */
    /* 0=big endian|xdr --  1=little endian|ndr */
    return *((uint8_t *) &endian_check_int);
}

static uint8_t
read_uint8(const uint8_t** from)
{
    return *(*from)++;
}

static void
write_uint8(uint8_t** from, uint8_t v)
{
    *(*from)++ = v; 
}

static int8_t
read_int8(const uint8_t** from)
{
    return (int8_t)read_uint8(from);
}

static void
write_int8(uint8_t** from, int8_t v)
{
    *(*from)++ = v; 
}

static uint16_t
read_uint16(const uint8_t** from, uint8_t littleEndian)
{
    uint16_t ret;
    if ( littleEndian )
    {
        ret = (*from)[0] |
              (*from)[1] << 8;
    }
    else
    {
        /* big endian */
        ret = (*from)[0] << 8 |
              (*from)[1];
    }
    *from += 2;
    return ret;
}

static void
write_uint16(uint8_t** to, uint8_t littleEndian, uint16_t v)
{
    if ( littleEndian )
    {
        (*to)[0] = v & 0x00FF;
        (*to)[1] = v >> 8;
    }
    else
    {
        (*to)[1] = v & 0x00FF;
        (*to)[0] = v >> 8;
    }
    *to += 2;
}

static int16_t
read_int16(const uint8_t** from, uint8_t littleEndian)
{
    return read_uint16(from, littleEndian);
}

static void
write_int16(uint8_t** to, uint8_t littleEndian, int16_t v)
{
    if ( littleEndian )
    {
        (*to)[0] = v & 0x00FF;
        (*to)[1] = v >> 8;
    }
    else
    {
        (*to)[1] = v & 0x00FF;
        (*to)[0] = v >> 8;
    }
    *to += 2;
}

static uint32_t
read_uint32(const uint8_t** from, uint8_t littleEndian)
{
    uint32_t ret;

    if ( littleEndian )
    {
        ret = (uint32_t) ((*from)[0] & 0xff) |
              (uint32_t) ((*from)[1] & 0xff) << 8 |
              (uint32_t) ((*from)[2] & 0xff) << 16 |
              (uint32_t) ((*from)[3] & 0xff) << 24;
    }
    else
    {
        /* big endian */
        ret = (uint32_t) ((*from)[3] & 0xff) |
              (uint32_t) ((*from)[2] & 0xff) << 8 |
              (uint32_t) ((*from)[1] & 0xff) << 16 |
              (uint32_t) ((*from)[0] & 0xff) << 24;
    }

    *from += 4;
    return ret;
}

static void
write_uint32(uint8_t** to, uint8_t littleEndian, uint32_t v)
{
    if ( littleEndian )
    {
        (*to)[0] = v & 0x000000FF;
        (*to)[1] = ( v & 0x0000FF00 ) >> 8;
        (*to)[2] = ( v & 0x00FF0000 ) >> 16;
        (*to)[3] = ( v & 0xFF000000 ) >> 24;
    }
    else
    {
        (*to)[3] = v & 0x000000FF;
        (*to)[2] = ( v & 0x0000FF00 ) >> 8;
        (*to)[1] = ( v & 0x00FF0000 ) >> 16;
        (*to)[0] = ( v & 0xFF000000 ) >> 24;
    }
    *to += 4;
}

static int32_t
read_int32(const uint8_t** from, uint8_t littleEndian)
{
    return read_uint32(from, littleEndian);
}

static void
write_int32(uint8_t** to, uint8_t littleEndian, int32_t v)
{
    if ( littleEndian )
    {
        (*to)[0] = v & 0x000000FF;
        (*to)[1] = ( v & 0x0000FF00 ) >> 8;
        (*to)[2] = ( v & 0x00FF0000 ) >> 16;
        (*to)[3] = ( v & 0xFF000000 ) >> 24;
    }
    else
    {
        (*to)[3] = v & 0x000000FF;
        (*to)[2] = ( v & 0x0000FF00 ) >> 8;
        (*to)[1] = ( v & 0x00FF0000 ) >> 16;
        (*to)[0] = ( v & 0xFF000000 ) >> 24;
    }
    *to += 4;
}

static float
read_float32(const uint8_t** from, uint8_t littleEndian)
{
    union {
        float f;
        uint32_t i;
    } ret;

    ret.i = read_uint32(from, littleEndian);

    return ret.f;
}

static void
write_float32(uint8_t** from, uint8_t littleEndian, float f)
{
    union {
        float f;
        uint32_t i;
    } u;

    u.f = f;
    write_uint32(from, littleEndian, u.i);
}

static double
read_float64(const uint8_t** from, uint8_t littleEndian)
{
    union {
        double d;
        uint64_t i;
    } ret;

    if ( littleEndian )
    {
        ret.i = (uint64_t) ((*from)[0] & 0xff) |
                (uint64_t) ((*from)[1] & 0xff) << 8 |
                (uint64_t) ((*from)[2] & 0xff) << 16 |
                (uint64_t) ((*from)[3] & 0xff) << 24 |
                (uint64_t) ((*from)[4] & 0xff) << 32 |
                (uint64_t) ((*from)[5] & 0xff) << 40 |
                (uint64_t) ((*from)[6] & 0xff) << 48 |
                (uint64_t) ((*from)[7] & 0xff) << 56;
    }
    else
    {
        /* big endian */
        ret.i = (uint64_t) ((*from)[7] & 0xff) |
                (uint64_t) ((*from)[6] & 0xff) << 8 |
                (uint64_t) ((*from)[5] & 0xff) << 16 |
                (uint64_t) ((*from)[4] & 0xff) << 24 |
                (uint64_t) ((*from)[3] & 0xff) << 32 |
                (uint64_t) ((*from)[2] & 0xff) << 40 |
                (uint64_t) ((*from)[1] & 0xff) << 48 |
                (uint64_t) ((*from)[0] & 0xff) << 56;
    }

    *from += 8;
    return ret.d;
}

static void
write_float64(uint8_t** to, uint8_t littleEndian, double v)
{
    union {
        double d;
        uint64_t i;
    } u;

    u.d = v;

    if ( littleEndian )
    {
        (*to)[0] =   u.i & 0x00000000000000FF;
        (*to)[1] = ( u.i & 0x000000000000FF00 ) >> 8;
        (*to)[2] = ( u.i & 0x0000000000FF0000 ) >> 16;
        (*to)[3] = ( u.i & 0x00000000FF000000 ) >> 24;
        (*to)[4] = ( u.i & 0x000000FF00000000 ) >> 32;
        (*to)[5] = ( u.i & 0x0000FF0000000000 ) >> 40;
        (*to)[6] = ( u.i & 0x00FF000000000000 ) >> 48;
        (*to)[7] = ( u.i & 0xFF00000000000000 ) >> 56;
    }
    else
    {
        (*to)[7] =   u.i & 0x00000000000000FF;
        (*to)[6] = ( u.i & 0x000000000000FF00 ) >> 8;
        (*to)[5] = ( u.i & 0x0000000000FF0000 ) >> 16;
        (*to)[4] = ( u.i & 0x00000000FF000000 ) >> 24;
        (*to)[3] = ( u.i & 0x000000FF00000000 ) >> 32;
        (*to)[2] = ( u.i & 0x0000FF0000000000 ) >> 40;
        (*to)[1] = ( u.i & 0x00FF000000000000 ) >> 48;
        (*to)[0] = ( u.i & 0xFF00000000000000 ) >> 56;
    }
    *to += 8;
}

#define BANDTYPE_FLAGS_MASK 0xF0
#define BANDTYPE_PIXTYPE_MASK 0x0F
#define BANDTYPE_FLAG_OFFDB     (1<<7)
#define BANDTYPE_FLAG_RESERVED1 (1<<6)
#define BANDTYPE_FLAG_RESERVED2 (1<<5)
#define BANDTYPE_FLAG_RESERVED3 (1<<4)

#define BANDTYPE_PIXTYPE(x) ((x)&BANDTYPE_PIXTYPE_MASK)
#define BANDTYPE_IS_OFFDB(x) ((x)&BANDTYPE_FLAG_OFFDB)

/* Read band from WKB as at start of band */
static rt_band
rt_band_from_wkb(rt_context ctx, uint16_t width, uint16_t height,
                 const uint8_t** ptr, const uint8_t* end,
                 uint8_t littleEndian)
{
    rt_band band;
    int pixbytes;
    uint8_t type;
    unsigned long sz;
    uint32_t v;
    
    band = ctx->alloc(sizeof(struct rt_band_t));
    if ( ! band )
    {
        ctx->err("Out of memory allocating rt_band during WKB parsing");
        return 0;
    }

    if ( end - *ptr < 1 ) {
        ctx->err("Premature end of WKB on band reading (%s:%d)",
            __FILE__, __LINE__);
        return 0;
    }
    type = read_uint8(ptr);

    if ( (type&BANDTYPE_PIXTYPE_MASK) >= PT_END )
    {
        ctx->err("Invalid pixtype %d", type&BANDTYPE_PIXTYPE_MASK);
        ctx->dealloc(band);
        return 0;
    }

    band->pixtype = type&BANDTYPE_PIXTYPE_MASK;
    band->offline = BANDTYPE_IS_OFFDB(type) ? 1 : 0;
    band->width = width;
    band->height = height;

#ifdef RT_API_DEBUG
    ctx->info(" Band pixtype:%s, offline:%d",
        rt_pixtype_name(ctx, band->pixtype),
        band->offline);
#endif

    /* Check there's enough bytes to read nodata value */

    pixbytes = rt_pixtype_size(ctx, band->pixtype);
    if (((*ptr)+pixbytes) >= end) {
        ctx->err("Premature end of WKB on band novalue reading");
        ctx->dealloc(band);
        return 0;
    }

    /* Read nodata value */
    switch (band->pixtype)
    {
        default:
        {
            ctx->err("Unknown pixeltype %d", band->pixtype);
            ctx->dealloc(band);
            return 0;
        }

        case PT_1BB:
        {
            band->nodataval = ((int)read_uint8(ptr)) & 0x01;
            break;
        }

        case PT_2BUI:
        {
            band->nodataval = ((int)read_uint8(ptr)) & 0x03;
            break;
        }

        case PT_4BUI:
        {
            band->nodataval = ((int)read_uint8(ptr)) & 0x0F;
            break;
        }

        case PT_8BSI:
        {
            band->nodataval = read_int8(ptr);
            break;
        }

        case PT_8BUI:
        {
            band->nodataval = read_uint8(ptr);
            break;
        }

        case PT_16BSI:
        {
            band->nodataval = read_int16(ptr, littleEndian);
            break;
        }

        case PT_16BUI:
        {
            band->nodataval = read_uint16(ptr, littleEndian);
            break;
        }

        case PT_16BF:
        {
            ctx->err("16BF pixel type not supported yet");
            ctx->dealloc(band);
            return 0;
        }

        case PT_32BSI:
        {
            band->nodataval = read_int32(ptr, littleEndian);
            break;
        }

        case PT_32BUI:
        {
            band->nodataval = read_uint32(ptr, littleEndian);
            break;
        }

        case PT_32BF:
        {
            band->nodataval = read_float32(ptr, littleEndian);
            break;
        }
        
        case PT_64BF:
        {
            band->nodataval = read_float64(ptr, littleEndian);
            break;
        }
    }

#ifdef RT_API_DEBUG
    ctx->info(" Nodata value: %g, pixbytes: %d, ptr @ %p, end @ %p",
        band->nodataval, pixbytes, *ptr, end);
#endif

    if ( band->offline )
    {
        if (((*ptr)+1) >= end) {
            ctx->err("Premature end of WKB on offline "
                     "band data bandNum reading (%s:%d)",
                     __FILE__, __LINE__);
            ctx->dealloc(band);
            return 0;
        }
        band->data.offline.bandNum = read_int8(ptr);

        {
            /* check we have a NULL-termination */
            sz=0;
            while ( (*ptr)[sz] && &((*ptr)[sz]) < end) ++sz;
            if ( &((*ptr)[sz]) >= end )
            {
                ctx->err("Premature end of WKB on band offline path reading");
                ctx->dealloc(band);
                return 0;
            }

            band->ownsData = 1;
        	band->data.offline.path = ctx->alloc(sz+1);
            memcpy(band->data.offline.path, *ptr, sz);
            band->data.offline.path[sz] = '\0';
#ifdef RT_API_DEBUG
            ctx->info("OFFDB band path is %s (size is %d)",
                band->data.offline.path, sz);
#endif
            *ptr += sz+1;
        }
        return band;
    }

    /* This is an on-disk band */
    sz = width*height*pixbytes;
    if (((*ptr)+sz) > end)
    {
        ctx->err("Premature end of WKB on band data reading (%s:%d)",
                 __FILE__, __LINE__);
        ctx->dealloc(band);
        return 0;
    }

    band->data.mem = ctx->alloc(sz);
    if ( ! band->data.mem )
    {
        ctx->err("Out of memory during band creation in WKB parser");
        ctx->dealloc(band);
        return 0;
    }

    memcpy(band->data.mem, *ptr, sz);
    *ptr += sz;

    /* Should now flip values if > 8bit and
     * littleEndian != isMachineLittleEndian */
    if ( pixbytes > 1 )
    {
        if ( isMachineLittleEndian() != littleEndian )
        {{
            void (*flipper)(uint8_t*) = 0;
            uint8_t *flipme;

            if ( pixbytes == 2 )  flipper = flip_endian_16;
            else if ( pixbytes == 4 )  flipper = flip_endian_32;
            else if ( pixbytes == 8 )  flipper = flip_endian_64;
            else {
                ctx->err("Unexpected pix bytes %d", pixbytes);
                ctx->dealloc(band);
                ctx->dealloc(band->data.mem);
                return 0;
            }

            flipme = band->data.mem;
            sz = width*height;
            for (v=0; v<sz; ++v)
            {
                flipper(flipme);
                flipme += pixbytes;
            }
        }}
    }

    /* And should check for invalid values for < 8bit types */
    else if ( band->pixtype == PT_1BB  ||
              band->pixtype == PT_2BUI ||
              band->pixtype == PT_4BUI )
    {{
        uint8_t maxVal = band->pixtype == PT_1BB  ?  1 :
                         band->pixtype == PT_2BUI ?  3 :
                                                    15 ;
        uint8_t val;

        sz = width*height;
        for (v=0; v<sz; ++v)
        {
            val = ((uint8_t*)band->data.mem)[v];
            if ( val > maxVal )
            {
                ctx->err("Invalid value %d for pixel of type %s",
                    val, rt_pixtype_name(ctx, band->pixtype));
                ctx->dealloc(band->data.mem);
                ctx->dealloc(band);
                return 0;
            }
        }
    }}

    return band;
}




/* -4 for size, +1 for endian */
#define RT_WKB_HDR_SZ (sizeof(struct rt_raster_serialized_t)-4+1)

rt_raster
rt_raster_from_wkb(rt_context ctx, const uint8_t* wkb, uint32_t wkbsize)
{
    uint8_t endian;
    const uint8_t *wkbend;
    const uint8_t *ptr = wkb;
    uint16_t version;
    rt_raster rast;
    uint16_t i;

    /* Check that wkbsize is >= sizeof(rt_raster_serialized) */
    if ( wkbsize < RT_WKB_HDR_SZ )
    {
        ctx->err("rt_raster_from_wkb: wkb size < min size (%d)",
            RT_WKB_HDR_SZ);
        return 0;
    }

    wkbend = wkb+wkbsize;
    endian = *ptr; ptr+=1;

    version = read_uint16(&ptr, endian);
    if ( version != 0 )
    {
        ctx->err("rt_raster_from_wkb: WKB version %d unsupported", version);
        return 0;
    }

    rast = (rt_raster)ctx->alloc(sizeof(struct rt_raster_t));
    if ( ! rast )
    {
        ctx->err("Out of memory allocating raster for wkb input");
        return 0;
    }
    rast->numBands = read_uint16(&ptr, endian);
    rast->scaleX = read_float64(&ptr, endian);
    rast->scaleY = read_float64(&ptr, endian);
    rast->ipX = read_float64(&ptr, endian);
    rast->ipY = read_float64(&ptr, endian);
    rast->skewX = read_float64(&ptr, endian);
    rast->skewY = read_float64(&ptr, endian);
    rast->srid = read_int32(&ptr, endian);
    rast->width = read_uint16(&ptr, endian);
    rast->height = read_uint16(&ptr, endian);

    /* consistency checking, should have been checked before */
    assert(ptr <= wkbend);

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_from_wkb: Raster numBands: %d",
        rast->numBands);
    ctx->info("rt_raster_from_wkb: Raster scale: %gx%g",
        rast->scaleX, rast->scaleY);
    ctx->info("rt_raster_from_wkb: Raster ip: %gx%g",
        rast->ipX, rast->ipY);
    ctx->info("rt_raster_from_wkb: Raster skew: %gx%g",
        rast->skewX, rast->skewY );
    ctx->info("rt_raster_from_wkb: Raster srid: %d",
        rast->srid);
    ctx->info("rt_raster_from_wkb: Raster dims: %dx%d",
        rast->width, rast->height);
#endif

    if ( ! rast->numBands )
    {
        /* Here ptr should have been left to right after last used byte */
        if (ptr < wkbend)
        {
            ctx->info("%d bytes of WKB remained unparsed", wkbend-ptr);
        }
        else if ( ptr > wkbend )
        {
            /* Easier to get a segfault before I guess */
            ctx->warn("We parsed %d bytes more then available!",
                ptr-wkbend);
        }
	rast->bands = 0;
        return rast;
    }

    /* Now read the bands */
    rast->bands = (rt_band*)ctx->alloc(sizeof(rt_band)*rast->numBands);
    if ( ! rast->bands )
    {
        ctx->err("Out of memory allocating bands for WKB raster decoding");
        ctx->dealloc(rast);
        return 0;
    }

    /* ptr should now point to start of first band */
    assert ( ptr <= wkbend ); /* we should have checked this before */

    for (i=0; i<rast->numBands; ++i)
    {
#ifdef RT_API_DEBUG
        ctx->info("Parsing band %d from wkb", i);
#endif
        rt_band band = rt_band_from_wkb(ctx, rast->width, rast->height,
                                        &ptr, wkbend, endian);
        if ( ! band )
        {
            ctx->err("Error reading WKB form of band %d", i);
            ctx->dealloc(rast);
            /* TODO: dealloc any previously allocated band too ! */
            return 0;
        }
        rast->bands[i] = band;
    }

    /* Here ptr should have been left to right after last used byte */
    if (ptr < wkbend)
    {
        ctx->info("%d bytes of WKB remained unparsed", wkbend-ptr);
    }
    else if ( ptr > wkbend )
    {
        /* Easier to get a segfault before I guess */
        ctx->warn("We parsed %d bytes more then available!",
            ptr-wkbend);
    }

    return rast;
    
}

rt_raster
rt_raster_from_hexwkb(rt_context ctx, const char* hexwkb,
                      uint32_t hexwkbsize)
{
    uint8_t* wkb;
    uint32_t wkbsize;
    uint32_t i;

    if ( hexwkbsize%2 )
    {
        ctx->err("Raster HEXWKB input must have an even number of characters");
        return 0;
    }
    wkbsize = hexwkbsize/2;

    wkb = ctx->alloc(wkbsize);
    if ( ! wkb )
    {
        ctx->err("Out of memory allocating memory for decoding HEXWKB");
        return 0;
    }

    for (i=0; i<wkbsize; ++i) /* parse full hex */
    {
        wkb[i] = parse_hex((char*)&(hexwkb[i*2]));
    }

    rt_raster r = rt_raster_from_wkb(ctx, wkb, wkbsize);

    ctx->dealloc(wkb); /* as long as rt_raster_from_wkb copies memory */

    return r;
}

static uint32_t
rt_raster_wkb_size(rt_context ctx, rt_raster raster)
{
    uint32_t size = RT_WKB_HDR_SZ;
    uint16_t i;

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_wkb_size: computing size for %d bands",
        raster->numBands);
#endif

    for (i=0; i<raster->numBands; ++i)
    {
        rt_band band = raster->bands[i];
        rt_pixtype pixtype = band->pixtype;
        int pixbytes = rt_pixtype_size(ctx, pixtype);

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_wkb_size: adding size of band %d",
        i);
#endif

        if ( pixbytes < 1 ) {
            ctx->err("Corrupted band: unkonwn pixtype");
            return 0;
        }

        /* Add space for band type */
        size += 1;

        /* Add space for nodata value */
        size += pixbytes;

        if ( band->offline )
        {
            /* Add space for band number */
            size += 1;

            /* Add space for null-terminated path */
            size += strlen(band->data.offline.path)+1;
        }
        else
        {
            /* Add space for actual data */
            size += pixbytes*raster->width*raster->height;
        }

    }

    return size;
}

uint8_t *
rt_raster_to_wkb(rt_context ctx, rt_raster raster, uint32_t *wkbsize)
{
    uint8_t *wkb, *ptr;
    uint16_t i;
    uint8_t littleEndian = isMachineLittleEndian();

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_to_wkb: about to call rt_raster_wkb_size");
#endif
    *wkbsize = rt_raster_wkb_size(ctx, raster);
#ifdef RT_API_DEBUG
    ctx->info("rt_raster_to_wkb: found size:%d", *wkbsize);
#endif

    wkb = (uint8_t*)ctx->alloc(*wkbsize);
    if ( ! wkb )
    {
        ctx->err("Out of memory allocating WKB for raster");
        return 0;
    }

    ptr = wkb;

    /* Write endianness */
    *ptr = littleEndian; ptr+=1;

    /* Write version */
    write_uint16(&ptr, littleEndian, 0);

    /* Copy header (from numBands up) */
    memcpy(ptr, &(raster->numBands), sizeof(struct rt_raster_serialized_t)-6);
    ptr += sizeof(struct rt_raster_serialized_t)-6;

    /* Serialize bands now */
    for (i=0; i<raster->numBands; ++i)
    {
        rt_band band = raster->bands[i];
        rt_pixtype pixtype = band->pixtype;
        int pixbytes = rt_pixtype_size(ctx, pixtype);

#ifdef RT_API_DEBUG
        ctx->info("Writing WKB for band %d", i);
#endif

        if ( pixbytes < 1 ) {
            ctx->err("Corrupted band: unkonwn pixtype");
            return 0;
        }

        /* Add band type */
        *ptr = band->pixtype;
        if ( band->offline ) *ptr |= BANDTYPE_FLAG_OFFDB;
        ptr += 1;

#if 0 // no padding required for WKB
        /* Add padding (if needed) */
        if ( pixbytes > 1 ) {
            memset(ptr, '\0', pixbytes-1);
            ptr += pixbytes-1;
        }
        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! (((uint64_t)ptr)%pixbytes) );
#endif

        /* Add nodata value */
        switch (pixtype)
        {
            default:
                abort(); /* shoudn't happen */
                return 0;
            case PT_1BB:
            case PT_2BUI:
            case PT_4BUI:
            case PT_8BUI:
            {
                uint8_t v = band->nodataval;
                *ptr = v; ptr+=1;
                break;
            }
            case PT_8BSI:
            {
                int8_t v = band->nodataval;
                *ptr = v; ptr+=1;
                break;
            }


            case PT_16BSI:
            case PT_16BUI:
            {
                uint16_t v = band->nodataval;
                memcpy(ptr, &v, 2); ptr+=2;
                break;
            }

            case PT_32BSI:
            case PT_32BUI:
            {
                uint32_t v = band->nodataval;
                memcpy(ptr, &v, 4); ptr+=4;
                break;
            }

            case PT_16BF:
                ctx->err("16BF pixel type not supported yet");
                break;

            case PT_32BF:
            {
                float v = band->nodataval;
                memcpy(ptr, &v, 4); ptr+=4;
                break;
            }

            case PT_64BF:
            {
                memcpy(ptr, &band->nodataval, 8); ptr+=8;
                break;
            }
        }

#if 0 // no padding for WKB
        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! ((uint64_t)ptr%pixbytes) );
#endif

        if ( band->offline )
        {
            /* Write band number */
            *ptr = band->data.offline.bandNum; ptr += 1;

            /* Write path */
            strcpy((char*)ptr, band->data.offline.path);
            ptr += strlen(band->data.offline.path)+1;
        }
        else
        {
            /* Write data */
            {
                uint32_t datasize = raster->width*raster->height*pixbytes;
                memcpy(ptr, band->data.mem, datasize);
                ptr += datasize;
            }
        }

#if 0 // no padding for WKB
        /* Pad up to 8-bytes boundary */
        while ( (uint64_t)ptr%8 ) {
            *ptr = 0; ++ptr;
        }

        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! ((uint64_t)ptr%pixbytes) );
#endif
    }

    return wkb;
}

char *
rt_raster_to_hexwkb(rt_context ctx, rt_raster raster, uint32_t *hexwkbsize)
{
    uint8_t *wkb;
    char* hexwkb;
    uint32_t i;
    uint32_t wkbsize;

#ifdef RT_API_DEBUG
    ctx->info("rt_raster_to_hexwkb: calling rt_raster_to_wkb");
#endif
    wkb = rt_raster_to_wkb(ctx, raster, &wkbsize);
#ifdef RT_API_DEBUG
    ctx->info("rt_raster_to_hexwkb: rt_raster_to_wkb returned");
#endif

    *hexwkbsize = wkbsize*2; /* hex is 2 times bytes */
    hexwkb = (char*)ctx->alloc( (*hexwkbsize)+1);
    if ( ! hexwkb )
    {
        ctx->dealloc(wkb);
        ctx->err("Out of memory hexifying raster WKB");
        return 0;
    }
    hexwkb[*hexwkbsize] = '\0'; /* Null-terminate */

    for (i=0; i<wkbsize; i++)
    {
        deparse_hex(wkb[i], &(hexwkb[2*i]));
    }

    ctx->dealloc(wkb); /* we don't need this anymore */

    return hexwkb;
}


/*--------- Serializer/Deserializer --------------------------------------*/

static uint32_t
rt_raster_serialized_size(rt_context ctx, rt_raster raster)
{
    uint32_t size = sizeof(struct rt_raster_serialized_t);
    uint16_t i;

    printf("Serialized size with just header:%d - now adding size of %d bands\n",
	size, raster->numBands);

    for (i=0; i<raster->numBands; ++i)
    {
        rt_band band = raster->bands[i];
        rt_pixtype pixtype = band->pixtype;
        int pixbytes = rt_pixtype_size(ctx, pixtype);

        if ( pixbytes < 1 ) {
            ctx->err("Corrupted band: unkonwn pixtype");
            return 0;
        }

        /* Add space for band type and data padding */
        size += pixbytes;

        /* Add space for nodata value */
        size += pixbytes;

        if ( band->offline )
        {
            /* Add space for band number */
            size += 1;

            /* Add space for null-terminated path */
            size += strlen(band->data.offline.path)+1;
        }
        else
        {
            /* Add space for nodata value */
            size += pixbytes*raster->width*raster->height;
        }

	printf("Size before alignment is %d\n", size);
        /* Align size to 8-bytes boundary (trailing padding) */
        size += 8 - (size%8);
	printf("Size after alignment is %d\n", size);
    }

    return size;
}

void*
rt_raster_serialize(rt_context ctx, rt_raster raster)
{
    uint32_t size = rt_raster_serialized_size(ctx, raster);
    uint8_t* ret;
    uint8_t* ptr;
    uint16_t i;

    ret = (uint8_t*)ctx->alloc(size);
    if ( ! ret )
    {
        ctx->err("Out of memory allocating %d bytes for serializing a raster",
                 size);
        return 0;
    }

    ptr = ret;

	printf("sizeof(struct rt_raster_serialized_t):%ld\n",
		sizeof(struct rt_raster_serialized_t));
	printf("sizeof(struct rt_raster_t):%ld\n",
		sizeof(struct rt_raster_t));
	printf("serialized size:%lu\n", (long unsigned)size);

    /* Set size */
    raster->size = size;

    /* Set version */
    raster->version = 0;

    /* Copy header */
    memcpy(ptr, raster, sizeof(struct rt_raster_serialized_t));
    ptr += sizeof(struct rt_raster_serialized_t);

    /* Serialize bands now */
    for (i=0; i<raster->numBands; ++i)
    {
        rt_band band = raster->bands[i];
        rt_pixtype pixtype = band->pixtype;
        int pixbytes = rt_pixtype_size(ctx, pixtype);

        if ( pixbytes < 1 ) {
            ctx->err("Corrupted band: unkonwn pixtype");
            return 0;
        }

        /* Add band type */
        *ptr = band->pixtype;
        if ( band->offline ) *ptr |= BANDTYPE_FLAG_OFFDB;
        ptr += 1;

        /* Add padding (if needed) */
        if ( pixbytes > 1 ) {
            memset(ptr, '\0', pixbytes-1);
            ptr += pixbytes-1;
        }

        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! (((uint64_t)ptr)%pixbytes) );

        /* Add nodata value */
        switch (pixtype)
        {
            default:
                abort(); /* shoudn't happen */
                return 0;
            case PT_1BB:
            case PT_2BUI:
            case PT_4BUI:
            case PT_8BUI:
            {
                uint8_t v = band->nodataval;
                *ptr = v; ptr+=1;
                break;
            }
            case PT_8BSI:
            {
                int8_t v = band->nodataval;
                *ptr = v; ptr+=1;
                break;
            }


            case PT_16BSI:
            case PT_16BUI:
            {
                uint16_t v = band->nodataval;
                memcpy(ptr, &v, 2); ptr+=2;
                break;
            }

            case PT_32BSI:
            case PT_32BUI:
            {
                uint32_t v = band->nodataval;
                memcpy(ptr, &v, 4); ptr+=4;
                break;
            }

            case PT_16BF:
                ctx->err("16BF pixel type not supported yet");
                break;

            case PT_32BF:
            {
                float v = band->nodataval;
                memcpy(ptr, &v, 4); ptr+=4;
                break;
            }

            case PT_64BF:
            {
                memcpy(ptr, &band->nodataval, 8); ptr+=8;
                break;
            }
        }

        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! ((uint64_t)ptr%pixbytes) );

        if ( band->offline )
        {
            /* Write band number */
            *ptr = band->data.offline.bandNum; ptr += 1;

            /* Write path */
            strcpy((char*)ptr, band->data.offline.path);
            ptr += strlen(band->data.offline.path)+1;
        }
        else
        {
            /* Write data */
            {
                uint32_t datasize = raster->width*raster->height*pixbytes;
                memcpy(ptr, band->data.mem, datasize);
                ptr += datasize;
            }
        }

        /* Pad up to 8-bytes boundary */
        while ( (uint64_t)ptr%8 ) {
            *ptr = 0; ++ptr;
        }

        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! ((uint64_t)ptr%pixbytes) );
    }

    return ret;
}

rt_raster
rt_raster_deserialize(rt_context ctx, void* serialized)
{
    rt_raster rast;
    uint16_t i;
    const uint8_t *ptr;
    uint8_t littleEndian = isMachineLittleEndian();

    rast = (rt_raster)ctx->alloc(sizeof(struct rt_raster_t));
    if ( ! rast )
    {
        ctx->err("Out of memory allocating raster for deserialization");
        return 0;
    }

    memcpy(rast, serialized, sizeof(struct rt_raster_serialized_t));

    if ( ! rast->numBands )
    {
        rast->bands = 0;
        return rast;
    }

    rast->bands = ctx->alloc(rast->numBands*sizeof(rt_band));

    /* Deserialize bands now */
    ptr = (uint8_t*)serialized+sizeof(struct rt_raster_serialized_t);
    for (i=0; i<rast->numBands; ++i)
    {
        rt_band band;
        uint8_t type;
        int pixbytes;

        band = ctx->alloc(sizeof(struct rt_band_t));
        if ( ! band )
        {
            ctx->err("Out of memory allocating rt_band during deserialization");
            return 0;
        }

        rast->bands[i] = band;

        type = *ptr; ptr++;
        band->pixtype = type&BANDTYPE_PIXTYPE_MASK;
        band->offline = BANDTYPE_IS_OFFDB(type) ? 1 : 0;
        band->width = rast->width;
        band->height = rast->height;
        band->ownsData = 0;

        /* Advance by data padding */
        pixbytes = rt_pixtype_size(ctx, band->pixtype);
        ptr += pixbytes-1;

        /* Read nodata value */
        switch (band->pixtype)
        {
            default:
            {
                ctx->err("Unknown pixeltype %d", band->pixtype);
                ctx->dealloc(band);
                ctx->dealloc(rast);
                return 0;
            }

            case PT_1BB:
            {
                band->nodataval = ((int)read_uint8(&ptr)) & 0x01;
                break;
            }

            case PT_2BUI:
            {
                band->nodataval = ((int)read_uint8(&ptr)) & 0x03;
                break;
            }

            case PT_4BUI:
            {
                band->nodataval = ((int)read_uint8(&ptr)) & 0x0F;
                break;
            }

            case PT_8BSI:
            {
                band->nodataval = read_int8(&ptr);
                break;
            }

            case PT_8BUI:
            {
                band->nodataval = read_uint8(&ptr);
                break;
            }

            case PT_16BSI:
            {
                band->nodataval = read_int16(&ptr, littleEndian);
                break;
            }

            case PT_16BUI:
            {
                band->nodataval = read_uint16(&ptr, littleEndian);
                break;
            }

            case PT_16BF:
            {
                ctx->err("16BF pixel type not supported yet");
                ctx->dealloc(band);
                return 0;
            }

            case PT_32BSI:
            {
                band->nodataval = read_int32(&ptr, littleEndian);
                break;
            }

            case PT_32BUI:
            {
                band->nodataval = read_uint32(&ptr, littleEndian);
                break;
            }

            case PT_32BF:
            {
                band->nodataval = read_float32(&ptr, littleEndian);
                break;
            }
            
            case PT_64BF:
            {
                band->nodataval = read_float64(&ptr, littleEndian);
                break;
            }
        }

        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! (((uint64_t)ptr)%pixbytes) );

        if ( band->offline )
        {
            /* Read band number */
            band->data.offline.bandNum = *ptr; ptr += 1;

            /* Register path */
            band->data.offline.path = (char*)ptr;
            ptr += strlen(band->data.offline.path)+1;
        }
        else
        {
            /* Register data */
            {
                uint32_t datasize = rast->width*rast->height*pixbytes;
                band->data.mem = (uint8_t*)ptr;
                ptr += datasize;
            }
        }

        /* Pad up to 8-bytes boundary */
        ptr += ( 8 - ((uint64_t)ptr%8) );

        /* Consistency checking (ptr is pixbytes-aligned) */
        assert(! ((uint64_t)ptr%pixbytes) );
    }

    return rast;
}

