
#include "map.h"

#include <assert.h>
MS_CVSID("$Id$")

typedef struct _FilterLayerInfo {
    layerObj *srclayer;
    hashTableObj cache;  /* cache for the selection shapes */
    int spatial_filter; /* denotes that the shape selection is active */
    double tolerance;
} FilterLayerInfo;

static int PopulateCache(layerObj *layer, rectObj rect) 
{
    int status;
    char* value;
    char* key[128];
    shapeObj *shape;
    layerObj* srclayer;
    double layer_tolerance;

    FilterLayerInfo* filterinfo = (FilterLayerInfo*)layer->layerinfo;

    /* removing the previous items */
    msClearHashItems(&filterinfo->cache);

    filterinfo->spatial_filter = MS_FALSE;

    value = msLayerGetProcessingKey( layer, "selection_layer" );
    if (value) {
        if ((srclayer = msGetSubLayer(layer, value)) == NULL)
        {
            msSetError(MS_MISCERR, "Unable to bind to the layer source.", "msFilterLayerInitializeVirtualTable()");
            return MS_FAILURE;
        }
    }
    else
        return MS_SUCCESS;  /* no spatial filter given */

    filterinfo->spatial_filter = MS_TRUE;

    /* Get the layer tolerance
       default is 3 for point and line layers, 0 for others */
    if(layer->tolerance == -1)
        if(layer->status == MS_LAYER_POINT || layer->status == MS_LAYER_LINE)
            layer_tolerance = 3;
        else
            layer_tolerance = 0;
    else
        layer_tolerance = layer->tolerance;
  
    if(layer->toleranceunits == MS_PIXELS)
      filterinfo->tolerance = layer_tolerance * msAdjustExtent(&(layer->map->extent), 
            layer->map->width, layer->map->height);
    else
      filterinfo->tolerance = layer_tolerance * 
            (msInchesPerUnit(layer->toleranceunits,0)/msInchesPerUnit(layer->map->units,0));

    /* open the source layer */
    if (msLayerOpen(srclayer) != MS_SUCCESS)
        return MS_FAILURE;
    
    if (msLayerGetItems(srclayer) != MS_SUCCESS)
        return MS_FAILURE;

    shape = (shapeObj*)malloc(sizeof(shapeObj));
    msInitShape(shape);

    value = msLayerGetProcessingKey( layer, "fetch_mode" );
    if (value && !stricmp(value, "selection")) {
        /* fetching only the resultcache */
        int i;
        if (srclayer->resultcache)
        {
            for(i=0; i<srclayer->resultcache->numresults; i++)
            { 
                if (msLayerGetShape(srclayer, shape, 
                    srclayer->resultcache->results[i].tileindex,
                    srclayer->resultcache->results[i].shapeindex) == MS_SUCCESS) 
                {
                    //TODO: reproject shape
                    sprintf((char*)key, "%d:%d", shape->index, shape->tileindex);
                    msInsertHashTablePtr(&filterinfo->cache,(char*)key,(void*)shape);
                    shape = (shapeObj*)malloc(sizeof(shapeObj));
                    msInitShape(shape);
                }
            }
        }
    }
    else {
        /* fetching all */
        status = msLayerWhichShapes(srclayer, rect);
        if(status == MS_DONE) { /* no data */
        msLayerClose(srclayer);
            return MS_SUCCESS;
        } else if(status != MS_SUCCESS) {
            msLayerClose(srclayer);
            return MS_FAILURE;
        }  
        while((status = msLayerNextShape(srclayer, shape)) == MS_SUCCESS) { /* step through the shapes */
            //TODO: reproject shape
            sprintf((char*)key, "%d:%d", shape->index, shape->tileindex);
            msInsertHashTablePtr(&filterinfo->cache,(char*)key,(void*)shape);
            shape = (shapeObj*)malloc(sizeof(shapeObj));
            msInitShape(shape);
        }
    }

    msFreeShape(shape);
    msFree(shape);
    msLayerClose(srclayer);

    return MS_SUCCESS;
}

static int IsShapeEnabled(shapeObj *shape, shapeObj *selectshape, double tolerance)
{
    int status;
    double distance;
    switch(selectshape->type) { /* may eventually support types other than polygon */
	case MS_SHAPE_POLYGON:
	  
	  switch(shape->type) { /* make sure shape actually intersects the selectshape */
	  case MS_SHAPE_POINT:
	    if(tolerance == 0) /* just test for intersection */
	      status = msIntersectMultipointPolygon(&shape->line[0], selectshape);
	    else { /* check distance, distance=0 means they intersect */
	      distance = msDistanceShapeToShape(selectshape, shape);
	      if(distance < tolerance) status = MS_TRUE;
            }
	    break;
	  case MS_SHAPE_LINE:
	    if(tolerance == 0) { /* just test for intersection */
	      status = msIntersectPolylinePolygon(shape, selectshape);
	    } else { /* check distance, distance=0 means they intersect */
	      distance = msDistanceShapeToShape(selectshape, shape);
	      if(distance < tolerance) status = MS_TRUE;
            }
	    break;
	  case MS_SHAPE_POLYGON:
	    if(tolerance == 0) /* just test for intersection */
	      status = msIntersectPolygons(shape, selectshape);
	    else { /* check distance, distance=0 means they intersect */
	      distance = msDistanceShapeToShape(selectshape, shape);
	      if(distance < tolerance) status = MS_TRUE;
            }
	    break;
	  default:
	    break;
	  }
    }
    return status;
}

static int FilterShape(shapeObj *shape, FilterLayerInfo* filterinfo)
{
    struct hashObj* lastItem = NULL;
    shapeObj* s;

    while ((lastItem = msNextItemFromHashTable( &filterinfo->cache, lastItem))!=NULL)
    {
        s = (shapeObj*)(lastItem->data);
        if (IsShapeEnabled(shape, s, filterinfo->tolerance))
            return MS_TRUE;
    }

    return MS_FALSE;
}

/*  
 * Virtual table functions 
 */

int msFilterLayerInitItemInfo( layerObj *layer )
{
    /* copying the item array an call the subsequent InitItemInfo*/
    return msLayerSetItems(((FilterLayerInfo*)layer->layerinfo)->srclayer, layer->items, layer->numitems);
}

int msFilterLayerGetItems(layerObj *layer)
{
    /* copying the item array back */
    int result;
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    result = msLayerGetItems(layerinfo->srclayer);
    msLayerSetItems(layer, layerinfo->srclayer->items, layerinfo->srclayer->numitems);
    return result;
}

int msFilterLayerOpen(layerObj *layer)
{
    int status;
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    status = msLayerOpen(layerinfo->srclayer);
    /* copy back the auto projection object */
    //msFreeProjection(&layer->projection);
    msCopyProjection(&layer->projection, &layerinfo->srclayer->projection);
    return status;
}

int msFilterLayerGetShape(layerObj *layer, shapeObj *shape, int tile, long shapeindex) 
{
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    return msLayerGetShape(layerinfo->srclayer, shape, tile, shapeindex);
}

int msFilterLayerNextShape(layerObj *layer, shapeObj *shape) 
{
    int result;
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    msInitShape(shape);
    while ((result = msLayerNextShape(layerinfo->srclayer, shape)) == MS_SUCCESS) {
        /* spatial filter (no overlap) */
        if (layerinfo->spatial_filter && FilterShape(shape, layerinfo) == MS_FALSE) {
            msFreeShape(shape); /* reset */
            continue;
        }
        /* attribute filter */
        if(layer->filter.string) {
            if (msEvalExpression(&(layer->filter), layer->filteritemindex, 
                                shape->values, layer->numitems) != MS_TRUE){
                msFreeShape(shape); /* reset */
                continue;
            }
        }   
        break;  /* OK to return with this shape */
    }
    return result;
}

void msFilterLayerDestroy(layerObj *layer) {
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    msFreeHashItems(&layerinfo->cache);
    freeLayer(layerinfo->srclayer); /* decrements the reference counter */
    msFree(layer->layerinfo);
    layer->layerinfo = NULL;
}

int msFilterLayerClose(layerObj *layer)
{
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)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 msFilterLayerCreateItems(layerObj *layer, int nt)
{
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    if (LayerDefaultCreateItems(layer, nt) != MS_SUCCESS)
        return MS_FAILURE;
    return layerinfo->srclayer->vtable->LayerCreateItems(layerinfo->srclayer, nt);
}

int msFilterLayerCloseConnection(layerObj *layer)
{
    FilterLayerInfo* layerinfo = (FilterLayerInfo*)layer->layerinfo;
    return layerinfo->srclayer->vtable->LayerCloseConnection(layerinfo->srclayer);
}

/* default vtable redirection */


int msFilterLayerWhichShapes(layerObj *layer, rectObj rect)
{
    /* fetching the selection shapes for the given extent */
    if (PopulateCache(layer, rect) != MS_SUCCESS)
        return MS_FAILURE;
    return msLayerWhichShapes(((FilterLayerInfo*)layer->layerinfo)->srclayer, rect);
}

int msFilterLayerIsOpen(layerObj *layer)
{
    return msLayerIsOpen(((FilterLayerInfo*)layer->layerinfo)->srclayer);
}

void msFilterLayerFreeItemInfo(layerObj *layer)
{
    msLayerFreeItemInfo(((FilterLayerInfo*)layer->layerinfo)->srclayer);
}

int msFilterLayerGetNumFeatures(layerObj *layer)
{
    return msLayerGetNumFeatures(((FilterLayerInfo*)layer->layerinfo)->srclayer);
}

int msFilterLayerGetExtent(layerObj *layer, rectObj *extent)
{
    return msLayerGetExtent(((FilterLayerInfo*)layer->layerinfo)->srclayer, extent);
}

int msFilterLayerGetAutoStyle(mapObj *map, layerObj *layer, classObj *c, int tile, long record)
{
    return msLayerGetAutoStyle(map, ((FilterLayerInfo*)layer->layerinfo)->srclayer, c, tile, record);
}

static void DestroyFeature(char* data)
{
    msFreeShape((shapeObj*)data);
    free(data);
}

int msFilterLayerInitializeVirtualTable(layerObj *layer)
{
    FilterLayerInfo* 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.", "msFilterLayerInitializeVirtualTable()");
        return MS_FAILURE;
    }
    
    /* creating the persistent layerinfo structure */
    if (layer->layerinfo)
        msFilterLayerDestroy(layer);

    layer->layerinfo = malloc(sizeof(FilterLayerInfo));
    if (!layer->layerinfo) {
       msSetError(MS_MEMERR, NULL, "msFilterLayerInitializeVirtualTable()");
       return MS_FAILURE;
    }

    layerinfo = (FilterLayerInfo*)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;
        }
    }

    /* setting the projection the same as the source layer */
    //msFreeProjection(&layer->projection);
    msCopyProjection(&layer->projection, &layerinfo->srclayer->projection);

    initHashTableEx(&layerinfo->cache, 512);
    layerinfo->cache.DestroyData = DestroyFeature;

    /* setting up the layer vtable */
    layer->vtable->LayerOpen = msFilterLayerOpen;
    layer->vtable->LayerClose = msFilterLayerClose;
    layer->vtable->LayerFreeItemInfo = msFilterLayerFreeItemInfo;
    layer->vtable->LayerInitItemInfo = msFilterLayerInitItemInfo;
    layer->vtable->LayerGetItems = msFilterLayerGetItems;
    layer->vtable->LayerGetExtent = msFilterLayerGetExtent;
    layer->vtable->LayerGetAutoStyle = msFilterLayerGetAutoStyle;
    layer->vtable->LayerIsOpen = msFilterLayerIsOpen;
    layer->vtable->LayerWhichShapes = msFilterLayerWhichShapes;
    layer->vtable->LayerNextShape = msFilterLayerNextShape;
    layer->vtable->LayerGetShape = msFilterLayerGetShape;
    layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter;
    layer->vtable->LayerGetNumFeatures = msFilterLayerGetNumFeatures;
    layer->vtable->LayerCreateItems = msFilterLayerCreateItems;
    layer->vtable->LayerCloseConnection = msFilterLayerCloseConnection;
    layer->vtable->LayerDestroy = msFilterLayerDestroy;

    return MS_SUCCESS;
}

