/******************************************************************************
 *
 * Project:  OGR
 * Purpose:  Implements OGRGMLLayer class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ogr_gml.h"
#include "gmlutils.h"
#include "cpl_conv.h"
#include "cpl_port.h"
#include "cpl_string.h"
#include "ogr_p.h"
#include "ogr_api.h"

#include <limits>

/************************************************************************/
/*                            OGRGMLLayer()                             */
/************************************************************************/

OGRGMLLayer::OGRGMLLayer(const char *pszName, bool bWriterIn,
                         OGRGMLDataSource *poDSIn)
    : poFeatureDefn(OGRFeatureDefnRefCountedPtr::makeInstance(
          pszName + (STARTS_WITH_CI(pszName, "ogr:") ? 4 : 0))),
      bWriter(bWriterIn), poDS(poDSIn),
      poFClass(!bWriter ? poDS->GetReader()->GetClass(pszName) : nullptr),
      // Compatibility option. Not advertized, because hopefully won't be
      // needed. Just put here in case.
      bUseOldFIDFormat(
          CPLTestBool(CPLGetConfigOption("GML_USE_OLD_FID_FORMAT", "FALSE"))),
      // Must be in synced in OGR_G_CreateFromGML(), OGRGMLLayer::OGRGMLLayer()
      // and GMLReader::GMLReader().
      bFaceHoleNegative(
          CPLTestBool(CPLGetConfigOption("GML_FACE_HOLE_NEGATIVE", "NO")))
{
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->SetGeomType(wkbNone);
}

/************************************************************************/
/*                            ~OGRGMLLayer()                            */
/************************************************************************/

OGRGMLLayer::~OGRGMLLayer()

{
    CPLFree(m_pszFIDPrefix);
}

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

void OGRGMLLayer::ResetReading()

{
    if (bWriter)
        return;

    if (poDS->GetReadMode() == INTERLEAVED_LAYERS ||
        poDS->GetReadMode() == SEQUENTIAL_LAYERS)
    {
        // Does the last stored feature belong to our layer ? If so, no
        // need to reset the reader.
        if (m_iNextGMLId == 0)
        {
            const auto poStoredGMLFeature = poDS->GetStoredGMLFeature();
            if (poStoredGMLFeature &&
                poStoredGMLFeature->GetClass() == poFClass)
                return;
        }

        poDS->SetStoredGMLFeature(nullptr);
    }

    m_iNextGMLId = 0;
    m_oSetFIDs.clear();
    poDS->GetReader()->ResetReading();
    CPLDebug("GML", "ResetReading()");
    if (poDS->GetLayerCount() > 1 && poDS->GetReadMode() == STANDARD)
    {
        const char *pszElementName = poFClass->GetElementName();
        const char *pszLastPipe = strrchr(pszElementName, '|');
        if (pszLastPipe != nullptr)
            pszElementName = pszLastPipe + 1;
        poDS->GetReader()->SetFilteredClassName(pszElementName);
    }
}

/************************************************************************/
/*                             Increment()                              */
/************************************************************************/

static GIntBig Increment(GIntBig nVal)
{
    if (nVal <= GINTBIG_MAX - 1)
        return nVal + 1;
    return nVal;
}

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

OGRFeature *OGRGMLLayer::GetNextFeature()

{
    if (bWriter)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot read features when writing a GML file");
        return nullptr;
    }

    if (poDS->GetLastReadLayer() != this)
    {
        if (poDS->GetReadMode() != INTERLEAVED_LAYERS)
            ResetReading();
        poDS->SetLastReadLayer(this);
    }

    const bool bSkipCorruptedFeatures = CPLFetchBool(
        poDS->GetOpenOptions(), "SKIP_CORRUPTED_FEATURES",
        CPLTestBool(CPLGetConfigOption("GML_SKIP_CORRUPTED_FEATURES", "NO")));

    /* ==================================================================== */
    /*      Loop till we find and translate a feature meeting all our       */
    /*      requirements.                                                   */
    /* ==================================================================== */
    while (true)
    {
        auto poGMLFeature = poDS->BorrowStoredGMLFeature();
        if (poGMLFeature == nullptr)
        {
            poGMLFeature.reset(poDS->GetReader()->NextFeature());
            if (poGMLFeature == nullptr)
                return nullptr;

            // We count reading low level GML features as a feature read for
            // work checking purposes, though at least we didn't necessary
            // have to turn it into an OGRFeature.
            m_nFeaturesRead++;
        }

        /* --------------------------------------------------------------------
         */
        /*      Is it of the proper feature class? */
        /* --------------------------------------------------------------------
         */

        if (poGMLFeature->GetClass() != poFClass)
        {
            if (poDS->GetReadMode() == INTERLEAVED_LAYERS ||
                (poDS->GetReadMode() == SEQUENTIAL_LAYERS && m_iNextGMLId != 0))
            {
                poDS->SetStoredGMLFeature(std::move(poGMLFeature));
                return nullptr;
            }
            else
            {
                continue;
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Extract the fid: */
        /*      -Assumes the fids are non-negative integers with an optional */
        /*       prefix */
        /*      -If a prefix differs from the prefix of the first feature from
         */
        /*       the poDS then the fids from the poDS are ignored and are */
        /*       assigned serially thereafter */
        /* --------------------------------------------------------------------
         */
        GIntBig nFID = -1;
        constexpr size_t MAX_FID_DIGIT_COUNT = 20;
        const char *pszGML_FID = poGMLFeature->GetFID();
        if (m_bInvalidFIDFound || pszGML_FID == nullptr || pszGML_FID[0] == 0)
        {
            // do nothing
        }
        else if (m_iNextGMLId == 0)
        {
            size_t j = 0;
            size_t i = strlen(pszGML_FID);
            while (i > 0 && j < MAX_FID_DIGIT_COUNT)
            {
                --i;
                if (!(pszGML_FID[i] >= '0' && pszGML_FID[i] <= '9'))
                    break;
                j++;
                if (i == 0)
                {
                    i = std::numeric_limits<size_t>::max();
                    break;
                }
            }
            // i points the last character of the fid prefix.
            if (i != std::numeric_limits<size_t>::max() &&
                j < MAX_FID_DIGIT_COUNT && m_pszFIDPrefix == nullptr)
            {
                m_pszFIDPrefix = static_cast<char *>(CPLMalloc(i + 2));
                memcpy(m_pszFIDPrefix, pszGML_FID, i + 1);
                m_pszFIDPrefix[i + 1] = '\0';
            }
            // m_pszFIDPrefix now contains the prefix or NULL if no prefix is
            // found.
            if (j < MAX_FID_DIGIT_COUNT)
            {
                char *endptr = nullptr;
                nFID = std::strtoll(
                    pszGML_FID +
                        (i != std::numeric_limits<size_t>::max() ? i + 1 : 0),
                    &endptr, 10);
                if (endptr == pszGML_FID + strlen(pszGML_FID))
                {
                    if (m_iNextGMLId <= nFID)
                        m_iNextGMLId = Increment(nFID);
                }
                else
                {
                    nFID = -1;
                }
            }
        }
        else  // if( iNextGMLId != 0 ).
        {
            const char *pszFIDPrefix_notnull = m_pszFIDPrefix;
            if (pszFIDPrefix_notnull == nullptr)
                pszFIDPrefix_notnull = "";
            const size_t nLenPrefix = strlen(pszFIDPrefix_notnull);

            if (strncmp(pszGML_FID, pszFIDPrefix_notnull, nLenPrefix) == 0 &&
                strlen(pszGML_FID + nLenPrefix) < MAX_FID_DIGIT_COUNT)
            {
                char *endptr = nullptr;
                nFID = std::strtoll(pszGML_FID + nLenPrefix, &endptr, 10);
                if (endptr == pszGML_FID + strlen(pszGML_FID))
                {
                    // fid with the prefix. Using its numerical part.
                    if (m_iNextGMLId <= nFID)
                        m_iNextGMLId = Increment(nFID);
                }
                else
                {
                    nFID = -1;
                }
            }
        }

        constexpr size_t MAX_FID_SET_SIZE = 10 * 1000 * 1000;
        if (nFID >= 0 && m_oSetFIDs.size() < MAX_FID_SET_SIZE)
        {
            // Make sure FIDs are unique
            if (!cpl::contains(m_oSetFIDs, nFID))
                m_oSetFIDs.insert(nFID);
            else
            {
                m_oSetFIDs.clear();
                nFID = -1;
            }
        }

        if (nFID < 0)
        {
            // fid without the aforementioned prefix or a valid numerical
            // part.
            m_bInvalidFIDFound = true;
            nFID = m_iNextGMLId;
            m_iNextGMLId = Increment(m_iNextGMLId);
        }

        /* --------------------------------------------------------------------
         */
        /*      Does it satisfy the spatial query, if there is one? */
        /* --------------------------------------------------------------------
         */

        std::vector<std::unique_ptr<OGRGeometry>> apoGeometries;
        std::unique_ptr<OGRGeometry> poSingleGeom;

        const CPLXMLNode *const *papsGeometry = poGMLFeature->GetGeometryList();

        const CPLXMLNode *apsGeometries[2] = {nullptr, nullptr};
        const CPLXMLNode *psBoundedByGeometry =
            poGMLFeature->GetBoundedByGeometry();
        if (psBoundedByGeometry && !(papsGeometry && papsGeometry[0]))
        {
            apsGeometries[0] = psBoundedByGeometry;
            papsGeometry = apsGeometries;
        }

        if (poFeatureDefn->GetGeomFieldCount() > 1)
        {
            apoGeometries.resize(poFeatureDefn->GetGeomFieldCount());
            const char *pszSRSName = poDS->GetGlobalSRSName();
            for (int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++)
            {
                const CPLXMLNode *psGeom = poGMLFeature->GetGeometryRef(i);
                if (psGeom != nullptr)
                {
                    const CPLXMLNode *myGeometryList[2] = {psGeom, nullptr};
                    std::unique_ptr<OGRGeometry> poGeom(
                        GML_BuildOGRGeometryFromList(
                            myGeometryList, true,
                            poDS->GetInvertAxisOrderIfLatLong(), pszSRSName,
                            poDS->GetConsiderEPSGAsURN(),
                            poDS->GetSwapCoordinates(),
                            poDS->GetSecondaryGeometryOption(),
                            m_srsCache.get(), bFaceHoleNegative));

                    // Do geometry type changes if needed to match layer
                    // geometry type.
                    if (poGeom != nullptr)
                    {
                        apoGeometries[i] = OGRGeometryFactory::forceTo(
                            std::move(poGeom),
                            poFeatureDefn->GetGeomFieldDefn(i)->GetType());
                    }
                    else
                    {
                        // We assume the createFromGML() function would have
                        // already reported the error.
                        return nullptr;
                    }
                }
            }

            if (m_poFilterGeom != nullptr && m_iGeomFieldFilter >= 0 &&
                m_iGeomFieldFilter < poFeatureDefn->GetGeomFieldCount() &&
                apoGeometries[m_iGeomFieldFilter] &&
                !FilterGeometry(apoGeometries[m_iGeomFieldFilter].get()))
            {
                continue;
            }
        }
        else if (papsGeometry[0] &&
                 strcmp(papsGeometry[0]->pszValue, "null") == 0)
        {
            // do nothing
        }
        else if (papsGeometry[0] != nullptr)
        {
            const char *pszSRSName = poDS->GetGlobalSRSName();
            {
                CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
                poSingleGeom.reset(GML_BuildOGRGeometryFromList(
                    papsGeometry, true, poDS->GetInvertAxisOrderIfLatLong(),
                    pszSRSName, poDS->GetConsiderEPSGAsURN(),
                    poDS->GetSwapCoordinates(),
                    poDS->GetSecondaryGeometryOption(), m_srsCache.get(),
                    bFaceHoleNegative));
            }

            // Do geometry type changes if needed to match layer geometry type.
            if (poSingleGeom)
            {
                poSingleGeom = OGRGeometryFactory::forceTo(
                    std::move(poSingleGeom), GetGeomType());
            }
            else
            {
                const CPLString osLastErrorMsg(CPLGetLastErrorMsg());

                CPLError(
                    bSkipCorruptedFeatures ? CE_Warning : CE_Failure,
                    CPLE_AppDefined,
                    "Geometry of feature " CPL_FRMT_GIB
                    " %scannot be parsed: %s%s",
                    nFID, pszGML_FID ? CPLSPrintf("%s ", pszGML_FID) : "",
                    osLastErrorMsg.c_str(),
                    bSkipCorruptedFeatures
                        ? ". Skipping to next feature."
                        : ". You may set the GML_SKIP_CORRUPTED_FEATURES "
                          "configuration option to YES to skip to the next "
                          "feature");
                if (bSkipCorruptedFeatures)
                    continue;
                return nullptr;
            }

            if (m_poFilterGeom != nullptr &&
                !FilterGeometry(poSingleGeom.get()))
            {
                continue;
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Convert the whole feature into an OGRFeature. */
        /* --------------------------------------------------------------------
         */
        int iDstField = 0;
        auto poOGRFeature = std::make_unique<OGRFeature>(poFeatureDefn.get());

        poOGRFeature->SetFID(nFID);
        if (poDS->ExposeId())
        {
            if (pszGML_FID)
                poOGRFeature->SetField(iDstField, pszGML_FID);
            iDstField++;
        }

        const int nPropertyCount = poFClass->GetPropertyCount();
        for (int iField = 0; iField < nPropertyCount; iField++, iDstField++)
        {
            const GMLProperty *psGMLProperty =
                poGMLFeature->GetProperty(iField);
            if (psGMLProperty == nullptr || psGMLProperty->nSubProperties == 0)
                continue;

            if (EQUAL(psGMLProperty->papszSubProperties[0], OGR_GML_NULL))
            {
                poOGRFeature->SetFieldNull(iDstField);
                continue;
            }

            switch (poFClass->GetProperty(iField)->GetType())
            {
                case GMLPT_Real:
                {
                    poOGRFeature->SetField(
                        iDstField,
                        CPLAtof(psGMLProperty->papszSubProperties[0]));
                }
                break;

                case GMLPT_IntegerList:
                {
                    const int nCount = psGMLProperty->nSubProperties;
                    int *panIntList =
                        static_cast<int *>(CPLMalloc(sizeof(int) * nCount));

                    for (int i = 0; i < nCount; i++)
                        panIntList[i] =
                            atoi(psGMLProperty->papszSubProperties[i]);

                    poOGRFeature->SetField(iDstField, nCount, panIntList);
                    CPLFree(panIntList);
                }
                break;

                case GMLPT_Integer64List:
                {
                    const int nCount = psGMLProperty->nSubProperties;
                    GIntBig *panIntList = static_cast<GIntBig *>(
                        CPLMalloc(sizeof(GIntBig) * nCount));

                    for (int i = 0; i < nCount; i++)
                        panIntList[i] =
                            CPLAtoGIntBig(psGMLProperty->papszSubProperties[i]);

                    poOGRFeature->SetField(iDstField, nCount, panIntList);
                    CPLFree(panIntList);
                }
                break;

                case GMLPT_RealList:
                {
                    const int nCount = psGMLProperty->nSubProperties;
                    double *padfList = static_cast<double *>(
                        CPLMalloc(sizeof(double) * nCount));

                    for (int i = 0; i < nCount; i++)
                        padfList[i] =
                            CPLAtof(psGMLProperty->papszSubProperties[i]);

                    poOGRFeature->SetField(iDstField, nCount, padfList);
                    CPLFree(padfList);
                }
                break;

                case GMLPT_StringList:
                case GMLPT_FeaturePropertyList:
                {
                    poOGRFeature->SetField(iDstField,
                                           psGMLProperty->papszSubProperties);
                }
                break;

                case GMLPT_Boolean:
                {
                    if (strcmp(psGMLProperty->papszSubProperties[0], "true") ==
                            0 ||
                        strcmp(psGMLProperty->papszSubProperties[0], "1") == 0)
                    {
                        poOGRFeature->SetField(iDstField, 1);
                    }
                    else if (strcmp(psGMLProperty->papszSubProperties[0],
                                    "false") == 0 ||
                             strcmp(psGMLProperty->papszSubProperties[0],
                                    "0") == 0)
                    {
                        poOGRFeature->SetField(iDstField, 0);
                    }
                    else
                    {
                        poOGRFeature->SetField(
                            iDstField, psGMLProperty->papszSubProperties[0]);
                    }
                    break;
                }

                case GMLPT_BooleanList:
                {
                    const int nCount = psGMLProperty->nSubProperties;
                    int *panIntList =
                        static_cast<int *>(CPLMalloc(sizeof(int) * nCount));

                    for (int i = 0; i < nCount; i++)
                    {
                        panIntList[i] =
                            (strcmp(psGMLProperty->papszSubProperties[i],
                                    "true") == 0 ||
                             strcmp(psGMLProperty->papszSubProperties[i],
                                    "1") == 0);
                    }

                    poOGRFeature->SetField(iDstField, nCount, panIntList);
                    CPLFree(panIntList);
                    break;
                }

                default:
                    poOGRFeature->SetField(
                        iDstField, psGMLProperty->papszSubProperties[0]);
                    break;
            }
        }

        // Assign the geometry before the attribute filter because
        // the attribute filter may use a special field like OGR_GEOMETRY.
        if (!apoGeometries.empty())
        {
            for (int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++)
            {
                poOGRFeature->SetGeomField(i, std::move(apoGeometries[i]));
            }
        }
        else
        {
            poOGRFeature->SetGeometry(std::move(poSingleGeom));
        }

        // Assign SRS.
        for (int i = 0; i < poFeatureDefn->GetGeomFieldCount(); i++)
        {
            OGRGeometry *poGeom = poOGRFeature->GetGeomFieldRef(i);
            if (poGeom != nullptr)
            {
                const OGRSpatialReference *poSRS =
                    poFeatureDefn->GetGeomFieldDefn(i)->GetSpatialRef();
                if (poSRS != nullptr)
                    poGeom->assignSpatialReference(poSRS);
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Test against the attribute query. */
        /* --------------------------------------------------------------------
         */
        if (m_poAttrQuery != nullptr &&
            !m_poAttrQuery->Evaluate(poOGRFeature.get()))
        {
            continue;
        }

        // Got the desired feature.
        return poOGRFeature.release();
    }
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/************************************************************************/

GIntBig OGRGMLLayer::GetFeatureCount(int bForce)

{
    if (poFClass == nullptr)
        return 0;

    if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr)
        return OGRLayer::GetFeatureCount(bForce);

    // If the schema is read from a .xsd file, we haven't read
    // the feature count, so compute it now.
    GIntBig nFeatureCount = poFClass->GetFeatureCount();
    if (nFeatureCount < 0)
    {
        nFeatureCount = OGRLayer::GetFeatureCount(bForce);
        poFClass->SetFeatureCount(nFeatureCount);
    }

    return nFeatureCount;
}

/************************************************************************/
/*                             IGetExtent()                             */
/************************************************************************/

OGRErr OGRGMLLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
                               bool bForce)

{
    if (GetGeomType() == wkbNone)
        return OGRERR_FAILURE;

    double dfXMin = 0.0;
    double dfXMax = 0.0;
    double dfYMin = 0.0;
    double dfYMax = 0.0;
    if (poFClass != nullptr &&
        poFClass->GetExtents(&dfXMin, &dfXMax, &dfYMin, &dfYMax))
    {
        psExtent->MinX = dfXMin;
        psExtent->MaxX = dfXMax;
        psExtent->MinY = dfYMin;
        psExtent->MaxY = dfYMax;

        return OGRERR_NONE;
    }

    return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
}

/************************************************************************/
/*                             GetExtent()                              */
/************************************************************************/

static void GMLWriteField(OGRGMLDataSource *poDS, VSILFILE *fp,
                          bool bWriteSpaceIndentation, const char *pszPrefix,
                          bool bRemoveAppPrefix, OGRFieldDefn *poFieldDefn,
                          const char *pszVal)

{
    const char *pszFieldName = poFieldDefn->GetNameRef();

    while (*pszVal == ' ')
        pszVal++;

    if (bWriteSpaceIndentation)
        VSIFPrintfL(fp, "      ");

    if (bRemoveAppPrefix)
        poDS->PrintLine(fp, "<%s>%s</%s>", pszFieldName, pszVal, pszFieldName);
    else
        poDS->PrintLine(fp, "<%s:%s>%s</%s:%s>", pszPrefix, pszFieldName,
                        pszVal, pszPrefix, pszFieldName);
}

/************************************************************************/
/*                           ICreateFeature()                           */
/************************************************************************/

OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature)

{
    const bool bIsGML3Output = poDS->IsGML3Output();
    VSILFILE *fp = poDS->GetOutputFP();
    const bool bWriteSpaceIndentation = poDS->WriteSpaceIndentation();
    const char *pszPrefix = poDS->GetAppPrefix();
    const bool bRemoveAppPrefix = poDS->RemoveAppPrefix();
    const bool bGMLFeatureCollection = poDS->GMLFeatureCollection();

    if (!bWriter || poDS->HasWriteError())
        return OGRERR_FAILURE;

    poFeature->FillUnsetWithDefault(TRUE, nullptr);
    if (!poFeature->Validate(OGR_F_VAL_ALL & ~OGR_F_VAL_GEOM_TYPE &
                                 ~OGR_F_VAL_ALLOW_NULL_WHEN_DEFAULT,
                             TRUE))
        return OGRERR_FAILURE;

    if (bWriteSpaceIndentation)
        VSIFPrintfL(fp, "  ");
    if (bIsGML3Output && !bGMLFeatureCollection)
    {
        if (bRemoveAppPrefix)
            poDS->PrintLine(fp, "<featureMember>");
        else
            poDS->PrintLine(fp, "<%s:featureMember>", pszPrefix);
    }
    else
    {
        poDS->PrintLine(fp, "<gml:featureMember>");
    }

    if (poFeature->GetFID() == OGRNullFID)
        poFeature->SetFID(m_iNextGMLId++);

    if (bWriteSpaceIndentation)
        VSIFPrintfL(fp, "    ");
    VSIFPrintfL(fp, "<");
    if (!bRemoveAppPrefix)
        VSIFPrintfL(fp, "%s:", pszPrefix);

    int nGMLIdIndex = -1;
    if (bIsGML3Output)
    {
        nGMLIdIndex = poFeatureDefn->GetFieldIndex("gml_id");
        if (nGMLIdIndex >= 0 && poFeature->IsFieldSetAndNotNull(nGMLIdIndex))
            poDS->PrintLine(fp, "%s gml:id=\"%s\">", poFeatureDefn->GetName(),
                            poFeature->GetFieldAsString(nGMLIdIndex));
        else
            poDS->PrintLine(fp, "%s gml:id=\"%s." CPL_FRMT_GIB "\">",
                            poFeatureDefn->GetName(), poFeatureDefn->GetName(),
                            poFeature->GetFID());
    }
    else
    {
        nGMLIdIndex = poFeatureDefn->GetFieldIndex("fid");
        if (bUseOldFIDFormat)
        {
            poDS->PrintLine(fp, "%s fid=\"F" CPL_FRMT_GIB "\">",
                            poFeatureDefn->GetName(), poFeature->GetFID());
        }
        else if (nGMLIdIndex >= 0 &&
                 poFeature->IsFieldSetAndNotNull(nGMLIdIndex))
        {
            poDS->PrintLine(fp, "%s fid=\"%s\">", poFeatureDefn->GetName(),
                            poFeature->GetFieldAsString(nGMLIdIndex));
        }
        else
        {
            poDS->PrintLine(fp, "%s fid=\"%s." CPL_FRMT_GIB "\">",
                            poFeatureDefn->GetName(), poFeatureDefn->GetName(),
                            poFeature->GetFID());
        }
    }

    for (int iGeomField = 0; iGeomField < poFeatureDefn->GetGeomFieldCount();
         iGeomField++)
    {
        const OGRGeomFieldDefn *poFieldDefn =
            poFeatureDefn->GetGeomFieldDefn(iGeomField);

        // Write out Geometry - for now it isn't indented properly.
        // GML geometries don't like very much the concept of empty geometry.
        OGRGeometry *poGeom = poFeature->GetGeomFieldRef(iGeomField);
        if (poGeom != nullptr && !poGeom->IsEmpty())
        {
            OGREnvelope3D sGeomBounds;

            const int nCoordDimension = poGeom->getCoordinateDimension();

            poGeom->getEnvelope(&sGeomBounds);
            if (poDS->HasWriteGlobalSRS())
                poDS->GrowExtents(&sGeomBounds, nCoordDimension);

            if (poGeom->getSpatialReference() == nullptr &&
                poFieldDefn->GetSpatialRef() != nullptr)
                poGeom->assignSpatialReference(poFieldDefn->GetSpatialRef());

            const auto &oCoordPrec = poFieldDefn->GetCoordinatePrecision();

            if (bIsGML3Output && poDS->WriteFeatureBoundedBy())
            {
                bool bCoordSwap = false;

                char *pszSRSName =
                    GML_GetSRSName(poGeom->getSpatialReference(),
                                   poDS->GetSRSNameFormat(), &bCoordSwap);
                char szLowerCorner[75] = {};
                char szUpperCorner[75] = {};

                OGRWktOptions coordOpts;

                if (oCoordPrec.dfXYResolution !=
                    OGRGeomCoordinatePrecision::UNKNOWN)
                {
                    coordOpts.format = OGRWktFormat::F;
                    coordOpts.xyPrecision =
                        OGRGeomCoordinatePrecision::ResolutionToPrecision(
                            oCoordPrec.dfXYResolution);
                }
                if (oCoordPrec.dfZResolution !=
                    OGRGeomCoordinatePrecision::UNKNOWN)
                {
                    coordOpts.format = OGRWktFormat::F;
                    coordOpts.zPrecision =
                        OGRGeomCoordinatePrecision::ResolutionToPrecision(
                            oCoordPrec.dfZResolution);
                }

                std::string wkt;
                if (bCoordSwap)
                {
                    wkt = OGRMakeWktCoordinate(
                        sGeomBounds.MinY, sGeomBounds.MinX, sGeomBounds.MinZ,
                        nCoordDimension, coordOpts);
                    memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);

                    wkt = OGRMakeWktCoordinate(
                        sGeomBounds.MaxY, sGeomBounds.MaxX, sGeomBounds.MaxZ,
                        nCoordDimension, coordOpts);
                    memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
                }
                else
                {
                    wkt = OGRMakeWktCoordinate(
                        sGeomBounds.MinX, sGeomBounds.MinY, sGeomBounds.MinZ,
                        nCoordDimension, coordOpts);
                    memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);

                    wkt = OGRMakeWktCoordinate(
                        sGeomBounds.MaxX, sGeomBounds.MaxY, sGeomBounds.MaxZ,
                        nCoordDimension, coordOpts);
                    memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
                }
                if (bWriteSpaceIndentation)
                    VSIFPrintfL(fp, "      ");
                poDS->PrintLine(
                    fp,
                    "<gml:boundedBy><gml:Envelope%s%s><gml:lowerCorner>%s"
                    "</gml:lowerCorner><gml:upperCorner>%s</gml:upperCorner>"
                    "</gml:Envelope></gml:boundedBy>",
                    (nCoordDimension == 3) ? " srsDimension=\"3\"" : "",
                    pszSRSName, szLowerCorner, szUpperCorner);
                CPLFree(pszSRSName);
            }

            char **papszOptions = nullptr;
            if (bIsGML3Output)
            {
                papszOptions = CSLAddString(papszOptions, "FORMAT=GML3");
                if (poDS->GetSRSNameFormat() == SRSNAME_SHORT)
                    papszOptions =
                        CSLAddString(papszOptions, "SRSNAME_FORMAT=SHORT");
                else if (poDS->GetSRSNameFormat() == SRSNAME_OGC_URN)
                    papszOptions =
                        CSLAddString(papszOptions, "SRSNAME_FORMAT=OGC_URN");
                else if (poDS->GetSRSNameFormat() == SRSNAME_OGC_URL)
                    papszOptions =
                        CSLAddString(papszOptions, "SRSNAME_FORMAT=OGC_URL");
            }
            const char *pszSRSDimensionLoc = poDS->GetSRSDimensionLoc();
            if (pszSRSDimensionLoc != nullptr)
                papszOptions = CSLSetNameValue(papszOptions, "SRSDIMENSION_LOC",
                                               pszSRSDimensionLoc);
            if (poDS->IsGML32Output())
            {
                if (poFeatureDefn->GetGeomFieldCount() > 1)
                    papszOptions = CSLAddString(
                        papszOptions, CPLSPrintf("GMLID=%s.%s." CPL_FRMT_GIB,
                                                 poFeatureDefn->GetName(),
                                                 poFieldDefn->GetNameRef(),
                                                 poFeature->GetFID()));
                else
                    papszOptions = CSLAddString(
                        papszOptions, CPLSPrintf("GMLID=%s.geom." CPL_FRMT_GIB,
                                                 poFeatureDefn->GetName(),
                                                 poFeature->GetFID()));
            }

            if (oCoordPrec.dfXYResolution !=
                OGRGeomCoordinatePrecision::UNKNOWN)
            {
                papszOptions = CSLAddString(
                    papszOptions, CPLSPrintf("XY_COORD_RESOLUTION=%g",
                                             oCoordPrec.dfXYResolution));
            }
            if (oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN)
            {
                papszOptions = CSLAddString(
                    papszOptions, CPLSPrintf("Z_COORD_RESOLUTION=%g",
                                             oCoordPrec.dfZResolution));
            }

            char *pszGeometry = nullptr;
            if (!bIsGML3Output && OGR_GT_IsNonLinear(poGeom->getGeometryType()))
            {
                auto poGeomTmp = OGRGeometryFactory::forceTo(
                    std::unique_ptr<OGRGeometry>(poGeom->clone()),
                    OGR_GT_GetLinear(poGeom->getGeometryType()));
                pszGeometry = poGeomTmp->exportToGML(papszOptions);
            }
            else
            {
                if (wkbFlatten(poGeom->getGeometryType()) == wkbTriangle)
                {
                    pszGeometry = poGeom->exportToGML(papszOptions);

                    const char *pszGMLID =
                        poDS->IsGML32Output()
                            ? CPLSPrintf(
                                  " gml:id=\"%s\"",
                                  CSLFetchNameValue(papszOptions, "GMLID"))
                            : "";
                    char *pszNewGeom = CPLStrdup(
                        CPLSPrintf("<gml:TriangulatedSurface%s><gml:patches>%s<"
                                   "/gml:patches></gml:TriangulatedSurface>",
                                   pszGMLID, pszGeometry));
                    CPLFree(pszGeometry);
                    pszGeometry = pszNewGeom;
                }
                else
                {
                    pszGeometry = poGeom->exportToGML(papszOptions);
                }
            }
            CSLDestroy(papszOptions);
            if (pszGeometry)
            {
                if (bWriteSpaceIndentation)
                    VSIFPrintfL(fp, "      ");
                if (bRemoveAppPrefix)
                    poDS->PrintLine(fp, "<%s>%s</%s>",
                                    poFieldDefn->GetNameRef(), pszGeometry,
                                    poFieldDefn->GetNameRef());
                else
                    poDS->PrintLine(fp, "<%s:%s>%s</%s:%s>", pszPrefix,
                                    poFieldDefn->GetNameRef(), pszGeometry,
                                    pszPrefix, poFieldDefn->GetNameRef());
            }
            else
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Export of geometry to GML failed");
            }
            CPLFree(pszGeometry);
        }
    }

    // Write all "set" fields.
    for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
    {
        if (iField == nGMLIdIndex)
            continue;
        OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(iField);

        if (poFeature->IsFieldNull(iField))
        {
            const char *pszFieldName = poFieldDefn->GetNameRef();

            if (bWriteSpaceIndentation)
                VSIFPrintfL(fp, "      ");

            if (bRemoveAppPrefix)
                poDS->PrintLine(fp, "<%s xsi:nil=\"true\"/>", pszFieldName);
            else
                poDS->PrintLine(fp, "<%s:%s xsi:nil=\"true\"/>", pszPrefix,
                                pszFieldName);
        }
        else if (poFeature->IsFieldSet(iField))
        {
            OGRFieldType eType = poFieldDefn->GetType();
            if (eType == OFTStringList)
            {
                char **papszIter = poFeature->GetFieldAsStringList(iField);
                while (papszIter != nullptr && *papszIter != nullptr)
                {
                    char *pszEscaped = OGRGetXML_UTF8_EscapedString(*papszIter);
                    GMLWriteField(poDS, fp, bWriteSpaceIndentation, pszPrefix,
                                  bRemoveAppPrefix, poFieldDefn, pszEscaped);
                    CPLFree(pszEscaped);

                    papszIter++;
                }
            }
            else if (eType == OFTIntegerList)
            {
                int nCount = 0;
                const int *panVals =
                    poFeature->GetFieldAsIntegerList(iField, &nCount);
                if (poFieldDefn->GetSubType() == OFSTBoolean)
                {
                    for (int i = 0; i < nCount; i++)
                    {
                        // 0 and 1 are OK, but the canonical representation is
                        // false and true.
                        GMLWriteField(poDS, fp, bWriteSpaceIndentation,
                                      pszPrefix, bRemoveAppPrefix, poFieldDefn,
                                      panVals[i] ? "true" : "false");
                    }
                }
                else
                {
                    for (int i = 0; i < nCount; i++)
                    {
                        GMLWriteField(poDS, fp, bWriteSpaceIndentation,
                                      pszPrefix, bRemoveAppPrefix, poFieldDefn,
                                      CPLSPrintf("%d", panVals[i]));
                    }
                }
            }
            else if (eType == OFTInteger64List)
            {
                int nCount = 0;
                const GIntBig *panVals =
                    poFeature->GetFieldAsInteger64List(iField, &nCount);
                if (poFieldDefn->GetSubType() == OFSTBoolean)
                {
                    for (int i = 0; i < nCount; i++)
                    {
                        // 0 and 1 are OK, but the canonical representation is
                        // false and true.
                        GMLWriteField(poDS, fp, bWriteSpaceIndentation,
                                      pszPrefix, bRemoveAppPrefix, poFieldDefn,
                                      panVals[i] ? "true" : "false");
                    }
                }
                else
                {
                    for (int i = 0; i < nCount; i++)
                    {
                        GMLWriteField(poDS, fp, bWriteSpaceIndentation,
                                      pszPrefix, bRemoveAppPrefix, poFieldDefn,
                                      CPLSPrintf(CPL_FRMT_GIB, panVals[i]));
                    }
                }
            }
            else if (eType == OFTRealList)
            {
                int nCount = 0;
                const double *padfVals =
                    poFeature->GetFieldAsDoubleList(iField, &nCount);
                for (int i = 0; i < nCount; i++)
                {
                    char szBuffer[80] = {};
                    CPLsnprintf(szBuffer, sizeof(szBuffer), "%.15g",
                                padfVals[i]);
                    GMLWriteField(poDS, fp, bWriteSpaceIndentation, pszPrefix,
                                  bRemoveAppPrefix, poFieldDefn, szBuffer);
                }
            }
            else if ((eType == OFTInteger || eType == OFTInteger64) &&
                     poFieldDefn->GetSubType() == OFSTBoolean)
            {
                // 0 and 1 are OK, but the canonical representation is false and
                // true.
                GMLWriteField(poDS, fp, bWriteSpaceIndentation, pszPrefix,
                              bRemoveAppPrefix, poFieldDefn,
                              (poFeature->GetFieldAsInteger(iField)) ? "true"
                                                                     : "false");
            }
            else if (eType == OFTDate)
            {
                const OGRField *poField = poFeature->GetRawFieldRef(iField);
                const char *pszXML =
                    CPLSPrintf("%04d-%02d-%02d", poField->Date.Year,
                               poField->Date.Month, poField->Date.Day);
                GMLWriteField(poDS, fp, bWriteSpaceIndentation, pszPrefix,
                              bRemoveAppPrefix, poFieldDefn, pszXML);
            }
            else if (eType == OFTDateTime)
            {
                char *pszXML =
                    OGRGetXMLDateTime(poFeature->GetRawFieldRef(iField));
                GMLWriteField(poDS, fp, bWriteSpaceIndentation, pszPrefix,
                              bRemoveAppPrefix, poFieldDefn, pszXML);
                CPLFree(pszXML);
            }
            else
            {
                const char *pszRaw = poFeature->GetFieldAsString(iField);

                char *pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);

                GMLWriteField(poDS, fp, bWriteSpaceIndentation, pszPrefix,
                              bRemoveAppPrefix, poFieldDefn, pszEscaped);
                CPLFree(pszEscaped);
            }
        }
    }

    if (bWriteSpaceIndentation)
        VSIFPrintfL(fp, "    ");
    if (bRemoveAppPrefix)
        poDS->PrintLine(fp, "</%s>", poFeatureDefn->GetName());
    else
        poDS->PrintLine(fp, "</%s:%s>", pszPrefix, poFeatureDefn->GetName());
    if (bWriteSpaceIndentation)
        VSIFPrintfL(fp, "  ");
    if (bIsGML3Output && !bGMLFeatureCollection)
    {
        if (bRemoveAppPrefix)
            poDS->PrintLine(fp, "</featureMember>");
        else
            poDS->PrintLine(fp, "</%s:featureMember>", pszPrefix);
    }
    else
    {
        poDS->PrintLine(fp, "</gml:featureMember>");
    }

    return !poDS->HasWriteError() ? OGRERR_NONE : OGRERR_FAILURE;
}

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

int OGRGMLLayer::TestCapability(const char *pszCap) const

{
    if (EQUAL(pszCap, OLCSequentialWrite))
        return bWriter;

    else if (EQUAL(pszCap, OLCCreateField))
        return bWriter && m_iNextGMLId == 0;

    else if (EQUAL(pszCap, OLCCreateGeomField))
        return bWriter && m_iNextGMLId == 0;

    else if (EQUAL(pszCap, OLCFastGetExtent))
    {
        if (poFClass == nullptr)
            return FALSE;

        double dfXMin = 0.0;
        double dfXMax = 0.0;
        double dfYMin = 0.0;
        double dfYMax = 0.0;

        return poFClass->GetExtents(&dfXMin, &dfXMax, &dfYMin, &dfYMax);
    }

    else if (EQUAL(pszCap, OLCFastFeatureCount))
    {
        if (poFClass == nullptr || m_poFilterGeom != nullptr ||
            m_poAttrQuery != nullptr)
            return FALSE;

        return poFClass->GetFeatureCount() != -1;
    }

    else if (EQUAL(pszCap, OLCStringsAsUTF8))
        return TRUE;

    else if (EQUAL(pszCap, OLCCurveGeometries))
        return poDS->IsGML3Output();

    else if (EQUAL(pszCap, OLCZGeometries))
        return TRUE;

    else
        return FALSE;
}

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

OGRErr OGRGMLLayer::CreateField(const OGRFieldDefn *poField, int bApproxOK)

{
    if (!bWriter || m_iNextGMLId != 0)
        return OGRERR_FAILURE;

    /* -------------------------------------------------------------------- */
    /*      Enforce XML naming semantics on element name.                   */
    /* -------------------------------------------------------------------- */
    OGRFieldDefn oCleanCopy(poField);
    char *pszName = CPLStrdup(poField->GetNameRef());
    CPLCleanXMLElementName(pszName);

    if (strcmp(pszName, poField->GetNameRef()) != 0)
    {
        if (!bApproxOK)
        {
            CPLFree(pszName);
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Unable to create field with name '%s', it would not\n"
                     "be valid as an XML element name.",
                     poField->GetNameRef());
            return OGRERR_FAILURE;
        }

        oCleanCopy.SetName(pszName);
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Field name '%s' adjusted to '%s' to be a valid\n"
                 "XML element name.",
                 poField->GetNameRef(), pszName);
    }

    CPLFree(pszName);

    poFeatureDefn->AddFieldDefn(&oCleanCopy);

    return OGRERR_NONE;
}

/************************************************************************/
/*                          CreateGeomField()                           */
/************************************************************************/

OGRErr OGRGMLLayer::CreateGeomField(const OGRGeomFieldDefn *poField,
                                    int bApproxOK)

{
    if (!bWriter || m_iNextGMLId != 0)
        return OGRERR_FAILURE;

    /* -------------------------------------------------------------------- */
    /*      Enforce XML naming semantics on element name.                   */
    /* -------------------------------------------------------------------- */
    OGRGeomFieldDefn oCleanCopy(poField);
    const auto poSRSOri = poField->GetSpatialRef();
    poDS->DeclareNewWriteSRS(poSRSOri);
    if (poSRSOri)
    {
        auto poSRSClone = OGRSpatialReferenceRefCountedPtr::makeClone(poSRSOri);
        poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
        oCleanCopy.SetSpatialRef(poSRSClone.get());
    }
    char *pszName = CPLStrdup(poField->GetNameRef());
    CPLCleanXMLElementName(pszName);

    if (strcmp(pszName, poField->GetNameRef()) != 0)
    {
        if (!bApproxOK)
        {
            CPLFree(pszName);
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Unable to create field with name '%s', it would not\n"
                     "be valid as an XML element name.",
                     poField->GetNameRef());
            return OGRERR_FAILURE;
        }

        oCleanCopy.SetName(pszName);
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Field name '%s' adjusted to '%s' to be a valid\n"
                 "XML element name.",
                 poField->GetNameRef(), pszName);
    }

    CPLFree(pszName);

    poFeatureDefn->AddGeomFieldDefn(&oCleanCopy);

    return OGRERR_NONE;
}

/************************************************************************/
/*                             GetDataset()                             */
/************************************************************************/

GDALDataset *OGRGMLLayer::GetDataset()
{
    return poDS;
}
