
#include "map.h"

#include <assert.h>
MS_CVSID("$Id$")

typedef struct _GeomTransLayerInfo {
    layerObj *srclayer;
    shapeObj* (*Operator1)(shapeObj *shape); /* the simple unary operator */
    shapeObj* (*Operator2)(shapeObj *shape1, shapeObj *shape2); /* the simple binary operator */
    shapeObj* (*Operator3)(shapeObj *shape, struct _GeomTransLayerInfo* context); /*operator with layer context */
    double buffer_width;
    shapeObj* refshape;
} GeomTransLayerInfo;

static shapeObj *GeomTransLayerGEOSBuffer(shapeObj *shape, GeomTransLayerInfo* context)
{
    return msGEOSBuffer(shape, context->buffer_width);
}

static shapeObj *GeomTransLayerTransform2(shapeObj *shape, GeomTransLayerInfo* context)
{
    if (context->refshape && context->Operator2)
        return context->Operator2(shape, context->refshape);
    return NULL;
}

static void GeomTransLayerCopyValues(shapeObj *from, shapeObj *to)
{
    int i;

    if(from->text) to->text = strdup(from->text);

    to->classindex = from->classindex;
    to->index = from->index;
    to->tileindex = from->tileindex;

    if(from->values) {    
        to->values = (char **)malloc(sizeof(char *)*from->numvalues);
        for(i=0; i<from->numvalues; i++)
            to->values[i] = strdup(from->values[i]);
        to->numvalues = from->numvalues;
    }
    msComputeBounds(to);
}

static int TransformShape(shapeObj *from, shapeObj *to, GeomTransLayerInfo* context)
{
    shapeObj *shape;
    if (context->Operator1)
        shape = context->Operator1(from);  /* simple unary transformation */
    else if (context->Operator3)
        shape = context->Operator3(from, context);
    else {
        memcpy(to, from, sizeof(shapeObj)); /* default copy operation */
        GeomTransLayerCopyValues(from,to);
        return MS_SUCCESS;
    }
     
    if (shape) { 
        memcpy(to, shape, sizeof(shapeObj));
        GeomTransLayerCopyValues(from,to);
        return MS_SUCCESS;
    }
    return MS_FAILURE;
}

static void GeomTransLayerUpdateParameters(layerObj *layer)
{
    char* value;
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;

    value = msLayerGetProcessingKey( layer, "buffer_width" );
    layerinfo->buffer_width = 0;
    if (value) sscanf(value,"%lf", &layerinfo->buffer_width);

    value = msLayerGetProcessingKey( layer, "ref_layer" );
    if (value) {
        /* loading the shape from the layer */
        /* TODO */
    }

    if (layerinfo->refshape) {
        msFreeShape(layerinfo->refshape);
        msFree(layerinfo->refshape);
    }
    layerinfo->refshape = NULL;
    value = msLayerGetProcessingKey( layer, "ref_shape" );
    if (value) layerinfo->refshape = msShapeFromWKT(value);

    layerinfo->Operator1 = NULL;
    layerinfo->Operator2 = NULL;
    layerinfo->Operator3 = NULL;
    value = msLayerGetProcessingKey( layer, "transformation" );
    if (!strcmp(value,"buffer"))
        layerinfo->Operator3 = GeomTransLayerGEOSBuffer;
    else if (!strcmp(value,"convexhull"))
        layerinfo->Operator1 = msGEOSConvexHull;
    else if (!strcmp(value,"boundary"))
        layerinfo->Operator1 = msGEOSBoundary;
    else if (!strcmp(value,"union")) {
        layerinfo->Operator2 = msGEOSUnion;
        layerinfo->Operator3 = GeomTransLayerTransform2;
    }
    else if (!strcmp(value,"intersection")) {
        layerinfo->Operator2 = msGEOSIntersection;
        layerinfo->Operator3 = GeomTransLayerTransform2;
    }
    else if (!strcmp(value,"difference")) {
        layerinfo->Operator2 = msGEOSDifference;
        layerinfo->Operator3 = GeomTransLayerTransform2;
    }
    else if (!strcmp(value,"symdifference")) {
        layerinfo->Operator2 = msGEOSSymDifference;
        layerinfo->Operator3 = GeomTransLayerTransform2;
    }
}

/*  
 * Virtual table functions 
 */

int msGeomTransLayerInitItemInfo( layerObj *layer )
{
    /* copying the item array an call the subsequent InitItemInfo*/
    return msLayerSetItems(((GeomTransLayerInfo*)layer->layerinfo)->srclayer, layer->items, layer->numitems);
}

int msGeomTransLayerGetItems(layerObj *layer)
{
    /* copying the item array back */
    int result;
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    result = msLayerGetItems(layerinfo->srclayer);
    msLayerSetItems(layer, layerinfo->srclayer->items, layerinfo->srclayer->numitems);
    return result;
}

int msGeomTransLayerOpen(layerObj *layer)
{
    int status;
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    GeomTransLayerUpdateParameters(layer);
    status = msLayerOpen(layerinfo->srclayer);
    /* copy back the auto projection object */
    //msFreeProjection(&layer->projection);
    if (layer->projection.proj == NULL)
        msCopyProjection(&layer->projection, &layerinfo->srclayer->projection);
    return status;
}

int msGeomTransLayerGetShape(layerObj *layer, shapeObj *shape, int tile, long shapeindex) 
{
    shapeObj srcshape;
    int result;
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    msInitShape(&srcshape);
    if ((result = msLayerGetShape(layerinfo->srclayer, &srcshape, tile, shapeindex)) == MS_SUCCESS) {
        result = TransformShape(&srcshape, shape, layerinfo);
        msFreeShape(&srcshape);
    }
    return result;
}

int msGeomTransLayerNextShape(layerObj *layer, shapeObj *shape) 
{
    shapeObj srcshape;
    int result;
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    msInitShape(&srcshape);
    if ((result = msLayerNextShape(layerinfo->srclayer, &srcshape)) == MS_SUCCESS) {
        result = TransformShape(&srcshape, shape, layerinfo);
        msFreeShape(&srcshape);
    }
    return result;
}

void msGeomTransLayerDestroy(layerObj *layer) {
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    if (layerinfo->refshape)
        msFreeShape(layerinfo->refshape);
    freeLayer(layerinfo->srclayer); /* decrements the reference counter */
    msFree(layer->layerinfo);
    layer->layerinfo = NULL;
}

int msGeomTransLayerClose(layerObj *layer)
{
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    /* no need for items once the layer is closed */
    msLayerFreeItemInfo(layerinfo->srclayer);
    if(layerinfo->srclayer->items) {
        msFreeCharArray(layerinfo->srclayer->items, layerinfo->srclayer->numitems);
        layerinfo->srclayer->items = NULL;
        layerinfo->srclayer->numitems = 0;
    }
    msLayerClose(layerinfo->srclayer);
    return MS_SUCCESS;
}

int msGeomTransLayerCreateItems(layerObj *layer, int nt)
{
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    if (LayerDefaultCreateItems(layer, nt) != MS_SUCCESS)
        return MS_FAILURE;
    return layerinfo->srclayer->vtable->LayerCreateItems(layerinfo->srclayer, nt);
}

int msGeomTransLayerCloseConnection(layerObj *layer)
{
    GeomTransLayerInfo* layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    return layerinfo->srclayer->vtable->LayerCloseConnection(layerinfo->srclayer);
}

/* default vtable redirection */


int msGeomTransLayerWhichShapes(layerObj *layer, rectObj rect)
{
    return msLayerWhichShapes(((GeomTransLayerInfo*)layer->layerinfo)->srclayer, rect);
}

int msGeomTransLayerIsOpen(layerObj *layer)
{
    return msLayerIsOpen(((GeomTransLayerInfo*)layer->layerinfo)->srclayer);
}

void msGeomTransLayerFreeItemInfo(layerObj *layer)
{
    msLayerFreeItemInfo(((GeomTransLayerInfo*)layer->layerinfo)->srclayer);
}

int msGeomTransLayerGetNumFeatures(layerObj *layer)
{
    return msLayerGetNumFeatures(((GeomTransLayerInfo*)layer->layerinfo)->srclayer);
}

int msGeomTransLayerGetExtent(layerObj *layer, rectObj *extent)
{
    return msLayerGetExtent(((GeomTransLayerInfo*)layer->layerinfo)->srclayer, extent);
}

int msGeomTransLayerGetAutoStyle(mapObj *map, layerObj *layer, classObj *c, int tile, long record)
{
    return msLayerGetAutoStyle(map, ((GeomTransLayerInfo*)layer->layerinfo)->srclayer, c, tile, record);
}

int msGeomTransLayerInitializeVirtualTable(layerObj *layer)
{
    GeomTransLayerInfo* layerinfo;
    layerObj* srclayer;
    
    assert(layer != NULL);
    assert(layer->vtable != NULL);

    /* binding to the layer source */
    if ((srclayer = msGetSubLayer(layer, layer->connection)) == NULL)
    {
        msSetError(MS_MISCERR, "Unable to bind to the layer source.", "msGeomTransLayerInitializeVirtualTable()");
        return MS_FAILURE;
    }
    
    /* creating the persistent layerinfo structure */
    if (layer->layerinfo)
        msGeomTransLayerDestroy(layer);

    layer->layerinfo = malloc(sizeof(GeomTransLayerInfo));
    if (!layer->layerinfo) {
       msSetError(MS_MEMERR, NULL, "msGeomTransLayerInitializeVirtualTable()");
       return MS_FAILURE;
    }

    layerinfo = (GeomTransLayerInfo*)layer->layerinfo;
    layerinfo->srclayer = srclayer;
    MS_REFCNT_INCR(srclayer);  /* adding reference to the layer source */

    /* init the vtable of the reference*/
    if (!layerinfo->srclayer->vtable) {
        int rv =  msInitializeVirtualTable(layerinfo->srclayer);
        if (rv != MS_SUCCESS) {
            return rv;
        }
    }

    layerinfo->refshape = NULL;
    GeomTransLayerUpdateParameters(layer);

    /* setting the projection the same as the source layer */
    //msFreeProjection(&layer->projection);
    if (layer->projection.proj == NULL)
        msCopyProjection(&layer->projection, &layerinfo->srclayer->projection);

    /* setting up the layer vtable */
    layer->vtable->LayerOpen = msGeomTransLayerOpen;
    layer->vtable->LayerClose = msGeomTransLayerClose;
    layer->vtable->LayerFreeItemInfo = msGeomTransLayerFreeItemInfo;
    layer->vtable->LayerInitItemInfo = msGeomTransLayerInitItemInfo;
    layer->vtable->LayerGetItems = msGeomTransLayerGetItems;
    layer->vtable->LayerGetExtent = msGeomTransLayerGetExtent;
    layer->vtable->LayerGetAutoStyle = msGeomTransLayerGetAutoStyle;
    layer->vtable->LayerIsOpen = msGeomTransLayerIsOpen;
    layer->vtable->LayerWhichShapes = msGeomTransLayerWhichShapes;
    layer->vtable->LayerNextShape = msGeomTransLayerNextShape;
    layer->vtable->LayerGetShape = msGeomTransLayerGetShape;
    layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter;
    layer->vtable->LayerGetNumFeatures = msGeomTransLayerGetNumFeatures;
    layer->vtable->LayerCreateItems = msGeomTransLayerCreateItems;
    layer->vtable->LayerCloseConnection = msGeomTransLayerCloseConnection;
    layer->vtable->LayerDestroy = msGeomTransLayerDestroy;

    return MS_SUCCESS;
}

