/******************************************************************************
 *
 * Project:  GDAL Utilities
 * Purpose:  Command line application to list info about a given CRS.
 *           Outputs a number of formats (WKT, PROJ.4, etc.).
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *           Etienne Tourigny, etourigny.dev-at-gmail-dot-com
 *
 * ****************************************************************************
 * Copyright (c) 1998, Frank Warmerdam
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_string.h"
#include "gdal_version.h"
#include "gdal_priv.h"
#include "ogr_spatialref.h"
#include "ogr_api.h"
#include "ogrsf_frmts.h"
#include "commonutils.h"

#include "proj.h"

bool FindSRS(const char *pszInput, OGRSpatialReference &oSRS);
CPLErr PrintSRS(const OGRSpatialReference &oSRS, const char *pszOutputType,
                bool bPretty, bool bPrintSep);
void PrintSRSOutputTypes(const OGRSpatialReference &oSRS,
                         const char *const *papszOutputTypes, bool bPretty);

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

static void Usage(bool bIsError, const char *pszErrorMsg = nullptr)

{
    fprintf(bIsError ? stderr : stdout,
            "Usage: gdalsrsinfo [options] <srs_def>\n"
            "\n"
            "srs_def may be the filename of a dataset supported by GDAL/OGR "
            "from which to extract SRS information\n"
            "OR any of the usual GDAL/OGR forms "
            "(complete WKT, PROJ.4, EPSG:n or a file containing the SRS)\n"
            "\n"
            "Options: \n"
            "   [--help-general]       Show help on general options and exit\n"
            "   [--help] [-h]          Show help and exit\n"
            "   [--single-line]        Print WKT on single line\n"
            "   [-V]                   Validate SRS\n"
            "   [-e]                   Search for EPSG number(s) corresponding "
            "to SRS\n"
            "   [-o <out_type>]          Output type { default, all, wkt_all,\n"
#if PROJ_VERSION_MAJOR > 6 || PROJ_VERSION_MINOR >= 2
            "                                        PROJJSON, proj4, epsg,\n"
#else
            "                                        proj4, epsg,\n"
#endif
            "                                        wkt1, wkt_simple, "
            "wkt_noct, wkt_esri,\n"
            "                                        wkt2, wkt2_2015, "
            "wkt2_2019, mapinfo, xml }\n\n");

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

    exit(bIsError ? 1 : 0);
}

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

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

MAIN_START(argc, argv)

{
    bool bGotSRS = false;
    bool bPretty = true;
    bool bValidate = false;
    bool bFindEPSG = false;
    std::string osIdentifiedCode = "EPSG:-1";
    const char *pszInput = nullptr;
    const char *pszOutputType = "default";
    OGRSpatialReference oSRS;

    /* Check strict compilation and runtime library version as we use C++ API */
    if (!GDAL_CHECK_VERSION(argv[0]))
        exit(1);

    EarlySetConfigOptions(argc, argv);

    /* -------------------------------------------------------------------- */
    /*      Register standard GDAL and OGR drivers.                         */
    /* -------------------------------------------------------------------- */
    GDALAllRegister();

    /* -------------------------------------------------------------------- */
    /*      Register standard GDAL drivers, and process generic GDAL        */
    /*      command options.                                                */
    /* -------------------------------------------------------------------- */
    argc = GDALGeneralCmdLineProcessor(argc, &argv, 0);
    if (argc < 1)
        exit(-argc);

    /* -------------------------------------------------------------------- */
    /*      Parse arguments.                                                */
    /* -------------------------------------------------------------------- */
    for (int i = 1; i < argc; i++)
    {
        CPLDebug("gdalsrsinfo", "got arg #%d : [%s]", i, argv[i]);

        if (EQUAL(argv[i], "--utility_version"))
        {
            printf("%s was compiled against GDAL %s and is running against "
                   "GDAL %s\n",
                   argv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME"));
            CSLDestroy(argv);
            return 0;
        }
        else if (EQUAL(argv[i], "-h") || EQUAL(argv[i], "--help"))
            Usage(false);
        else if (EQUAL(argv[i], "-e"))
            bFindEPSG = true;
        else if (EQUAL(argv[i], "-o"))
        {
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            pszOutputType = argv[++i];
        }
        else if (EQUAL(argv[i], "-p"))
            bPretty = true;
        else if (EQUAL(argv[i], "--single-line"))
            bPretty = false;
        else if (EQUAL(argv[i], "-V"))
            bValidate = true;
        else if (argv[i][0] == '-')
        {
            Usage(true, CPLSPrintf("Unknown option name '%s'", argv[i]));
        }
        else
            pszInput = argv[i];
    }

    if (pszInput == nullptr)
    {
        CSLDestroy(argv);
        Usage(true, "No input specified.");
    }

    /* Search for SRS */
    /* coverity[tainted_data] */
    bGotSRS = FindSRS(pszInput, oSRS) == TRUE;

    CPLDebug("gdalsrsinfo",
             "bGotSRS: %d bValidate: %d pszOutputType: %s bPretty: %d",
             static_cast<int>(bGotSRS), static_cast<int>(bValidate),
             pszOutputType, static_cast<int>(bPretty));

    /* Make sure we got a SRS */
    if (!bGotSRS)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "ERROR - failed to load SRS definition from %s", pszInput);
        exit(1);
    }

    else
    {
        int nEntries = 0;
        int *panConfidence = nullptr;
        OGRSpatialReferenceH *pahSRS = nullptr;

        /* Find EPSG code */
        if (EQUAL(pszOutputType, "epsg"))
            bFindEPSG = true;

        if (bFindEPSG)
        {

            pahSRS =
                OSRFindMatches(reinterpret_cast<OGRSpatialReferenceH>(
                                   const_cast<OGRSpatialReference *>(&oSRS)),
                               nullptr, &nEntries, &panConfidence);
        }

        for (int i = 0; i < (nEntries ? nEntries : 1); i++)
        {
            if (nEntries)
            {
                oSRS = *reinterpret_cast<OGRSpatialReference *>(pahSRS[i]);
                if (panConfidence[i] != 100)
                {
                    printf("Confidence in this match: %d %%\n",
                           panConfidence[i]);
                }

                const char *pszAuthorityName = oSRS.GetAuthorityName();
                const char *pszAuthorityCode = oSRS.GetAuthorityCode();
                if (pszAuthorityName && pszAuthorityCode)
                {
                    osIdentifiedCode = pszAuthorityName;
                    osIdentifiedCode += ':';
                    osIdentifiedCode += pszAuthorityCode;
                }
            }

            /* Validate - not well tested!*/
            if (bValidate)
            {
                OGRErr eErr = oSRS.Validate();
                if (eErr != OGRERR_NONE)
                {
                    printf("\nValidate Fails");
                    if (eErr == OGRERR_CORRUPT_DATA)
                        printf(" - SRS is not well formed");
                    else if (eErr == OGRERR_UNSUPPORTED_SRS)
                        printf(" - contains non-standard PROJECTION[] values");
                    printf("\n");
                }
                else
                    printf("\nValidate Succeeds\n");
            }

            /* Output */
            if (EQUAL("default", pszOutputType))
            {
                const char *papszOutputTypes[] = {"proj4", "wkt2", nullptr};
                if (bFindEPSG)
                    printf("\n%s\n", osIdentifiedCode.c_str());
                PrintSRSOutputTypes(oSRS, papszOutputTypes, bPretty);
            }
            else if (EQUAL("all", pszOutputType))
            {
                if (bFindEPSG)
                    printf("\n%s\n", osIdentifiedCode.c_str());
                const char *papszOutputTypes[] = {
                    "proj4",     "wkt1",       "wkt2_2015",
                    "wkt2_2019", "wkt_simple", "wkt_noct",
                    "wkt_esri",  "mapinfo",    "xml",
#if PROJ_VERSION_MAJOR > 6 || PROJ_VERSION_MINOR >= 2
                    "PROJJSON",
#endif
                    nullptr};
                PrintSRSOutputTypes(oSRS, papszOutputTypes, bPretty);
            }
            else if (EQUAL("wkt_all", pszOutputType))
            {
                const char *papszOutputTypes[] = {
                    "wkt1",     "wkt2_2015", "wkt2_2019", "wkt_simple",
                    "wkt_noct", "wkt_esri",  nullptr};
                PrintSRSOutputTypes(oSRS, papszOutputTypes, bPretty);
            }
            else
            {
                if (bPretty)
                    printf("\n");
                if (EQUAL(pszOutputType, "epsg"))
                    printf("\n%s\n", osIdentifiedCode.c_str());
                else
                    PrintSRS(oSRS, pszOutputType, bPretty, FALSE);
                if (bPretty)
                    printf("\n");
            }
        }

        OSRFreeSRSArray(pahSRS);
        CPLFree(panConfidence);
    }

    /* cleanup anything left */
    GDALDestroyDriverManager();
    OGRCleanupAll();
    CSLDestroy(argv);

    return 0;
}

MAIN_END

/************************************************************************/
/*                      FindSRS()                                       */
/*                                                                      */
/*      Search for SRS from pszInput, update oSRS.                      */
/************************************************************************/
bool FindSRS(const char *pszInput, OGRSpatialReference &oSRS)

{
    bool bGotSRS = false;
    GDALDataset *poGDALDS = nullptr;
    OGRLayer *poLayer = nullptr;
    bool bIsFile = false;

    /* temporarily suppress error messages we may get from xOpen() */
    bool bDebug = CPLTestBool(CPLGetConfigOption("CPL_DEBUG", "OFF"));
    if (!bDebug)
        CPLPushErrorHandler(CPLQuietErrorHandler);

    /* Test if argument is a file */
    VSILFILE *fp = VSIFOpenL(pszInput, "r");
    if (fp)
    {
        bIsFile = true;
        VSIFCloseL(fp);
        CPLDebug("gdalsrsinfo", "argument is a file");
    }

    /* try to open with GDAL */
    if (!STARTS_WITH(pszInput, "http://spatialreference.org/"))
    {
        CPLDebug("gdalsrsinfo", "trying to open with GDAL");
        poGDALDS = static_cast<GDALDataset *>(
            GDALOpenEx(pszInput, 0, nullptr, nullptr, nullptr));
    }
    if (poGDALDS != nullptr)
    {
        const OGRSpatialReference *poSRS = poGDALDS->GetSpatialRef();
        if (poSRS)
        {
            oSRS = *poSRS;
            CPLDebug("gdalsrsinfo", "got SRS from GDAL");
            bGotSRS = true;
        }
        else if (poGDALDS->GetLayerCount() > 0)
        {
            poLayer = poGDALDS->GetLayer(0);
            if (poLayer != nullptr)
            {
                poSRS = poLayer->GetSpatialRef();
                if (poSRS != nullptr)
                {
                    CPLDebug("gdalsrsinfo", "got SRS from OGR");
                    bGotSRS = true;
                    oSRS = *poSRS;
                }
            }
        }
        GDALClose(poGDALDS);
        if (!bGotSRS)
            CPLDebug("gdalsrsinfo", "did not open with GDAL");
    }

    /* Try ESRI file */
    if (!bGotSRS && bIsFile && (strstr(pszInput, ".prj") != nullptr))
    {
        CPLDebug("gdalsrsinfo", "trying to get SRS from ESRI .prj file [%s]",
                 pszInput);

        char **pszTemp;
        if (strstr(pszInput, "ESRI::") != nullptr)
            pszTemp = CSLLoad(pszInput + 6);
        else
            pszTemp = CSLLoad(pszInput);

        OGRErr eErr = OGRERR_UNSUPPORTED_SRS;
        if (pszTemp)
        {
            eErr = oSRS.importFromESRI(pszTemp);
            CSLDestroy(pszTemp);
        }

        if (eErr != OGRERR_NONE)
        {
            CPLDebug("gdalsrsinfo", "did not get SRS from ESRI .prj file");
        }
        else
        {
            CPLDebug("gdalsrsinfo", "got SRS from ESRI .prj file");
            bGotSRS = true;
        }
    }

    /* restore error messages */
    if (!bDebug)
        CPLPopErrorHandler();

    /* Last resort, try OSRSetFromUserInput() */
    if (!bGotSRS)
    {
        CPLDebug("gdalsrsinfo", "trying to get SRS from user input [%s]",
                 pszInput);

        if (CPLGetConfigOption("CPL_ALLOW_VSISTDIN", nullptr) == nullptr)
            CPLSetConfigOption("CPL_ALLOW_VSISTDIN", "YES");

        const OGRErr eErr = oSRS.SetFromUserInput(pszInput);

        if (eErr != OGRERR_NONE)
        {
            CPLDebug("gdalsrsinfo", "did not get SRS from user input");
        }
        else
        {
            CPLDebug("gdalsrsinfo", "got SRS from user input");
            bGotSRS = true;
        }
    }

    return bGotSRS;
}

/************************************************************************/
/*                      PrintSRS()                                      */
/*                                                                      */
/*      Print spatial reference in specified format.                    */
/************************************************************************/
CPLErr PrintSRS(const OGRSpatialReference &oSRS, const char *pszOutputType,
                bool bPretty, bool bPrintSep)

{
    if (!pszOutputType || EQUAL(pszOutputType, ""))
        return CE_None;

    CPLDebug("gdalsrsinfo", "PrintSRS( oSRS, %s, %d, %d )\n", pszOutputType,
             static_cast<int>(bPretty), static_cast<int>(bPrintSep));

    char *pszOutput = nullptr;

    if (EQUAL("proj4", pszOutputType))
    {
        if (bPrintSep)
            printf("PROJ.4 : ");
        oSRS.exportToProj4(&pszOutput);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("PROJJSON", pszOutputType))
    {
        if (bPrintSep)
            printf("PROJJSON :\n");
        const char *const apszOptions[] = {
            bPretty ? "MULTILINE=YES" : "MULTILINE=NO", nullptr};
        oSRS.exportToPROJJSON(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("wkt1", pszOutputType))
    {
        if (bPrintSep)
            printf("OGC WKT1 :\n");
        const char *const apszOptions[] = {
            "FORMAT=WKT1_GDAL", bPretty ? "MULTILINE=YES" : nullptr, nullptr};
        oSRS.exportToWkt(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("wkt_simple", pszOutputType))
    {
        if (bPrintSep)
            printf("OGC WKT1 (simple) :\n");
        const char *const apszOptions[] = {
            "FORMAT=WKT1_SIMPLE", bPretty ? "MULTILINE=YES" : nullptr, nullptr};
        oSRS.exportToWkt(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("wkt_noct", pszOutputType))
    {
        if (bPrintSep)
            printf("OGC WKT1 (no CT) :\n");
        const char *const apszOptions[] = {
            "FORMAT=SFSQL", bPretty ? "MULTILINE=YES" : nullptr, nullptr};
        oSRS.exportToWkt(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("wkt_esri", pszOutputType))
    {
        if (bPrintSep)
            printf("ESRI WKT :\n");
        const char *const apszOptions[] = {
            "FORMAT=WKT1_ESRI", bPretty ? "MULTILINE=YES" : nullptr, nullptr};
        oSRS.exportToWkt(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("wkt2_2015", pszOutputType))
    {
        if (bPrintSep)
            printf("OGC WKT2:2015 :\n");
        const char *const apszOptions[] = {
            "FORMAT=WKT2_2015", bPretty ? "MULTILINE=YES" : nullptr, nullptr};
        oSRS.exportToWkt(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("wkt", pszOutputType) || EQUAL("wkt2", pszOutputType) ||
             EQUAL("wkt2_2018", pszOutputType) ||
             EQUAL("wkt2_2019", pszOutputType))
    {
        if (bPrintSep)
            printf("OGC WKT2:2019 :\n");
        const char *const apszOptions[] = {
            "FORMAT=WKT2_2018", bPretty ? "MULTILINE=YES" : nullptr, nullptr};
        oSRS.exportToWkt(&pszOutput, apszOptions);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("mapinfo", pszOutputType))
    {
        if (bPrintSep)
            printf("MAPINFO : ");
        oSRS.exportToMICoordSys(&pszOutput);
        printf("\'%s\'\n", pszOutput ? pszOutput : "(error)");
    }

    else if (EQUAL("xml", pszOutputType))
    {
        if (bPrintSep)
            printf("XML :\n");
        oSRS.exportToXML(&pszOutput, nullptr);
        printf("%s\n", pszOutput ? pszOutput : "(error)");
    }

    else
    {
        CPLError(CE_Failure, CPLE_AppDefined, "ERROR - %s output not supported",
                 pszOutputType);
        return CE_Failure;
    }

    CPLFree(pszOutput);

    return CE_None;
}

/************************************************************************/
/*                      PrintSRSOutputTypes()                           */
/*                                                                      */
/*      Print spatial reference in specified formats.                   */
/************************************************************************/
void PrintSRSOutputTypes(const OGRSpatialReference &oSRS,
                         const char *const *papszOutputTypes, bool bPretty)

{
    int nOutputTypes = CSLCount(papszOutputTypes);
    printf("\n");
    for (int i = 0; i < nOutputTypes; i++)
    {
        PrintSRS(oSRS, papszOutputTypes[i], bPretty, true);
        printf("\n");
    }
}
