/******************************************************************************
 *
 * Project:  BAG Driver
 * Purpose:  Implements code to parse ISO 19115 metadata to extract a
 *           spatial reference system.  Eventually intended to be made
 *           a method on OGRSpatialReference.
 * Author:   Frank Warmerdam <warmerdam@pobox.com>
 *
 ******************************************************************************
 * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#ifdef _POSIX_C_SOURCE
#undef _POSIX_C_SOURCE
#endif

#include "cpl_port.h"

#include <cstdlib>
#include <cstring>

#include "cpl_error.h"
#include "cpl_minixml.h"
#include "iso19115_srs.h"
#include "ogr_core.h"
#include "ogr_spatialref.h"

/************************************************************************/
/*                     OGR_SRS_ImportFromISO19115()                     */
/************************************************************************/

OGRErr OGR_SRS_ImportFromISO19115(OGRSpatialReference *poThis,
                                  const char *pszISOXML)

{
    // Parse the XML into tree form.
    CPLXMLNode *psRoot = CPLParseXMLString(pszISOXML);

    if (psRoot == nullptr)
        return OGRERR_FAILURE;

    CPLStripXMLNamespace(psRoot, nullptr, TRUE);

    // For now we look for projection codes recognised in the BAG
    // format (see ons_fsd.pdf: Metadata Dataset Character String
    // Constants).
    CPLXMLNode *psRSI = CPLSearchXMLNode(psRoot, "=referenceSystemInfo");
    if (psRSI == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unable to find <referenceSystemInfo> in metadata.");
        CPLDestroyXMLNode(psRoot);
        return OGRERR_FAILURE;
    }

    poThis->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    poThis->Clear();

    // First, set the datum.
    const char *pszDatum =
        CPLGetXMLValue(psRSI, "MD_CRS.datum.RS_Identifier.code", "");

    if (strlen(pszDatum) > 0 &&
        poThis->SetWellKnownGeogCS(pszDatum) != OGRERR_NONE)
    {
        CPLDestroyXMLNode(psRoot);
        return OGRERR_FAILURE;
    }

    // Then try to extract the projection.
    const char *pszProjection =
        CPLGetXMLValue(psRSI, "MD_CRS.projection.RS_Identifier.code", "");

    if (EQUAL(pszProjection, "UTM"))
    {
        int nZone = atoi(CPLGetXMLValue(
            psRSI, "MD_CRS.projectionParameters.MD_ProjectionParameters.zone",
            "0"));

        // We have encountered files (#5152) that identify the southern
        // hemisphere with a false northing of 10000000 value.  The existing
        // code checked for negative zones, but it isn't clear if any actual
        // files use that.
        int bNorth = nZone > 0;
        if (bNorth)
        {
            const char *pszFalseNorthing =
                CPLGetXMLValue(psRSI,
                               "MD_CRS.projectionParameters.MD_"
                               "ProjectionParameters.falseNorthing",
                               "");
            if (strlen(pszFalseNorthing) > 0)
            {
                if (CPLAtof(pszFalseNorthing) == 0.0)
                {
                    bNorth = TRUE;
                }
                else if (CPLAtof(pszFalseNorthing) == 10000000.0)
                {
                    bNorth = FALSE;
                }
                else
                {
                    CPLError(CE_Failure, CPLE_AppDefined,
                             "falseNorthing value not recognized: %s",
                             pszFalseNorthing);
                }
            }
        }
        poThis->SetUTM(std::abs(nZone), bNorth);
    }
    else if (EQUAL(pszProjection, "Geodetic"))
    {
        const char *pszEllipsoid =
            CPLGetXMLValue(psRSI, "MD_CRS.ellipsoid.RS_Identifier.code", "");

        if (!EQUAL(pszDatum, "WGS84") || !EQUAL(pszEllipsoid, "WGS84"))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "ISO 19115 parser does not support custom GCS.");
            CPLDestroyXMLNode(psRoot);
            return OGRERR_FAILURE;
        }
    }
    else
    {
        if (!EQUAL(pszProjection, ""))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "projection = %s not recognised by ISO 19115 parser.",
                     pszProjection);
        }
        CPLDestroyXMLNode(psRoot);
        return OGRERR_FAILURE;
    }

    CPLDestroyXMLNode(psRoot);

    return OGRERR_NONE;
}
