
#include "map.h"

#include <assert.h>
MS_CVSID("$Id$")


typedef struct {
    layerObj* srclayer;
    hashTableObj cache;
    hashTableObj stylecache;
    struct hashObj* lastItem; /* last retrieved element in the hashtable*/
    int IsOpen; /* layer open flag */
    rectObj extent; /* extent of the cache */
    rectObj orig_extent; /* extent of the recent query */
    char **items;  /* variables to preserve the items of the source layer */
    int numitems;
} CacheLayerInfo;

int msCacheInfoSetItems(CacheLayerInfo *cacheinfo, char **items, int numitems)
{
  int i;
  /* Cleanup any previous item selection */
  if(cacheinfo->items) {
    msFreeCharArray(cacheinfo->items, cacheinfo->numitems);
    cacheinfo->items = NULL;
    cacheinfo->numitems = 0;
  }

  /* now allocate and set the layer item parameters  */
  cacheinfo->items = (char **)malloc(sizeof(char *)*numitems);
  if(!cacheinfo->items) {
    msSetError(MS_MEMERR, NULL, "msCacheInfoSetItems()");
    return(MS_FAILURE);
  }

  for(i=0; i<numitems; i++)
    cacheinfo->items[i] = strdup(items[i]);
  cacheinfo->numitems = numitems;

  return(MS_SUCCESS);
}

/*  
 * Cache: Virtual table functions 
 */

void msCacheLayerFreeItemInfo(layerObj *layer)
{
  if (layer->iteminfo)
      free(layer->iteminfo);
  layer->iteminfo = NULL;
}

int msCacheLayerInitItemInfo( layerObj *layer )
{
    int i, j;
    int *itemindexes;
    CacheLayerInfo* cacheinfo;

    if (layer->iteminfo)
        free( layer->iteminfo );

    if (layer->numitems == 0)
        return MS_SUCCESS;

    cacheinfo = (CacheLayerInfo*)layer->layerinfo;

    if ((layer->iteminfo = (long *)malloc(sizeof(int)*layer->numitems))== NULL)
    {
        msSetError(MS_MEMERR, NULL, "msCacheLayerInitItemInfo()");
        return MS_FAILURE;
    }

    itemindexes = (int*)layer->iteminfo;

    /* selecting only the relevant item indexes */
    for(i=0; i < layer->numitems; i++) {
        itemindexes[i] = -1;  /* no mapping */
        for (j=0; j < cacheinfo->numitems; j++) {
            if (!strcasecmp(layer->items[i], cacheinfo->items[i])) {
                itemindexes[i] = j;
            }
        }
    }
    return MS_SUCCESS;
}

int msCacheLayerGetItems(layerObj *layer)
{
    /* copying the item array back */
    int result = MS_SUCCESS;
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    if (!cacheinfo->items) {
        /* open the source layer */
        if (msLayerOpen(cacheinfo->srclayer) != MS_SUCCESS)
            return MS_FAILURE;
        result = msLayerGetItems(cacheinfo->srclayer);
        msLayerSetItems(layer, cacheinfo->srclayer->items, cacheinfo->srclayer->numitems);
        msCacheInfoSetItems(cacheinfo, cacheinfo->srclayer->items, cacheinfo->srclayer->numitems);
        msLayerClose(cacheinfo->srclayer);
    }
    else {
        result = msLayerSetItems(layer, cacheinfo->items, cacheinfo->numitems);
    }
    return result;
}

static int PopulateCache(layerObj *layer, rectObj rect) 
{
    int status, autostyle;
    const char* value;
    char* key[128];
    shapeObj *shape;
    double bufferX;
    double bufferY;
    int i;

    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;

    /* retieving the search rect */
    value = msLayerGetProcessingKey( layer, "spatial_selection_mode" );
    if (value && !stricmp(value, "mapextent"))
        cacheinfo->extent = layer->map->extent;
    else
        cacheinfo->extent = rect;

    /* apply a 20% buffer on the search rect */
    bufferX = (cacheinfo->extent.maxx - cacheinfo->extent.minx) * 0.1;
    bufferY = (cacheinfo->extent.maxy - cacheinfo->extent.miny) * 0.1;
    cacheinfo->extent.maxx += bufferX;
    cacheinfo->extent.minx -= bufferX;
    cacheinfo->extent.maxy += bufferY;
    cacheinfo->extent.miny -= bufferY;
        
    /* removing the previous items */
    msClearHashItems(&cacheinfo->cache);

    autostyle = (layer->styleitem && strcasecmp(layer->styleitem, "AUTO") == 0);

    /* open the source layer */
    if (msLayerOpen(cacheinfo->srclayer) != MS_SUCCESS)
        return MS_FAILURE;

    /* setting the projection the same as the source layer */
    //msFreeProjection(&layer->projection);
    if (layer->projection.proj == NULL)
        msCopyProjection(&layer->projection, &cacheinfo->srclayer->projection);
    
    if (msLayerGetItems(cacheinfo->srclayer) != MS_SUCCESS)
        return MS_FAILURE;

    /* storing the items for further use */
    msCacheInfoSetItems(cacheinfo, cacheinfo->srclayer->items, cacheinfo->srclayer->numitems);

    if (msCacheLayerInitItemInfo(layer) != 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 (cacheinfo->srclayer->resultcache)
        {
            for(i=0; i<cacheinfo->srclayer->resultcache->numresults; i++)
            { 
                if (msLayerGetShape(cacheinfo->srclayer, shape, 
                    cacheinfo->srclayer->resultcache->results[i].tileindex,
                    cacheinfo->srclayer->resultcache->results[i].shapeindex) == MS_SUCCESS) 
                {
                    sprintf((char*)key, "%d:%d", shape->index, shape->tileindex);
                    msInsertHashTablePtr(&cacheinfo->cache,(char*)key,(void*)shape);
                    if (autostyle) {
                        classObj* style_class = (classObj*)malloc(sizeof(classObj));
                        initClass(style_class);
                        msLayerGetAutoStyle(layer->map, cacheinfo->srclayer, style_class, shape->tileindex, shape->index);
                        msInsertHashTablePtr(&cacheinfo->stylecache,(char*)key,(void*)style_class);
                    }
                    shape = (shapeObj*)malloc(sizeof(shapeObj));
                    msInitShape(shape);
                }
            }
        }
    }
    else {
        /* fetching all */
        status = msLayerWhichShapes(cacheinfo->srclayer, cacheinfo->extent);
        if(status == MS_DONE) { /* no data */
        msLayerClose(cacheinfo->srclayer);
        return MS_SUCCESS;
        } else if(status != MS_SUCCESS) {
        msLayerClose(cacheinfo->srclayer);
        return MS_FAILURE;
        }
      
        while((status = msLayerNextShape(cacheinfo->srclayer, shape)) == MS_SUCCESS) { /* step through the shapes */
            sprintf((char*)key, "%d:%d", shape->index, shape->tileindex);
            msInsertHashTablePtr(&cacheinfo->cache,(char*)key,(void*)shape);
            if (autostyle) {
                classObj* style_class = (classObj*)malloc(sizeof(classObj));
                initClass(style_class);
                msLayerGetAutoStyle(layer->map, cacheinfo->srclayer, style_class, shape->tileindex, shape->index);
                msInsertHashTablePtr(&cacheinfo->stylecache,(char*)key,(void*)style_class);
            }
            shape = (shapeObj*)malloc(sizeof(shapeObj));
            msInitShape(shape);
        }
    }
    cacheinfo->orig_extent = rect;

    msFreeShape(shape);
    msFree(shape);
    msLayerClose(cacheinfo->srclayer);

    return MS_SUCCESS;
}

int msCacheCopyShape(shapeObj *from, shapeObj *to, layerObj* layer) {
  int i;

  if(!from || !to) return(-1);

  for(i=0; i<from->numlines; i++)
    msAddLine(to, &(from->line[i])); /* copy each line */

  to->type = from->type;

  to->bounds.minx = from->bounds.minx;
  to->bounds.miny = from->bounds.miny;
  to->bounds.maxx = from->bounds.maxx;
  to->bounds.maxy = from->bounds.maxy;

  if(from->text) to->text = strdup(from->text);

  to->classindex = from->classindex;
  to->index = from->index;
  to->tileindex = from->tileindex;

  if (layer->iteminfo) {
      /* copy only the relevant items*/
      int* itemindexes = layer->iteminfo;
      if (layer->numitems) {
          to->values = (char **)malloc(sizeof(char *)*layer->numitems);
          for(i=0; i<layer->numitems; i++) {
              if (itemindexes[i] >= 0 && from->numvalues > itemindexes[i])
                  to->values[i] = strdup(from->values[itemindexes[i]]);
              else
                  to->values[i] = strdup("");
          }
      }
      to->numvalues = layer->numitems;
  }
  else
  {
    /* raw copy */
      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;
    }
  }

  to->geometry = NULL; /* GEOS code will build automatically if necessary */

  return(0);
}

int msCacheLayerIsOpen(layerObj *layer)
{
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    return cacheinfo->IsOpen;
}

int msCacheLayerOpen(layerObj *layer)
{
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    cacheinfo->lastItem = NULL;
    cacheinfo->IsOpen = MS_TRUE;
    return MS_SUCCESS;
}

int msCacheLayerClose(layerObj *layer)
{
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    cacheinfo->IsOpen = MS_FALSE;
    return MS_SUCCESS;
}

int msCacheLayerWhichShapes(layerObj *layer, rectObj rect) 
{
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    if (rect.minx >= cacheinfo->extent.minx && rect.maxx <= cacheinfo->extent.maxx &&
        rect.miny >= cacheinfo->extent.miny && rect.maxy <= cacheinfo->extent.maxy)
        return MS_SUCCESS;  /* this extent has already been fetched */
    return PopulateCache(layer, rect);
}

int msCacheLayerGetShape(layerObj *layer, shapeObj *shape, int tile, long shapeindex) 
{
    char key[128];
    shapeObj* s;

    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;

    sprintf(key, "%d:%d", shapeindex, tile);
    s = (shapeObj*)msLookupHashTable(&cacheinfo->cache, key);
    if (s == NULL) {
        /* need to fetch the shape manually */
        int autostyle = (layer->styleitem && strcasecmp(layer->styleitem, "AUTO") == 0);

        /* open the source layer */
        if (msLayerOpen(cacheinfo->srclayer) != MS_SUCCESS)
            return MS_FAILURE;

        /* setting the projection the same as the source layer */
        //msFreeProjection(&layer->projection);
        if (layer->projection.proj == NULL)
            msCopyProjection(&layer->projection, &cacheinfo->srclayer->projection);
        
        if (msLayerGetItems(cacheinfo->srclayer) != MS_SUCCESS)
            return MS_FAILURE;

        if (msCacheLayerInitItemInfo(layer) != MS_SUCCESS)
            return MS_FAILURE;

        s = (shapeObj*)malloc(sizeof(shapeObj));
        msInitShape(s);
        if (msLayerGetShape(cacheinfo->srclayer, s, tile, shapeindex) == MS_SUCCESS) {
            msInsertHashTablePtr(&cacheinfo->cache,(char*)key,(void*)s);
            if (autostyle) {
                classObj* style_class = (classObj*)malloc(sizeof(classObj));
                initClass(style_class);
                msLayerGetAutoStyle(layer->map, cacheinfo->srclayer, style_class, s->tileindex, s->index);
                msInsertHashTablePtr(&cacheinfo->stylecache,(char*)key,(void*)style_class);
            }
        }
        else {
            return MS_FAILURE;
        }
        msLayerClose(cacheinfo->srclayer);
    }
    msCacheCopyShape(s, shape, layer);
    return MS_SUCCESS;
}

int msCacheLayerGetAutoStyle(mapObj *map, layerObj *layer, classObj *c, int tile, long record)
{
    char key[128];
    classObj* style_class;

    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;

    sprintf(key, "%d:%d", record, tile);
    style_class = (classObj*)msLookupHashTable(&cacheinfo->stylecache, key);
    if (style_class) {
        msCopyClass(c, style_class, layer);
    }
    return MS_SUCCESS;
}

int msCacheLayerNextShape(layerObj *layer, shapeObj *shape) 
{
    shapeObj* s;

    /* Read until we find a feature that matches attribute filter */
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;

  msFreeShape(shape);
  shape->type = MS_SHAPE_NULL;

  while ((cacheinfo->lastItem = msNextItemFromHashTable( &cacheinfo->cache, cacheinfo->lastItem))!=NULL)
  {
      s = (shapeObj*)(cacheinfo->lastItem->data);
      /* spatial filter (no overlap) */
      if (s->bounds.maxx < cacheinfo->orig_extent.minx || s->bounds.minx > cacheinfo->orig_extent.maxx ||
          s->bounds.maxy < cacheinfo->orig_extent.miny || s->bounds.miny > cacheinfo->orig_extent.maxy)
          continue;
      /* attribute filter */
      if(layer->filter.string) {
         if (msEvalExpression(&(layer->filter), layer->filteritemindex, 
                              s->values, layer->numitems) != MS_TRUE)
                              continue;
      }
      msCacheCopyShape(s, shape, layer);
      return MS_SUCCESS;
  }
  return MS_DONE;
}

int msCacheLayerGetNumFeatures(layerObj *layer)
{
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    return msGetHashTableItemCount(&cacheinfo->cache);
}

void msCacheLayerDestroy(layerObj *layer) {
    CacheLayerInfo* cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    if(cacheinfo->items) {
        msFreeCharArray(cacheinfo->items, cacheinfo->numitems);
        cacheinfo->items = NULL;
        cacheinfo->numitems = 0;
    }
    msFreeHashItems(&cacheinfo->cache);
    msFreeHashItems(&cacheinfo->stylecache);
    freeLayer(cacheinfo->srclayer); /* decrements the reference counter */
    msFree(layer->layerinfo);
    layer->layerinfo = NULL;
}

static void DestroyFeature(char* data)
{
    msFreeShape((shapeObj*)data);
    free(data);
}

static void DestroyStyleClass(char* data)
{
    freeClass((classObj*)data);
    free(data);
}

int
msCacheLayerInitializeVirtualTable(layerObj *layer)
{
    CacheLayerInfo* cacheinfo;
    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.", "msCacheLayerInitializeVirtualTable()");
        return MS_FAILURE;
    }

    /* creating the persistent layerinfo structure */
    if (layer->layerinfo)
        msCacheLayerDestroy(layer);

    layer->layerinfo = malloc(sizeof(CacheLayerInfo));
    if (!layer->layerinfo) {
       msSetError(MS_MEMERR, NULL, "msCacheLayerInitializeVirtualTable()");
       return MS_FAILURE;
    }

    cacheinfo = (CacheLayerInfo*)layer->layerinfo;
    cacheinfo->srclayer = srclayer;
    MS_REFCNT_INCR(srclayer);  /* adding reference to the layer source */

     /* init the vtable of the reference*/
    if (!cacheinfo->srclayer->vtable) {
        int rv =  msInitializeVirtualTable(cacheinfo->srclayer);
        if (rv != MS_SUCCESS) {
            return rv;
        }
    }

    /* setting the projection the same as the source layer */
    //msFreeProjection(&layer->projection);
    if (layer->projection.proj == NULL)
        msCopyProjection(&layer->projection, &cacheinfo->srclayer->projection);

    initHashTableEx(&cacheinfo->cache, 512);
    cacheinfo->cache.DestroyData = DestroyFeature;

    initHashTableEx(&cacheinfo->stylecache, 512);
    cacheinfo->stylecache.DestroyData = DestroyStyleClass;
    cacheinfo->extent.minx = -1;
    cacheinfo->extent.maxx = -1;
    cacheinfo->extent.miny = -1;
    cacheinfo->extent.maxy = -1;
    cacheinfo->orig_extent = cacheinfo->extent; 

    cacheinfo->items = NULL;
    cacheinfo->numitems = 0;

    layer->vtable->LayerOpen = msCacheLayerOpen;
    layer->vtable->LayerClose = msCacheLayerClose;
    layer->vtable->LayerFreeItemInfo = msCacheLayerFreeItemInfo;
    layer->vtable->LayerInitItemInfo = msCacheLayerInitItemInfo;
    layer->vtable->LayerGetItems = msCacheLayerGetItems;
    layer->vtable->LayerIsOpen = msCacheLayerIsOpen;
    layer->vtable->LayerWhichShapes = msCacheLayerWhichShapes;
    layer->vtable->LayerNextShape = msCacheLayerNextShape;
    layer->vtable->LayerGetShape = msCacheLayerGetShape;
    layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter;
    layer->vtable->LayerGetNumFeatures = msCacheLayerGetNumFeatures;
    layer->vtable->LayerGetAutoStyle = msCacheLayerGetAutoStyle;
    layer->vtable->LayerDestroy = msCacheLayerDestroy;

    return MS_SUCCESS;
}

