/******************************************************************************
 *
 * Project:  LV BAG Translator
 * Purpose:  Implements OGRLVBAGLayer.
 * Author:   Laixer B.V., info at laixer dot com
 *
 ******************************************************************************
 * Copyright (c) 2021, Laixer B.V. <info at laixer dot 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 "cpl_conv.h"
#include "ogr_geos.h"
#include "ogr_lvbag.h"
#include "ogr_p.h"

constexpr const char *pszSpecificationUrn = "urn:ogc:def:crs:EPSG::28992";
constexpr const size_t nDefaultIdentifierSize = 16;

/************************************************************************/
/*                           OGRLVBAGLayer()                            */
/*                                                                      */
/*      Note that the OGRLVBAGLayer assumes ownership of the passed     */
/*      file pointer.                                                   */
/************************************************************************/

OGRLVBAGLayer::OGRLVBAGLayer(const char *pszFilename, OGRLayerPool *poPoolIn,
                             char **papszOpenOptions)
    : OGRAbstractProxiedLayer{poPoolIn},
      poFeatureDefn{new OGRFeatureDefn{}}, fp{nullptr}, osFilename{pszFilename},
      eFileDescriptorsState{FD_CLOSED}, oParser{nullptr}, bSchemaOnly{false},
      bHasReadSchema{false}, bFixInvalidData{CPLFetchBool(
                                 papszOpenOptions, "AUTOCORRECT_INVALID_DATA",
                                 false)},
      bLegacyId{CPLFetchBool(papszOpenOptions, "LEGACY_ID", false)},
      nNextFID{0}, nCurrentDepth{0}, nGeometryElementDepth{0},
      nFeatureCollectionDepth{0}, nFeatureElementDepth{0},
      nAttributeElementDepth{0},
      eAddressRefState{AddressRefState::ADDRESS_PRIMARY}, bCollectData{false}
{
    SetDescription(CPLGetBasename(pszFilename));

    poFeatureDefn->Reference();

    memset(aBuf, '\0', sizeof(aBuf));
}

/************************************************************************/
/*                           ~OGRLVBAGLayer()                           */
/************************************************************************/

OGRLVBAGLayer::~OGRLVBAGLayer()
{
    delete m_poFeature;
    poFeatureDefn->Release();
    OGRLVBAGLayer::CloseUnderlyingLayer();
}

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

void OGRLVBAGLayer::ResetReading()
{
    if (!TouchLayer())
        return;

    VSIRewindL(fp);

    nNextFID = 0;
    nCurrentDepth = 0;
    nGeometryElementDepth = 0;
    nFeatureCollectionDepth = 0;
    nFeatureElementDepth = 0;
    nAttributeElementDepth = 0;
    eAddressRefState = AddressRefState::ADDRESS_PRIMARY;
    bCollectData = false;
}

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

OGRFeatureDefn *OGRLVBAGLayer::GetLayerDefn()
{
    if (!TouchLayer())
        return nullptr;

    if (!bHasReadSchema)
    {
        bSchemaOnly = true;

        ConfigureParser();
        ParseDocument();
    }

    return poFeatureDefn;
}

/************************************************************************/
/*                            XMLTagSplit()                             */
/************************************************************************/

static inline const char *XMLTagSplit(const char *pszName)
{
    const char *pszTag = pszName;
    const char *pszSep = strchr(pszTag, ':');
    if (pszSep)
        pszTag = pszSep + 1;

    return pszTag;
}

/************************************************************************/
/*                           AddSpatialRef()                            */
/************************************************************************/

void OGRLVBAGLayer::AddSpatialRef(OGRwkbGeometryType eTypeIn)
{
    OGRGeomFieldDefn *poGeomField = poFeatureDefn->GetGeomFieldDefn(0);
    OGRSpatialReference *poSRS = new OGRSpatialReference();
    poSRS->importFromURN(pszSpecificationUrn);
    poGeomField->SetSpatialRef(poSRS);
    poGeomField->SetType(eTypeIn);
    poSRS->Release();
}

/************************************************************************/
/*                      AddIdentifierFieldDefn()                        */
/************************************************************************/

void OGRLVBAGLayer::AddIdentifierFieldDefn()
{
    OGRFieldDefn oField0("identificatie", OFTString);

    poFeatureDefn->AddFieldDefn(&oField0);
}

/************************************************************************/
/*                       AddDocumentFieldDefn()                         */
/************************************************************************/

void OGRLVBAGLayer::AddDocumentFieldDefn()
{
    OGRFieldDefn oField0("status", OFTString);
    OGRFieldDefn oField1("geconstateerd", OFTInteger);
    oField1.SetSubType(OFSTBoolean);
    OGRFieldDefn oField2("documentDatum", OFTDate);
    OGRFieldDefn oField3("documentNummer", OFTString);

    poFeatureDefn->AddFieldDefn(&oField0);
    poFeatureDefn->AddFieldDefn(&oField1);
    poFeatureDefn->AddFieldDefn(&oField2);
    poFeatureDefn->AddFieldDefn(&oField3);
}

/************************************************************************/
/*                      AddOccurrenceFieldDefn()                        */
/************************************************************************/

void OGRLVBAGLayer::AddOccurrenceFieldDefn()
{
    OGRFieldDefn oField0("voorkomenIdentificatie", OFTInteger);
    OGRFieldDefn oField1("beginGeldigheid", OFTDate);
    OGRFieldDefn oField2("eindGeldigheid", OFTDate);
    OGRFieldDefn oField3("tijdstipRegistratie", OFTDateTime);
    OGRFieldDefn oField4("eindRegistratie", OFTDateTime);
    OGRFieldDefn oField5("tijdstipInactief", OFTDateTime);
    OGRFieldDefn oField6("tijdstipRegistratieLV", OFTDateTime);
    OGRFieldDefn oField7("tijdstipEindRegistratieLV", OFTDateTime);
    OGRFieldDefn oField8("tijdstipInactiefLV", OFTDateTime);
    OGRFieldDefn oField9("tijdstipNietBagLV", OFTDateTime);

    poFeatureDefn->AddFieldDefn(&oField0);
    poFeatureDefn->AddFieldDefn(&oField1);
    poFeatureDefn->AddFieldDefn(&oField2);
    poFeatureDefn->AddFieldDefn(&oField3);
    poFeatureDefn->AddFieldDefn(&oField4);
    poFeatureDefn->AddFieldDefn(&oField5);
    poFeatureDefn->AddFieldDefn(&oField6);
    poFeatureDefn->AddFieldDefn(&oField7);
    poFeatureDefn->AddFieldDefn(&oField8);
    poFeatureDefn->AddFieldDefn(&oField9);
}

/************************************************************************/
/*                         CreateFeatureDefn()                          */
/************************************************************************/

void OGRLVBAGLayer::CreateFeatureDefn(const char *pszDataset)
{
    if (EQUAL("pnd", pszDataset))
    {
        OGRFieldDefn oField0("oorspronkelijkBouwjaar", OFTInteger);

        poFeatureDefn->AddFieldDefn(&oField0);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Pand");
        SetDescription(poFeatureDefn->GetName());

        AddSpatialRef(wkbPolygon);
    }
    else if (EQUAL("num", pszDataset))
    {
        OGRFieldDefn oField0("huisnummer", OFTInteger);
        OGRFieldDefn oField1("huisletter", OFTString);
        OGRFieldDefn oField2("huisnummerToevoeging", OFTString);
        OGRFieldDefn oField3("postcode", OFTString);
        OGRFieldDefn oField4("typeAdresseerbaarObject", OFTString);
        OGRFieldDefn oField5("openbareruimteRef", OFTString);
        OGRFieldDefn oField6("woonplaatsRef", OFTString);

        poFeatureDefn->AddFieldDefn(&oField0);
        poFeatureDefn->AddFieldDefn(&oField1);
        poFeatureDefn->AddFieldDefn(&oField2);
        poFeatureDefn->AddFieldDefn(&oField3);
        poFeatureDefn->AddFieldDefn(&oField4);
        poFeatureDefn->AddFieldDefn(&oField5);
        poFeatureDefn->AddFieldDefn(&oField6);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Nummeraanduiding");
        SetDescription(poFeatureDefn->GetName());
    }
    else if (EQUAL("lig", pszDataset))
    {
        OGRFieldDefn oField0("hoofdadresNummeraanduidingRef", OFTString);
        OGRFieldDefn oField1("nevenadresNummeraanduidingRef", OFTStringList);

        poFeatureDefn->AddFieldDefn(&oField0);
        poFeatureDefn->AddFieldDefn(&oField1);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Ligplaats");
        SetDescription(poFeatureDefn->GetName());

        AddSpatialRef(wkbPolygon);
    }
    else if (EQUAL("sta", pszDataset))
    {
        OGRFieldDefn oField0("hoofdadresNummeraanduidingRef", OFTString);
        OGRFieldDefn oField1("nevenadresNummeraanduidingRef", OFTStringList);

        poFeatureDefn->AddFieldDefn(&oField0);
        poFeatureDefn->AddFieldDefn(&oField1);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Standplaats");
        SetDescription(poFeatureDefn->GetName());

        AddSpatialRef(wkbPolygon);
    }
    else if (EQUAL("opr", pszDataset))
    {
        OGRFieldDefn oField0("naam", OFTString);
        OGRFieldDefn oField1("type", OFTString);
        OGRFieldDefn oField2("woonplaatsRef", OFTString);
        OGRFieldDefn oField3("verkorteNaam", OFTString);

        poFeatureDefn->AddFieldDefn(&oField0);
        poFeatureDefn->AddFieldDefn(&oField1);
        poFeatureDefn->AddFieldDefn(&oField2);
        poFeatureDefn->AddFieldDefn(&oField3);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Openbareruimte");
        SetDescription(poFeatureDefn->GetName());
    }
    else if (EQUAL("vbo", pszDataset))
    {
        OGRFieldDefn oField0("gebruiksdoel", OFTStringList);
        OGRFieldDefn oField1("oppervlakte", OFTInteger);
        OGRFieldDefn oField2("hoofdadresNummeraanduidingRef", OFTString);
        OGRFieldDefn oField3("nevenadresNummeraanduidingRef", OFTStringList);
        OGRFieldDefn oField4("pandRef", OFTStringList);

        poFeatureDefn->AddFieldDefn(&oField0);
        poFeatureDefn->AddFieldDefn(&oField1);
        poFeatureDefn->AddFieldDefn(&oField2);
        poFeatureDefn->AddFieldDefn(&oField3);
        poFeatureDefn->AddFieldDefn(&oField4);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Verblijfsobject");
        SetDescription(poFeatureDefn->GetName());

        AddSpatialRef(wkbPoint);
    }
    else if (EQUAL("wpl", pszDataset))
    {
        OGRFieldDefn oField0("naam", OFTString);

        poFeatureDefn->AddFieldDefn(&oField0);

        AddIdentifierFieldDefn();
        AddDocumentFieldDefn();
        AddOccurrenceFieldDefn();

        poFeatureDefn->SetName("Woonplaats");
        SetDescription(poFeatureDefn->GetName());

        AddSpatialRef(wkbMultiPolygon);
    }
    else
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Parsing LV BAG extract failed : invalid layer definition");
}

/************************************************************************/
/*                         StartDataCollect()                           */
/************************************************************************/

void OGRLVBAGLayer::StartDataCollect()
{
    osElementString.Clear();
    osAttributeString.Clear();
    bCollectData = true;
}

/************************************************************************/
/*                         StopDataCollect()                            */
/************************************************************************/

void OGRLVBAGLayer::StopDataCollect()
{
    bCollectData = false;
    osElementString.Trim();
    osAttributeString.Trim();
}

/************************************************************************/
/*                           DataHandlerCbk()                           */
/************************************************************************/

void OGRLVBAGLayer::DataHandlerCbk(const char *data, int nLen)
{
    if (nLen && bCollectData)
        osElementString.append(data, nLen);
}

/************************************************************************/
/*                              TouchLayer()                            */
/************************************************************************/

bool OGRLVBAGLayer::TouchLayer()
{
    poPool->SetLastUsedLayer(this);

    switch (eFileDescriptorsState)
    {
        case FD_OPENED:
            return true;
        case FD_CANNOT_REOPEN:
            return false;
        case FD_CLOSED:
            break;
    }

    fp = VSIFOpenExL(osFilename, "rb", true);
    if (!fp)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Opening LV BAG extract failed : %s", osFilename.c_str());
        eFileDescriptorsState = FD_CANNOT_REOPEN;
        return false;
    }

    eFileDescriptorsState = FD_OPENED;

    return true;
}

/************************************************************************/
/*                        CloseUnderlyingLayer()                        */
/************************************************************************/

void OGRLVBAGLayer::CloseUnderlyingLayer()
{
    if (fp)
        VSIFCloseL(fp);
    fp = nullptr;

    eFileDescriptorsState = FD_CLOSED;
}

/************************************************************************/
/*                        startElementCbk()                            */
/************************************************************************/

void OGRLVBAGLayer::StartElementCbk(const char *pszName, const char **ppszAttr)
{
    if (nFeatureElementDepth > 0 && nAttributeElementDepth > 0 &&
        nGeometryElementDepth == 0 && EQUAL("objecten:geometrie", pszName))
    {
        nGeometryElementDepth = nCurrentDepth;
        StartDataCollect();
    }
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth > 0 &&
             nGeometryElementDepth + 1 == nCurrentDepth &&
             !STARTS_WITH_CI(pszName, "gml"))
    {
        nGeometryElementDepth = nCurrentDepth;
        StartDataCollect();
    }
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth == 0 &&
             nGeometryElementDepth == 0 && STARTS_WITH_CI(pszName, "objecten"))
        nAttributeElementDepth = nCurrentDepth;
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth > 0 &&
             nGeometryElementDepth == 0 &&
             (EQUAL("objecten:identificatie", pszName) ||
              STARTS_WITH_CI(pszName, "objecten-ref")))
    {
        StartDataCollect();
        const char **papszIter = ppszAttr;
        while (papszIter && *papszIter != nullptr)
        {
            if (EQUAL("domein", papszIter[0]))
            {
                osAttributeString = papszIter[1];
                break;
            }
            papszIter += 2;
        }
    }
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth > 0 &&
             nGeometryElementDepth == 0 &&
             EQUAL("objecten:heeftalshoofdadres", pszName))
        eAddressRefState = AddressRefState::ADDRESS_PRIMARY;
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth > 0 &&
             nGeometryElementDepth == 0 &&
             EQUAL("objecten:heeftalsnevenadres", pszName))
        eAddressRefState = AddressRefState::ADDRESS_SECONDARY;
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth > 0 &&
             nGeometryElementDepth == 0)
        StartDataCollect();
    else if (nGeometryElementDepth > 0 && STARTS_WITH_CI(pszName, "gml"))
    {
        osElementString += "<";
        osElementString += pszName;

        const char **papszIter = ppszAttr;
        while (papszIter && *papszIter != nullptr)
        {
            OGRGeomFieldDefn *poGeomField = poFeatureDefn->GetGeomFieldDefn(0);
            if (EQUAL("srsname", papszIter[0]) &&
                poGeomField->GetSpatialRef() == nullptr)
            {
                OGRSpatialReference *poSRS = new OGRSpatialReference();
                poSRS->importFromURN(papszIter[1]);
                poGeomField->SetSpatialRef(poSRS);
                poSRS->Release();
            }

            osElementString += " ";
            osElementString += papszIter[0];
            osElementString += "=\"";
            osElementString += papszIter[1];
            osElementString += "\"";
            papszIter += 2;
        }

        osElementString += ">";
    }
    else if (nFeatureCollectionDepth > 0 && nFeatureElementDepth == 0 &&
             EQUAL("sl-bag-extract:bagObject", pszName) && bHasReadSchema)
    {
        nFeatureElementDepth = nCurrentDepth;
        m_poFeature = new OGRFeature(poFeatureDefn);
        m_poFeature->SetFID(nNextFID++);
    }
    else if (nFeatureCollectionDepth == 0 && EQUAL("sl:standBestand", pszName))
        nFeatureCollectionDepth = nCurrentDepth;
    else if (nFeatureCollectionDepth > 0 && EQUAL("sl:objectType", pszName))
        StartDataCollect();

    nCurrentDepth++;
}

/************************************************************************/
/*                           endElementCbk()                            */
/************************************************************************/

void OGRLVBAGLayer::EndElementCbk(const char *pszName)
{
    nCurrentDepth--;

    if (nCurrentDepth > nAttributeElementDepth && nAttributeElementDepth > 0 &&
        nGeometryElementDepth == 0)
    {
        const char *pszTag = XMLTagSplit(pszName);

        StopDataCollect();
        if (!osElementString.empty())
        {
            int iFieldIndex = poFeatureDefn->GetFieldIndex(pszTag);

            if (EQUAL("nummeraanduidingref", pszTag))
            {
                switch (eAddressRefState)
                {
                    case AddressRefState::ADDRESS_SECONDARY:
                        iFieldIndex = poFeatureDefn->GetFieldIndex(
                            "nevenadresnummeraanduidingref");
                        break;

                    default:
                        iFieldIndex = poFeatureDefn->GetFieldIndex(
                            "hoofdadresnummeraanduidingref");
                        break;
                }
            }

            if (EQUAL("identificatie", pszTag) ||
                STARTS_WITH_CI(pszName, "objecten-ref"))
            {
                bool bIsIdInvalid = false;
                if (osElementString.size() == nDefaultIdentifierSize - 1)
                {
                    osElementString = '0' + osElementString;
                }
                else if (osElementString.size() > nDefaultIdentifierSize)
                {
                    bIsIdInvalid = true;
                    m_poFeature->SetFieldNull(iFieldIndex);
                    CPLError(CE_Warning, CPLE_AppDefined,
                             "Invalid identificatie : %s, value set to null",
                             osElementString.c_str());
                }
                if (!bIsIdInvalid)
                {
                    if (!bLegacyId && !osAttributeString.empty())
                    {
                        osElementString =
                            osAttributeString + '.' + osElementString;
                    }
                }
            }

            if (iFieldIndex > -1)
            {
                const OGRFieldDefn *poFieldDefn =
                    poFeatureDefn->GetFieldDefn(iFieldIndex);
                if (poFieldDefn->GetType() == OFTStringList)
                {
                    if (m_poFeature->IsFieldSetAndNotNull(iFieldIndex))
                    {
                        CPLStringList aoList;
                        char **papszIter =
                            m_poFeature->GetFieldAsStringList(iFieldIndex);
                        while (papszIter != nullptr && *papszIter != nullptr)
                        {
                            aoList.AddString(*papszIter);
                            papszIter++;
                        }

                        aoList.AddString(osElementString.c_str());
                        m_poFeature->UnsetField(iFieldIndex);
                        m_poFeature->SetField(iFieldIndex, aoList.List());
                    }
                    else
                        m_poFeature->SetField(iFieldIndex,
                                              osElementString.c_str());
                }
                else if (poFieldDefn->GetSubType() ==
                         OGRFieldSubType::OFSTBoolean)
                {
                    if (EQUAL("n", osElementString.c_str()))
                        m_poFeature->SetField(iFieldIndex, 0);
                    else if (EQUAL("j", osElementString.c_str()))
                        m_poFeature->SetField(iFieldIndex, 1);
                    else
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Parsing boolean failed");
                        XML_StopParser(oParser.get(), XML_FALSE);
                    }
                }
                else
                    m_poFeature->SetField(iFieldIndex, osElementString.c_str());

                if (bFixInvalidData && (poFieldDefn->GetType() == OFTDate ||
                                        poFieldDefn->GetType() == OFTDateTime))
                {
                    int nYear;
                    m_poFeature->GetFieldAsDateTime(
                        iFieldIndex, &nYear, nullptr, nullptr, nullptr, nullptr,
                        static_cast<float *>(nullptr), nullptr);
                    if (nYear > 2100)
                    {
                        m_poFeature->SetFieldNull(iFieldIndex);
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "Invalid date : %s, value set to null",
                                 osElementString.c_str());
                    }
                }
            }
            osElementString.Clear();
        }
    }
    else if (nAttributeElementDepth == nCurrentDepth)
        nAttributeElementDepth = 0;
    else if (nGeometryElementDepth > 0 && nCurrentDepth > nGeometryElementDepth)
    {
        osElementString += "</";
        osElementString += pszName;
        osElementString += ">";
    }
    else if (nGeometryElementDepth == nCurrentDepth)
    {
        StopDataCollect();
        if (!osElementString.empty())
        {
            std::unique_ptr<OGRGeometry> poGeom =
                std::unique_ptr<OGRGeometry>(OGRGeometry::FromHandle(
                    OGR_G_CreateFromGML(osElementString.c_str())));
            if (poGeom && !poGeom->IsEmpty())
            {
                // The specification only accounts for 2-dimensional datasets
                if (poGeom->Is3D())
                    poGeom->flattenTo2D();

// GEOS >= 3.8.0 for MakeValid.
#ifdef HAVE_GEOS
#if GEOS_VERSION_MAJOR > 3 ||                                                  \
    (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 8)
                if (!poGeom->IsValid() && bFixInvalidData)
                {
                    std::unique_ptr<OGRGeometry> poSubGeom =
                        std::unique_ptr<OGRGeometry>{poGeom->MakeValid()};
                    if (poSubGeom && poSubGeom->IsValid())
                        poGeom.reset(poSubGeom.release());
                }
#endif
#endif

                OGRGeomFieldDefn *poGeomField =
                    poFeatureDefn->GetGeomFieldDefn(0);
                if (!poGeomField->GetSpatialRef())
                    poGeomField->SetSpatialRef(poGeom->getSpatialReference());
                if (poGeomField->GetType() == wkbUnknown)
                    poGeomField->SetType(poGeom->getGeometryType());

                if (poGeomField->GetType() == wkbPoint)
                {
                    switch (poGeom->getGeometryType())
                    {
                        case wkbPolygon:
                        case wkbMultiPolygon:
                        {
                            auto poPoint = cpl::make_unique<OGRPoint>();
#ifdef HAVE_GEOS
                            if (poGeom->Centroid(poPoint.get()) == OGRERR_NONE)
                                poGeom.reset(poPoint.release());
#else
                            CPLError(CE_Warning, CPLE_AppDefined,
                                     "Cannot shape geometry, GEOS support not "
                                     "enabled.");
                            poGeom.reset(poPoint.release());
#endif
                            break;
                        }

                        default:
                            break;
                    }
                }
                else if (poGeomField->GetType() == wkbMultiPolygon &&
                         poGeom->getGeometryType() == wkbPolygon)
                {
                    auto poMultiPolygon = cpl::make_unique<OGRMultiPolygon>();
                    poMultiPolygon->addGeometry(poGeom.get());
                    poGeom.reset(poMultiPolygon.release());
                }
                else if (poGeomField->GetType() == wkbMultiPolygon &&
                         poGeom->getGeometryType() == wkbGeometryCollection &&
                         poGeom->toGeometryCollection()->getNumGeometries() >
                             0 &&
                         poGeom->toGeometryCollection()
                                 ->getGeometryRef(0)
                                 ->getGeometryType() == wkbPolygon)
                {
                    auto poMultiPolygon = cpl::make_unique<OGRMultiPolygon>();
                    for (const auto &poChildGeom :
                         poGeom->toGeometryCollection())
                        poMultiPolygon->addGeometry(poChildGeom);
                    poGeom.reset(poMultiPolygon.release());
                }
                else if (poGeomField->GetType() == wkbPolygon &&
                         (poGeom->getGeometryType() == wkbMultiPolygon ||
                          poGeom->getGeometryType() == wkbGeometryCollection))
                {
                    const OGRPolygon *poSubGeomLargest = nullptr;
                    for (const auto &poChildGeom :
                         poGeom->toGeometryCollection())
                    {
                        if (poChildGeom->getGeometryType() == wkbPolygon)
                        {
                            if (!poSubGeomLargest)
                                poSubGeomLargest = poChildGeom->toPolygon();
                            else if (poChildGeom->toPolygon()->get_Area() >
                                     poSubGeomLargest->get_Area())
                                poSubGeomLargest = poChildGeom->toPolygon();
                        }
                    }
                    if (poSubGeomLargest)
                        poGeom.reset(poSubGeomLargest->clone());
                }

                if (poGeomField->GetSpatialRef())
                    poGeom->assignSpatialReference(
                        poGeomField->GetSpatialRef());
                m_poFeature->SetGeometryDirectly(poGeom.release());
            }
            else
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Parsing geometry as GML failed");
                XML_StopParser(oParser.get(), XML_FALSE);
            }
        }

        osElementString.Clear();
        osAttributeString.Clear();
        nGeometryElementDepth = 0;
    }
    else if (nFeatureElementDepth == nCurrentDepth)
    {
        nFeatureElementDepth = 0;
        XML_StopParser(oParser.get(), XML_TRUE);
    }
    else if (nFeatureCollectionDepth == nCurrentDepth)
        nFeatureCollectionDepth = 0;
    else if (EQUAL("sl:objecttype", pszName) && !poFeatureDefn->GetFieldCount())
    {
        StopDataCollect();
        if (osElementString.empty())
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Parsing LV BAG extract failed");
            XML_StopParser(oParser.get(), XML_FALSE);
        }

        if (!bHasReadSchema)
            CreateFeatureDefn(osElementString.c_str());
        bHasReadSchema = true;

        // The parser is suspended but never resumed. Stop
        // without resume indicated an error.
        if (bSchemaOnly)
            XML_StopParser(oParser.get(), XML_TRUE);
    }
}

/************************************************************************/
/*                          ConfigureParser()                           */
/************************************************************************/

void OGRLVBAGLayer::ConfigureParser()
{
    ResetReading();

    const auto startElementWrapper =
        [](void *pUserData, const char *pszName, const char **ppszAttr)
    {
        static_cast<OGRLVBAGLayer *>(pUserData)->StartElementCbk(pszName,
                                                                 ppszAttr);
    };

    const auto endElementWrapper = [](void *pUserData, const char *pszName)
    { static_cast<OGRLVBAGLayer *>(pUserData)->EndElementCbk(pszName); };

    const auto dataHandlerWrapper =
        [](void *pUserData, const XML_Char *data, int nLen)
    { static_cast<OGRLVBAGLayer *>(pUserData)->DataHandlerCbk(data, nLen); };

    oParser = OGRExpatUniquePtr{OGRCreateExpatXMLParser()};
    XML_SetElementHandler(oParser.get(), startElementWrapper,
                          endElementWrapper);
    XML_SetCharacterDataHandler(oParser.get(), dataHandlerWrapper);
    XML_SetUserData(oParser.get(), this);
}

/************************************************************************/
/*                         IsParserFinished()                           */
/************************************************************************/

bool OGRLVBAGLayer::IsParserFinished(XML_Status status)
{
    switch (status)
    {
        case XML_STATUS_OK:
            return false;

        case XML_STATUS_ERROR:
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Parsing of LV BAG file failed : %s at line %d, "
                "column %d",
                XML_ErrorString(XML_GetErrorCode(oParser.get())),
                static_cast<int>(XML_GetCurrentLineNumber(oParser.get())),
                static_cast<int>(XML_GetCurrentColumnNumber(oParser.get())));

            delete m_poFeature;
            m_poFeature = nullptr;
            return true;

        case XML_STATUS_SUSPENDED:
            return true;
    }

    return true;
}

/************************************************************************/
/*                           ParseDocument()                            */
/************************************************************************/

void OGRLVBAGLayer::ParseDocument()
{
    while (true)
    {
        XML_ParsingStatus status;
        XML_GetParsingStatus(oParser.get(), &status);
        switch (status.parsing)
        {
            case XML_INITIALIZED:
            case XML_PARSING:
            {
                memset(aBuf, '\0', sizeof(aBuf));
                const unsigned int nLen = static_cast<unsigned int>(
                    VSIFReadL(aBuf, 1, sizeof(aBuf), fp));

                if (IsParserFinished(
                        XML_Parse(oParser.get(), aBuf, nLen, VSIFEofL(fp))))
                    return;

                break;
            }

            case XML_SUSPENDED:
            {
                if (IsParserFinished(XML_ResumeParser(oParser.get())))
                    return;

                break;
            }

            case XML_FINISHED:
            default:
                return;
        }
    }
}

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

OGRFeature *OGRLVBAGLayer::GetNextFeature()
{
    if (!TouchLayer())
        return nullptr;

    if (!bHasReadSchema)
    {
        GetLayerDefn();
        if (!bHasReadSchema)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Parsing LV BAG extract failed : invalid layer definition");
            return nullptr;
        }
    }

    return OGRGetNextFeatureThroughRaw<OGRLVBAGLayer>::GetNextFeature();
}

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

OGRFeature *OGRLVBAGLayer::GetNextRawFeature()
{
    bSchemaOnly = false;

    if (nNextFID == 0)
        ConfigureParser();

    if (m_poFeature)
    {
        delete m_poFeature;
        m_poFeature = nullptr;
    }

    ParseDocument();
    OGRFeature *poFeatureRet = m_poFeature;
    m_poFeature = nullptr;
    return poFeatureRet;
}

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

int OGRLVBAGLayer::TestCapability(const char *pszCap)
{
    if (!TouchLayer())
        return FALSE;

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

    return FALSE;
}
