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

#include "cpl_conv.h"
#include "cpl_port.h"
#include "cpl_string.h"
#include "ogr_nas.h"

/************************************************************************/
/*                            OGRNASLayer()                             */
/************************************************************************/

OGRNASLayer::OGRNASLayer(const char *pszName, OGRNASDataSource *poDSIn)
    : poFeatureDefn(new OGRFeatureDefn(
          pszName + (STARTS_WITH_CI(pszName, "ogr:") ? 4 : 0))),
      iNextNASId(0), poDS(poDSIn),
      // Readers should get the corresponding GMLFeatureClass and cache it.
      poFClass(poDS->GetReader()->GetClass(pszName))
{
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();
    poFeatureDefn->SetGeomType(wkbNone);
}

/************************************************************************/
/*                            ~OGRNASLayer()                            */
/************************************************************************/

OGRNASLayer::~OGRNASLayer()

{
    if (poFeatureDefn)
        poFeatureDefn->Release();
}

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

void OGRNASLayer::ResetReading()

{
    iNextNASId = 0;
    poDS->GetReader()->ResetReading();
    if (poFClass)
        poDS->GetReader()->SetFilteredClassName(poFClass->GetElementName());
}

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

OGRFeature *OGRNASLayer::GetNextFeature()

{
    GMLFeature *poNASFeature = nullptr;

    if (iNextNASId == 0)
        ResetReading();

    /* ==================================================================== */
    /*      Loop till we find and translate a feature meeting all our       */
    /*      requirements.                                                   */
    /* ==================================================================== */
    while (true)
    {
        /* --------------------------------------------------------------------
         */
        /*      Cleanup last feature, and get a new raw nas feature. */
        /* --------------------------------------------------------------------
         */
        delete poNASFeature;
        poNASFeature = poDS->GetReader()->NextFeature();
        if (poNASFeature == nullptr)
            return nullptr;

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

        // We count reading low level NAS 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++;

        if (poNASFeature->GetClass() != poFClass)
            continue;

        iNextNASId++;

        /* --------------------------------------------------------------------
         */
        /*      Does it satisfy the spatial query, if there is one? */
        /* --------------------------------------------------------------------
         */
        const CPLXMLNode *const *papsGeometry = poNASFeature->GetGeometryList();

        std::vector<std::unique_ptr<OGRGeometry>> apoGeom(
            poNASFeature->GetGeometryCount());

        bool bErrored = false, bFiltered = false;
        CPLString osLastErrorMsg;
        for (int iGeom = 0; iGeom < poNASFeature->GetGeometryCount(); ++iGeom)
        {
            if (papsGeometry[iGeom])
            {
                CPLPushErrorHandler(CPLQuietErrorHandler);

                apoGeom[iGeom].reset(OGRGeometry::FromHandle(
                    OGR_G_CreateFromGMLTree(papsGeometry[iGeom])));
                CPLPopErrorHandler();
                if (apoGeom[iGeom] == nullptr)
                    osLastErrorMsg = CPLGetLastErrorMsg();
                apoGeom[iGeom] =
                    NASReader::ConvertGeometry(std::move(apoGeom[iGeom]));
                apoGeom[iGeom] = OGRGeometryFactory::forceTo(
                    std::move(apoGeom[iGeom]), GetGeomType());

                if (apoGeom[iGeom] == nullptr)
                    bErrored = true;
            }

            bFiltered = m_poFilterGeom != nullptr &&
                        !FilterGeometry(apoGeom[iGeom].get());
            if (bErrored || bFiltered)
            {
                break;
            }
        }

        if (bErrored)
        {

            CPLString osGMLId;
            if (poFClass->GetPropertyIndex("gml_id") == 0)
            {
                const GMLProperty *psGMLProperty = poNASFeature->GetProperty(0);
                if (psGMLProperty && psGMLProperty->nSubProperties == 1)
                {
                    osGMLId.Printf("(gml_id=%s) ",
                                   psGMLProperty->papszSubProperties[0]);
                }
            }

            delete poNASFeature;
            poNASFeature = nullptr;

            const bool bGoOn = CPLTestBool(
                CPLGetConfigOption("NAS_SKIP_CORRUPTED_FEATURES", "NO"));
            CPLError(bGoOn ? CE_Warning : CE_Failure, CPLE_AppDefined,
                     "Geometry of feature %d %scannot be parsed: %s%s",
                     iNextNASId, osGMLId.c_str(), osLastErrorMsg.c_str(),
                     bGoOn ? ". Skipping to next feature."
                           : ". You may set the NAS_SKIP_CORRUPTED_FEATURES "
                             "configuration option to YES to skip to the next "
                             "feature");
            if (bGoOn)
                continue;

            return nullptr;
        }

        if (bFiltered)
            continue;

        /* --------------------------------------------------------------------
         */
        /*      Convert the whole feature into an OGRFeature. */
        /* --------------------------------------------------------------------
         */
        OGRFeature *poOGRFeature = new OGRFeature(GetLayerDefn());

        poOGRFeature->SetFID(iNextNASId);

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

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

                case GMLPT_IntegerList:
                {
                    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(iField, nCount, panIntList);
                    CPLFree(panIntList);
                }
                break;

                case GMLPT_RealList:
                {
                    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(iField, nCount, padfList);
                    CPLFree(padfList);
                }
                break;

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

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

        for (int iGeom = 0; iGeom < poNASFeature->GetGeometryCount(); ++iGeom)
        {
            poOGRFeature->SetGeomField(iGeom, std::move(apoGeom[iGeom]));
        }

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

        /* --------------------------------------------------------------------
         */
        /*      Wow, we got our desired feature. Return it. */
        /* --------------------------------------------------------------------
         */
        delete poNASFeature;

        return poOGRFeature;
    }
}

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

GIntBig OGRNASLayer::GetFeatureCount(int bForce)

{
    if (poFClass == nullptr)
        return 0;

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

    return poFClass->GetFeatureCount();
}

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

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

{
    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);
}

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

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

{
    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);
    }

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

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

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

    return FALSE;
}
