/******************************************************************************
 *
 * Project:  GDAL Utilities
 * Purpose:  Command line application to list info about a file.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 * ****************************************************************************
 * Copyright (c) 1998, Frank Warmerdam
 * Copyright (c) 2007-2015, Even Rouault <even.rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "gdal_version.h"
#include "gdal.h"
#include "cpl_string.h"
#include "cpl_multiproc.h"
#include "cpl_vsi_virtual.h"
#include "commonutils.h"
#include "gdal_utils_priv.h"

/************************************************************************/
/*                               GDALExit()                             */
/*  This function exits and cleans up GDAL and OGR resources            */
/*  Perhaps it should be added to C api and used in all apps?           */
/************************************************************************/

static int GDALExit(int nCode)
{
    const char *pszDebug = CPLGetConfigOption("CPL_DEBUG", nullptr);
    if (pszDebug && (EQUAL(pszDebug, "ON") || EQUAL(pszDebug, "")))
    {
        GDALDumpOpenDatasets(stderr);
        CPLDumpSharedList(nullptr);
    }

    GDALDestroyDriverManager();

    OGRCleanupAll();

    exit(nCode);
}

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

static void Usage()

{
    fprintf(stderr, "%s\n", GDALInfoAppGetParserUsage().c_str());
    GDALExit(1);
}

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

MAIN_START(argc, argv)

{
    EarlySetConfigOptions(argc, argv);

    /* -------------------------------------------------------------------- */
    /*      Register standard GDAL drivers, and process generic GDAL        */
    /*      command options.                                                */
    /* -------------------------------------------------------------------- */

    GDALAllRegister();
    argc = GDALGeneralCmdLineProcessor(argc, &argv, 0);
    if (argc < 1)
        GDALExit(-argc);

    /* -------------------------------------------------------------------- */
    /*      Parse command line                                              */
    /* -------------------------------------------------------------------- */

    GDALInfoOptionsForBinary sOptionsForBinary;

    if (CSLFindString(argv, "-stdout") < 0)
    {
        argv = CSLAddString(argv, "-stdout");
    }

    std::unique_ptr<GDALInfoOptions, decltype(&GDALInfoOptionsFree)> psOptions{
        GDALInfoOptionsNew(argv + 1, &sOptionsForBinary), GDALInfoOptionsFree};
    CSLDestroy(argv);

    if (!psOptions)
    {
        Usage();
    }

/* -------------------------------------------------------------------- */
/*      Open dataset.                                                   */
/* -------------------------------------------------------------------- */
#ifdef __AFL_HAVE_MANUAL_CONTROL
    int iIter = 0;
    while (__AFL_LOOP(1000))
    {
        iIter++;
#endif

        GDALDatasetH hDataset = GDALOpenEx(
            sOptionsForBinary.osFilename.c_str(),
            GDAL_OF_READONLY | GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
            sOptionsForBinary.aosAllowedInputDrivers,
            sOptionsForBinary.aosOpenOptions, nullptr);

        if (hDataset == nullptr)
        {
#ifdef __AFL_HAVE_MANUAL_CONTROL
            continue;
#else
        VSIStatBuf sStat;
        CPLString message;
        message.Printf("gdalinfo failed - unable to open '%s'.",
                       sOptionsForBinary.osFilename.c_str());
        if (VSIStat(sOptionsForBinary.osFilename.c_str(), &sStat) == 0)
        {
            GDALDriverH drv =
                GDALIdentifyDriverEx(sOptionsForBinary.osFilename.c_str(),
                                     GDAL_OF_VECTOR, nullptr, nullptr);
            if (drv)
            {
                message += " Did you intend to call ogrinfo?";
            }
        }
        fprintf(stderr, "%s\n", message.c_str());

        /* --------------------------------------------------------------------
         */
        /*      If argument is a VSIFILE, then print its contents */
        /* --------------------------------------------------------------------
         */
        if (VSIFileManager::GetHandler(sOptionsForBinary.osFilename.c_str())
                ->IsArchive(sOptionsForBinary.osFilename.c_str()))
        {
            const char *const apszOptions[] = {"NAME_AND_TYPE_ONLY=YES",
                                               nullptr};
            VSIDIR *psDir = VSIOpenDir(sOptionsForBinary.osFilename.c_str(), -1,
                                       apszOptions);
            if (psDir)
            {
                fprintf(stdout,
                        "Unable to open source `%s' directly.\n"
                        "The archive contains several files:\n",
                        sOptionsForBinary.osFilename.c_str());
                int nCount = 0;
                while (auto psEntry = VSIGetNextDirEntry(psDir))
                {
                    if (VSI_ISDIR(psEntry->nMode) && psEntry->pszName[0] &&
                        psEntry->pszName[strlen(psEntry->pszName) - 1] != '/')
                    {
                        fprintf(stdout, "       %s/%s/\n",
                                sOptionsForBinary.osFilename.c_str(),
                                psEntry->pszName);
                    }
                    else
                    {
                        fprintf(stdout, "       %s/%s\n",
                                sOptionsForBinary.osFilename.c_str(),
                                psEntry->pszName);
                    }
                    nCount++;
                    if (nCount == 100)
                    {
                        fprintf(stdout, "[...trimmed...]\n");
                        break;
                    }
                }
                VSICloseDir(psDir);
            }
        }

        GDALDumpOpenDatasets(stderr);

        GDALDestroyDriverManager();

        CPLDumpSharedList(nullptr);

        exit(1);
#endif
        }

        /* --------------------------------------------------------------------
         */
        /*      Read specified subdataset if requested. */
        /* --------------------------------------------------------------------
         */
        if (sOptionsForBinary.nSubdataset > 0)
        {
            CSLConstList papszSubdatasets =
                GDALGetMetadata(hDataset, "SUBDATASETS");
            const int nSubdatasets = CSLCount(papszSubdatasets) / 2;

            if (nSubdatasets > 0 &&
                sOptionsForBinary.nSubdataset <= nSubdatasets)
            {
                char szKeyName[1024];
                char *pszSubdatasetName;

                snprintf(szKeyName, sizeof(szKeyName), "SUBDATASET_%d_NAME",
                         sOptionsForBinary.nSubdataset);
                szKeyName[sizeof(szKeyName) - 1] = '\0';
                pszSubdatasetName =
                    CPLStrdup(CSLFetchNameValue(papszSubdatasets, szKeyName));
                GDALClose(hDataset);
                hDataset = GDALOpen(pszSubdatasetName, GA_ReadOnly);
                CPLFree(pszSubdatasetName);
            }
            else
            {
                fprintf(stderr,
                        "gdalinfo warning: subdataset %d of %d requested. "
                        "Reading the main dataset.\n",
                        sOptionsForBinary.nSubdataset, nSubdatasets);
            }
        }

        char *pszGDALInfoOutput = GDALInfo(hDataset, psOptions.get());

        if (pszGDALInfoOutput)
            printf("%s", pszGDALInfoOutput);

        CPLFree(pszGDALInfoOutput);

        GDALClose(hDataset);
#ifdef __AFL_HAVE_MANUAL_CONTROL
    }
#endif

    GDALDumpOpenDatasets(stderr);

    GDALDestroyDriverManager();

    CPLDumpSharedList(nullptr);

    GDALDestroy();

    exit(0);
}

MAIN_END
