/*
 * 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 <float.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>

#include <postgres.h> /* for palloc */
#include <access/gist.h>
#include <access/itup.h>
#include <fmgr.h>
#include <utils/elog.h>
#include <funcapi.h>

/*#include "lwgeom_pg.h"*/
#include "liblwgeom.h"
#include "rt_pg.h"
#include "pgsql_compat.h"
#include "rt_api.h"
#include "../wktraster_config.h"

/* Define this to debug pgsql RASTER activity */
/* Alternative, use ./configure --enable-rtpg-debug */
/*#define RT_PG_DEBUG 1*/

/* Define this to debug pgsql RASTER memory activity */
/* Alternative, use ./configure --enable-rtpgmem-debug */
/*#define RT_PG_DEBUG_MEM 1*/

/*
 * This is required for builds against pgsql 8.2
 */
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* Internal funcs */
static rt_context get_rt_context(FunctionCallInfoData *fcinfo);
static void *rt_pgalloc(size_t size);
static void *rt_pgrealloc(void *mem, size_t size);
static void rt_pgfree(void *mem);


/* Prototypes */
Datum RASTER_lib_version(PG_FUNCTION_ARGS);
Datum RASTER_lib_build_date(PG_FUNCTION_ARGS);
Datum RASTER_in(PG_FUNCTION_ARGS);
Datum RASTER_out(PG_FUNCTION_ARGS);

Datum RASTER_to_bytea(PG_FUNCTION_ARGS);

Datum RASTER_to_BOX2DFLOAT4(PG_FUNCTION_ARGS);
Datum RASTER_envelope(PG_FUNCTION_ARGS);
Datum RASTER_makeEmpty(PG_FUNCTION_ARGS);

Datum RASTER_to_LWGEOM(PG_FUNCTION_ARGS);
Datum RASTER_getFactor(PG_FUNCTION_ARGS);
Datum RASTER_setFactor(PG_FUNCTION_ARGS);
Datum RASTER_setSRID(PG_FUNCTION_ARGS);
/* Get all the properties of a raster */
Datum RASTER_getSRID(PG_FUNCTION_ARGS);
Datum RASTER_getWidth(PG_FUNCTION_ARGS);
Datum RASTER_getHeight(PG_FUNCTION_ARGS);
Datum RASTER_getNumBands(PG_FUNCTION_ARGS);
Datum RASTER_getXPixelSize(PG_FUNCTION_ARGS);
Datum RASTER_getYPixelSize(PG_FUNCTION_ARGS);
Datum RASTER_getXRotation(PG_FUNCTION_ARGS);
Datum RASTER_getYRotation(PG_FUNCTION_ARGS);
Datum RASTER_getXUpperLeft(PG_FUNCTION_ARGS);
Datum RASTER_getYUpperLeft(PG_FUNCTION_ARGS);
Datum RASTER_getGeoReference(PG_FUNCTION_ARGS);
Datum RASTER_getBandPixelType(PG_FUNCTION_ARGS);
Datum RASTER_getBandPixelTypeName(PG_FUNCTION_ARGS);
Datum RASTER_getBandNoDataValue(PG_FUNCTION_ARGS);
Datum RASTER_getBandPath(PG_FUNCTION_ARGS);
Datum RASTER_getPixelValue(PG_FUNCTION_ARGS);
Datum RASTER_setPixelValue(PG_FUNCTION_ARGS);

Datum RASTER_send(PG_FUNCTION_ARGS);
Datum RASTER_dump(PG_FUNCTION_ARGS);
Datum RASTER_construct(PG_FUNCTION_ARGS);
Datum RASTER_getpixel(PG_FUNCTION_ARGS);
Datum RASTER_setpixel(PG_FUNCTION_ARGS);
Datum RASTER_draw(PG_FUNCTION_ARGS);
Datum RASTER_fill(PG_FUNCTION_ARGS);

static void *
rt_pgalloc(size_t size)
{
    void* ret;
#ifdef RT_PG_DEBUG_MEM
    elog(NOTICE, "rt_pgalloc(%ld) called", size);
#endif
    ret = palloc(size);
#ifdef RT_PG_DEBUG_MEM
    elog(NOTICE, "palloc(%ld) returned", size);
#endif
    return ret;
}

static void *
rt_pgrealloc(void *mem, size_t size)
{
    void* ret;
#ifdef RT_PG_DEBUG_MEM
    elog(NOTICE, "rt_pgrealloc(%p, %ld) called", mem, size);
#endif
    if ( mem )
    {
#ifdef RT_PG_DEBUG_MEM
        elog(NOTICE, "rt_pgrealloc calling repalloc(%p, %ld)", mem, size);
#endif
        ret = repalloc(mem, size);
    }
    else
    {
#ifdef RT_PG_DEBUG_MEM
        elog(NOTICE, "rt_pgrealloc calling palloc(%ld)", size);
#endif
        ret = palloc(size);
    }
    return ret;
}

static void
rt_pgfree(void *mem)
{
#ifdef RT_PG_DEBUG_MEM
    elog(NOTICE, "rt_pgfree(%p) calling pfree", mem);
#endif
    pfree(mem);
}

static void
rt_pgerr(const char *fmt, ...)
{
    va_list ap;
    char msg[1024];

    va_start(ap, fmt);

    vsnprintf(msg, 1023, fmt, ap);

    elog(ERROR, "%s", msg);

    va_end(ap);
}

static void
rt_pgwarn(const char *fmt, ...)
{
    va_list ap;
    char msg[1024];

    va_start(ap, fmt);

    vsnprintf(msg, 1023, fmt, ap);

    elog(WARNING, "%s", msg);

    va_end(ap);
}

static void
rt_pginfo(const char *fmt, ...)
{
    va_list ap;
    char msg[1024];

    va_start(ap, fmt);

    vsnprintf(msg, 1023, fmt, ap);

    elog(INFO, "%s", msg);

    va_end(ap);
}


static rt_context
get_rt_context(FunctionCallInfoData *fcinfo)
{
    rt_context ctx = 0;
    MemoryContext old_context;

    if ( ctx ) return ctx;

    /* We switch memory context info so the rt_context
     * is not freed by the end of function call
     * TODO: check _which_ context we should be using
     * for a whole-plugin-lifetime persistence */
    old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
    ctx = rt_context_new(rt_pgalloc, rt_pgrealloc, rt_pgfree);
    MemoryContextSwitchTo(old_context);

    rt_context_set_message_handlers(ctx, rt_pgerr, rt_pgwarn, rt_pginfo);

    return ctx;
}

PG_FUNCTION_INFO_V1(RASTER_lib_version);
Datum RASTER_lib_version(PG_FUNCTION_ARGS)
{
    char *ver = RT_LIB_VERSION;
    text *result;
    result = palloc(VARHDRSZ  + strlen(ver));
    SET_VARSIZE(result, VARHDRSZ + strlen(ver));
    memcpy(VARDATA(result), ver, strlen(ver));
    PG_RETURN_POINTER(result);
}

PG_FUNCTION_INFO_V1(RASTER_lib_build_date);
Datum RASTER_lib_build_date(PG_FUNCTION_ARGS)
{
    char *ver = RT_BUILD_DATE;
    text *result;
    result = palloc(VARHDRSZ  + strlen(ver));
    SET_VARSIZE(result, VARHDRSZ + strlen(ver));
    memcpy(VARDATA(result), ver, strlen(ver));
    PG_RETURN_POINTER(result);
}

/*
 * Input is a string with hex chars in it.
 * Convert to binary and put in the result
 */
PG_FUNCTION_INFO_V1(RASTER_in);
Datum RASTER_in(PG_FUNCTION_ARGS)
{
    rt_raster raster;
    char *hexwkb = PG_GETARG_CSTRING(0);
    void *result;
    rt_context ctx = get_rt_context(fcinfo);

    raster = rt_raster_from_hexwkb(ctx, hexwkb, strlen(hexwkb));
    result = rt_raster_serialize(ctx, raster);

    SET_VARSIZE(result, ((rt_pgraster*)result)->size);

    if ( result ) PG_RETURN_POINTER(result);
    else PG_RETURN_NULL();
}

/*given a RASTER structure, convert it to Hex and put it in a string */
PG_FUNCTION_INFO_V1(RASTER_out);
Datum RASTER_out(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    uint32_t hexwkbsize;
    char *hexwkb;
    rt_context ctx = get_rt_context(fcinfo);

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    /*elog(NOTICE, "rt_raster_deserialize returned %p", raster);*/

    hexwkb = rt_raster_to_hexwkb(ctx, raster, &hexwkbsize);
    if ( ! hexwkb )
    {
        elog(ERROR, "Could not HEX-WKBize raster");
        PG_RETURN_NULL();
    }

    /*elog(NOTICE, "rt_raster_to_hexwkb returned");*/

    PG_RETURN_CSTRING(hexwkb);
}

/*
 * Return bytea object with raster in Well-Known-Binary form.
 */
PG_FUNCTION_INFO_V1(RASTER_to_bytea);
Datum RASTER_to_bytea(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    uint8_t *wkb = NULL;
    uint32_t wkb_size = 0;
    bytea *result = NULL;
    int result_size = 0;

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }
    /*elog(NOTICE, "rt_raster_deserialize returned %p", raster);*/

    /* Uses context allocator */
    wkb = rt_raster_to_wkb(ctx, raster, &wkb_size);
    if ( ! wkb ) {
        elog(ERROR, "Could not allocate and generate WKB data");
        PG_RETURN_NULL();
    }

    /* TODO: Replace all palloc/pfree, malloc/free, etc. with rt_context_t->{alloc/dealloc}
     * FIXME: ATM, impossible as rt_context_t is incomplete type for rt_pg layer. */
    result_size = wkb_size + VARHDRSZ;
    result = (bytea *)palloc(result_size);
    SET_VARSIZE(result, result_size);
    memcpy(VARDATA(result), wkb, VARSIZE(result) - VARHDRSZ);
    pfree(wkb);

    PG_RETURN_POINTER(result);
}

/**
 * Return the envelope of this raster as a 4-vertices POLYGON.
 */
PG_FUNCTION_INFO_V1(RASTER_envelope);
Datum RASTER_envelope(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    LWPOLY* envelope;
    uchar* pglwgeom;

    { /* TODO: can be optimized to only detoast the header! */
        raster = rt_raster_deserialize(ctx, pgraster);
        if ( ! raster ) {
            elog(ERROR, "Could not deserialize raster");
            PG_RETURN_NULL();
        }

        /*elog(NOTICE, "rt_raster_deserialize returned %p", raster);*/

        envelope = rt_raster_get_envelope(ctx, raster);
        if ( ! envelope ) {
            elog(ERROR, "Could not get raster's envelope");
            PG_RETURN_NULL();
        }
    }

    {
        size_t sz = lwpoly_serialize_size(envelope);
        pglwgeom = palloc(VARHDRSZ+sz);
        lwpoly_serialize_buf(envelope, (uchar*)VARDATA(pglwgeom), &sz);
        SET_VARSIZE(pglwgeom, VARHDRSZ+sz);
    }

    /* PG_FREE_IF_COPY(pgraster, 0); */
    /* lwfree(envelope) */
    /* ... more deallocs ... */


    PG_RETURN_POINTER(pglwgeom);
}

/**
 * Return the bounding box of this raster as a BOX2D.
 */
PG_FUNCTION_INFO_V1(RASTER_to_BOX2DFLOAT4);
Datum RASTER_to_BOX2DFLOAT4(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    LWPOLY* envelope;
    BOX2DFLOAT4 *bbox = (BOX2DFLOAT4 *) palloc(sizeof(BOX2DFLOAT4));

    { /* TODO: can be optimized to only detoast the header! */
        raster = rt_raster_deserialize(ctx, pgraster);
        if ( ! raster ) {
            elog(ERROR, "Could not deserialize raster");
            PG_RETURN_NULL();
        }

        /*elog(NOTICE, "rt_raster_deserialize returned %p", raster);*/

        envelope = rt_raster_get_envelope(ctx, raster);
        if ( ! envelope ) {
            elog(ERROR, "Could not get raster's envelope");
            PG_RETURN_NULL();
        }
    }

    if ( ! lwpoly_compute_box2d_p(envelope, bbox) )
    {
        elog(ERROR, "Empty raster's envelope!");
        PG_RETURN_NULL();
    }

    PG_RETURN_POINTER(bbox);
}

/**
 * rt_MakeEmptyRaster( <width>, <height>, <ipx>, <ipy>,
 *                                        <scalex>, <scaley>,
 *                                        <skewx>, <skewy>,
 *                                        <srid>)
 */
PG_FUNCTION_INFO_V1(RASTER_makeEmpty);
Datum RASTER_makeEmpty(PG_FUNCTION_ARGS)
{
    uint16 width, height;
    double ipx, ipy, scalex, scaley, skewx, skewy;
    int32_t srid;
    rt_pgraster *pgraster;
    rt_raster raster;
    rt_context ctx;

    if ( PG_NARGS() < 9 )
    {
        elog(ERROR, "MakeEmptyRaster requires 9 args");
        PG_RETURN_NULL();
    }

    width = PG_GETARG_UINT16(0);
    height = PG_GETARG_UINT16(1);
    ipx = PG_GETARG_FLOAT8(2);
    ipy = PG_GETARG_FLOAT8(3);
    scalex = PG_GETARG_FLOAT8(4);
    scaley = PG_GETARG_FLOAT8(5);
    skewx = PG_GETARG_FLOAT8(6);
    skewy = PG_GETARG_FLOAT8(7);
    srid = PG_GETARG_INT32(8);

#ifdef RT_PG_DEBUG
    elog(NOTICE, "%dx%d, ip:%g,%g, scale:%g,%g, skew:%g,%g srid:%d",
                  width, height, ipx, ipy, scalex, scaley,
                  skewx, skewy, srid);
#endif

    ctx = get_rt_context(fcinfo);

    raster = rt_raster_new(ctx, width, height);
    if ( ! raster ) {
        PG_RETURN_NULL(); /* error was supposedly printed already */
    }

    rt_raster_set_pixel_sizes(ctx, raster, scalex, scaley);
    rt_raster_set_offsets(ctx, raster, ipx, ipy);
    rt_raster_set_rotations(ctx, raster, skewx, skewy);
    rt_raster_set_srid(ctx, raster, srid);

    pgraster = rt_raster_serialize(ctx, raster);
    if ( ! pgraster ) PG_RETURN_NULL();

    SET_VARSIZE(pgraster, pgraster->size);
    PG_RETURN_POINTER(pgraster);
}

/**
 * Return the SRID associated with the raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getSRID);
Datum RASTER_getSRID(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    int32_t srid;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    srid = rt_raster_get_srid(ctx, raster);
    PG_RETURN_INT32(srid);
}

/**
 * Return the width of the raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getWidth);
Datum RASTER_getWidth(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    uint16_t width;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    width = rt_raster_get_width(ctx, raster);
    PG_RETURN_INT32(width);
}

/**
 * Return the height of the raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getHeight);
Datum RASTER_getHeight(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    uint16_t height;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    height = rt_raster_get_height(ctx, raster);
    PG_RETURN_INT32(height);
}

/**
 * Return the number of bands included in the raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getNumBands);
Datum RASTER_getNumBands(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    int32_t num_bands;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    num_bands = rt_raster_get_num_bands(ctx, raster);
    PG_RETURN_INT32(num_bands);
}

/**
 * Return X size of pixel from georeference of the raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getXPixelSize);
Datum RASTER_getXPixelSize(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    double xsize;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    xsize = rt_raster_get_pixel_width(ctx, raster);
    PG_RETURN_FLOAT8(xsize);
}

/**
 * Return Y size of pixel from georeference of the raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getYPixelSize);
Datum RASTER_getYPixelSize(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    double ysize;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    ysize = rt_raster_get_pixel_height(ctx, raster);
    PG_RETURN_FLOAT8(ysize);
}

/**
 * Return value of the raster rotation about the X axis.
 */
PG_FUNCTION_INFO_V1(RASTER_getXRotation);
Datum RASTER_getXRotation(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    double xrotation;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    xrotation = rt_raster_get_x_rotation(ctx, raster);
    PG_RETURN_FLOAT8(xrotation);
}

/**
 * Return value of the raster rotation about the Y axis.
 */
PG_FUNCTION_INFO_V1(RASTER_getYRotation);
Datum RASTER_getYRotation(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    double yrotation;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    yrotation = rt_raster_get_y_rotation(ctx, raster);
    PG_RETURN_FLOAT8(yrotation);
}

/**
 * Return
 */
PG_FUNCTION_INFO_V1(RASTER_getXUpperLeft);
Datum RASTER_getXUpperLeft(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    double xul;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    xul = rt_raster_get_x_offset(ctx, raster);
    PG_RETURN_FLOAT8(xul);
}

/**
 * Return
 */
PG_FUNCTION_INFO_V1(RASTER_getYUpperLeft);
Datum RASTER_getYUpperLeft(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    double yul;

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    yul = rt_raster_get_y_offset(ctx, raster);
    PG_RETURN_FLOAT8(yul);
}

/**
 * Return the georeference of the raster as a string representing
 * the 6 doubles of an equivalent world file (including the carriage return).
 */
PG_FUNCTION_INFO_V1(RASTER_getGeoReference);
Datum RASTER_getGeoReference(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    rt_raster raster;
    rt_context ctx = get_rt_context(fcinfo);
    text *result = NULL;
    size_t text_size = 0;
    const size_t buf_size = 6 * 32 + 128; /* modest buffer size for 6 factors */
    char buf[buf_size];
    double xdim, ydim;  /* line 1 and 4 */
    double xrot, yrot;  /* line 2 and 3 */
    double xul, yul;    /* line 5 and 6 */

    memset(buf, 0, buf_size);

    /* TODO: can be optimized to only detoast the header! */
    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    xdim = rt_raster_get_pixel_width(ctx, raster);
    xrot = rt_raster_get_x_rotation(ctx, raster);
    yrot = rt_raster_get_y_rotation(ctx, raster);
    ydim = rt_raster_get_pixel_height(ctx, raster);
    xul = rt_raster_get_x_offset(ctx, raster);
    yul = rt_raster_get_y_offset(ctx, raster);

    /* TODO:
     * 1) Do we want to stick to WorldFile spec and output y-dim as negative?
     *    Now, we output what we store.
     * 2) Do we need to 'rotate' x-/y-coorindate of upper left pixel?
     */
    assert('\0' == buf[0] && '\0' == buf[buf_size - 1]);
    snprintf(buf, buf_size, "%.10f\n%.10f\n%.10f\n%.10f\n%.10f\n%.10f\n",
             xdim, xrot, yrot, ydim, xul, yul);
    buf[buf_size - 1] = '\0';

    text_size = strlen(buf);
    result = palloc(VARHDRSZ + text_size);
    if ( ! result ) {
        elog(ERROR, "Could not allocate memory for output text object");
        PG_RETURN_NULL();
    }

    SET_VARSIZE(result, VARHDRSZ + text_size);
    memcpy(VARDATA(result), buf, text_size);
    PG_RETURN_TEXT_P(result);
}

/**
 * Return pixel type of the specified band of raster.
 * Band index is 1-based.
 */
PG_FUNCTION_INFO_V1(RASTER_getBandPixelType);
Datum RASTER_getBandPixelType(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = NULL;
    rt_raster raster = NULL;
    rt_band band = NULL;
    rt_context ctx = NULL;
    rt_pixtype pixtype;
    int32_t index;

    /* Index is 1-based */
    index = PG_GETARG_INT32(1);
    if ( index < 1 ) {
        elog(ERROR, "Invalid band index (must use 1-based)");
        PG_RETURN_NULL();
    }
    assert(0 <= (index - 1));

    /* Deserialize raster */
    pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    ctx = get_rt_context(fcinfo);

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    /* Fetch requested band and its pixel type */
    band = rt_raster_get_band(ctx, raster, index - 1);
    if ( ! band ) {
        /* TODO: Throw rrror or notice or nothing? */
        elog(ERROR, "Could not find raster band of index %d", index);
        PG_RETURN_NULL();
    }

    pixtype = rt_band_get_pixtype(ctx, band);
    PG_RETURN_INT32(pixtype);
}

/**
 * Return name of pixel type of the specified band of raster.
 * Band index is 1-based.
 * NOTE: This is unofficial utility not included in the spec.
 */
PG_FUNCTION_INFO_V1(RASTER_getBandPixelTypeName);
Datum RASTER_getBandPixelTypeName(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = NULL;
    rt_raster raster = NULL;
    rt_band band = NULL;
    rt_context ctx = NULL;
    rt_pixtype pixtype;
    int32_t index;
    const size_t name_size = 8; /* size of type name */
    size_t size = 0;
    char *ptr = NULL;
    text *result = NULL;

    /* Index is 1-based */
    index = PG_GETARG_INT32(1);
    if ( index < 1 ) {
        elog(ERROR, "Invalid band index (must use 1-based)");
        PG_RETURN_NULL();
    }
    assert(0 <= (index - 1));

    /* Deserialize raster */
    pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    ctx = get_rt_context(fcinfo);

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    /* Fetch requested band and its pixel type */
    band = rt_raster_get_band(ctx, raster, index - 1);
    if ( ! band ) {
        /* TODO: Throw rrror or notice or nothing? */
        elog(ERROR, "Could not find raster band of index %d", index);
        PG_RETURN_NULL();
    }

    pixtype = rt_band_get_pixtype(ctx, band);

    result = palloc(VARHDRSZ + name_size);
    if ( ! result ) {
        elog(ERROR, "Could not allocate memory for output text object");
        PG_RETURN_NULL();
    }
    memset(VARDATA(result), 0, name_size);
    ptr = (char *)result + VARHDRSZ;

    switch (pixtype)
    {
        case PT_1BB:  /* 1-bit boolean            */
            strcpy(ptr, "1BB");
            break;
        case PT_2BUI: /* 2-bit unsigned integer   */
            strcpy(ptr, "2BUI");
            break;
        case PT_4BUI: /* 4-bit unsigned integer   */
            strcpy(ptr, "4BUI");
            break;
        case PT_8BSI: /* 8-bit signed integer     */
            strcpy(ptr, "8BSI");
            break;
        case PT_8BUI: /* 8-bit unsigned integer   */
            strcpy(ptr, "8BUI");
            break;
        case PT_16BSI:/* 16-bit signed integer    */
            strcpy(ptr, "16BSI");
            break;
        case PT_16BUI:/* 16-bit unsigned integer  */
            strcpy(ptr, "16BUI");
            break;
        case PT_32BSI:/* 32-bit signed integer    */
            strcpy(ptr, "32BSI");
            break;
        case PT_32BUI:/* 32-bit unsigned integer  */
            strcpy(ptr, "32BUI");
            break;
        case PT_16BF: /* 16-bit float             */
            strcpy(ptr, "16BF");
            break;
        case PT_32BF: /* 32-bit float             */
            strcpy(ptr, "32BF");
            break;
        case PT_64BF: /* 64-bit float             */

            strcpy(ptr, "64BF");
            break;
        default:      /* PT_END=13 */
            elog(ERROR, "Invalid value of pixel type");
            pfree(result);
            PG_RETURN_NULL();
    }

    size = VARHDRSZ + strlen(ptr);
    SET_VARSIZE(result, size);

    PG_RETURN_TEXT_P(result);
}

/**
 * Return NODATA value of the specified band of raster.
 * The value is always returned as FLOAT32 even if the pixel type is INTEGER.
 */
PG_FUNCTION_INFO_V1(RASTER_getBandNoDataValue);
Datum RASTER_getBandNoDataValue(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = NULL;
    rt_raster raster = NULL;
    rt_band band = NULL;
    rt_context ctx = NULL;
    double nodata;
    int32_t index;

    /* Index is 1-based */
    index = PG_GETARG_INT32(1);
    if ( index < 1 ) {
        elog(ERROR, "Invalid band index (must use 1-based)");
        PG_RETURN_NULL();
    }
    assert(0 <= (index - 1));

    /* Deserialize raster */
    pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    ctx = get_rt_context(fcinfo);

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    /* Fetch requested band and its NODATA value */
    band = rt_raster_get_band(ctx, raster, index - 1);
    if ( ! band ) {
        /* TODO: Throw rrror or notice or nothing? */
        elog(ERROR, "Could not find raster band of index %d", index);
        PG_RETURN_NULL();
    }

    nodata = rt_band_get_nodata(ctx, band);
    PG_RETURN_FLOAT4(nodata);
}

/**
 * Return the path of the raster for out-db raster.
 */
PG_FUNCTION_INFO_V1(RASTER_getBandPath);
Datum RASTER_getBandPath(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = NULL;
    rt_raster raster = NULL;
    rt_band band = NULL;
    rt_context ctx = NULL;
    int32_t index;
    const char *bandpath;
	text *result;

    /* Index is 1-based */
    index = PG_GETARG_INT32(1);
    if ( index < 1 ) {
        elog(ERROR, "Invalid band index (must use 1-based)");
        PG_RETURN_NULL();
    }
    assert(0 <= (index - 1));

    /* Deserialize raster */
    pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    ctx = get_rt_context(fcinfo);

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    /* Fetch requested band and its NODATA value */
    band = rt_raster_get_band(ctx, raster, index - 1);
    if ( ! band ) {
        /* TODO: Throw rrror or notice or nothing? */
        elog(ERROR, "Could not find raster band of index %d", index);
        PG_RETURN_NULL();
    }

    bandpath = rt_band_get_ext_path(ctx, band);
    if ( ! bandpath )
    {
        elog(ERROR, "Could not get band path");
        PG_RETURN_NULL();
    }

    result = (text *) palloc(VARHDRSZ + strlen(bandpath) + 1);
    if ( ! result ) {
        elog(ERROR, "Could not allocate memory for output text object");
        PG_RETURN_NULL();
    }
    SET_VARSIZE(result, VARHDRSZ + strlen(bandpath) + 1);

    strcpy((char *) VARDATA(result), bandpath);
    PG_RETURN_TEXT_P(result);
}


/**
 * Return value of a single pixel.
 * Pixel location is specified by 1-based index of Nth band of raster and
 * X,Y coordinates (X <= RT_Width(raster) and Y <= RT_Height(raster)).
 *
 * TODO: Should we returen NUMERIC instead of FLOAT8 ?
 */
PG_FUNCTION_INFO_V1(RASTER_getPixelValue);
Datum RASTER_getPixelValue(PG_FUNCTION_ARGS)
{
    rt_pgraster *pgraster = NULL;
    rt_raster raster = NULL;
    rt_band band = NULL;
    rt_context ctx = NULL;
    double pixvalue = 0;
    int32_t nband = 0;
    int32_t x = 0;
    int32_t y = 0;

    /* Index is 1-based */
    nband = PG_GETARG_INT32(1);
    if ( nband < 1 ) {
        elog(ERROR, "Invalid band index (must use 1-based)");
        PG_RETURN_NULL();
    }
    assert(0 <= (nband - 1));

    /* Validate pixel coordinates are in range */
    x = PG_GETARG_INT32(2);
    y = PG_GETARG_INT32(3);
    elog(NOTICE, "Pixel coordinates (%d, %d)", x, y);

    /* Deserialize raster */
    pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
    ctx = get_rt_context(fcinfo);

    raster = rt_raster_deserialize(ctx, pgraster);
    if ( ! raster ) {
        elog(ERROR, "Could not deserialize raster");
        PG_RETURN_NULL();
    }

    /* Fetch Nth band using 0-based internal index */
    band = rt_raster_get_band(ctx, raster, nband - 1);
    if ( ! band ) {
        /* TODO: Throw rrror or notice or nothing? */
        elog(ERROR, "Could not find raster band of index %d", nband);
        PG_RETURN_NULL();
    }

    /* Fetch pixel using 0-based coordiantes */
    pixvalue = rt_band_get_pixel(ctx, band, x - 1, y - 1);
    PG_RETURN_FLOAT8(pixvalue);
}

/**
 * Write value of raster sample on given position and in specified band.
 */
PG_FUNCTION_INFO_V1(RASTER_setPixelValue);
Datum RASTER_setPixelValue(PG_FUNCTION_ARGS)
{
    double pixvalue = 0;

    /* TODO: TO BE IMPLEMENTED */
    pixvalue = PG_GETARG_FLOAT8(4);
    PG_RETURN_FLOAT8(pixvalue);
}

/* ---------------------------------------------------------------- */
/*  Memory allocation / error reporting hooks                       */
/* ---------------------------------------------------------------- */

static void *
rt_pg_alloc(size_t size)
{
	void * result;

	result = palloc(size);

	if ( ! result )
	{
		ereport(ERROR, (errmsg_internal("Out of virtual memory")));
		return NULL;
	}
	return result;
}

static void *
rt_pg_realloc(void *mem, size_t size)
{
	void * result;

	result = repalloc(mem, size);

	return result;
}

static void
rt_pg_free(void *ptr)
{
	pfree(ptr);
}

static void
rt_pg_error(const char *fmt, va_list ap)
{
#define ERRMSG_MAXLEN 256

	char errmsg[ERRMSG_MAXLEN+1];

	vsnprintf (errmsg, ERRMSG_MAXLEN, fmt, ap);

	errmsg[ERRMSG_MAXLEN]='\0';
	ereport(ERROR, (errmsg_internal("%s", errmsg)));
}

static void
rt_pg_notice(const char *fmt, va_list ap)
{
	char *msg;

	/*
	 * This is a GNU extension.
	 * Dunno how to handle errors here.
	 */
	if (!lw_vasprintf (&msg, fmt, ap))
	{
		va_end (ap);
		return;
	}
	ereport(NOTICE, (errmsg_internal("%s", msg)));
	free(msg);
}


/* This is needed by liblwgeom */
void
lwgeom_init_allocators(void)
{
    /* liblwgeom callback - install PostgreSQL handlers */
    lwalloc_var = rt_pg_alloc;
    lwrealloc_var = rt_pg_realloc;
    lwfree_var = rt_pg_free;

    lwerror_var = rt_pg_error;
    lwnotice_var = rt_pg_notice;
}

/* ---------------------------------------------------------------- */
/*  Memory allocation / error reporting hooks                       */
/* ---------------------------------------------------------------- */
