#ifndef GGL_PROJECTIONS_ROBIN_HPP
#define GGL_PROJECTIONS_ROBIN_HPP

// Generic Geometry Library - projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright Barend Gehrels (1995-2009), Geodan Holding B.V. Amsterdam, the Netherlands.
// Copyright Bruno Lalande (2008-2009)
// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Geometry Library by Barend Gehrels (Geodan, Amsterdam)

// Original copyright notice:
 
// 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 <boost/math/special_functions/hypot.hpp>

#include <ggl/projections/impl/base_static.hpp>
#include <ggl/projections/impl/base_dynamic.hpp>
#include <ggl/projections/impl/projects.hpp>
#include <ggl/projections/impl/factory_entry.hpp>
#include <ggl/projections/impl/function_overloads.hpp>

namespace ggl { namespace projection
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace robin{ 
            static const double FXC = 0.8487;
            static const double FYC = 1.3523;
            static const double C1 = 11.45915590261646417544;
            static const double RC1 = 0.08726646259971647884;
            static const int NODES = 18;
            static const double ONEEPS = 1.000001;
            static const double EPS = 1e-8;

            /* note: following terms based upon 5 deg. intervals in degrees. */
            static struct COEFS {
                double c0, c1, c2, c3;
            } X[] = {
            {1,  -5.67239e-12,   -7.15511e-05,   3.11028e-06},
            {0.9986, -0.000482241,   -2.4897e-05,    -1.33094e-06},
            {0.9954, -0.000831031,   -4.4861e-05,    -9.86588e-07},
            {0.99,   -0.00135363,    -5.96598e-05,   3.67749e-06},
            {0.9822, -0.00167442,    -4.4975e-06,    -5.72394e-06},
            {0.973,  -0.00214869,    -9.03565e-05,   1.88767e-08},
            {0.96,   -0.00305084,    -9.00732e-05,   1.64869e-06},
            {0.9427, -0.00382792,    -6.53428e-05,   -2.61493e-06},
            {0.9216, -0.00467747,    -0.000104566,   4.8122e-06},
            {0.8962, -0.00536222,    -3.23834e-05,   -5.43445e-06},
            {0.8679, -0.00609364,    -0.0001139, 3.32521e-06},
            {0.835,  -0.00698325,    -6.40219e-05,   9.34582e-07},
            {0.7986, -0.00755337,    -5.00038e-05,   9.35532e-07},
            {0.7597, -0.00798325,    -3.59716e-05,   -2.27604e-06},
            {0.7186, -0.00851366,    -7.0112e-05,    -8.63072e-06},
            {0.6732, -0.00986209,    -0.000199572,   1.91978e-05},
            {0.6213, -0.010418,  8.83948e-05,    6.24031e-06},
            {0.5722, -0.00906601,    0.000181999,    6.24033e-06},
            {0.5322, 0.,0.,0.  }},
            Y[] = {
            {0,  0.0124, 3.72529e-10,    1.15484e-09},
            {0.062,  0.0124001,  1.76951e-08,    -5.92321e-09},
            {0.124,  0.0123998,  -7.09668e-08,   2.25753e-08},
            {0.186,  0.0124008,  2.66917e-07,    -8.44523e-08},
            {0.248,  0.0123971,  -9.99682e-07,   3.15569e-07},
            {0.31,   0.0124108,  3.73349e-06,    -1.1779e-06},
            {0.372,  0.0123598,  -1.3935e-05,    4.39588e-06},
            {0.434,  0.0125501,  5.20034e-05,    -1.00051e-05},
            {0.4968, 0.0123198,  -9.80735e-05,   9.22397e-06},
            {0.5571, 0.0120308,  4.02857e-05,    -5.2901e-06},
            {0.6176, 0.0120369,  -3.90662e-05,   7.36117e-07},
            {0.6769, 0.0117015,  -2.80246e-05,   -8.54283e-07},
            {0.7346, 0.0113572,  -4.08389e-05,   -5.18524e-07},
            {0.7903, 0.0109099,  -4.86169e-05,   -1.0718e-06},
            {0.8435, 0.0103433,  -6.46934e-05,   5.36384e-09},
            {0.8936, 0.00969679, -6.46129e-05,   -8.54894e-06},
            {0.9394, 0.00840949, -0.000192847,   -4.21023e-06},
            {0.9761, 0.00616525, -0.000256001,   -4.21021e-06},
            {1., 0.,0.,0 }};

            // template class, using CRTP to implement forward/inverse
            template <typename Geographic, typename Cartesian, typename Parameters>
            struct base_robin_spheroid : public base_t_fi<base_robin_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>
            {

                 typedef double geographic_type;
                 typedef double cartesian_type;


                inline base_robin_spheroid(const Parameters& par)
                    : base_t_fi<base_robin_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>(*this, par) {}

                inline double  V(COEFS const& C, double z) const
                { return (C.c0 + z * (C.c1 + z * (C.c2 + z * C.c3))); }
                inline double DV(COEFS const& C, double z) const
                { return (C.c1 + z * (C.c2 + C.c2 + z * 3. * C.c3)); }

                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    int i;
                    double dphi;
                
                    i = int_floor((dphi = fabs(lp_lat)) * C1);
                    if (i >= NODES) i = NODES - 1;
                    dphi = RAD_TO_DEG * (dphi - RC1 * i);
                    xy_x = V(X[i], dphi) * FXC * lp_lon;
                    xy_y = V(Y[i], dphi) * FYC;
                    if (lp_lat < 0.) xy_y = -xy_y;
                }

                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    int i;
                    double t, t1;
                    struct COEFS T;
                
                    lp_lon = xy_x / FXC;
                    lp_lat = fabs(xy_y / FYC);
                    if (lp_lat >= 1.) { /* simple pathologic cases */
                        if (lp_lat > ONEEPS) throw proj_exception();
                        else {
                            lp_lat = xy_y < 0. ? -HALFPI : HALFPI;
                            lp_lon /= X[NODES].c0;
                        }
                    } else { /* general problem */
                        /* in Y space, reduce to table interval */
                        for (i = int_floor(lp_lat * NODES);;) {
                            if (Y[i].c0 > lp_lat) --i;
                            else if (Y[i+1].c0 <= lp_lat) ++i;
                            else break;
                        }
                        T = Y[i];
                        /* first guess, linear interp */
                        t = 5. * (lp_lat - T.c0)/(Y[i+1].c0 - T.c0);
                        /* make into root */
                        T.c0 -= lp_lat;
                        for (;;) { /* Newton-Raphson reduction */
                            t -= t1 = V(T,t) / DV(T,t);
                            if (fabs(t1) < EPS)
                                break;
                        }
                        lp_lat = (5 * i + t) * DEG_TO_RAD;
                        if (xy_y < 0.) lp_lat = -lp_lat;
                        lp_lon /= V(X[i], t);
                    }
                }
            };

            // Robinson
            template <typename Parameters>
            void setup_robin(Parameters& par)
            {
                par.es = 0.;
                // par.inv = s_inverse;
                // par.fwd = s_forward;
            }

        }} // namespace detail::robin
    #endif // doxygen 

    /*!
        \brief Robinson projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Pseudocylindrical
         - Spheroid
        \par Example
        \image html ex_robin.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct robin_spheroid : public detail::robin::base_robin_spheroid<Geographic, Cartesian, Parameters>
    {
        inline robin_spheroid(const Parameters& par) : detail::robin::base_robin_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::robin::setup_robin(this->m_par);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Factory entry(s)
        template <typename Geographic, typename Cartesian, typename Parameters>
        class robin_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<robin_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        inline void robin_init(detail::base_factory<Geographic, Cartesian, Parameters>& factory)
        {
            factory.add_to_factory("robin", new robin_entry<Geographic, Cartesian, Parameters>);
        }

    } // namespace detail 
    #endif // doxygen

}} // namespace ggl::projection

#endif // GGL_PROJECTIONS_ROBIN_HPP

