/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Simple client for viewing OGR driver data.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.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_port.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <set>

#include "commonutils.h"
#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_multiproc.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "gdal_version.h"
#include "gdal.h"
#include "gdal_priv.h"
#include "gdal_version.h"
#include "ogr_api.h"
#include "ogr_core.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"
#include "ogrsf_frmts.h"


CPL_CVSID("$Id: ogrinfo.cpp 90bba2e599f71074513a455a943a45ed056bf22b 2021-10-19 08:39:52 +1000 Nyall Dawson $")

bool bVerbose = true;
bool bSuperQuiet = false;
bool bSummaryOnly = false;
GIntBig nFetchFID = OGRNullFID;
char** papszOptions = nullptr;

/************************************************************************/
/*                               Usage()                                */
/************************************************************************/

static void Usage( const char* pszErrorMsg = nullptr )
{
    printf("Usage: ogrinfo [--help-general] [-ro] [-q] [-where restricted_where|@filename]\n"
           "               [-spat xmin ymin xmax ymax] [-geomfield field] [-fid fid]\n"
           "               [-sql statement|@filename] [-dialect sql_dialect] [-al] [-rl] [-so] [-fields={YES/NO}]\n"
           "               [-geom={YES/NO/SUMMARY}] [[-oo NAME=VALUE] ...]\n"
           "               [-nomd] [-listmdd] [-mdd domain|`all`]*\n"
           "               [-nocount] [-noextent] [-nogeomtype] [-wkt_format WKT1|WKT2|...]\n"
           "               [-fielddomain name]\n"
           "               datasource_name [layer [layer ...]]\n");

    if( pszErrorMsg != nullptr )
        fprintf(stderr, "\nFAILURE: %s\n", pszErrorMsg);

    exit(1);
}

/************************************************************************/
/*                       GDALInfoPrintMetadata()                        */
/************************************************************************/
static void GDALInfoPrintMetadata( GDALMajorObjectH hObject,
                                   const char *pszDomain,
                                   const char *pszDisplayedname,
                                   const char *pszIndent )
{
    bool bIsxml = false;

    if( pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "xml:") )
        bIsxml = true;

    char **papszMetadata = GDALGetMetadata(hObject, pszDomain);
    if( CSLCount(papszMetadata) > 0 )
    {
        printf("%s%s:\n", pszIndent, pszDisplayedname);
        for( int i = 0; papszMetadata[i] != nullptr; i++ )
        {
            if( bIsxml )
                printf("%s%s\n", pszIndent, papszMetadata[i]);
            else
                printf("%s  %s\n", pszIndent, papszMetadata[i]);
        }
    }
}

/************************************************************************/
/*                       GDALInfoReportMetadata()                       */
/************************************************************************/
static void GDALInfoReportMetadata( GDALMajorObjectH hObject,
                                    bool bListMDD,
                                    bool bShowMetadata,
                                    char **papszExtraMDDomains )
{
    const char* pszIndent = "";

    /* -------------------------------------------------------------------- */
    /*      Report list of Metadata domains                                 */
    /* -------------------------------------------------------------------- */
    if( bListMDD )
    {
        char** papszMDDList = GDALGetMetadataDomainList(hObject);
        char** papszIter = papszMDDList;

        if( papszMDDList != nullptr )
            printf("%sMetadata domains:\n", pszIndent);
        while( papszIter != nullptr && *papszIter != nullptr )
        {
            if( EQUAL(*papszIter, "") )
                printf("%s  (default)\n", pszIndent);
            else
                printf("%s  %s\n", pszIndent, *papszIter);
            papszIter ++;
        }
        CSLDestroy(papszMDDList);
    }

    if( !bShowMetadata )
        return;

    /* -------------------------------------------------------------------- */
    /*      Report default Metadata domain.                                 */
    /* -------------------------------------------------------------------- */
    GDALInfoPrintMetadata(hObject, nullptr, "Metadata", pszIndent);

    /* -------------------------------------------------------------------- */
    /*      Report extra Metadata domains                                   */
    /* -------------------------------------------------------------------- */
    if( papszExtraMDDomains != nullptr )
    {
        char **papszExtraMDDomainsExpanded = nullptr;

        if( EQUAL(papszExtraMDDomains[0], "all") &&
            papszExtraMDDomains[1] == nullptr )
        {
            char** papszMDDList = GDALGetMetadataDomainList(hObject);
            char** papszIter = papszMDDList;

            while( papszIter != nullptr && *papszIter != nullptr )
            {
                if( !EQUAL(*papszIter, "") && !EQUAL(*papszIter, "SUBDATASETS") )
                {
                    papszExtraMDDomainsExpanded = CSLAddString(papszExtraMDDomainsExpanded, *papszIter);
                }
                papszIter ++;
            }
            CSLDestroy(papszMDDList);
        }
        else
        {
            papszExtraMDDomainsExpanded = CSLDuplicate(papszExtraMDDomains);
        }

        for( int iMDD = 0;
             papszExtraMDDomainsExpanded != nullptr &&
               papszExtraMDDomainsExpanded[iMDD] != nullptr;
             iMDD++ )
        {
            char pszDisplayedname[256];
            snprintf(pszDisplayedname, 256, "Metadata (%s)",
                     papszExtraMDDomainsExpanded[iMDD]);
            GDALInfoPrintMetadata(hObject, papszExtraMDDomainsExpanded[iMDD],
                                  pszDisplayedname, pszIndent);
        }

        CSLDestroy(papszExtraMDDomainsExpanded);
    }
    GDALInfoPrintMetadata(hObject, "SUBDATASETS", "Subdatasets", pszIndent);
}

/************************************************************************/
/*                           ReportOnLayer()                            */
/************************************************************************/

static void ReportOnLayer( OGRLayer * poLayer, const char *pszWHERE,
                           const char* pszGeomField,
                           OGRGeometry *poSpatialFilter,
                           bool bListMDD,
                           bool bShowMetadata,
                           char** papszExtraMDDomains,
                           bool bFeatureCount,
                           bool bExtent,
                           bool bGeomType,
                           const char* pszWKTFormat )
{
    OGRFeatureDefn      *poDefn = poLayer->GetLayerDefn();

/* -------------------------------------------------------------------- */
/*      Set filters if provided.                                        */
/* -------------------------------------------------------------------- */
    if( pszWHERE != nullptr )
    {
        if( poLayer->SetAttributeFilter(pszWHERE) != OGRERR_NONE )
        {
            printf("FAILURE: SetAttributeFilter(%s) failed.\n", pszWHERE);
            exit(1);
        }
    }

    if( poSpatialFilter != nullptr )
    {
        if( pszGeomField != nullptr )
        {
            const int iGeomField = poDefn->GetGeomFieldIndex(pszGeomField);
            if( iGeomField >= 0 )
                poLayer->SetSpatialFilter(iGeomField, poSpatialFilter);
            else
                printf("WARNING: Cannot find geometry field %s.\n",
                       pszGeomField);
        }
        else
        {
            poLayer->SetSpatialFilter(poSpatialFilter);
        }
    }

/* -------------------------------------------------------------------- */
/*      Report various overall information.                             */
/* -------------------------------------------------------------------- */
    if( !bSuperQuiet )
    {
        printf("\n");
        printf("Layer name: %s\n", poLayer->GetName());
    }

    GDALInfoReportMetadata(static_cast<GDALMajorObjectH>(poLayer),
                           bListMDD,
                           bShowMetadata,
                           papszExtraMDDomains);

    if( bVerbose )
    {
        const int nGeomFieldCount =
            bGeomType ? poLayer->GetLayerDefn()->GetGeomFieldCount() : 0;
        if( nGeomFieldCount > 1 )
        {
            for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
            {
                OGRGeomFieldDefn* poGFldDefn =
                    poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
                printf("Geometry (%s): %s\n", poGFldDefn->GetNameRef(),
                       OGRGeometryTypeToName(poGFldDefn->GetType()));
            }
        }
        else if( bGeomType )
        {
            printf("Geometry: %s\n",
                   OGRGeometryTypeToName(poLayer->GetGeomType()));
        }

        if( bFeatureCount )
            printf("Feature Count: " CPL_FRMT_GIB "\n",
                   poLayer->GetFeatureCount());

        OGREnvelope oExt;
        if( bExtent && nGeomFieldCount > 1 )
        {
            for( int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
            {
                if( poLayer->GetExtent(iGeom, &oExt, TRUE) == OGRERR_NONE )
                {
                    OGRGeomFieldDefn* poGFldDefn =
                        poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
                    CPLprintf("Extent (%s): (%f, %f) - (%f, %f)\n",
                              poGFldDefn->GetNameRef(),
                              oExt.MinX, oExt.MinY, oExt.MaxX, oExt.MaxY);
                }
            }
        }
        else if( bExtent && poLayer->GetExtent(&oExt, TRUE) == OGRERR_NONE )
        {
            CPLprintf("Extent: (%f, %f) - (%f, %f)\n",
                      oExt.MinX, oExt.MinY, oExt.MaxX, oExt.MaxY);
        }

        const auto displayExtraInfoSRS = [](const OGRSpatialReference* poSRS) {

            const double dfCoordinateEpoch = poSRS->GetCoordinateEpoch();
            if( dfCoordinateEpoch > 0 )
            {
                std::string osCoordinateEpoch = CPLSPrintf("%f", dfCoordinateEpoch);
                if( osCoordinateEpoch.find('.') != std::string::npos )
                {
                    while( osCoordinateEpoch.back() == '0' )
                        osCoordinateEpoch.resize(osCoordinateEpoch.size()-1);
                }
                printf("Coordinate epoch: %s\n", osCoordinateEpoch.c_str());
            }

            const auto mapping = poSRS->GetDataAxisToSRSAxisMapping();
            printf("Data axis to CRS axis mapping: ");
            for( size_t i = 0; i < mapping.size(); i++ )
            {
                if( i > 0 )
                {
                    printf(",");
                }
                printf("%d", mapping[i]);
            }
            printf("\n");
        };

        if( nGeomFieldCount > 1 )
        {
            for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
            {
                OGRGeomFieldDefn* poGFldDefn =
                    poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
                OGRSpatialReference* poSRS = poGFldDefn->GetSpatialRef();
                char *pszWKT = nullptr;
                if( poSRS == nullptr )
                {
                    pszWKT = CPLStrdup("(unknown)");
                }
                else
                {
                    CPLString osWKTFormat("FORMAT=");
                    osWKTFormat += pszWKTFormat;
                    const char* const apszWKTOptions[] =
                        { osWKTFormat.c_str(), "MULTILINE=YES", nullptr };
                    poSRS->exportToWkt(&pszWKT, apszWKTOptions);
                }

                printf("SRS WKT (%s):\n%s\n",
                       poGFldDefn->GetNameRef(), pszWKT);
                CPLFree(pszWKT);
                if( poSRS )
                {
                    displayExtraInfoSRS(poSRS);
                }
            }
        }
        else
        {
            char *pszWKT = nullptr;
            auto poSRS = poLayer->GetSpatialRef();
            if( poSRS == nullptr )
            {
                pszWKT = CPLStrdup("(unknown)");
            }
            else
            {
                CPLString osWKTFormat("FORMAT=");
                osWKTFormat += pszWKTFormat;
                const char* const apszWKTOptions[] =
                    { osWKTFormat.c_str(), "MULTILINE=YES", nullptr };
                poSRS->exportToWkt(&pszWKT, apszWKTOptions);
            }

            printf("Layer SRS WKT:\n%s\n", pszWKT);
            CPLFree(pszWKT);
            if( poSRS )
            {
                displayExtraInfoSRS(poSRS);
            }
        }

        if( strlen(poLayer->GetFIDColumn()) > 0 )
            printf("FID Column = %s\n",
                   poLayer->GetFIDColumn());

        for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
        {
            OGRGeomFieldDefn* poGFldDefn =
                poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
            if( nGeomFieldCount == 1 &&
                EQUAL(poGFldDefn->GetNameRef(), "") &&
                poGFldDefn->IsNullable() )
                break;
            printf("Geometry Column ");
            if( nGeomFieldCount > 1 )
                printf("%d ", iGeom + 1);
            if( !poGFldDefn->IsNullable() )
                printf("NOT NULL ");
            printf("= %s\n", poGFldDefn->GetNameRef());
        }

        for( int iAttr = 0; iAttr < poDefn->GetFieldCount(); iAttr++ )
        {
            OGRFieldDefn *poField = poDefn->GetFieldDefn(iAttr);
            const char* pszType = (poField->GetSubType() != OFSTNone)
                ? CPLSPrintf(
                      "%s(%s)",
                      OGRFieldDefn::GetFieldTypeName(poField->GetType()),
                      OGRFieldDefn::GetFieldSubTypeName(poField->GetSubType()))
                : OGRFieldDefn::GetFieldTypeName(poField->GetType());
            printf("%s: %s (%d.%d)",
                   poField->GetNameRef(),
                   pszType,
                   poField->GetWidth(),
                   poField->GetPrecision());
            if( poField->IsUnique() )
                printf(" UNIQUE");
            if( !poField->IsNullable() )
                printf(" NOT NULL");
            if( poField->GetDefault() != nullptr )
                printf(" DEFAULT %s", poField->GetDefault());
            const char* pszAlias = poField->GetAlternativeNameRef();
            if( pszAlias != nullptr && pszAlias[0])
                printf(", alternative name=\"%s\"", pszAlias);
            const std::string& osDomain = poField->GetDomainName();
            if( !osDomain.empty() )
                printf(", domain name=%s", osDomain.c_str());
            printf("\n");
        }
    }

/* -------------------------------------------------------------------- */
/*      Read, and dump features.                                        */
/* -------------------------------------------------------------------- */

    if( nFetchFID == OGRNullFID && !bSummaryOnly )
    {
        if( !bSuperQuiet )
        {
            for( auto& poFeature: poLayer )
            {
                    poFeature->DumpReadable(nullptr, papszOptions);
            }
        }
    }
    else if( nFetchFID != OGRNullFID )
    {
        OGRFeature *poFeature = poLayer->GetFeature(nFetchFID);
        if( poFeature == nullptr )
        {
            printf("Unable to locate feature id " CPL_FRMT_GIB
                   " on this layer.\n",
                   nFetchFID);
        }
        else
        {
            poFeature->DumpReadable(nullptr, papszOptions);
            OGRFeature::DestroyFeature(poFeature);
        }
    }
}

/************************************************************************/
/*                        ReportFieldDomain()                           */
/************************************************************************/

static void ReportFieldDomain(const OGRFieldDomain* poDomain)
{
    printf("Domain %s:\n", poDomain->GetName().c_str());
    const std::string& osDesc = poDomain->GetDescription();
    if( !osDesc.empty() )
    {
        printf("  Description: %s\n", osDesc.c_str());
    }
    switch( poDomain->GetDomainType() )
    {
        case OFDT_CODED: printf("  Type: coded\n"); break;
        case OFDT_RANGE: printf("  Type: range\n"); break;
        case OFDT_GLOB:  printf("  Type: glob\n"); break;
    }
    const char* pszFieldType = (poDomain->GetFieldSubType() != OFSTNone)
        ? CPLSPrintf(
              "%s(%s)",
              OGRFieldDefn::GetFieldTypeName(poDomain->GetFieldType()),
              OGRFieldDefn::GetFieldSubTypeName(poDomain->GetFieldSubType()))
        : OGRFieldDefn::GetFieldTypeName(poDomain->GetFieldType());
    printf("  Field type: %s\n", pszFieldType);
    switch( poDomain->GetSplitPolicy() )
    {
        case OFDSP_DEFAULT_VALUE:  printf("  Split policy: default value\n"); break;
        case OFDSP_DUPLICATE:      printf("  Split policy: duplicate\n"); break;
        case OFDSP_GEOMETRY_RATIO: printf("  Split policy: geometry ratio\n"); break;
    }
    switch( poDomain->GetMergePolicy() )
    {
        case OFDMP_DEFAULT_VALUE:     printf("  Merge policy: default value\n"); break;
        case OFDMP_SUM:               printf("  Merge policy: sum\n"); break;
        case OFDMP_GEOMETRY_WEIGHTED: printf("  Merge policy: geometry weighted\n"); break;
    }
    switch( poDomain->GetDomainType() )
    {
        case OFDT_CODED:
        {
            const auto poCodedFieldDomain =
                cpl::down_cast<const OGRCodedFieldDomain*>(poDomain);
            const OGRCodedValue* enumeration = poCodedFieldDomain->GetEnumeration();
            printf("  Coded values:\n");
            for( int i = 0; enumeration[i].pszCode != nullptr; ++i )
            {
                if( enumeration[i].pszValue )
                {
                    printf("    %s: %s\n",
                           enumeration[i].pszCode,
                           enumeration[i].pszValue);
                }
                else
                {
                    printf("    %s\n", enumeration[i].pszCode);
                }
            }
            break;
        }

        case OFDT_RANGE:
        {
            const auto poRangeFieldDomain =
                cpl::down_cast<const OGRRangeFieldDomain*>(poDomain);
            bool bMinIsIncluded = false;
            const OGRField& sMin = poRangeFieldDomain->GetMin(bMinIsIncluded);
            bool bMaxIsIncluded = false;
            const OGRField& sMax = poRangeFieldDomain->GetMax(bMaxIsIncluded);
            if( poDomain->GetFieldType() == OFTInteger )
            {
                if( !OGR_RawField_IsUnset(&sMin) )
                {
                    printf("  Minimum value: %d%s\n",
                           sMin.Integer,
                           bMinIsIncluded ? "" : " (excluded)");
                }
                if( !OGR_RawField_IsUnset(&sMax) )
                {
                    printf("  Maximum value: %d%s\n",
                           sMax.Integer,
                           bMaxIsIncluded ? "" : " (excluded)");
                }
            }
            else if( poDomain->GetFieldType() == OFTInteger64 )
            {
                if( !OGR_RawField_IsUnset(&sMin) )
                {
                    printf("  Minimum value: " CPL_FRMT_GIB "%s\n",
                           sMin.Integer64,
                           bMinIsIncluded ? "" : " (excluded)");
                }
                if( !OGR_RawField_IsUnset(&sMax) )
                {
                    printf("  Maximum value: " CPL_FRMT_GIB "%s\n",
                           sMax.Integer64,
                           bMaxIsIncluded ? "" : " (excluded)");
                }
            }
            else if( poDomain->GetFieldType() == OFTReal )
            {
                if( !OGR_RawField_IsUnset(&sMin) )
                {
                    printf("  Minimum value: %g%s\n",
                           sMin.Real,
                           bMinIsIncluded ? "" : " (excluded)");
                }
                if( !OGR_RawField_IsUnset(&sMax) )
                {
                    printf("  Maximum value: %g%s\n",
                           sMax.Real,
                           bMaxIsIncluded ? "" : " (excluded)");
                }
            }
            break;
        }

        case OFDT_GLOB:
        {
            const auto poGlobFieldDomain =
                cpl::down_cast<const OGRGlobFieldDomain*>(poDomain);
            printf("  Glob: %s\n", poGlobFieldDomain->GetGlob().c_str());
            break;
        }
    }
}

/************************************************************************/
/*                             RemoveBOM()                              */
/************************************************************************/

/* Remove potential UTF-8 BOM from data (must be NUL terminated) */
static void RemoveBOM(GByte* pabyData)
{
    if( pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF )
    {
        memmove(pabyData, pabyData + 3,
                strlen(reinterpret_cast<char *>(pabyData) + 3) + 1);
    }
}

static void RemoveSQLComments(char*& pszSQL)
{
    char** papszLines = CSLTokenizeStringComplex(pszSQL, "\r\n", FALSE, FALSE);
    CPLString osSQL;
    for( char** papszIter = papszLines; papszIter && *papszIter; ++papszIter )
    {
        const char* pszLine = *papszIter;
        char chQuote = 0;
        int i = 0;
        for(; pszLine[i] != '\0'; ++i )
        {
            if( chQuote )
            {
                if( pszLine[i] == chQuote )
                {
                    if( pszLine[i+1] == chQuote )
                    {
                        i++;
                    }
                    else
                    {
                        chQuote = 0;
                    }
                }
            }
            else if( pszLine[i] == '\'' || pszLine[i] == '"' )
            {
                chQuote = pszLine[i];
            }
            else if( pszLine[i] == '-' && pszLine[i+1] == '-' )
            {
                break;
            }
        }
        if( i > 0 )
        {
            osSQL.append(pszLine, i);
        }
        osSQL += ' ';
    }
    CSLDestroy(papszLines);
    CPLFree(pszSQL);
    pszSQL = CPLStrdup(osSQL);
}

/************************************************************************/
/*                           PrintLayerSummary()                        */
/************************************************************************/

static void PrintLayerSummary(OGRLayer* poLayer, bool bGeomType, bool bIsPrivate=false)
{
    printf("%s", poLayer->GetName());

    const char* pszTitle = poLayer->GetMetadataItem("TITLE");
    if( pszTitle )
    {
        printf(" (title: %s)", pszTitle);
    }

    const int nGeomFieldCount =
        bGeomType ? poLayer->GetLayerDefn()->GetGeomFieldCount() : 0;
    if( nGeomFieldCount > 1 )
    {
        printf(" (");
        for( int iGeom = 0; iGeom < nGeomFieldCount; iGeom++ )
        {
            if( iGeom > 0 )
                printf(", ");
            OGRGeomFieldDefn* poGFldDefn =
                poLayer->GetLayerDefn()->
                    GetGeomFieldDefn(iGeom);
            printf(
                "%s",
                OGRGeometryTypeToName(
                    poGFldDefn->GetType()));
        }
        printf(")");
    }
    else if( bGeomType && poLayer->GetGeomType() != wkbUnknown )
        printf(" (%s)",
               OGRGeometryTypeToName(
                   poLayer->GetGeomType()));

    if( bIsPrivate )
    {
        printf(" [private]");
    }

    printf("\n");
}

/************************************************************************/
/*                       ReportHiearchicalLayers()                      */
/************************************************************************/

static void ReportHiearchicalLayers(const GDALGroup* group,
                                    const std::string& indent,
                                    bool bGeomType)
{
    const auto aosVectorLayerNames = group->GetVectorLayerNames();
    for( const auto& osVectorLayerName: aosVectorLayerNames )
    {
        OGRLayer* poLayer = group->OpenVectorLayer(osVectorLayerName);
        if( poLayer )
        {
            printf("%sLayer: ", indent.c_str());
            PrintLayerSummary(poLayer, bGeomType);
        }
    }

    const std::string subIndent(indent + "  ");
    auto aosSubGroupNames = group->GetGroupNames();
    for( const auto& osSubGroupName: aosSubGroupNames )
    {
        auto poSubGroup = group->OpenGroup(osSubGroupName);
        if( poSubGroup )
        {
            printf("Group %s", indent.c_str());
            printf("%s:\n", osSubGroupName.c_str());
            ReportHiearchicalLayers(poSubGroup.get(), subIndent, bGeomType);
        }
    }
}

/************************************************************************/
/*                                main()                                */
/************************************************************************/

#define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \
    do { if (iArg + nExtraArg >= nArgc) \
        Usage(CPLSPrintf("%s option requires %d argument(s)", \
                         papszArgv[iArg], nExtraArg)); } while( false )

MAIN_START(nArgc, papszArgv)
{
    // Check strict compilation and runtime library version as we use C++ API.
    if( !GDAL_CHECK_VERSION(papszArgv[0]) )
        exit(1);

    EarlySetConfigOptions(nArgc, papszArgv);

    OGRRegisterAll();

/* -------------------------------------------------------------------- */
/*      Processing command line arguments.                              */
/* -------------------------------------------------------------------- */
    nArgc = OGRGeneralCmdLineProcessor(nArgc, &papszArgv, 0);

    if( nArgc < 1 )
        exit(-nArgc);

    char *pszWHERE = nullptr;
    const char *pszDataSource = nullptr;
    char **papszLayers = nullptr;
    OGRGeometry *poSpatialFilter = nullptr;
    int nRepeatCount = 1;
    bool bAllLayers = false;
    char *pszSQLStatement = nullptr;
    const char *pszDialect = nullptr;
    int nRet = 0;
    const char* pszGeomField = nullptr;
    char **papszOpenOptions = nullptr;
    char **papszExtraMDDomains = nullptr;
    bool bListMDD = false;
    bool bShowMetadata = true;
    bool bFeatureCount = true;
    bool bExtent = true;
    bool bGeomType = true;
    bool bDatasetGetNextFeature = false;
    bool bReadOnly = false;
    bool bUpdate = false;
    const char* pszWKTFormat = "WKT2";
    std::string osFieldDomain;

    for( int iArg = 1; iArg < nArgc; iArg++ )
    {
        if( EQUAL(papszArgv[iArg], "--utility_version") )
        {
            printf("%s was compiled against GDAL %s and "
                   "is running against GDAL %s\n",
                   papszArgv[0], GDAL_RELEASE_NAME,
                   GDALVersionInfo("RELEASE_NAME"));
            CSLDestroy(papszArgv);
            return 0;
        }
        else if( EQUAL(papszArgv[iArg], "--help") )
        {
            Usage();
        }
        else if( EQUAL(papszArgv[iArg], "-ro") )
        {
            bReadOnly = true;
        }
        else if( EQUAL(papszArgv[iArg], "-update") )
        {
            bUpdate = true;
        }
        else if( EQUAL(papszArgv[iArg], "-q") ||
                 EQUAL(papszArgv[iArg], "-quiet"))
        {
            bVerbose = false;
        }
        else if( EQUAL(papszArgv[iArg], "-qq") )
        {
            /* Undocumented: mainly only useful for AFL testing */
            bVerbose = false;
            bSuperQuiet = true;
        }
        else if( EQUAL(papszArgv[iArg], "-fid") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            nFetchFID = CPLAtoGIntBig(papszArgv[++iArg]);
        }
        else if( EQUAL(papszArgv[iArg], "-spat") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(4);

            OGRLinearRing oRing;
            oRing.addPoint(CPLAtof(papszArgv[iArg+1]),
                           CPLAtof(papszArgv[iArg+2]));
            oRing.addPoint(CPLAtof(papszArgv[iArg+1]),
                           CPLAtof(papszArgv[iArg+4]));
            oRing.addPoint(CPLAtof(papszArgv[iArg+3]),
                           CPLAtof(papszArgv[iArg+4]));
            oRing.addPoint(CPLAtof(papszArgv[iArg+3]),
                           CPLAtof(papszArgv[iArg+2]));
            oRing.addPoint(CPLAtof(papszArgv[iArg+1]),
                           CPLAtof(papszArgv[iArg+2]));

            poSpatialFilter = new OGRPolygon();
            static_cast<OGRPolygon *>(poSpatialFilter)->addRing(&oRing);
            iArg += 4;
        }
        else if( EQUAL(papszArgv[iArg], "-geomfield") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            pszGeomField = papszArgv[++iArg];
        }
        else if( EQUAL(papszArgv[iArg], "-where") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            iArg++;
            CPLFree(pszWHERE);
            GByte* pabyRet = nullptr;
            if( papszArgv[iArg][0] == '@' &&
                VSIIngestFile(nullptr, papszArgv[iArg] + 1, &pabyRet,
                              nullptr, 1024*1024) )
            {
                RemoveBOM(pabyRet);
                pszWHERE = reinterpret_cast<char *>(pabyRet);
            }
            else
            {
                pszWHERE = CPLStrdup(papszArgv[iArg]);
            }
        }
        else if( EQUAL(papszArgv[iArg], "-sql") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            iArg++;
            CPLFree(pszSQLStatement);
            GByte* pabyRet = nullptr;
            if( papszArgv[iArg][0] == '@' &&
                VSIIngestFile(nullptr, papszArgv[iArg] + 1, &pabyRet,
                              nullptr, 1024*1024) )
            {
                RemoveBOM(pabyRet);
                pszSQLStatement = reinterpret_cast<char *>(pabyRet);
                RemoveSQLComments(pszSQLStatement);
            }
            else
            {
                pszSQLStatement = CPLStrdup(papszArgv[iArg]);
            }
        }
        else if( EQUAL(papszArgv[iArg], "-dialect") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            pszDialect = papszArgv[++iArg];
        }
        else if( EQUAL(papszArgv[iArg], "-rc") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            nRepeatCount = atoi(papszArgv[++iArg]);
        }
        else if( EQUAL(papszArgv[iArg], "-al") )
        {
            bAllLayers = true;
        }
        else if( EQUAL(papszArgv[iArg], "-so") ||
                 EQUAL(papszArgv[iArg], "-summary")  )
        {
            bSummaryOnly = true;
        }
        else if( STARTS_WITH_CI(papszArgv[iArg], "-fields=") )
        {
            char* pszTemp =
                static_cast<char *>(CPLMalloc(32 + strlen(papszArgv[iArg])));
            snprintf(pszTemp,
                    32 + strlen(papszArgv[iArg]),
                    "DISPLAY_FIELDS=%s", papszArgv[iArg] + strlen("-fields="));
            papszOptions = CSLAddString(papszOptions, pszTemp);
            CPLFree(pszTemp);
        }
        else if( STARTS_WITH_CI(papszArgv[iArg], "-geom=") )
        {
            char* pszTemp =
                static_cast<char *>(CPLMalloc(32 + strlen(papszArgv[iArg])));
            snprintf(pszTemp,
                    32 + strlen(papszArgv[iArg]),
                    "DISPLAY_GEOMETRY=%s", papszArgv[iArg] + strlen("-geom="));
            papszOptions = CSLAddString(papszOptions, pszTemp);
            CPLFree(pszTemp);
        }
        else if( EQUAL(papszArgv[iArg], "-oo") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            papszOpenOptions = CSLAddString(papszOpenOptions,
                                            papszArgv[++iArg]);
        }
        else if( EQUAL(papszArgv[iArg], "-nomd") )
        {
            bShowMetadata = false;
        }
        else if( EQUAL(papszArgv[iArg], "-listmdd") )
        {
            bListMDD = true;
        }
        else if( EQUAL(papszArgv[iArg], "-mdd") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            papszExtraMDDomains = CSLAddString(papszExtraMDDomains,
                                               papszArgv[++iArg]);
        }
        else if( EQUAL(papszArgv[iArg], "-nocount") )
        {
            bFeatureCount = false;
        }
        else if( EQUAL(papszArgv[iArg], "-noextent") )
        {
            bExtent = false;
        }
        else if( EQUAL(papszArgv[iArg], "-nogeomtype") )
        {
            bGeomType = false;
        }
        else if( EQUAL(papszArgv[iArg], "-rl"))
        {
            bDatasetGetNextFeature = true;
        }
        else if( EQUAL(papszArgv[iArg], "-wkt_format") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            pszWKTFormat = papszArgv[++iArg];
        }
        else if( EQUAL(papszArgv[iArg], "-fielddomain") )
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            osFieldDomain = papszArgv[++iArg];
        }

        else if( papszArgv[iArg][0] == '-' )
        {
            Usage(CPLSPrintf("Unknown option name '%s'", papszArgv[iArg]));
        }
        else if( pszDataSource == nullptr )
        {
            pszDataSource = papszArgv[iArg];
        }
        else
        {
            papszLayers = CSLAddString(papszLayers, papszArgv[iArg]);
            bAllLayers = false;
        }
    }

    if( pszDataSource == nullptr )
        Usage("No datasource specified.");

    if( pszDialect != nullptr && pszWHERE != nullptr &&
        pszSQLStatement == nullptr )
        printf("Warning: -dialect is ignored with -where. Use -sql instead");

    if( bDatasetGetNextFeature && pszSQLStatement )
    {
        Usage("-rl is incompatible with -sql");
    }

#ifdef __AFL_HAVE_MANUAL_CONTROL
    while (__AFL_LOOP(1000)) {
#endif
/* -------------------------------------------------------------------- */
/*      Open data source.                                               */
/* -------------------------------------------------------------------- */
    GDALDataset *poDS = static_cast<GDALDataset *>(GDALOpenEx(
        pszDataSource,
        ((bReadOnly || pszSQLStatement == nullptr) &&
         !bUpdate ? GDAL_OF_READONLY : GDAL_OF_UPDATE) | GDAL_OF_VECTOR,
        nullptr, papszOpenOptions, nullptr));
    if( poDS == nullptr && !bReadOnly && !bUpdate &&
        pszSQLStatement == nullptr )
    {
        // In some cases (empty geopackage for example), opening in read-only
        // mode fails, so retry in update mode
        if( GDALIdentifyDriverEx(pszDataSource, GDAL_OF_VECTOR,
                                 nullptr, nullptr) )
        {
            poDS = static_cast<GDALDataset *>(GDALOpenEx(
                pszDataSource,
                GDAL_OF_UPDATE | GDAL_OF_VECTOR, nullptr,
                papszOpenOptions, nullptr));
        }
    }
    if( poDS == nullptr && !bReadOnly && !bUpdate &&
        pszSQLStatement != nullptr )
    {
        poDS = static_cast<GDALDataset *>(GDALOpenEx(
            pszDataSource,
            GDAL_OF_READONLY | GDAL_OF_VECTOR, nullptr,
            papszOpenOptions, nullptr));
        if( poDS != nullptr && bVerbose )
        {
            printf("Had to open data source read-only.\n");
#ifdef __AFL_HAVE_MANUAL_CONTROL
            bReadOnly = true;
#endif
        }
    }

    GDALDriver *poDriver = nullptr;
    if( poDS != nullptr )
        poDriver = poDS->GetDriver();
    const int nLayerCount = poDS ? poDS->GetLayerCount() : 0;

/* -------------------------------------------------------------------- */
/*      Report failure                                                  */
/* -------------------------------------------------------------------- */
    if( poDS == nullptr )
    {
        printf("FAILURE:\n"
               "Unable to open datasource `%s' with the following drivers.\n",
               pszDataSource);
#ifdef __AFL_HAVE_MANUAL_CONTROL
        continue;
#else
        OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar();
        for( int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++ )
        {
            printf("  -> %s\n", poR->GetDriver(iDriver)->GetDescription());
        }

        nRet = 1;
        goto end;
#endif
    }

    CPLAssert(poDriver != nullptr);

/* -------------------------------------------------------------------- */
/*      Some information messages.                                      */
/* -------------------------------------------------------------------- */
    if( bVerbose )
        printf("INFO: Open of `%s'\n"
               "      using driver `%s' successful.\n",
               pszDataSource, poDriver->GetDescription());

    if( bVerbose && !EQUAL(pszDataSource,poDS->GetDescription()) )
    {
        printf("INFO: Internal data source name `%s'\n"
               "      different from user name `%s'.\n",
               poDS->GetDescription(), pszDataSource);
    }

    GDALInfoReportMetadata(static_cast<GDALMajorObjectH>(poDS),
                           bListMDD,
                           bShowMetadata,
                           papszExtraMDDomains);

    if( !osFieldDomain.empty() )
    {
        auto poDomain = poDS->GetFieldDomain(osFieldDomain);
        if( poDomain == nullptr )
        {
            printf("Domain %s cannot be found.\n", osFieldDomain.c_str());
            exit(1);
        }
        printf("\n");
        ReportFieldDomain(poDomain);
        printf("\n");
    }

    if( bDatasetGetNextFeature )
    {
        nRepeatCount = 0;  // skip layer reporting.

/* -------------------------------------------------------------------- */
/*      Set filters if provided.                                        */
/* -------------------------------------------------------------------- */
        if( pszWHERE != nullptr || poSpatialFilter != nullptr )
        {
            for( int iLayer = 0; iLayer < poDS->GetLayerCount(); iLayer++ )
            {
                OGRLayer *poLayer = poDS->GetLayer(iLayer);

                if( poLayer == nullptr )
                {
                    printf("FAILURE: Couldn't fetch advertised layer %d!\n",
                           iLayer);
                    exit(1);
                }

                if( pszWHERE != nullptr )
                {
                    if( poLayer->SetAttributeFilter(pszWHERE) != OGRERR_NONE )
                    {
                        printf("WARNING: SetAttributeFilter(%s) "
                               "failed on layer %s.\n",
                               pszWHERE, poLayer->GetName());
                    }
                }

                if( poSpatialFilter != nullptr )
                {
                    if( pszGeomField != nullptr )
                    {
                        OGRFeatureDefn *poDefn = poLayer->GetLayerDefn();
                        const int iGeomField =
                            poDefn->GetGeomFieldIndex(pszGeomField);
                        if( iGeomField >= 0 )
                            poLayer->SetSpatialFilter(iGeomField,
                                                      poSpatialFilter);
                        else
                            printf("WARNING: Cannot find geometry field %s.\n",
                                   pszGeomField);
                    }
                    else
                    {
                        poLayer->SetSpatialFilter(poSpatialFilter);
                    }
                }
            }
        }

        std::set<OGRLayer*> oSetLayers;
        while( true )
        {
            OGRLayer* poLayer = nullptr;
            OGRFeature* poFeature = poDS->GetNextFeature(&poLayer, nullptr,
                                                         nullptr, nullptr);
            if( poFeature == nullptr )
                break;
            if( papszLayers == nullptr || poLayer == nullptr ||
                CSLFindString(papszLayers, poLayer->GetName()) >= 0 )
            {
                if( bVerbose && poLayer != nullptr &&
                    oSetLayers.find(poLayer) == oSetLayers.end() )
                {
                    oSetLayers.insert(poLayer);
                    const bool bSummaryOnlyBackup = bSummaryOnly;
                    bSummaryOnly = true;
                    ReportOnLayer(poLayer, nullptr, nullptr, nullptr,
                                  bListMDD, bShowMetadata,
                                  papszExtraMDDomains,
                                  bFeatureCount,
                                  bExtent,
                                  bGeomType,
                                  pszWKTFormat);
                    bSummaryOnly = bSummaryOnlyBackup;
                }
                if( !bSuperQuiet && !bSummaryOnly )
                    poFeature->DumpReadable(nullptr, papszOptions);
            }
            OGRFeature::DestroyFeature(poFeature);
        }
    }

/* -------------------------------------------------------------------- */
/*      Special case for -sql clause.  No source layers required.       */
/* -------------------------------------------------------------------- */
    else if( pszSQLStatement != nullptr )
    {
        nRepeatCount = 0;  // skip layer reporting.

        if( CSLCount(papszLayers) > 0 )
            printf("layer names ignored in combination with -sql.\n");

        OGRLayer *poResultSet =
            poDS->ExecuteSQL(
                pszSQLStatement,
                pszGeomField == nullptr ? poSpatialFilter : nullptr,
                pszDialect);

        if( poResultSet != nullptr )
        {
            if( pszWHERE != nullptr )
            {
                if( poResultSet->SetAttributeFilter(pszWHERE) != OGRERR_NONE )
                {
                    printf("FAILURE: SetAttributeFilter(%s) failed.\n",
                           pszWHERE);
                    exit(1);
                }
            }

            if( pszGeomField != nullptr )
                ReportOnLayer(poResultSet, nullptr,
                              pszGeomField, poSpatialFilter,
                              bListMDD, bShowMetadata, papszExtraMDDomains,
                              bFeatureCount, bExtent, bGeomType, pszWKTFormat);
            else
                ReportOnLayer(poResultSet, nullptr, nullptr, nullptr,
                              bListMDD, bShowMetadata, papszExtraMDDomains,
                              bFeatureCount, bExtent, bGeomType, pszWKTFormat);
            poDS->ReleaseResultSet(poResultSet);
        }
    }

    // coverity[tainted_data]
    for( int iRepeat = 0; iRepeat < nRepeatCount; iRepeat++ )
    {
        if( papszLayers == nullptr || *papszLayers == nullptr )
        {
            if( iRepeat == 0 )
                CPLDebug("OGR", "GetLayerCount() = %d\n",
                         nLayerCount);

            bool bDone = false;
            auto poRootGroup = poDS->GetRootGroup();
            if( !bAllLayers && poRootGroup &&
                (!poRootGroup->GetGroupNames().empty() ||
                 !poRootGroup->GetVectorLayerNames().empty()) )
            {
                ReportHiearchicalLayers(poRootGroup.get(),
                                        std::string(), bGeomType);
                bDone = true;
            }

/* -------------------------------------------------------------------- */
/*      Process each data source layer.                                 */
/* -------------------------------------------------------------------- */
            for( int iLayer = 0; !bDone && iLayer < nLayerCount; iLayer++ )
            {
                OGRLayer *poLayer = poDS->GetLayer(iLayer);

                if( poLayer == nullptr )
                {
                    printf("FAILURE: Couldn't fetch advertised layer %d!\n",
                           iLayer);
                    exit(1);
                }

                if( !bAllLayers )
                {
                    printf("%d: ", iLayer + 1);
                    PrintLayerSummary(poLayer, bGeomType, poDS->IsLayerPrivate(iLayer));
                }
                else
                {
                    if( iRepeat != 0 )
                        poLayer->ResetReading();

                    ReportOnLayer(poLayer, pszWHERE,
                                  pszGeomField, poSpatialFilter,
                                  bListMDD, bShowMetadata, papszExtraMDDomains,
                                  bFeatureCount, bExtent, bGeomType, pszWKTFormat);
                }
            }
        }
        else
        {
/* -------------------------------------------------------------------- */
/*      Process specified data source layers.                           */
/* -------------------------------------------------------------------- */

            for( char** papszIter = papszLayers;
                 *papszIter != nullptr;
                 ++papszIter )
            {
                OGRLayer *poLayer = poDS->GetLayerByName(*papszIter);

                if( poLayer == nullptr )
                {
                    printf("FAILURE: Couldn't fetch requested layer %s!\n",
                           *papszIter);
                    exit(1);
                }

                if( iRepeat != 0 )
                    poLayer->ResetReading();

                ReportOnLayer(poLayer, pszWHERE, pszGeomField, poSpatialFilter,
                              bListMDD, bShowMetadata, papszExtraMDDomains,
                              bFeatureCount, bExtent, bGeomType, pszWKTFormat);
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Close down.                                                     */
/* -------------------------------------------------------------------- */
    GDALClose(poDS);

#ifdef __AFL_HAVE_MANUAL_CONTROL
    }
#else
end:
#endif

    CSLDestroy(papszArgv);
    CSLDestroy(papszLayers);
    CSLDestroy(papszOptions);
    CSLDestroy(papszOpenOptions);
    CSLDestroy(papszExtraMDDomains);
    if( poSpatialFilter )
        OGRGeometryFactory::destroyGeometry(poSpatialFilter);
    CPLFree(pszSQLStatement);
    CPLFree(pszWHERE);

    GDALDestroy();

    return nRet;
}
MAIN_END
