/******************************************************************************
 * $Id$
 *
 * Project:  Google Maps Engine API Driver
 * Purpose:  OGRGMELayer Implementation.
 * Author:   Wolf Beregnheim <wolf+grass@bergenheim.net>
 *           Frank Warmerdam <warmerdam@pobox.com>
 *           (derived from GFT driver by Even)
 *
 ******************************************************************************
 * Copyright (c) 2013, Frank Warmerdam <warmerdam@pobox.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "ogr_gme.h"
#include "ogrgmejson.h"

#include "cpl_multiproc.h"

CPL_CVSID("$Id: ogrgmetablelayer.cpp 25475 2013-01-09 09:09:59Z warmerdam $");

/************************************************************************/
/*                            OGRGMELayer()                             */
/************************************************************************/

OGRGMELayer::OGRGMELayer(OGRGMEDataSource* poDS,
                         const char* pszTableId)

{
    CPLDebug("GME", "Opening existing layer %s", pszTableId);
    this->poDS = poDS;
    poSRS = new OGRSpatialReference(SRS_WKT_WGS84);
    poFeatureDefn = NULL;
    current_feature_page = NULL;
    bDirty = false;
    iBatchPatchSize = 50;
    bCreateTablePending = false;
    osTableId = pszTableId;
    bInTransaction = false;
    m_poFilterGeom = NULL;
}


OGRGMELayer::OGRGMELayer(OGRGMEDataSource* poDS,
                         const char* pszTableName,
                         char ** papszOptions)

{
    CPLDebug("GME", "Creating new layer %s", pszTableName);
    this->poDS = poDS;
    poSRS = new OGRSpatialReference(SRS_WKT_WGS84);
    poFeatureDefn = NULL;
    current_feature_page = NULL;
    bDirty = false;
    iBatchPatchSize = 50;
    bCreateTablePending = true;
    osTableName = pszTableName;
    osProjectId = CSLFetchNameValue( papszOptions, "projectId" );
    osDraftACL = CSLFetchNameValueDef( papszOptions, "draftAccessList", "Map Editors" );
    osPublishedACL = CSLFetchNameValueDef( papszOptions, "publishedAccessList", "Map Viewers" );
    // TODO: support tags and description
}

/************************************************************************/
/*                            ~OGRGMELayer()                            */
/************************************************************************/

OGRGMELayer::~OGRGMELayer()

{
    SyncToDisk();
    ResetReading();
    if( poSRS )
        poSRS->Release();
    if( poFeatureDefn )
        poFeatureDefn->Release();
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRGMELayer::ResetReading()

{
    if (current_feature_page != NULL)
    {
        json_object_put(current_feature_page);
        current_feature_page = NULL;
        m_nFeaturesRead = 0;

        // TODO - clear current page.
    }
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRGMELayer::TestCapability( const char * pszCap )

{
    if(EQUAL(pszCap,OLCStringsAsUTF8))
        return TRUE;
    else if(EQUAL(pszCap,OLCIgnoreFields))
        return TRUE;
    else if(EQUAL(pszCap,OLCFastSpatialFilter))
        return TRUE;
    else if(EQUAL(pszCap,OLCSequentialWrite))
        return TRUE;
    else if(EQUAL(pszCap,OLCRandomWrite))
        return TRUE;
    else if(EQUAL(pszCap,OLCDeleteFeature))
        return TRUE;
    else if(EQUAL(pszCap,OLCTransactions))
        return TRUE;
    return FALSE;
}

/************************************************************************/
/*                             SyncToDisk()                             */
/************************************************************************/

OGRErr OGRGMELayer::SyncToDisk()

{
    CPLDebug("GME", "SyncToDisk()");
    if (bDirty) {
        if (omnpoInsertedFeatures.size() > 0) {
            BatchInsert();
        }
        if (omnpoUpdatedFeatures.size() > 0) {
            BatchPatch();
        }
        if (oListOfDeletedFeatures.size() > 0) {
            BatchDelete();
        }
        bDirty = false;
    }
    return OGRERR_NONE;
}

/************************************************************************/
/*                           FetchDescribe()                            */
/************************************************************************/

int OGRGMELayer::FetchDescribe()
{
    CPLString osRequest = "tables/" + osTableId;

    CPLHTTPResult *psDescribe = poDS->MakeRequest(osRequest);
    if (psDescribe == NULL)
        return FALSE;

    CPLDebug("GME", "table doc = %s\n", psDescribe->pabyData);

    json_object *table_doc =
        OGRGMEParseJSON((const char *) psDescribe->pabyData);

    CPLHTTPDestroyResult(psDescribe);

    osTableName = OGRGMEGetJSONString(table_doc, "name");

    poFeatureDefn = new OGRFeatureDefn(osTableName);
    poFeatureDefn->Reference();

    json_object *schema_doc = json_object_object_get(table_doc, "schema");
    json_object *columns_doc = json_object_object_get(schema_doc, "columns");
    array_list *column_list = json_object_get_array(columns_doc);

    CPLString osLastGeomColumn;

    int field_count = array_list_length(column_list);
    for( int i = 0; i < field_count; i++ )
    {
        OGRwkbGeometryType eFieldGeomType = wkbNone;

        json_object *field_obj = (json_object*)
            array_list_get_idx(column_list, i);

	const char* name = OGRGMEGetJSONString(field_obj, "name");
        OGRFieldDefn oFieldDefn(name, OFTString);
        const char *type = OGRGMEGetJSONString(field_obj, "type");

        if (EQUAL(type, "integer"))
            oFieldDefn.SetType(OFTInteger);
        else if (EQUAL(type, "double"))
            oFieldDefn.SetType(OFTReal);
        else if (EQUAL(type, "boolean"))
            oFieldDefn.SetType(OFTInteger);
        else if (EQUAL(type, "string"))
            oFieldDefn.SetType(OFTString);
        else if (EQUAL(type, "string")) {
            if (EQUAL(name, "gx_id")) {
                iGxIdField = i;
            }
            oFieldDefn.SetType(OFTString);
        }
        else if (EQUAL(type, "points"))
            eFieldGeomType = wkbPoint;
        else if (EQUAL(type, "linestrings"))
            eFieldGeomType = wkbLineString;
        else if (EQUAL(type, "polygons"))
            eFieldGeomType = wkbPolygon;
        else if (EQUAL(type, "mixedGeometry"))
            eFieldGeomType = wkbGeometryCollection;

        if (eFieldGeomType == wkbNone)
        {
            poFeatureDefn->AddFieldDefn(&oFieldDefn);
        }
        else
        {
            CPLAssert(EQUAL(osGeomColumnName,""));
            osGeomColumnName = oFieldDefn.GetNameRef();
            poFeatureDefn->SetGeomType(eFieldGeomType);
            poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
        }
    }

    json_object_put(table_doc);
    return TRUE;
}

/************************************************************************/
/*                         GetPageOfFeatures()                          */
/************************************************************************/

void OGRGMELayer::GetPageOfFeatures()
{
    CPLString osNextPageToken;

    if (current_feature_page != NULL)
    {
        osNextPageToken = OGRGMEGetJSONString(current_feature_page,
                                              "nextPageToken", "");
        json_object_put(current_feature_page);
        current_feature_page = NULL;

        // End of query results?
        if (EQUAL(osNextPageToken,""))
            return;
    }

    index_in_page = 0;
    current_features_array = NULL;

/* -------------------------------------------------------------------- */
/*      Fetch features.                                                 */
/* -------------------------------------------------------------------- */
    CPLString osRequest = "tables/" + osTableId + "/features";
    CPLString osMoreOptions = "&maxResults=1000";

    if (!EQUAL(osNextPageToken,""))
    {
        osMoreOptions += "&pageToken=";
        osMoreOptions += osNextPageToken;
    }
    if (!osSelect.empty()) {
        CPLDebug( "GME", "found select=%s", osSelect.c_str());
        osMoreOptions += "&select=";
        osMoreOptions += osSelect;
    }
    if (!osWhere.empty()) {
        CPLDebug( "GME Layer", "found where=%s", osWhere.c_str());
        osMoreOptions += "&where=";
        osMoreOptions += osWhere;
    }

    if (!osIntersects.empty()) {
        CPLDebug( "GME Layer", "found intersects=%s", osIntersects.c_str());
        osMoreOptions += "&intersects=";
        osMoreOptions += osIntersects;
    }

    CPLHTTPResult *psFeaturesResult =
        poDS->MakeRequest(osRequest, osMoreOptions);

    if (psFeaturesResult == NULL) {
        CPLDebug("GME", "Got NULL from MakeRequest. Something went wrong. You figure it out!");
        current_feature_page = NULL;
        return;
    }
    CPLDebug("GME",
             "features doc = %s...",
             psFeaturesResult->pabyData);

/* -------------------------------------------------------------------- */
/*      Parse result.                                                   */
/* -------------------------------------------------------------------- */

    current_feature_page =
        OGRGMEParseJSON((const char *) psFeaturesResult->pabyData);
    CPLHTTPDestroyResult(psFeaturesResult);

    current_features_array =
        json_object_object_get(current_feature_page, "features");
}

/************************************************************************/
/*                         GetNextRawFeature()                          */
/************************************************************************/

OGRFeature *OGRGMELayer::GetNextRawFeature()

{
/* -------------------------------------------------------------------- */
/*      Fetch a new page of features if needed.                         */
/* -------------------------------------------------------------------- */
    if (current_feature_page == NULL
        || index_in_page >= json_object_array_length(current_features_array))
    {
        GetPageOfFeatures();
    }

    if (current_feature_page == NULL)
    {
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Identify our json feature.                                      */
/* -------------------------------------------------------------------- */
    json_object *feature_obj =
        json_object_array_get_idx(current_features_array, index_in_page++);
    if (feature_obj == NULL) 
        return NULL;

    OGRFeature *poFeature = new OGRFeature(poFeatureDefn);

/* -------------------------------------------------------------------- */
/*      Handle properties.                                              */
/* -------------------------------------------------------------------- */
    json_object *properties_obj =
        json_object_object_get(feature_obj, "properties");
    for (int iOGRField = 0;
         iOGRField < poFeatureDefn->GetFieldCount(); 
         iOGRField++ ) 
    {
        const char *pszValue = 
            OGRGMEGetJSONString(
                properties_obj, 
                poFeatureDefn->GetFieldDefn(iOGRField)->GetNameRef(),
                NULL);
        if (pszValue != NULL) {
            poFeature->SetField(iOGRField, pszValue);
	}
    }
/* -------------------------------------------------------------------- */
/*      Handle gx_id.                                                   */
/* -------------------------------------------------------------------- */
    const char *gx_id = OGRGMEGetJSONString(properties_obj, "gx_id");
    if (gx_id) {
        CPLString gmeId(gx_id);
        omnosIdToGMEKey[++m_nFeaturesRead] = gmeId;
        poFeature->SetFID(m_nFeaturesRead);
        CPLDebug("GME", "Mapping ids: \"%s\" to %d", gx_id, (int)m_nFeaturesRead);
    }

/* -------------------------------------------------------------------- */
/*      Handle geometry.                                                */
/* -------------------------------------------------------------------- */
    json_object *geometry_obj =
        json_object_object_get(feature_obj, "geometry");
    OGRGeometry *poGeometry = NULL;

    if (geometry_obj != NULL) 
    {
        poGeometry = OGRGeoJSONReadGeometry(geometry_obj);
    }

    if (poGeometry != NULL) 
    {
        poGeometry->assignSpatialReference(poSRS);
        poFeature->SetGeometryDirectly(poGeometry);
    }

    return poFeature;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRGMELayer::GetNextFeature()
{
    OGRFeature *poFeature = NULL;
    
    while( TRUE )
    {
        poFeature = GetNextRawFeature();
        if( poFeature == NULL )
            break;

        // incremeted in GetNextRawFeature()
        // m_nFeaturesRead++;

        if( (m_poFilterGeom == NULL
             || poFeature->GetGeometryRef() == NULL 
             || FilterGeometry( poFeature->GetGeometryRef() ) )
            && (m_poAttrQuery == NULL
                || m_poAttrQuery->Evaluate( poFeature )) )
            break;

        delete poFeature;
    }

    return poFeature;
}

/************************************************************************/
/*                           GetLayerDefn()                             */
/************************************************************************/

OGRFeatureDefn * OGRGMELayer::GetLayerDefn()
{
    if (poFeatureDefn == NULL)
    {
        if (osTableId.size() == 0)
            return NULL;
        if (!FetchDescribe())
	    return NULL;
    }

    return poFeatureDefn;
}


/************************************************************************/
/*                        SetAttributeFilter()                          */
/************************************************************************/

OGRErr OGRGMELayer::SetAttributeFilter( const char *pszWhere )
{
    OGRErr eErr;
    eErr = OGRLayer::SetAttributeFilter(pszWhere);
    if( eErr == OGRERR_NONE ) {
        if ( pszWhere ) {
            char * pszEscaped = CPLEscapeString(pszWhere, -1, CPLES_URL);
            osWhere = CPLString(pszEscaped);
            CPLFree(pszEscaped);
        }
        else {
            osWhere = "";
        }
    }
    return eErr;
}

/************************************************************************/
/*                       SetIgnoredFields()                             */
/************************************************************************/

OGRErr OGRGMELayer::SetIgnoredFields(const char ** papszFields )
{
    osSelect = "geometry";
    OGRErr eErr;
    eErr = OGRLayer::SetIgnoredFields(papszFields);

    if( eErr == OGRERR_NONE ) {
        for ( int iOGRField = 0; iOGRField < poFeatureDefn->GetFieldCount(); iOGRField++ )
        {
            if (!poFeatureDefn->GetFieldDefn(iOGRField)->IsIgnored()) {
                osSelect += ",";
                osSelect += poFeatureDefn->GetFieldDefn(iOGRField)->GetNameRef();
            }
        }
    }
    return eErr;
}

/************************************************************************/
/*                       SetSpatialFilter()                             */
/************************************************************************/

void OGRGMELayer::SetSpatialFilter( OGRGeometry *poGeomIn)
{
    if (poGeomIn == NULL) {
        osIntersects.clear();
        OGRLayer::SetSpatialFilter( poGeomIn );
        return;
    }
    switch( poGeomIn->getGeometryType() )
    {
      case wkbPolygon:
        WindPolygonCCW((OGRPolygon *) poGeomIn);
      case wkbPoint:
      case wkbLineString:
        if( poGeomIn == NULL ) {
          osIntersects = "";
        }
        else {
            char * pszWkt;
            poGeomIn->exportToWkt(&pszWkt);
            char * pszEscaped = CPLEscapeString(pszWkt, -1, CPLES_URL);
            osIntersects = CPLString(pszEscaped);
            CPLFree(pszEscaped);
            CPLFree(pszWkt);
        }
        ResetReading();
        break;
      default:
        m_iGeomFieldFilter = 0;
        if( InstallFilter( poGeomIn ) )
            ResetReading();
        break;
    }
}

/************************************************************************/
/*                          WindPolygonCCW()                            */
/************************************************************************/

OGRPolygon* OGRGMELayer::WindPolygonCCW( OGRPolygon *poPolygon )
{
    CPLAssert( NULL != poPolygon );

    OGRLinearRing* poRing = poPolygon->getExteriorRing();
    if (poRing == NULL) {
        return poPolygon;
    }

    // If the linear ring is CW re-wind it CCW
    if (poRing->isClockwise() ) {
      poRing->reverseWindingOrder();
    }

    /* Interior rings. */
    const int nCount = poPolygon->getNumInteriorRings();
    for( int i = 0; i < nCount; ++i ) {
        poRing = poPolygon->getInteriorRing( i );
        if (poRing == NULL)
            continue;
        // If the linear ring is CW re-wind it CCW

        if (poRing->isClockwise() ) {
            poRing->reverseWindingOrder();
        }
    }

    return poPolygon;
}


/************************************************************************/
/*                            BatchPatch()                              */
/************************************************************************/

OGRErr OGRGMELayer::BatchPatch()
{
    CPLDebug("GME", "BatchPatch() - <%d>", (int)oListOfDeletedFeatures.size() );
    return BatchRequest("batchPatch", omnpoUpdatedFeatures);
}

/************************************************************************/
/*                            BatchInsert()                             */
/************************************************************************/

OGRErr OGRGMELayer::BatchInsert()
{
    CPLDebug("GME", "BatchInsert() - <%d>", (int)oListOfDeletedFeatures.size() );
    return BatchRequest("batchInsert", omnpoInsertedFeatures);
}

/************************************************************************/
/*                            BatchDelete()                             */
/************************************************************************/

OGRErr OGRGMELayer::BatchDelete()
{
    json_object *pjoBatchDelete = json_object_new_object();
    json_object *pjoGxIds = json_object_new_array();
    std::vector<long>::const_iterator fit;
    CPLDebug("GME", "BatchDelete() - <%d>", (int)oListOfDeletedFeatures.size() );
    if (oListOfDeletedFeatures.size() == 0) {
        CPLDebug("GME", "Empty list, not doing BatchDelete");
        return OGRERR_NONE;
    }
    for ( fit = oListOfDeletedFeatures.begin(); fit != oListOfDeletedFeatures.end(); fit++)
    {
        long nFID = *fit;
        if (nFID > 0) {
            CPLString osGxId(omnosIdToGMEKey[nFID]);
            CPLDebug("GME", "Deleting feature %ld -> '%s'", nFID, osGxId.c_str());
            json_object *pjoGxId = json_object_new_string(osGxId.c_str());
            omnosIdToGMEKey.erase(nFID);
            json_object_array_add( pjoGxIds, pjoGxId );
        }
    }
    oListOfDeletedFeatures.clear();
    if (json_object_array_length(pjoGxIds) == 0)
        return OGRERR_FAILURE;
    json_object_object_add( pjoBatchDelete, "gx_ids", pjoGxIds );
    const char *body =
        json_object_to_json_string_ext(pjoBatchDelete,
                                       JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY);

/* -------------------------------------------------------------------- */
/*      POST changes                                                    */
/* -------------------------------------------------------------------- */
    CPLString osRequest = "tables/" + osTableId + "/features/batchDelete";
    CPLHTTPResult *poBatchDeleteResult = poDS->PostRequest(osRequest, body);
    if (poBatchDeleteResult) {
        CPLDebug("GME", "batchDelete returned %d", poBatchDeleteResult->nStatus);
        return OGRERR_NONE;
    }
    else {
        CPLDebug("GME", "batchPatch failed, NULL was returned.");
        CPLError(CE_Failure, CPLE_AppDefined, "Server error for batchDelete");
        return OGRERR_FAILURE;
    }
}

/************************************************************************/
/*                            BatchRequest()                            */
/************************************************************************/

OGRErr OGRGMELayer::BatchRequest(const char *pszMethod, std::map<int, OGRFeature *> &omnpoFeatures)
{
    json_object *pjoBatchDoc = json_object_new_object();
    json_object *pjoFeatures = json_object_new_array();
    std::map<int, OGRFeature *>::const_iterator fit;
    CPLDebug("GME", "BatchRequest('%s', <%d>)", pszMethod, (int)omnpoFeatures.size() );
    if (omnpoFeatures.size() == 0) {
        CPLDebug("GME", "Empty map, not doing '%s'", pszMethod);
        return OGRERR_NONE;
    }
    for ( fit = omnpoFeatures.begin(); fit != omnpoFeatures.end(); fit++)
    {
        long nFID = fit->first;
        OGRFeature *poFeature = fit->second;
        CPLDebug("GME", "Processing feature: %ld", nFID );
        json_object *pjoFeature = OGRGMEFeatureToGeoJSON(poFeature);

        if (pjoFeature != NULL)
            json_object_array_add( pjoFeatures, pjoFeature );
        delete poFeature;
    }
    omnpoFeatures.clear();
    if (json_object_array_length(pjoFeatures) == 0)
        return OGRERR_FAILURE;
    json_object_object_add( pjoBatchDoc, "features", pjoFeatures );
    const char *body =
        json_object_to_json_string_ext(pjoBatchDoc,
                                       JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY);

/* -------------------------------------------------------------------- */
/*      POST changes                                                    */
/* -------------------------------------------------------------------- */
    CPLString osRequest = "tables/" + osTableId + "/features/" + pszMethod;
    CPLHTTPResult *psBatchResult = poDS->PostRequest(osRequest, body);
    if (psBatchResult) {
        CPLDebug("GME", "%s returned %d", pszMethod, psBatchResult->nStatus);
        return OGRERR_NONE;
    }
    else {
        CPLDebug("GME", "%s failed, NULL was returned.", pszMethod );
        CPLError(CE_Failure, CPLE_AppDefined, "Server error for %s", pszMethod);
        return OGRERR_FAILURE;
    }
}

/************************************************************************/
/*                         SetBatchPatchSize()                          */
/************************************************************************/

void OGRGMELayer::SetBatchPatchSize(unsigned int iSize)

{
    iBatchPatchSize = iSize;
}

/************************************************************************/
/*                         GetBatchPatchSize()                          */
/************************************************************************/

unsigned int OGRGMELayer::GetBatchPatchSize()

{
    CPLString osBatchPatchSize;
    osBatchPatchSize = CPLGetConfigOption("GME_BATCH_PATCH_SIZE","0");
    int iSize = atoi( osBatchPatchSize.c_str() );
    if (iSize < 1)
        return iBatchPatchSize;
    else {
        iBatchPatchSize = iSize;
        return (unsigned int) iSize;
    }
}

/************************************************************************/
/*                           CreateFeature()                            */
/************************************************************************/

OGRErr OGRGMELayer::CreateFeature( OGRFeature *poFeature )

{
    if (!poFeature)
        return OGRERR_FAILURE;
    if (!CreateTableIfNotCreated()) {
        return OGRERR_FAILURE;
    }

    long nFID = ++m_nFeaturesRead;
    poFeature->SetFID(nFID);

    int nGxId = poFeature->GetFieldIndex("gx_id");
    CPLDebug("GME", "gx_id is field %d", iGxIdField);
    CPLString osGxId;
    CPLDebug("GME", "Inserting feature %ld as %s", poFeature->GetFID(), osGxId.c_str());
    if (nGxId >= 0) {
        iGxIdField = nGxId;
        if(poFeature->IsFieldSet(iGxIdField)) {
          osGxId = poFeature->GetFieldAsString(iGxIdField);
          CPLDebug("GME", "Feature already has %ld gx_id='%s'", poFeature->GetFID(),
                   osGxId.c_str());
        }
        else {
            osGxId = CPLSPrintf("GDAL-%ld", nFID);
            CPLDebug("GME", "Setting field %d as %s", iGxIdField, osGxId.c_str() );
            poFeature->SetField( iGxIdField, osGxId.c_str() );
        }
    }
    omnosIdToGMEKey[poFeature->GetFID()] = osGxId;
    omnpoInsertedFeatures[nFID] = poFeature->Clone();

    if (bInTransaction) {
        CPLDebug("GME", "In Transaction, added feature to memory only");
        bDirty = true;
        return OGRERR_NONE;
    }
    else {
        CPLDebug("GME", "Not in Transaction, BatchInsert()");
        return BatchInsert();
    }
}

/************************************************************************/
/*                           SetFeature()                               */
/************************************************************************/

OGRErr OGRGMELayer::SetFeature( OGRFeature *poFeature )

{
    if (!poFeature)
        return OGRERR_FAILURE;
    long nFID = poFeature->GetFID();
    if(bInTransaction) {
        std::map<int, OGRFeature *>::const_iterator fit;
        fit = omnpoInsertedFeatures.find(nFID);
        if (fit != omnpoInsertedFeatures.end()) {
            omnpoInsertedFeatures[nFID] = poFeature->Clone();
            CPLDebug("GME", "Updated Feature %ld in Transaction", nFID);
        }
        else {
            CPLDebug("GME", "In Transaction, add update to Transaction");
            bDirty = true;
            omnpoUpdatedFeatures[nFID] = poFeature->Clone();
        }
        return OGRERR_NONE;
    }
    else {
        omnpoUpdatedFeatures[nFID] = poFeature->Clone();
        CPLDebug("GME", "Not in Transaction, BatchPatch()");
        return BatchPatch();
    }
}

/************************************************************************/
/*                           DeleteteFeature()                          */
/************************************************************************/

OGRErr OGRGMELayer::DeleteFeature( long nFID )
{
    if(bInTransaction) {
        std::map<int, OGRFeature *>::iterator fit;
        fit = omnpoInsertedFeatures.find(nFID);
        if (fit != omnpoInsertedFeatures.end()) {
            omnpoInsertedFeatures.erase(fit);
            CPLDebug("GME", "Found %ld in omnpoInsertedFeatures", nFID);
        }
        else {
            CPLDebug("GME", "In Transaction, adding feature to List");
            bDirty = true;
            oListOfDeletedFeatures.push_back(nFID); 
        }
        return OGRERR_NONE;
    }
    else {
        CPLDebug("GME", "Not in Transaction, BatchDelete()");
        return BatchDelete();
    }
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr OGRGMELayer::CreateField( OGRFieldDefn *poField, CPL_UNUSED int bApproxOK )
{
    CPLDebug("GME", "create field %s of type %s, pending = %d",
             poField->GetNameRef(), OGRFieldDefn::GetFieldTypeName(poField->GetType()),
             bCreateTablePending);
    if (!bCreateTablePending) {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot add field to table after schema is defined.");
        return OGRERR_FAILURE;
    }

    if (poFeatureDefn == NULL) {
        poFeatureDefn = new OGRFeatureDefn( osTableName );

        poFeatureDefn->Reference();
        poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
        poFeatureDefn->GetGeomFieldDefn(0)->SetName("geometry");
    }
    poFeatureDefn->AddFieldDefn(poField);
    return OGRERR_NONE;
}

/************************************************************************/
/*                      CreateTableIfNotCreated()                       */
/************************************************************************/

bool OGRGMELayer::CreateTableIfNotCreated()
{
    if (!bCreateTablePending || (osTableId.size() != 0)) {
        CPLDebug("GME", "Not creating table since already created");
        CPLDebug("GME", "bCreateTablePending = %d osTableId ='%s'",
                 bCreateTablePending, osTableId.c_str());
        return true;
    }
    CPLDebug("GME", "Creating table...");

    json_object *pjoCreateDoc = json_object_new_object();
    json_object *pjoProjectId = json_object_new_string( osProjectId.c_str() );
    json_object_object_add( pjoCreateDoc, "projectId", pjoProjectId );
    json_object *pjoName = json_object_new_string( osTableName.c_str() );
    json_object_object_add( pjoCreateDoc, "name",  pjoName );
    json_object *pjoDraftACL = json_object_new_string( osDraftACL.c_str() );
    json_object_object_add( pjoCreateDoc, "draftAccessList", pjoDraftACL );
    json_object *pjoPublishedACL = json_object_new_string( osPublishedACL.c_str() );
    json_object_object_add( pjoCreateDoc, "publishedAccessList", pjoPublishedACL );
    json_object *pjoSchema = json_object_new_object();

    json_object *pjoColumns = json_object_new_array();

    poFeatureDefn->SetGeomType( eGTypeForCreation );

    json_object *pjoGeometryColumn = json_object_new_object();
    json_object *pjoGeometryName = json_object_new_string( "geometry" );
    json_object *pjoGeometryType;
    switch(eGTypeForCreation) {
    case wkbPoint:
    case wkbPoint25D:
    case wkbMultiPoint:
    case wkbMultiPoint25D:
        pjoGeometryType = json_object_new_string( "points" );
        break;
    case wkbLineString:
    case wkbLineString25D:
    case wkbMultiLineString:
    case wkbLinearRing:
    case wkbMultiLineString25D:
        pjoGeometryType = json_object_new_string( "lineStrings" );
        break;
    case wkbPolygon:
    case wkbPolygon25D:
    case wkbMultiPolygon:
    case wkbGeometryCollection:
    case wkbMultiPolygon25D:
        pjoGeometryType = json_object_new_string( "polygons" );
        break;
    case wkbGeometryCollection25D:
        pjoGeometryType = json_object_new_string( "mixedGeometry" );
        break;
    default:
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unsupported Geometry type. Defaulting to Points");
        pjoGeometryType = json_object_new_string( "points" );
        poFeatureDefn->SetGeomType( wkbPoint );
    }
    json_object_object_add( pjoGeometryColumn, "name", pjoGeometryName );
    json_object_object_add( pjoGeometryColumn, "type", pjoGeometryType );
    json_object_array_add( pjoColumns, pjoGeometryColumn );

    for (int iOGRField = 0; iOGRField < poFeatureDefn->GetFieldCount(); iOGRField++ )
    {
        if (iOGRField == iGxIdField)
            continue; // don't create the gx_id field.
        json_object *pjoColumn = json_object_new_object();
        json_object *pjoFieldName =
            json_object_new_string( poFeatureDefn->GetFieldDefn(iOGRField)->GetNameRef() );
        json_object *pjoFieldType;

        switch(poFeatureDefn->GetFieldDefn(iOGRField)->GetType()) {
        case OFTInteger:
            pjoFieldType = json_object_new_string( "integer" );
            break;
        case OFTReal:
            pjoFieldType = json_object_new_string( "double" );
            break;
        default:
            pjoFieldType = json_object_new_string( "string" );
        }
        json_object_object_add( pjoColumn, "name", pjoFieldName );
        json_object_object_add( pjoColumn, "type", pjoFieldType );
        json_object_array_add( pjoColumns, pjoColumn );
    }

    json_object_object_add( pjoSchema, "columns", pjoColumns );
    json_object_object_add( pjoCreateDoc, "schema", pjoSchema );
    const char *body =
        json_object_to_json_string_ext(pjoCreateDoc,
                                       JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY);

    CPLDebug("GME", "Create Table Doc:\n%s", body);

/* -------------------------------------------------------------------- */
/*      POST changes                                                    */
/* -------------------------------------------------------------------- */
    CPLString osRequest = "tables";
    CPLHTTPResult *poCreateResult = poDS->PostRequest(osRequest, body);
    if( poCreateResult == NULL || poCreateResult->pabyData == NULL )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Table creation failed.");
        if( poCreateResult )
            CPLHTTPDestroyResult(poCreateResult);
        return false;
    }
    CPLDebug("GME", "CreateTable returned %d\n%s", poCreateResult->nStatus,
             poCreateResult->pabyData);

    json_object *pjoResponseDoc = OGRGMEParseJSON((const char *) poCreateResult->pabyData);

    osTableId = OGRGMEGetJSONString(pjoResponseDoc, "id", "");
    CPLHTTPDestroyResult(poCreateResult);
    if (osTableId.size() == 0) {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Table creation failed, or could not find table id.");
        return false;
    }

    /*
    OGRFieldDefn *poGxIdField = new OGRFieldDefn("gx_id", OFTString);

    poFeatureDefn->AddFieldDefn(poGxIdField);
    iGxIdField = poFeatureDefn->GetFieldCount() - 1;
    CPLDebug("GME", "create field %s(%d) of type %s",
             "gx_id", iGxIdField, OGRFieldDefn::GetFieldTypeName(OFTString));
    */
    bCreateTablePending = false;
    CPLDebug("GME", "sleeping 3s to give GME time to create the table...");
    CPLSleep( 3.0 );
    return true;
}

/************************************************************************/
/*                          SetGeometryType()                           */
/************************************************************************/

void OGRGMELayer::SetGeometryType(OGRwkbGeometryType eGType)
{
    eGTypeForCreation = eGType;
}

/************************************************************************/
/*                         StartTransaction()                           */
/************************************************************************/

OGRErr OGRGMELayer::StartTransaction()
{
    if (bInTransaction)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Already in transaction");
        return OGRERR_FAILURE;
    }

    if (!poDS->IsReadWrite())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in read-only mode");
        return OGRERR_FAILURE;
    }

    bInTransaction = TRUE;

    return OGRERR_NONE;
}

/************************************************************************/
/*                         CommitTransaction()                          */
/************************************************************************/

OGRErr OGRGMELayer::CommitTransaction()
{
    if (!bInTransaction)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Should be in transaction");
        return OGRERR_FAILURE;
    }
    bInTransaction = FALSE;
    return SyncToDisk();
}

/************************************************************************/
/*                        RollbackTransaction()                         */
/************************************************************************/

OGRErr OGRGMELayer::RollbackTransaction()
{
    if (!bInTransaction)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Should be in transaction");
        return OGRERR_FAILURE;
    }
    bInTransaction = FALSE;
    omnpoUpdatedFeatures.clear();
    omnpoInsertedFeatures.clear();
    oListOfDeletedFeatures.clear();
    return OGRERR_NONE;
}
