/** \file
 *
 *  Contains the CodedStr class declaration.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef DATUM_H_
#define DATUM_H_

#include <math.h>
#include <cassert>

#include "AuvMath.h"

#include "../../Lib/geotrans/dt_cc/utm/utm.h"
#include "../../Lib/geotrans/dt_cc/tranmerc/tranmerc.h"

/// The width of one UTM zone (6 deg) in radians
static const double UTM_ZONE_WIDTH = M_PI / 30.;

/**
 *  Provides Datum information, for only WGS84
 *
 *  Derived from constants calculated on:
 *  http://www.uwgb.edu/dutchs/UsefulData/UTMFormulas.HTM
 *
 *  Modified to match:
 *  http://home.hiwaay.net/~taylorc/toolbox/geography/geoutm.html
 *  as the above uwgb.edu link had some transcription errors
 *
 *  Some code also taken from:
 *  http://www.koders.com/cpp/fid56D52408FAC344874E65BF9A1C54F3731C96A39B.aspx
 *  From his notation eqRadius_ = a, polRadius_ = b
 *  All in meters.
 *
 *  \ingroup utils
 */
class Wgs84
{
public:

#ifdef __GAZEBO
    static constexpr double Wgs84_a = 6378137.0;              /* Semi-major axis of ellipsoid in meters */
    static constexpr double Wgs84_f = 1 / 298.257223563;      /* Flattening of ellipsoid  */
#else
    static const double Wgs84_a = 6378137.0;              /* Semi-major axis of ellipsoid in meters */
    static const double Wgs84_f = 1 / 298.257223563;      /* Flattening of ellipsoid  */
#endif

    /// Converts a UTM and zone to lat/lon (in radians!).  This function complies
    /// with the standard UTM zone system.
    ///
    /// Operates on _corrected_ eastings (i.e. e=500,000 is on the center
    /// meridian of the zone)
    static long UtmToLatLon( const double northings, const double eastings,
                             const unsigned int zone, const bool northernHemi,
                             double &lat, double &lon )
    {
        double myLat = 0.0, myLon = 0.0;
        long myZone = zone;
        long error = Convert_UTM_To_Geodetic( myZone, northernHemi ? 'N' : 'S',
                                              eastings, northings, &myLat, &myLon );

        lat = myLat;
        lon = myLon;
        /// \todo error handling
        return error;
    }

    /// Converts a lat and lon (in radians!) to northings/eastings/zone.
    /// This function complies with the standard UTM zone system.
    static long LatLonToUtm( const double lat, const double lon,
                             double &northings, double &eastings,
                             unsigned int &zone, bool &northernHemi )
    {
        long myZone = 0;
        double myNorthings = 0.0, myEastings = 0.0;
        char myHemi = ' ';
        long error = Convert_Geodetic_To_UTM( lat, lon,
                                              &myZone, &myHemi,
                                              &myEastings, &myNorthings );
        zone = myZone;
        eastings = myEastings;
        northings = myNorthings;
        northernHemi = ( myHemi == 'N' );
        return error;
    }

    /// Converts a lat and lon (in radians!) to northings/eastings
    /// given the specified transverse mercator parameters
    static long LatLonToTransverseMercator( const double lat, const double lon,
                                            double &northings, double &eastings,
                                            const double originLatitude, const double centralMeridian,
                                            const double falseEasting, const double falseNorthing,
                                            const double scaleFactor )
    {
        long error = Set_Transverse_Mercator_Parameters( Wgs84_a, Wgs84_f, originLatitude, centralMeridian,
                     falseEasting, falseNorthing, scaleFactor );
        if( error == TRANMERC_NO_ERROR )
        {
            error = Convert_Geodetic_To_Transverse_Mercator( lat, lon, &eastings, &northings );
        }
        return error;
    }

    static const char *UtmErrorToString( long error )
    {
        switch( error )
        {
        case UTM_NO_ERROR:
            return "UTM_NO_ERROR";
        case UTM_LAT_ERROR:
            return "UTM_LAT_ERROR";
        case UTM_LON_ERROR:
            return "UTM_LON_ERROR";
        case UTM_EASTING_ERROR:
            return "UTM_EASTING_ERROR";
        case UTM_NORTHING_ERROR:
            return "UTM_NORTHING_ERROR";
        case UTM_ZONE_ERROR:
            return "UTM_ZONE_ERROR";
        case UTM_HEMISPHERE_ERROR:
            return "UTM_HEMISPHERE_ERROR";
        case UTM_ZONE_OVERRIDE_ERROR:
            return "UTM_ZONE_OVERRIDE_ERROR";
        case UTM_A_ERROR:
            return "UTM_A_ERROR";
        case UTM_INV_F_ERROR:
            return "UTM_INV_F_ERROR";
        default:
            return "(unknown)";
        }
    }

    static const char *TransMercErrorToString( long error )
    {
        switch( error )
        {
        case TRANMERC_NO_ERROR:
            return "TRANMERC_NO_ERROR";
        case TRANMERC_LAT_ERROR:
            return "TRANMERC_LAT_ERROR";
        case TRANMERC_LON_ERROR:
            return "TRANMERC_LON_ERROR";
        case TRANMERC_EASTING_ERROR:
            return "TRANMERC_EASTING_ERROR";
        case TRANMERC_NORTHING_ERROR:
            return "TRANMERC_NORTHING_ERROR";
        case TRANMERC_ORIGIN_LAT_ERROR:
            return "TRANMERC_ORIGIN_LAT_ERROR";
        case TRANMERC_CENT_MER_ERROR:
            return "TRANMERC_CENT_MER_ERROR";
        case TRANMERC_A_ERROR:
            return "TRANMERC_A_ERROR";
        case TRANMERC_INV_F_ERROR:
            return "TRANMERC_INV_F_ERROR";
        case TRANMERC_SCALE_FACTOR_ERROR:
            return "TRANMERC_SCALE_FACTOR_ERROR";
        case TRANMERC_LON_WARNING:
            return "TRANMERC_LON_WARNING";
        default:
            return "(unknown)";
        }
    }

private:

    Wgs84()
    {}
    ;
};

#endif /*DATUM_H_*/
