/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implementation of OGRGeoJSONDataSource class (OGR GeoJSON Driver).
 * Author:   Mateusz Loskot, mateusz@loskot.net
 *
 ******************************************************************************
 * Copyright (c) 2007, Mateusz Loskot
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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 "cpl_port.h"
#include "ogr_geojson.h"

#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <string>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_http.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "cpl_vsi_error.h"
#include "json.h"
// #include "json_object.h"
#include "gdal_utils.h"
#include "gdal.h"
#include "gdal_priv.h"
#include "ogr_core.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_spatialref.h"
#include "ogrgeojsonreader.h"
#include "ogrgeojsonutils.h"
#include "ogrgeojsonwriter.h"
#include "ogrsf_frmts.h"
// #include "symbol_renames.h"


CPL_CVSID("$Id: ogrgeojsondatasource.cpp 40541 2017-10-23 20:46:00Z rouault $");

/************************************************************************/
/*                           OGRGeoJSONDataSource()                     */
/************************************************************************/

OGRGeoJSONDataSource::OGRGeoJSONDataSource() :
    pszName_(NULL),
    pszGeoData_(NULL),
    nGeoDataLen_(0),
    papoLayers_(NULL),
    papoLayersWriter_(NULL),
    nLayers_(0),
    fpOut_(NULL),
    flTransGeom_(OGRGeoJSONDataSource::eGeometryPreserve),
    flTransAttrs_(OGRGeoJSONDataSource::eAttributesPreserve),
    bOtherPages_(false),
    bFpOutputIsSeekable_(false),
    nBBOXInsertLocation_(0),
    bUpdatable_(false)
{}

/************************************************************************/
/*                           ~OGRGeoJSONDataSource()                    */
/************************************************************************/

OGRGeoJSONDataSource::~OGRGeoJSONDataSource()
{
    FlushCache();
    Clear();
}

/************************************************************************/
/*                           Open()                                     */
/************************************************************************/

int OGRGeoJSONDataSource::Open( GDALOpenInfo* poOpenInfo,
                                GeoJSONSourceType nSrcType )
{
    if( eGeoJSONSourceService == nSrcType )
    {
        if( !ReadFromService( poOpenInfo->pszFilename ) )
            return FALSE;
        if( poOpenInfo->eAccess == GA_Update )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Update from remote service not supported");
            return FALSE;
        }
    }
    else if( eGeoJSONSourceText == nSrcType )
    {
        pszGeoData_ = CPLStrdup( poOpenInfo->pszFilename );
    }
    else if( eGeoJSONSourceFile == nSrcType )
    {
        if( !ReadFromFile( poOpenInfo ) )
            return FALSE;
    }
    else
    {
        Clear();
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Construct OGR layer and feature objects from                    */
/*      GeoJSON text tree.                                              */
/* -------------------------------------------------------------------- */
    if( NULL == pszGeoData_ ||
        STARTS_WITH(pszGeoData_, "{\"couchdb\":\"Welcome\"") ||
        STARTS_WITH(pszGeoData_, "{\"db_name\":\"") ||
        STARTS_WITH(pszGeoData_, "{\"total_rows\":") ||
        STARTS_WITH(pszGeoData_, "{\"rows\":["))
    {
        Clear();
        return FALSE;
    }

    SetDescription( poOpenInfo->pszFilename );
    LoadLayers(poOpenInfo->papszOpenOptions);
    if( nLayers_ == 0 )
    {
        bool bEmitError = true;
        if( eGeoJSONSourceService == nSrcType )
        {
            const CPLString osTmpFilename =
                CPLSPrintf("/vsimem/%p/%s", this,
                           CPLGetFilename(poOpenInfo->pszFilename));
            VSIFCloseL(VSIFileFromMemBuffer( osTmpFilename,
                                             (GByte*)pszGeoData_,
                                             nGeoDataLen_,
                                             TRUE ));
            pszGeoData_ = NULL;
            if( GDALIdentifyDriver(osTmpFilename, NULL) )
                bEmitError = false;
            VSIUnlink(osTmpFilename);
        }
        Clear();

        if( bEmitError )
        {
            CPLError( CE_Failure, CPLE_OpenFailed,
                      "Failed to read GeoJSON data" );
        }
        return FALSE;
    }

    if( eGeoJSONSourceText == nSrcType && poOpenInfo->eAccess == GA_Update )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                    "Update from inline definition not supported");
        return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                           GetName()                                  */
/************************************************************************/

const char* OGRGeoJSONDataSource::GetName()
{
    return pszName_ ? pszName_ : "";
}

/************************************************************************/
/*                           GetLayerCount()                            */
/************************************************************************/

int OGRGeoJSONDataSource::GetLayerCount()
{
    return nLayers_;
}

/************************************************************************/
/*                           GetLayer()                                 */
/************************************************************************/

OGRLayer* OGRGeoJSONDataSource::GetLayer( int nLayer )
{
    if( 0 <= nLayer && nLayer < nLayers_ )
    {
        if( papoLayers_ )
            return papoLayers_[nLayer];
        else
            return papoLayersWriter_[nLayer];
    }

    return NULL;
}

/************************************************************************/
/*                           ICreateLayer()                             */
/************************************************************************/

OGRLayer* OGRGeoJSONDataSource::ICreateLayer( const char* pszNameIn,
                                              OGRSpatialReference* poSRS,
                                              OGRwkbGeometryType eGType,
                                              char** papszOptions )
{
    if( NULL == fpOut_ )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "GeoJSON driver doesn't support creating a layer "
                 "on a read-only datasource");
        return NULL;
    }

    if( nLayers_ != 0 )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "GeoJSON driver doesn't support creating more than one layer");
        return NULL;
    }

    VSIFPrintfL( fpOut_, "{\n\"type\": \"FeatureCollection\",\n" );

    bool bWriteFC_BBOX =
        CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRITE_BBOX", "FALSE"));

    const bool bRFC7946 = CPLTestBool(
        CSLFetchNameValueDef(papszOptions, "RFC7946", "FALSE"));

    const char* pszNativeData = CSLFetchNameValue(papszOptions, "NATIVE_DATA");
    const char* pszNativeMediaType =
        CSLFetchNameValue(papszOptions, "NATIVE_MEDIA_TYPE");
    bool bWriteCRSIfWGS84 = true;
    bool bFoundNameInNativeData = false;
    if( pszNativeData && pszNativeMediaType &&
        EQUAL(pszNativeMediaType, "application/vnd.geo+json") )
    {
        json_object *poObj = NULL;
        if( OGRJSonParse(pszNativeData, &poObj) &&
            json_object_get_type(poObj) == json_type_object )
        {
            json_object_iter it;
            it.key = NULL;
            it.val = NULL;
            it.entry = NULL;
            CPLString osNativeData;
            bWriteCRSIfWGS84 = false;
            json_object_object_foreachC(poObj, it)
            {
                if( strcmp(it.key, "type") == 0 ||
                    strcmp(it.key, "features") == 0  )
                {
                    continue;
                }
                if( strcmp(it.key, "bbox") == 0 )
                {
                    if( CSLFetchNameValue(papszOptions, "WRITE_BBOX") == NULL )
                        bWriteFC_BBOX = true;
                    continue;
                }
                if( strcmp(it.key, "crs") == 0 )
                {
                    if( !bRFC7946 )
                        bWriteCRSIfWGS84 = true;
                    continue;
                }
                // See https://tools.ietf.org/html/rfc7946#section-7.1
                if( bRFC7946 &&
                    (strcmp(it.key, "coordinates") == 0 ||
                     strcmp(it.key, "geometries") == 0 ||
                     strcmp(it.key, "geometry") == 0 ||
                     strcmp(it.key, "properties") == 0 ) )
                {
                    continue;
                }

                if( strcmp(it.key, "name") == 0 )
                    bFoundNameInNativeData = true;

                // If a native description exists, ignore it if an explicit
                // DESCRIPTION option has been provided.
                if( strcmp(it.key, "description") == 0 &&
                    CSLFetchNameValue(papszOptions, "DESCRIPTION") )
                {
                    continue;
                }

                json_object* poKey = json_object_new_string(it.key);
                VSIFPrintfL( fpOut_, "%s: ",
                             json_object_to_json_string(poKey) );
                json_object_put(poKey);
                VSIFPrintfL( fpOut_, "%s,\n",
                             json_object_to_json_string(it.val) );
            }
            json_object_put(poObj);
        }
    }

    if( !bFoundNameInNativeData &&
        CPLFetchBool(papszOptions, "WRITE_NAME", true) &&
        !EQUAL(pszNameIn, OGRGeoJSONLayer::DefaultName) &&
        !EQUAL(pszNameIn, "") )
    {
        json_object* poName = json_object_new_string(pszNameIn);
        VSIFPrintfL( fpOut_, "\"name\": %s,\n",
                     json_object_to_json_string(poName) );
        json_object_put(poName);
    }

    const char* pszDescription = CSLFetchNameValue(papszOptions, "DESCRIPTION");
    if( pszDescription )
    {
        json_object* poDesc = json_object_new_string(pszDescription);
        VSIFPrintfL( fpOut_, "\"description\": %s,\n",
                     json_object_to_json_string(poDesc) );
        json_object_put(poDesc);
    }

    OGRCoordinateTransformation* poCT = NULL;
    if( bRFC7946 )
    {
        if( poSRS == NULL )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "No SRS set on layer. Assuming it is long/lat on WGS84 ellipsoid");
        }
        else
        {
            OGRSpatialReference oSRSWGS84;
            oSRSWGS84.SetWellKnownGeogCS( "WGS84" );
            if( !poSRS->IsSame(&oSRSWGS84) )
            {
                poCT = OGRCreateCoordinateTransformation( poSRS, &oSRSWGS84 );
                if( poCT == NULL )
                {
                    CPLError(
                        CE_Warning, CPLE_AppDefined,
                        "Failed to create coordinate transformation between the "
                        "input coordinate system and WGS84.  This may be because "
                        "they are not transformable, or because projection "
                        "services (PROJ.4 DLL/.so) could not be loaded." );

                    return NULL;
                }
            }
        }
    }
    else if( poSRS )
    {
        const char* pszAuthority = poSRS->GetAuthorityName(NULL);
        const char* pszAuthorityCode = poSRS->GetAuthorityCode(NULL);
        if( pszAuthority != NULL && pszAuthorityCode != NULL &&
            EQUAL(pszAuthority, "EPSG") &&
            (bWriteCRSIfWGS84 || !EQUAL(pszAuthorityCode, "4326")) )
        {
            json_object* poObjCRS = json_object_new_object();
            json_object_object_add(poObjCRS, "type",
                                   json_object_new_string("name"));
            json_object* poObjProperties = json_object_new_object();
            json_object_object_add(poObjCRS, "properties", poObjProperties);

            if( strcmp(pszAuthorityCode, "4326") == 0 )
            {
                json_object_object_add(
                    poObjProperties, "name",
                    json_object_new_string("urn:ogc:def:crs:OGC:1.3:CRS84"));
            }
            else
            {
                json_object_object_add(
                    poObjProperties, "name",
                    json_object_new_string(
                        CPLSPrintf("urn:ogc:def:crs:EPSG::%s",
                                   pszAuthorityCode)));
            }

            const char* pszCRS = json_object_to_json_string( poObjCRS );
            VSIFPrintfL( fpOut_, "\"crs\": %s,\n", pszCRS );

            json_object_put(poObjCRS);
        }
    }

    if( bFpOutputIsSeekable_ && bWriteFC_BBOX )
    {
        nBBOXInsertLocation_ = static_cast<int>(VSIFTellL( fpOut_ ));

        const std::string osSpaceForBBOX(SPACE_FOR_BBOX + 1, ' ');
        VSIFPrintfL( fpOut_, "%s\n", osSpaceForBBOX.c_str());
    }

    VSIFPrintfL( fpOut_, "\"features\": [\n" );

    OGRGeoJSONWriteLayer* poLayer =
        new OGRGeoJSONWriteLayer( pszNameIn, eGType, papszOptions,
                                  bWriteFC_BBOX, poCT, this );

/* -------------------------------------------------------------------- */
/*      Add layer to data source layer list.                            */
/* -------------------------------------------------------------------- */
    CPLAssert(papoLayers_ == NULL);
    papoLayersWriter_ = static_cast<OGRGeoJSONWriteLayer **>(
        CPLRealloc(papoLayers_,
                   sizeof(OGRGeoJSONWriteLayer *) * (nLayers_ + 1)));

    papoLayersWriter_[nLayers_++] = poLayer;

    return poLayer;
}

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

int OGRGeoJSONDataSource::TestCapability( const char* pszCap )
{
    if( EQUAL( pszCap, ODsCCreateLayer ) )
        return fpOut_ != NULL && nLayers_ == 0;

    return FALSE;
}

/************************************************************************/
/*                              Create()                                */
/************************************************************************/

int OGRGeoJSONDataSource::Create( const char* pszName,
                                  char** /* papszOptions */ )
{
    CPLAssert( NULL == fpOut_ );

    if( strcmp(pszName, "/dev/stdout") == 0 )
        pszName = "/vsistdout/";

    bFpOutputIsSeekable_ =
        !(strcmp(pszName,"/vsistdout/") == 0 ||
          STARTS_WITH(pszName, "/vsigzip/") ||
          STARTS_WITH(pszName, "/vsizip/"));

/* -------------------------------------------------------------------- */
/*     File overwrite not supported.                                    */
/* -------------------------------------------------------------------- */
    VSIStatBufL sStatBuf;
    if( 0 == VSIStatL( pszName, &sStatBuf ) )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "The GeoJSON driver does not overwrite existing files." );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Create the output file.                                         */
/* -------------------------------------------------------------------- */
    fpOut_ = VSIFOpenExL( pszName, "w", true );
    if( NULL == fpOut_)
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Failed to create GeoJSON datasource: %s: %s",
                  pszName, VSIGetLastErrorMsg() );
        return FALSE;
    }

    pszName_ = CPLStrdup( pszName );

    return TRUE;
}

/************************************************************************/
/*                           SetGeometryTranslation()                   */
/************************************************************************/

void
OGRGeoJSONDataSource::SetGeometryTranslation( GeometryTranslation type )
{
    flTransGeom_ = type;
}

/************************************************************************/
/*                           SetAttributesTranslation()                 */
/************************************************************************/

void
OGRGeoJSONDataSource::SetAttributesTranslation( AttributesTranslation type )
{
    flTransAttrs_ = type;
}

/************************************************************************/
/*                  PRIVATE FUNCTIONS IMPLEMENTATION                    */
/************************************************************************/

void OGRGeoJSONDataSource::Clear()
{
    for( int i = 0; i < nLayers_; i++ )
    {
        if( papoLayers_ != NULL )
            delete papoLayers_[i];
        else
            delete papoLayersWriter_[i];
    }

    CPLFree( papoLayers_ );
    papoLayers_ = NULL;
    CPLFree( papoLayersWriter_ );
    papoLayersWriter_ = NULL;
    nLayers_ = 0;

    CPLFree( pszName_ );
    pszName_ = NULL;

    CPLFree( pszGeoData_ );
    pszGeoData_ = NULL;
    nGeoDataLen_ = 0;

    if( fpOut_ )
    {
        VSIFCloseL( fpOut_ );
        fpOut_ = NULL;
    }
}

/************************************************************************/
/*                           ReadFromFile()                             */
/************************************************************************/

int OGRGeoJSONDataSource::ReadFromFile( GDALOpenInfo* poOpenInfo )
{
    GByte* pabyOut = NULL;
    if( poOpenInfo->fpL == NULL ||
        !VSIIngestFile(poOpenInfo->fpL, poOpenInfo->pszFilename,
                       &pabyOut, NULL, -1) )
    {
        return FALSE;
    }

    VSIFCloseL(poOpenInfo->fpL);
    poOpenInfo->fpL = NULL;
    pszGeoData_ = reinterpret_cast<char *>(pabyOut);

    pszName_ = CPLStrdup( poOpenInfo->pszFilename );

    CPLAssert( NULL != pszGeoData_ );

    bUpdatable_ = ( poOpenInfo->eAccess == GA_Update );

    return TRUE;
}

/************************************************************************/
/*                           ReadFromService()                          */
/************************************************************************/

int OGRGeoJSONDataSource::ReadFromService( const char* pszSource )
{
    CPLAssert( NULL == pszGeoData_ );
    CPLAssert( NULL != pszSource );

    if( eGeoJSONProtocolUnknown == GeoJSONGetProtocolType( pszSource ) )
    {
        CPLDebug( "GeoJSON", "Unknown service type (use HTTP, HTTPS, FTP)" );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Fetch the GeoJSON result.                                        */
/* -------------------------------------------------------------------- */
    CPLErrorReset();

    char* papsOptions[] = {
        const_cast<char *>("HEADERS=Accept: text/plain, application/json"),
        NULL
    };

    CPLHTTPResult* pResult = CPLHTTPFetch( pszSource, papsOptions );

/* -------------------------------------------------------------------- */
/*      Try to handle CURL/HTTP errors.                                 */
/* -------------------------------------------------------------------- */
    if( NULL == pResult
        || 0 == pResult->nDataLen || 0 != CPLGetLastErrorNo() )
    {
        CPLHTTPDestroyResult( pResult );
        return FALSE;
    }

   if( 0 != pResult->nStatus )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Curl reports error: %d: %s",
                  pResult->nStatus, pResult->pszErrBuf );
        CPLHTTPDestroyResult( pResult );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Copy returned GeoJSON data to text buffer.                      */
/* -------------------------------------------------------------------- */
    char* pszData = reinterpret_cast<char *>(pResult->pabyData);

    if( eGeoJSONProtocolUnknown != GeoJSONGetProtocolType( pszData ) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
            "The data that was downloaded also starts with "
            "protocol prefix (http://, https:// or ftp://) "
            "and cannot be processed as GeoJSON data.");
        CPLHTTPDestroyResult( pResult );
        return FALSE;
    }

    // Directly assign CPLHTTPResult::pabyData to pszGeoData_.
    pszGeoData_ = pszData;
    nGeoDataLen_ = pResult->nDataLen;
    pResult->pabyData = NULL;
    pResult->nDataLen = 0;

    pszName_ = CPLStrdup( pszSource );

/* -------------------------------------------------------------------- */
/*      Cleanup HTTP resources.                                         */
/* -------------------------------------------------------------------- */
    CPLHTTPDestroyResult( pResult );

    CPLAssert( NULL != pszGeoData_ );
    return TRUE;
}

/************************************************************************/
/*                           LoadLayers()                               */
/************************************************************************/

void OGRGeoJSONDataSource::LoadLayers(char** papszOpenOptionsIn)
{
    if( NULL == pszGeoData_ )
    {
        CPLError( CE_Failure, CPLE_ObjectNull,
                  "GeoJSON data buffer empty" );
        return;
    }

    const char* const apszPrefix[] = { "loadGeoJSON(", "jsonp(" };
    for( size_t iP = 0; iP < sizeof(apszPrefix) / sizeof(apszPrefix[0]); iP++ )
    {
        if( strncmp(pszGeoData_, apszPrefix[iP], strlen(apszPrefix[iP])) == 0 )
        {
            const size_t nDataLen = strlen(pszGeoData_);
            memmove( pszGeoData_, pszGeoData_ + strlen(apszPrefix[iP]),
                    nDataLen - strlen(apszPrefix[iP]) );
            size_t i = nDataLen - strlen(apszPrefix[iP]);
            pszGeoData_[i] = '\0';
            while( i > 0 && pszGeoData_[i] != ')' )
            {
                i--;
            }
            pszGeoData_[i] = '\0';
        }
    }

    if( !GeoJSONIsObject( pszGeoData_) )
    {
        CPLDebug( "GeoJSON",
                  "No valid GeoJSON data found in source '%s'", pszName_ );
        return;
    }

/* -------------------------------------------------------------------- */
/*      Is it ESRI Feature Service data ?                               */
/* -------------------------------------------------------------------- */
    if( strstr(pszGeoData_, "esriGeometry") ||
        strstr(pszGeoData_, "esriFieldType") ||
        strstr(pszGeoData_, "\"fieldAliases\"") )
    {
        OGRESRIJSONReader reader;
        OGRErr err = reader.Parse( pszGeoData_ );
        if( OGRERR_NONE == err )
        {
            json_object* poObj = reader.GetJSonObject();
            if( poObj && json_object_get_type(poObj) == json_type_object )
            {
                json_object* poExceededTransferLimit =
                    CPL_json_object_object_get(poObj, "exceededTransferLimit");
                if( poExceededTransferLimit &&
                    json_object_get_type(poExceededTransferLimit) ==
                        json_type_boolean )
                {
                    bOtherPages_ = CPL_TO_BOOL(
                        json_object_get_boolean(poExceededTransferLimit) );
                }
            }
            reader.ReadLayers( this );
        }
        return;
    }

/* -------------------------------------------------------------------- */
/*      Is it TopoJSON data ?                                           */
/* -------------------------------------------------------------------- */
    if( strstr(pszGeoData_, "\"type\"") &&
        strstr(pszGeoData_, "\"Topology\"") )
    {
        OGRTopoJSONReader reader;
        OGRErr err = reader.Parse( pszGeoData_ );
        if( OGRERR_NONE == err )
        {
            reader.ReadLayers( this );
        }
        return;
    }

/* -------------------------------------------------------------------- */
/*      Configure GeoJSON format translator.                            */
/* -------------------------------------------------------------------- */
    OGRGeoJSONReader reader;

    if( eGeometryAsCollection == flTransGeom_ )
    {
        reader.SetPreserveGeometryType( false );
        CPLDebug( "GeoJSON", "Geometry as OGRGeometryCollection type." );
    }

    if( eAttributesSkip == flTransAttrs_ )
    {
        reader.SetSkipAttributes( true );
        CPLDebug( "GeoJSON", "Skip all attributes." );
    }

    reader.SetFlattenNestedAttributes(
        CPLFetchBool(papszOpenOptionsIn, "FLATTEN_NESTED_ATTRIBUTES", false),
        CSLFetchNameValueDef(papszOpenOptionsIn,
                             "NESTED_ATTRIBUTE_SEPARATOR", "_")[0]);

    const bool bDefaultNativeData = bUpdatable_;
    reader.SetStoreNativeData(
        CPLFetchBool(papszOpenOptionsIn, "NATIVE_DATA", bDefaultNativeData));

    reader.SetArrayAsString(
        CPLTestBool(CSLFetchNameValueDef(papszOpenOptionsIn, "ARRAY_AS_STRING",
                CPLGetConfigOption("OGR_GEOJSON_ARRAY_AS_STRING", "NO"))));

/* -------------------------------------------------------------------- */
/*      Parse GeoJSON and build valid OGRLayer instance.                */
/* -------------------------------------------------------------------- */
    const OGRErr err = reader.Parse( pszGeoData_ );
    if( OGRERR_NONE == err )
    {
        json_object* poObj = reader.GetJSonObject();
        if( poObj && json_object_get_type(poObj) == json_type_object )
        {
            json_object* poProperties =
                CPL_json_object_object_get(poObj, "properties");
            if( poProperties &&
                json_object_get_type(poProperties) == json_type_object )
            {
                json_object* poExceededTransferLimit =
                    CPL_json_object_object_get(poProperties,
                                               "exceededTransferLimit");
                if( poExceededTransferLimit &&
                    json_object_get_type(poExceededTransferLimit) ==
                        json_type_boolean )
                {
                    bOtherPages_ = CPL_TO_BOOL(
                        json_object_get_boolean(poExceededTransferLimit) );
                }
            }
        }

        reader.ReadLayers( this );
    }

    return;
}

/************************************************************************/
/*                            AddLayer()                                */
/************************************************************************/

void OGRGeoJSONDataSource::AddLayer( OGRGeoJSONLayer* poLayer )
{
    CPLAssert(papoLayersWriter_ == NULL);

    poLayer->DetectGeometryType();

    // Return layer in readable state.
    poLayer->ResetReading();

    papoLayers_ = static_cast<OGRGeoJSONLayer**>(
        CPLRealloc( papoLayers_, sizeof(OGRGeoJSONLayer*) * (nLayers_ + 1)));
    papoLayers_[nLayers_] = poLayer;
    nLayers_++;
}

/************************************************************************/
/*                            FlushCache()                              */
/************************************************************************/

void OGRGeoJSONDataSource::FlushCache()
{
    if( papoLayersWriter_ != NULL )
        return;

    for( int i = 0; i < nLayers_; i++ )
    {
        if( papoLayers_[i]->HasBeenUpdated() )
        {
            papoLayers_[i]->SetUpdated(false);

            bool bOK = false;

            // Disable all filters.
            OGRFeatureQuery *poAttrQueryBak = papoLayers_[i]->m_poAttrQuery;
            papoLayers_[i]->m_poAttrQuery = NULL;
            OGRGeometry* poFilterGeomBak = papoLayers_[i]->m_poFilterGeom;
            papoLayers_[i]->m_poFilterGeom = NULL;

            // If the source data only contained one single feature and
            // that's still the case, then do not use a FeatureCollection
            // on writing.
            bool bAlreadyDone = false;
            if( papoLayers_[i]->GetFeatureCount(TRUE) == 1 &&
                papoLayers_[i]->GetMetadata("NATIVE_DATA") == NULL )
            {
                papoLayers_[i]->ResetReading();
                OGRFeature* poFeature = papoLayers_[i]->GetNextFeature();
                if( poFeature != NULL )
                {
                    if( poFeature->GetNativeData() != NULL )
                    {
                        bAlreadyDone = true;
                        OGRGeoJSONWriteOptions oOptions;
                        json_object* poObj =
                            OGRGeoJSONWriteFeature(poFeature, oOptions);
                        VSILFILE* fp = VSIFOpenL(pszName_, "wb");
                        if( fp != NULL )
                        {
                            bOK =
                                VSIFPrintfL(
                                    fp, "%s",
                                    json_object_to_json_string(poObj) ) > 0;
                            VSIFCloseL( fp );
                        }
                        json_object_put( poObj );
                    }
                    delete poFeature;
                }
            }

            // Otherwise do layer translation.
            if( !bAlreadyDone )
            {
                char** papszOptions = CSLAddString(NULL, "-f");
                papszOptions = CSLAddString(papszOptions, "GeoJSON");
                GDALVectorTranslateOptions* psOptions =
                    GDALVectorTranslateOptionsNew(papszOptions, NULL);
                CSLDestroy(papszOptions);
                GDALDatasetH hSrcDS = this;
                CPLString osNewFilename(pszName_);
                osNewFilename += ".tmp";
                GDALDatasetH hOutDS =
                    GDALVectorTranslate(osNewFilename, NULL, 1, &hSrcDS,
                                        psOptions, NULL);
                GDALVectorTranslateOptionsFree(psOptions);

                if( hOutDS != NULL )
                {
                    CPLErrorReset();
                    GDALClose(hOutDS);
                    bOK = (CPLGetLastErrorType() == CE_None);
                }
                if( bOK )
                {
                    CPLString osBackup(pszName_);
                    osBackup += ".bak";
                    if( VSIRename(pszName_, osBackup) < 0 )
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                "Cannot create backup copy");
                    }
                    else if( VSIRename(osNewFilename, pszName_) < 0 )
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                "Cannot rename %s to %s",
                                osNewFilename.c_str(), pszName_);
                    }
                    else
                    {
                        VSIUnlink(osBackup);
                    }
                }
            }

            // Restore filters.
            papoLayers_[i]->m_poAttrQuery = poAttrQueryBak;
            papoLayers_[i]->m_poFilterGeom = poFilterGeomBak;
        }
    }
}
