///////////////////////////////////////////////////////////////////////////////
//
// Project:  C++ Test Suite for GDAL/OGR
// Purpose:  OGR Spatial Reference general features test.
// Author:   Mateusz Loskot <mateusz@loskot.net>
//
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2006, Mateusz Loskot <mateusz@loskot.net>
/*
 * 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 "gdal_unit_test.h"

#include "cpl_string.h"
#include "ogr_srs_api.h"
#include "ogr_spatialref.h"

#include <algorithm>
#include <cmath>
#include <string>

namespace tut
{

// Common fixture with test data
struct test_osr_data
{
    OGRErr err_;
    OGRSpatialReferenceH srs_;

    test_osr_data() : err_(OGRERR_NONE), srs_(nullptr)
    {
        srs_ = OSRNewSpatialReference(nullptr);
    }

    ~test_osr_data()
    {
        OSRDestroySpatialReference(srs_);
    }
};

// Register test group
typedef test_group<test_osr_data> group;
typedef group::object object;
group test_osr_group("OSR");

// Test UTM WGS84 coordinate system and its various items
template <> template <> void object::test<1>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    err_ = OSRSetUTM(srs_, 11, TRUE);
    ensure_equals("Can't set UTM zone", err_, OGRERR_NONE);

    err_ = OSRSetWellKnownGeogCS(srs_, "WGS84");
    ensure_equals("Can't set GeogCS", err_, OGRERR_NONE);

    double val = 0;

    val = OSRGetProjParm(srs_, SRS_PP_CENTRAL_MERIDIAN, -1111, &err_);
    ensure("Invalid UTM central meridian",
           std::fabs(val - (-117.0)) <= .00000000000010);

    val = OSRGetProjParm(srs_, SRS_PP_LATITUDE_OF_ORIGIN, -1111, &err_);
    ensure("Invalid UTM latitude of origin",
           std::fabs(val - 0.0) <= .00000000000010);

    val = OSRGetProjParm(srs_, SRS_PP_SCALE_FACTOR, -1111, &err_);
    ensure("Invalid UTM scale factor",
           std::fabs(val - 0.9996) <= .00000000000010);

    val = OSRGetProjParm(srs_, SRS_PP_FALSE_EASTING, -1111, &err_);
    ensure("Invalid UTM false easting",
           std::fabs(val - 500000.0) <= .00000000000010);

    val = OSRGetProjParm(srs_, SRS_PP_FALSE_NORTHING, -1111, &err_);
    ensure("Invalid UTM false northing",
           std::fabs(val - 0.0) <= .00000000000010);

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "GEOGCS")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "GEOGCS")),
                  std::string("4326"));

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "DATUM")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "DATUM")),
                  std::string("6326"));
}

// Simple default NAD83 State Plane zone
template <> template <> void object::test<2>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    // California III NAD83
    OSRSetStatePlane(srs_, 403, 1);

    double val = 0;

    val = OSRGetProjParm(srs_, SRS_PP_STANDARD_PARALLEL_1, -1111, &err_);
    ensure_approx_equals(val, 38.43333333333333);

    val = OSRGetProjParm(srs_, SRS_PP_STANDARD_PARALLEL_2, -1111, &err_);
    ensure_approx_equals(val, 37.06666666666667);

    val = OSRGetProjParm(srs_, SRS_PP_LATITUDE_OF_ORIGIN, -1111, &err_);
    ensure_approx_equals(val, 36.5);

    val = OSRGetProjParm(srs_, SRS_PP_CENTRAL_MERIDIAN, -1111, &err_);
    ensure_approx_equals(val, -120.5);

    val = OSRGetProjParm(srs_, SRS_PP_FALSE_EASTING, -1111, &err_);
    ensure_approx_equals(val, 2000000.0);

    val = OSRGetProjParm(srs_, SRS_PP_FALSE_NORTHING, -1111, &err_);
    ensure_approx_equals(val, 500000.0);

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "GEOGCS")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "GEOGCS")),
                  std::string("4269"));

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "DATUM")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "DATUM")),
                  std::string("6269"));

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "PROJCS")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "PROJCS")),
                  std::string("26943"));

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "PROJCS|UNIT")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "PROJCS|UNIT")),
                  std::string("9001"));
}

// NAD83 State Plane zone, but overridden to be in Feet
template <> template <> void object::test<3>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    // California III NAD83 (feet)
    OSRSetStatePlaneWithUnits(srs_, 403, 1, "Foot", 0.3048006096012192);

    double val = 0;

    val = OSRGetProjParm(srs_, SRS_PP_STANDARD_PARALLEL_1, -1111, &err_);
    ensure_approx_equals(val, 38.43333333333333);

    val = OSRGetProjParm(srs_, SRS_PP_STANDARD_PARALLEL_2, -1111, &err_);
    ensure_approx_equals(val, 37.06666666666667);

    val = OSRGetProjParm(srs_, SRS_PP_LATITUDE_OF_ORIGIN, -1111, &err_);
    ensure_approx_equals(val, 36.5);

    val = OSRGetProjParm(srs_, SRS_PP_CENTRAL_MERIDIAN, -1111, &err_);
    ensure_approx_equals(val, -120.5);

    val = OSRGetProjParm(srs_, SRS_PP_FALSE_EASTING, -1111, &err_);
    ensure_approx_equals(val, 6561666.666666667);

    val = OSRGetProjParm(srs_, SRS_PP_FALSE_NORTHING, -1111, &err_);
    ensure_approx_equals(val, 1640416.666666667);

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "GEOGCS")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "GEOGCS")),
                  std::string("4269"));

    ensure_equals("Invalid authority name",
                  std::string(OSRGetAuthorityName(srs_, "DATUM")),
                  std::string("EPSG"));
    ensure_equals("Invalid authority code",
                  std::string(OSRGetAuthorityCode(srs_, "DATUM")),
                  std::string("6269"));

    ensure("Got a PROJCS Authority but we should not",
           nullptr == OSRGetAuthorityName(srs_, "PROJCS"));

    ensure("Got METER authority code on linear units",
           nullptr == OSRGetAuthorityCode(srs_, "PROJCS|UNIT"));

    char *unitsName = nullptr;
    val = OSRGetLinearUnits(srs_, &unitsName);
    ensure("Units name is NULL", nullptr != unitsName);
    ensure("Did not get Foot linear units", std::string("Foot") == unitsName);
}

// Translate a coordinate system with NAD shift into to PROJ.4 and back.
// Also, verify that the TOWGS84 parameters are preserved.
template <> template <> void object::test<4>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    err_ = OSRSetGS(srs_, -117.0, 100000.0, 100000);
    ensure_equals("OSRSetGS failed", err_, OGRERR_NONE);

    err_ = OSRSetGeogCS(srs_, "Test GCS", "Test Datum", "WGS84",
                        SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING, nullptr,
                        0, nullptr, 0);
    ensure_equals("OSRSetGeogCS failed", err_, OGRERR_NONE);

    err_ = OSRSetTOWGS84(srs_, 1, 2, 3, 0, 0, 0, 0);
    ensure_equals("OSRSetTOWGS84 failed", err_, OGRERR_NONE);

    const int coeffSize = 7;
    double coeff[coeffSize] = {0};
    const double expect[coeffSize] = {1, 2, 3, 0, 0, 0, 0};

    err_ = OSRGetTOWGS84(srs_, coeff, 7);
    ensure_equals("OSRSetTOWGS84 failed", err_, OGRERR_NONE);
    ensure("GetTOWGS84 result is wrong",
           std::equal(coeff, coeff + coeffSize, expect));
    OSRSetLinearUnits(srs_, "Metre", 1);

    char *proj4 = nullptr;
    err_ = OSRExportToProj4(srs_, &proj4);
    ensure_equals("OSRExportToProj4 failed", err_, OGRERR_NONE);

    OGRSpatialReferenceH srs2 = nullptr;
    srs2 = OSRNewSpatialReference(nullptr);

    err_ = OSRImportFromProj4(srs2, proj4);
    ensure_equals("OSRImportFromProj4 failed", err_, OGRERR_NONE);

    err_ = OSRGetTOWGS84(srs2, coeff, 7);
    ensure_equals("OSRSetTOWGS84 failed", err_, OGRERR_NONE);
    ensure("GetTOWGS84 result is wrong",
           std::equal(coeff, coeff + coeffSize, expect));

    OSRDestroySpatialReference(srs2);
    CPLFree(proj4);
}

// Test URN support for OGC:CRS84
template <> template <> void object::test<5>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    err_ = OSRSetFromUserInput(srs_, "urn:ogc:def:crs:OGC:1.3:CRS84");
    ensure_equals("OSRSetFromUserInput failed", err_, OGRERR_NONE);

    char *wkt1 = nullptr;
    err_ = OSRExportToWkt(srs_, &wkt1);
    ensure_equals("OSRExportToWkt failed", err_, OGRERR_NONE);
    ensure("OSRExportToWkt returned NULL", nullptr != wkt1);

    CPLFree(wkt1);
}

// Test URN support for EPSG
template <> template <> void object::test<6>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    err_ = OSRSetFromUserInput(srs_, "urn:ogc:def:crs:EPSG::4326");
    ensure_equals("OSRSetFromUserInput failed", err_, OGRERR_NONE);

    char *wkt1 = nullptr;
    err_ = OSRExportToWkt(srs_, &wkt1);
    ensure_equals("OSRExportToWkt failed", err_, OGRERR_NONE);
    ensure("OSRExportToWkt returned NULL", nullptr != wkt1);

    err_ = OSRSetFromUserInput(srs_, "EPSGA:4326");
    ensure_equals("OSRSetFromUserInput failed", err_, OGRERR_NONE);

    char *wkt2 = nullptr;
    err_ = OSRExportToWkt(srs_, &wkt2);
    ensure_equals("OSRExportToWkt failed", err_, OGRERR_NONE);
    ensure("OSRExportToWkt returned NULL", nullptr != wkt2);

    ensure_equals("EPSG:4326 urn lookup not as expected", std::string(wkt1),
                  std::string(wkt2));
    CPLFree(wkt1);
    CPLFree(wkt2);
}

// Test URN support for auto projection
template <> template <> void object::test<7>()
{
    ensure("SRS handle is NULL", nullptr != srs_);

    err_ = OSRSetFromUserInput(srs_, "urn:ogc:def:crs:OGC::AUTO42001:-117:33");
    ensure_equals("OSRSetFromUserInput failed", err_, OGRERR_NONE);

    OGRSpatialReference oSRS;
    oSRS.importFromEPSG(32611);

    ensure(oSRS.IsSame(OGRSpatialReference::FromHandle(srs_)));
}

// Test StripTOWGS84IfKnownDatum
template <> template <> void object::test<8>()
{
    // Not a boundCRS
    {
        OGRSpatialReference oSRS;
        oSRS.importFromEPSG(4326);
        ensure(!oSRS.StripTOWGS84IfKnownDatum());
    }
    // Custom boundCRS --> do not strip TOWGS84
    {
        OGRSpatialReference oSRS;
        oSRS.SetFromUserInput(
            "+proj=longlat +ellps=GRS80 +towgs84=1,2,3,4,5,6,7");
        ensure(!oSRS.StripTOWGS84IfKnownDatum());
        double vals[7] = {0};
        ensure(oSRS.GetTOWGS84(vals, 7) == OGRERR_NONE);
    }
    // BoundCRS whose base CRS has a known code --> strip TOWGS84
    {
        OGRSpatialReference oSRS;
        oSRS.importFromEPSG(4326);
        oSRS.SetTOWGS84(1, 2, 3, 4, 5, 6, 7);
        ensure(oSRS.StripTOWGS84IfKnownDatum());
        double vals[7] = {0};
        ensure(oSRS.GetTOWGS84(vals, 7) != OGRERR_NONE);
    }
    // BoundCRS whose datum code is known --> strip TOWGS84
    {
        OGRSpatialReference oSRS;
        oSRS.SetFromUserInput("GEOGCS[\"bar\","
                              "DATUM[\"foo\","
                              "SPHEROID[\"WGS 84\",6378137,298.257223563],"
                              "TOWGS84[1,2,3,4,5,6,7],"
                              "AUTHORITY[\"FOO\",\"1\"]],"
                              "PRIMEM[\"Greenwich\",0],"
                              "UNIT[\"degree\",0.0174532925199433]]");
        ensure(oSRS.StripTOWGS84IfKnownDatum());
        double vals[7] = {0};
        ensure(oSRS.GetTOWGS84(vals, 7) != OGRERR_NONE);
    }
    // BoundCRS whose datum name is known --> strip TOWGS84
    {
        OGRSpatialReference oSRS;
        oSRS.SetFromUserInput("GEOGCS[\"WGS 84\","
                              "DATUM[\"WGS_1984\","
                              "SPHEROID[\"WGS 84\",6378137,298.257223563],"
                              "TOWGS84[1,2,3,4,5,6,7]],"
                              "PRIMEM[\"Greenwich\",0],"
                              "UNIT[\"degree\",0.0174532925199433]]");
        ensure(oSRS.StripTOWGS84IfKnownDatum());
        double vals[7] = {0};
        ensure(oSRS.GetTOWGS84(vals, 7) != OGRERR_NONE);
    }
    // BoundCRS whose datum name is unknown --> do not strip TOWGS84
    {
        OGRSpatialReference oSRS;
        oSRS.SetFromUserInput("GEOGCS[\"WGS 84\","
                              "DATUM[\"i am unknown\","
                              "SPHEROID[\"WGS 84\",6378137,298.257223563],"
                              "TOWGS84[1,2,3,4,5,6,7]],"
                              "PRIMEM[\"Greenwich\",0],"
                              "UNIT[\"degree\",0.0174532925199433]]");
        ensure(!oSRS.StripTOWGS84IfKnownDatum());
        double vals[7] = {0};
        ensure(oSRS.GetTOWGS84(vals, 7) == OGRERR_NONE);
    }
}

// Test GetEPSGGeogCS
template <> template <> void object::test<9>()
{
    // When export to WKT1 is not possible
    OGRSpatialReference oSRS;
    oSRS.SetFromUserInput(
        "PROJCRS[\"World_Vertical_Perspective\",\n"
        "    BASEGEOGCRS[\"WGS 84\",\n"
        "        DATUM[\"World Geodetic System 1984\",\n"
        "            ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
        "                LENGTHUNIT[\"metre\",1]]],\n"
        "        PRIMEM[\"Greenwich\",0,\n"
        "            ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n"
        "    CONVERSION[\"World_Vertical_Perspective\",\n"
        "        METHOD[\"Vertical Perspective\",\n"
        "            ID[\"EPSG\",9838]],\n"
        "        PARAMETER[\"Latitude of topocentric origin\",0,\n"
        "            ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
        "            ID[\"EPSG\",8834]],\n"
        "        PARAMETER[\"Longitude of topocentric origin\",0,\n"
        "            ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
        "            ID[\"EPSG\",8835]],\n"
        "        PARAMETER[\"Viewpoint height\",35800000,\n"
        "            LENGTHUNIT[\"metre\",1],\n"
        "            ID[\"EPSG\",8840]]],\n"
        "    CS[Cartesian,2],\n"
        "        AXIS[\"(E)\",east,\n"
        "            ORDER[1],\n"
        "            LENGTHUNIT[\"metre\",1]],\n"
        "        AXIS[\"(N)\",north,\n"
        "            ORDER[2],\n"
        "            LENGTHUNIT[\"metre\",1]],\n"
        "    USAGE[\n"
        "        SCOPE[\"Not known.\"],\n"
        "        AREA[\"World.\"],\n"
        "        BBOX[-90,-180,90,180]],\n"
        "    ID[\"ESRI\",54049]]");
    ensure_equals(oSRS.GetEPSGGeogCS(), 4326);
}

// Test GetOGCURN
template <> template <> void object::test<10>()
{
    {
        OGRSpatialReference oSRS;
        ensure(oSRS.GetOGCURN() == nullptr);
    }
    {
        OGRSpatialReference oSRS;
        oSRS.SetFromUserInput("+proj=longlat");
        ensure(oSRS.GetOGCURN() == nullptr);
    }

    {
        OGRSpatialReference oSRS;
        oSRS.importFromEPSG(32631);
        char *pszRet = oSRS.GetOGCURN();
        ensure(pszRet != nullptr);
        ensure(strcmp(pszRet, "urn:ogc:def:crs:EPSG::32631") == 0);
        CPLFree(pszRet);
    }

    {
        OGRSpatialReference oSRS;
        oSRS.SetFromUserInput("EPSG:32631+5773");
        char *pszRet = oSRS.GetOGCURN();
        ensure(pszRet != nullptr);
        ensure(strcmp(pszRet,
                      "urn:ogc:def:crs,crs:EPSG::32631,crs:EPSG::5773") == 0);
        CPLFree(pszRet);
    }
}

// Test constructors and assignment operators
template <> template <> void object::test<11>()
{
    OGRSpatialReference oSRS;
    oSRS.importFromEPSG(32631);

    OGRSpatialReference oSRS2(oSRS);
    ensure(oSRS2.GetAuthorityCode(nullptr) != nullptr);

    OGRSpatialReference oSRS3;
    OGRSpatialReference &oSRSRef(oSRS);
    oSRS = oSRSRef;
    ensure(oSRS.GetAuthorityCode(nullptr) != nullptr);
    oSRS3 = oSRS;
    ensure(oSRS3.GetAuthorityCode(nullptr) != nullptr);

    OGRSpatialReference oSRS4(std::move(oSRS));
    ensure(oSRS4.GetAuthorityCode(nullptr) != nullptr);

    OGRSpatialReference oSRS5;
    OGRSpatialReference &oSRS4Ref(oSRS4);
    oSRS4 = std::move(oSRS4Ref);
    ensure(oSRS4.GetAuthorityCode(nullptr) != nullptr);
    oSRS5 = std::move(oSRS4);
    ensure(oSRS5.GetAuthorityCode(nullptr) != nullptr);
}

}  // namespace tut
