// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// 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 Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// 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.

#ifndef BOOST_GEOMETRY_PROJECTIONS_SCONICS_HPP
#define BOOST_GEOMETRY_PROJECTIONS_SCONICS_HPP

#include <boost/geometry/util/math.hpp>
#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace sconics
    {

            enum proj_type {
                proj_euler  = 0,
                proj_murd1  = 1,
                proj_murd2  = 2,
                proj_murd3  = 3,
                proj_pconic = 4,
                proj_tissot = 5,
                proj_vitk1  = 6
            };
            static const double epsilon10 = 1.e-10;
            static const double epsilon = 1e-10;

            template <typename T>
            struct par_sconics
            {
                T   n;
                T   rho_c;
                T   rho_0;
                T   sig;
                T   c1, c2;
                proj_type type;
            };

            /* get common factors for simple conics */
            template <typename Params, typename T>
            inline int phi12(Params const& params, par_sconics<T>& proj_parm, T *del)
            {
                T p1, p2;
                int err = 0;

                if (!pj_param_r<srs::spar::lat_1>(params, "lat_1", srs::dpar::lat_1, p1) ||
                    !pj_param_r<srs::spar::lat_2>(params, "lat_2", srs::dpar::lat_2, p2)) {
                    err = -41;
                } else {
                    //p1 = pj_get_param_r(par.params, "lat_1"); // set above
                    //p2 = pj_get_param_r(par.params, "lat_2"); // set above
                    *del = 0.5 * (p2 - p1);
                    proj_parm.sig = 0.5 * (p2 + p1);
                    err = (fabs(*del) < epsilon || fabs(proj_parm.sig) < epsilon) ? -42 : 0;
                }
                return err;
            }

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_sconics_spheroid
                : public base_t_fi<base_sconics_spheroid<T, Parameters>, T, Parameters>
            {
                par_sconics<T> m_proj_parm;

                inline base_sconics_spheroid(const Parameters& par)
                    : base_t_fi<base_sconics_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T rho;

                    switch (this->m_proj_parm.type) {
                    case proj_murd2:
                        rho = this->m_proj_parm.rho_c + tan(this->m_proj_parm.sig - lp_lat);
                        break;
                    case proj_pconic:
                        rho = this->m_proj_parm.c2 * (this->m_proj_parm.c1 - tan(lp_lat - this->m_proj_parm.sig));
                        break;
                    default:
                        rho = this->m_proj_parm.rho_c - lp_lat;
                        break;
                    }
                    xy_x = rho * sin( lp_lon *= this->m_proj_parm.n );
                    xy_y = this->m_proj_parm.rho_0 - rho * cos(lp_lon);
                }

                // INVERSE(s_inverse)  ellipsoid & spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T xy_x, T xy_y, T& lp_lon, T& lp_lat) const
                {
                    T rho;

                    rho = boost::math::hypot(xy_x, xy_y = this->m_proj_parm.rho_0 - xy_y);
                    if (this->m_proj_parm.n < 0.) {
                        rho = - rho;
                        xy_x = - xy_x;
                        xy_y = - xy_y;
                    }

                    lp_lon = atan2(xy_x, xy_y) / this->m_proj_parm.n;

                    switch (this->m_proj_parm.type) {
                    case proj_pconic:
                        lp_lat = atan(this->m_proj_parm.c1 - rho / this->m_proj_parm.c2) + this->m_proj_parm.sig;
                        break;
                    case proj_murd2:
                        lp_lat = this->m_proj_parm.sig - atan(rho - this->m_proj_parm.rho_c);
                        break;
                    default:
                        lp_lat = this->m_proj_parm.rho_c - rho;
                    }
                }

                static inline std::string get_name()
                {
                    return "sconics_spheroid";
                }

            };

            template <typename Params, typename Parameters, typename T>
            inline void setup(Params const& params, Parameters& par, par_sconics<T>& proj_parm, proj_type type)
            {
                static const T half_pi = detail::half_pi<T>();

                T del, cs;
                int err;

                proj_parm.type = type;

                err = phi12(params, proj_parm, &del);
                if(err)
                    BOOST_THROW_EXCEPTION( projection_exception(err) );

                switch (proj_parm.type) {
                case proj_tissot:
                    proj_parm.n = sin(proj_parm.sig);
                    cs = cos(del);
                    proj_parm.rho_c = proj_parm.n / cs + cs / proj_parm.n;
                    proj_parm.rho_0 = sqrt((proj_parm.rho_c - 2 * sin(par.phi0))/proj_parm.n);
                    break;
                case proj_murd1:
                    proj_parm.rho_c = sin(del)/(del * tan(proj_parm.sig)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    proj_parm.n = sin(proj_parm.sig);
                    break;
                case proj_murd2:
                    proj_parm.rho_c = (cs = sqrt(cos(del))) / tan(proj_parm.sig);
                    proj_parm.rho_0 = proj_parm.rho_c + tan(proj_parm.sig - par.phi0);
                    proj_parm.n = sin(proj_parm.sig) * cs;
                    break;
                case proj_murd3:
                    proj_parm.rho_c = del / (tan(proj_parm.sig) * tan(del)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    proj_parm.n = sin(proj_parm.sig) * sin(del) * tan(del) / (del * del);
                    break;
                case proj_euler:
                    proj_parm.n = sin(proj_parm.sig) * sin(del) / del;
                    del *= 0.5;
                    proj_parm.rho_c = del / (tan(del) * tan(proj_parm.sig)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    break;
                case proj_pconic:
                    proj_parm.n = sin(proj_parm.sig);
                    proj_parm.c2 = cos(del);
                    proj_parm.c1 = 1./tan(proj_parm.sig);
                    if (fabs(del = par.phi0 - proj_parm.sig) - epsilon10 >= half_pi)
                        BOOST_THROW_EXCEPTION( projection_exception(error_lat_0_half_pi_from_mean) );
                    proj_parm.rho_0 = proj_parm.c2 * (proj_parm.c1 - tan(del));
                    break;
                case proj_vitk1:
                    proj_parm.n = (cs = tan(del)) * sin(proj_parm.sig) / del;
                    proj_parm.rho_c = del / (cs * tan(proj_parm.sig)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    break;
                }

                par.es = 0;
            }

            // Euler
            template <typename Params, typename Parameters, typename T>
            inline void setup_euler(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_euler);
            }

            // Tissot
            template <typename Params, typename Parameters, typename T>
            inline void setup_tissot(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_tissot);
            }

            // Murdoch I
            template <typename Params, typename Parameters, typename T>
            inline void setup_murd1(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_murd1);
            }

            // Murdoch II
            template <typename Params, typename Parameters, typename T>
            inline void setup_murd2(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_murd2);
            }

            // Murdoch III
            template <typename Params, typename Parameters, typename T>
            inline void setup_murd3(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_murd3);
            }

            // Perspective Conic
            template <typename Params, typename Parameters, typename T>
            inline void setup_pconic(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_pconic);
            }

            // Vitkovsky I
            template <typename Params, typename Parameters, typename T>
            inline void setup_vitk1(Params const& params, Parameters& par, par_sconics<T>& proj_parm)
            {
                setup(params, par, proj_parm, proj_vitk1);
            }

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

    /*!
        \brief Tissot projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_tissot.gif
    */
    template <typename T, typename Parameters>
    struct tissot_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline tissot_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_tissot(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Murdoch I projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_murd1.gif
    */
    template <typename T, typename Parameters>
    struct murd1_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline murd1_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_murd1(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Murdoch II projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_murd2.gif
    */
    template <typename T, typename Parameters>
    struct murd2_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline murd2_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_murd2(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Murdoch III projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_murd3.gif
    */
    template <typename T, typename Parameters>
    struct murd3_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline murd3_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_murd3(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Euler projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_euler.gif
    */
    template <typename T, typename Parameters>
    struct euler_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline euler_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_euler(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Perspective Conic projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_pconic.gif
    */
    template <typename T, typename Parameters>
    struct pconic_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline pconic_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_pconic(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Vitkovsky I projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel
         - lat_2: Latitude of second standard parallel
        \par Example
        \image html ex_vitk1.gif
    */
    template <typename T, typename Parameters>
    struct vitk1_spheroid : public detail::sconics::base_sconics_spheroid<T, Parameters>
    {
        template <typename Params>
        inline vitk1_spheroid(Params const& params, const Parameters& par)
            : detail::sconics::base_sconics_spheroid<T, Parameters>(par)
        {
            detail::sconics::setup_vitk1(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_euler, euler_spheroid, euler_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_murd1, murd1_spheroid, murd1_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_murd2, murd2_spheroid, murd2_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_murd3, murd3_spheroid, murd3_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_pconic, pconic_spheroid, pconic_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_tissot, tissot_spheroid, tissot_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_vitk1, vitk1_spheroid, vitk1_spheroid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(euler_entry, euler_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(murd1_entry, murd1_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(murd2_entry, murd2_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(murd3_entry, murd3_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(pconic_entry, pconic_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(tissot_entry, tissot_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(vitk1_entry, vitk1_spheroid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(sconics_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(euler, euler_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(murd1, murd1_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(murd2, murd2_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(murd3, murd3_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(pconic, pconic_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(tissot, tissot_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(vitk1, vitk1_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_SCONICS_HPP


/*
 * The MIT License (MIT)
 * =====================
 *
 * Copyright © 2019-2020 Azavea
 *
 * 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 <cstdio>
#include <cstdint>
#include <cmath>

#include <set>
#include <vector>

#include <boost/polygon/segment_data.hpp>
#include <boost/polygon/voronoi.hpp>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/index/rtree.hpp>

namespace bp = boost::polygon;
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;

typedef int64_t integral_coordinate_t;

struct integral_point_t
{
    integral_coordinate_t x;
    integral_coordinate_t y;
} __attribute__((packed));

bool operator<(const integral_point_t &a, const integral_point_t &b)
{
    if (a.x == b.x)
    {
        return (a.y < b.y);
    }
    else
    {
        return (a.x < b.x);
    }
}

struct integral_segment_t
{
    integral_point_t v0;
    integral_point_t v1;
} __attribute__((packed));

bool operator<(const integral_segment_t &a, const integral_segment_t &b)
{
    if (a.v0.x == b.v0.x)
    {
        return (a.v0.y <= b.v0.y);
    }
    else
    {
        return (a.v0.x <= b.v0.x);
    }
}

typedef bp::voronoi_edge<double> voronoi_edge_t;
typedef bp::voronoi_diagram<double> voronoi_diagram_t;

typedef std::pair<const integral_segment_t *, uint32_t> integral_rtree_value_t;
typedef bgi::rtree<integral_rtree_value_t, bgi::linear<16>> integral_rtree_t;

typedef bg::model::point<double, 2, bg::cs::cartesian> real_point_t;
typedef bg::model::segment<real_point_t> real_segment_t;
typedef std::pair<real_segment_t, uint32_t> real_rtree_value_t;
typedef bgi::rtree<real_rtree_value_t, bgi::linear<16>> real_rtree_t;

// Geometry concepts
namespace boost
{
namespace geometry
{
namespace traits
{
template <>
struct tag<integral_point_t>
{
    typedef point_tag type;
};

template <>
struct coordinate_type<integral_point_t>
{
    typedef integral_coordinate_t type;
};

template <>
struct coordinate_system<integral_point_t>
{
    typedef cs::cartesian type;
};

template <>
struct dimension<integral_point_t> : boost::mpl::int_<2>
{
};

template <std::size_t Dimension>
struct access<integral_point_t, Dimension>
{
    static inline integral_coordinate_t get(integral_point_t const &p)
    {
        if (Dimension == 0)
        {
            return p.x;
        }
        else if (Dimension == 1)
        {
            return p.y;
        }
        else
        {
            throw __LINE__;
        }
    }

    static inline void set(integral_point_t &p, integral_coordinate_t c)
    {
        if (Dimension == 0)
        {
            p.x = c;
        }
        else if (Dimension == 1)
        {
            p.y = c;
        }
        else
        {
            throw __LINE__;
        }
    }
};

template <>
struct tag<integral_segment_t>
{
    typedef segment_tag type;
};

template <>
struct point_type<integral_segment_t>
{
    typedef integral_point_t type;
};

template <std::size_t Index, std::size_t Dimension>
struct indexed_access<integral_segment_t, Index, Dimension>
{
    static inline integral_coordinate_t get(integral_segment_t const &s)
    {
        if (Index == 0 && Dimension == 0)
        {
            return s.v0.x;
        }
        else if (Index == 0 && Dimension == 1)
        {
            return s.v0.y;
        }
        else if (Index == 1 && Dimension == 0)
        {
            return s.v1.x;
        }
        else if (Index == 1 && Dimension == 1)
        {
            return s.v1.y;
        }
        else
        {
            throw __LINE__;
        }
    }
};
} // namespace traits
} // namespace geometry
} // namespace boost

// Polygon concepts
namespace boost
{
namespace polygon
{
template <>
struct geometry_concept<integral_point_t>
{
    typedef point_concept type;
};

template <>
struct point_traits<integral_point_t>
{
    typedef int64_t coordinate_type;

    static coordinate_type get(const integral_point_t &p, orientation_2d orient)
    {
        if (orient.to_int() == 0)
        {
            return p.x;
        }
        else
        {
            return p.y;
        }
    }
};

template <>
struct geometry_concept<integral_segment_t>
{
    typedef segment_concept type;
};

template <>
struct segment_traits<integral_segment_t>
{
    typedef point_traits<integral_point_t>::coordinate_type coordinate_type;
    typedef integral_point_t point_type;

    static inline point_type get(const integral_segment_t &s, direction_1d dir)
    {
        if (dir.to_int() == 0)
        {
            return s.v0;
        }
        else
        {
            return s.v1;
        }
    }
};
} // namespace polygon
} // namespace boost

#define MAGIC_COLOR (33)

extern "C" int get_skeleton(int n, void *segment_data, double **return_data)
{
    auto segments = static_cast<integral_segment_t *>(segment_data);
    std::vector<real_segment_t> axis_vector;
    std::vector<double> sorted_axis_vector;
    voronoi_diagram_t vd;
    integral_rtree_t input_segment_rtree;
    real_rtree_t voronoi_segment_rtree;

    // construct voronoi diagram
    bp::construct_voronoi(segments, segments + (n >> 2), &vd);

    // construct rtree over input segments
    for (int i = 0; i < (n >> 2); ++i)
    {
        auto segment = static_cast<const integral_segment_t *>(segment_data) + i;
        input_segment_rtree.insert(std::make_pair(segment, i));
    }

    // iterate through the voronoi vertices looking for boundary vertices
    for (auto vit = vd.vertices().cbegin(); vit != vd.vertices().cend(); ++vit)
    {
        auto starting_edge = vit->incident_edge()->rot_next();
        for (; starting_edge->cell() != vit->incident_edge()->cell(); starting_edge = starting_edge->rot_next())
        {
            if (starting_edge->is_primary())
            {
                break;
            }
        }
        if (!starting_edge->is_primary())
        {
            break;
        }
        auto starting_source_segment = segments[starting_edge->cell()->source_index()];
        auto shared_endpoints = std::set<integral_point_t>();

        // Initialization
        shared_endpoints.insert(static_cast<integral_point_t>(starting_source_segment.v0));
        shared_endpoints.insert(static_cast<integral_point_t>(starting_source_segment.v1));

        for (auto edge = starting_edge->rot_next(); edge->cell() != starting_edge->cell(); edge = edge->rot_next())
        {
            if (!edge->is_primary())
            {
                continue;
            }
            auto associated_cell = edge->cell();
            auto source_segment = segments[associated_cell->source_index()];
            auto old_shared_endpoints = shared_endpoints;

            // Remove non-shared endpoints
            shared_endpoints.clear();
            if (old_shared_endpoints.count(source_segment.v0) > 0)
            {
                shared_endpoints.insert(source_segment.v0);
            }
            if (old_shared_endpoints.count(source_segment.v1) > 0)
            {
                shared_endpoints.insert(source_segment.v1);
            }

            // If the number of shared endpoints has dropped to zero, leave
            if (shared_endpoints.size() == 0)
            {
                break;
            }
        }

        // If there is a shared endpoint between the source segments
        // of all of the cells that meet this voronoi vertex, then it
        // is a boundary vertex (see
        // http://boost.2283326.n4.nabble.com/voronoi-medial-axis-tp4651161p4651225.html )
        if (shared_endpoints.size() > 0)
        {
            vit->color(MAGIC_COLOR);
        }
    }

    // Loop over voronoi edges, recording those that do not meet a boundary vertex
    for (auto eit = vd.edges().cbegin(); eit != vd.edges().cend(); ++eit)
    {
        if (eit->is_primary() && eit->is_finite() && eit->vertex0()->color() != MAGIC_COLOR && eit->vertex1()->color() != MAGIC_COLOR)
        {
            double x1, y1, x2, y2;

            x1 = eit->vertex0()->x();
            y1 = eit->vertex0()->y();
            x2 = eit->vertex1()->x();
            y2 = eit->vertex1()->y();
            if ((x1 <= x2) && std::isfinite(x1) && std::isfinite(y1) && std::isfinite(x2) && std::isfinite(y2))
            {
                axis_vector.push_back(real_segment_t(real_point_t(x1, y1), real_point_t(x2, y2)));
                voronoi_segment_rtree.insert(std::make_pair(axis_vector.back(), axis_vector.size() - 1));
            }
        }
    }

    // Get segments in "sorted" order
    real_point_t &current_point = axis_vector.front().first;
    while (voronoi_segment_rtree.size() > 0)
    {
        auto results = std::vector<real_rtree_value_t>();
        voronoi_segment_rtree.query(bgi::nearest(current_point, 1), std::back_inserter(results));
        auto result = results.front();
        const auto current_segment = axis_vector[result.second]; // XXX first entry is not only mutable but apparently mutated!!
        double x1, y1, d1, x2, y2, d2;

        if (bg::distance(current_point, current_segment.first) < bg::distance(current_point, current_segment.second))
        {
            // Insert segment in original orientation

            x1 = current_segment.first.get<0>();
            y1 = current_segment.first.get<1>();
            x2 = current_segment.second.get<0>();
            y2 = current_segment.second.get<1>();
        }
        else
        {
            // Insert flipped segment
            x1 = current_segment.second.get<0>();
            y1 = current_segment.second.get<1>();
            x2 = current_segment.first.get<0>();
            y2 = current_segment.first.get<1>();
        }

        auto results1 = std::vector<integral_rtree_value_t>();
        auto v1 = integral_point_t{.x = static_cast<integral_coordinate_t>(x1), .y = static_cast<integral_coordinate_t>(y1)};
        input_segment_rtree.query(bgi::nearest(v1, 1), std::back_inserter(results1)); // This is okay due to (pre-)scaling
        d1 = bg::distance(v1, segments[results1.front().second]);

        auto results2 = std::vector<integral_rtree_value_t>();
        auto v2 = integral_point_t{.x = static_cast<integral_coordinate_t>(x2), .y = static_cast<integral_coordinate_t>(y2)};
        input_segment_rtree.query(bgi::nearest(v2, 1), std::back_inserter(results2)); // okay due to scaling
        d2 = bg::distance(v2, segments[results2.front().second]);

        sorted_axis_vector.push_back(x1);
        sorted_axis_vector.push_back(y1);
        sorted_axis_vector.push_back(x2);
        sorted_axis_vector.push_back(y2);
        sorted_axis_vector.push_back(d1);
        sorted_axis_vector.push_back(d2);

        current_point = current_segment.second;
        auto return_code = voronoi_segment_rtree.remove(result);
        assert(return_code > 0);
    }
    axis_vector.clear();

    // Copy results back
    auto m = sorted_axis_vector.size();
    auto l = m * sizeof(double);
    *return_data = static_cast<double *>(malloc(l));
    memcpy(*return_data, sorted_axis_vector.data(), l);

    return sorted_axis_vector.size();
}


/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef MAPNIK_QUAD_TREE_HPP
#define MAPNIK_QUAD_TREE_HPP

// mapnik
#include <mapnik/box2d.hpp>
#include <mapnik/noncopyable.hpp>

// boost
#include <boost/ptr_container/ptr_vector.hpp>

// stl
#include <vector>
#include <cstring>

namespace mapnik
{
template <typename T>
class quad_tree : mapnik::noncopyable
{
    struct node
    {
        typedef T value_t;
        typedef std::vector<T> cont_t;
        typedef typename cont_t::iterator iterator;
        typedef typename cont_t::const_iterator const_iterator;
        box2d<double> extent_;
        cont_t cont_;
        node * children_[4];

        explicit node(box2d<double> const& ext)
            : extent_(ext)
        {
            std::memset(children_,0,4*sizeof(node*));
        }

        box2d<double> const& extent() const
        {
            return extent_;
        }

        iterator begin()
        {
            return cont_.begin();
        }

        const_iterator begin() const
        {
            return cont_.begin();
        }

        iterator end()
        {
            return cont_.end();
        }

        const_iterator end() const
        {
            return cont_.end();
        }
        ~node () {}
    };

    typedef boost::ptr_vector<node> nodes_t;
    typedef typename node::cont_t cont_t;
    typedef typename cont_t::iterator node_data_iterator;

public:
    typedef typename nodes_t::iterator iterator;
    typedef typename nodes_t::const_iterator const_iterator;
    typedef typename boost::ptr_vector<T,boost::view_clone_allocator> result_t;
    typedef typename result_t::iterator query_iterator;

    explicit quad_tree(box2d<double> const& ext,
                       unsigned int max_depth = 8,
                       double ratio = 0.55)
        : max_depth_(max_depth),
          ratio_(ratio),
          query_result_(),
          nodes_()
    {
        nodes_.push_back(new node(ext));
        root_ = &nodes_[0];
    }

    void insert(T data, box2d<double> const& box)
    {
        unsigned int depth=0;
        do_insert_data(data,box,root_,depth);
    }

    query_iterator query_in_box(box2d<double> const& box)
    {
        query_result_.clear();
        query_node(box,query_result_,root_);
        return query_result_.begin();
    }

    query_iterator query_end()
    {
        return query_result_.end();
    }

    const_iterator begin() const
    {
        return nodes_.begin();
    }

    const_iterator end() const
    {
        return  nodes_.end();
    }

    void clear ()
    {
        box2d<double> ext = root_->extent_;
        nodes_.clear();
        nodes_.push_back(new node(ext));
        root_ = &nodes_[0];
    }

    box2d<double> const& extent() const
    {
        return root_->extent_;
    }

private:

    void query_node(box2d<double> const& box, result_t & result, node * node_) const
    {
        if (node_)
        {
            box2d<double> const& node_extent = node_->extent();
            if (box.intersects(node_extent))
            {
                node_data_iterator i=node_->begin();
                node_data_iterator end=node_->end();
                while ( i!=end)
                {
                    result.push_back(&(*i));
                    ++i;
                }
                for (int k = 0; k < 4; ++k)
                {
                    query_node(box,result,node_->children_[k]);
                }
            }
        }
    }

    void do_insert_data(T data, box2d<double> const& box, node * n, unsigned int& depth)
    {
        if (++depth >= max_depth_)
        {
            n->cont_.push_back(data);
        }
        else
        {
            box2d<double> const& node_extent = n->extent();
            box2d<double> ext[4];
            split_box(node_extent,ext);
            for (int i=0;i<4;++i)
            {
                if (ext[i].contains(box))
                {
                    if (!n->children_[i])
                    {
                        nodes_.push_back(new node(ext[i]));
                        n->children_[i]=&nodes_.back();
                    }
                    do_insert_data(data,box,n->children_[i],depth);
                    return;
                }
            }
            n->cont_.push_back(data);
        }
    }

    void split_box(box2d<double> const& node_extent,box2d<double> * ext)
    {
        //coord2d c=node_extent.center();

        double width=node_extent.width();
        double height=node_extent.height();

        double lox=node_extent.minx();
        double loy=node_extent.miny();
        double hix=node_extent.maxx();
        double hiy=node_extent.maxy();

        ext[0]=box2d<double>(lox,loy,lox + width * ratio_,loy + height * ratio_);
        ext[1]=box2d<double>(hix - width * ratio_,loy,hix,loy + height * ratio_);
        ext[2]=box2d<double>(lox,hiy - height*ratio_,lox + width * ratio_,hiy);
        ext[3]=box2d<double>(hix - width * ratio_,hiy - height*ratio_,hix,hiy);
    }

    const unsigned int max_depth_;
    const double ratio_;
    result_t query_result_;
    nodes_t nodes_;
    node * root_;

};
}

#endif // MAPNIK_QUAD_TREE_HPP


/*=============================================================================
Copyright (c) 2011, The Trustees of Indiana University
All rights reserved.

Authors: Michael Hansen (mihansen@indiana.edu), Shinya Ito

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.

  3. Neither the name of Indiana University nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=============================================================================*/

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

#include <boost/lexical_cast.hpp>
#include <boost/limits.hpp>
#include <boost/multi_array.hpp>
#include <boost/program_options.hpp>

#include "transent.hpp"

// Typedefs
typedef int TimeType;
typedef std::vector<TimeType> TimeSeries;
typedef std::vector< std::vector<TimeType> > TimeSeriesCollection;

typedef boost::multi_array<double, 2> ResultMatrix;
typedef ResultMatrix::index arr_index;

int main(int argc, char *argv[]) {

  namespace opt = boost::program_options;
  opt::options_description desc("Calculates transfer entropy for a block of time series (y -> x)");
  desc.add_options()
    ("help", "Show this help message")
    ("x-order", opt::value<TimeType>()->default_value(1), "Order of predicted time series (default 1)")
    ("y-order", opt::value<TimeType>()->default_value(1), "Order of predictor time series (default 1)")
    ("y-delay", opt::value<TimeType>()->default_value(1), "Delay of predictor time series (default 1)")
    ("in-file", opt::value<std::string>(), "Input time series file path")
    ("out-file", opt::value<std::string>(), "Output transfer entropy file path")
    ("col-start", opt::value<arr_index>()->default_value(0), "Column offset of block (default 0)")
    ("cols", opt::value<arr_index>()->default_value(0), "Columns in block (default 0 for remainder)")
    ("row-start", opt::value<arr_index>()->default_value(0), "Row offset of block (default 0)")
    ("rows", opt::value<arr_index>()->default_value(0), "Rows in block (default 0 for remainder)")
    ;

  opt::variables_map opt_vars;
  opt::store(opt::parse_command_line(argc, argv, desc), opt_vars);
  opt::notify(opt_vars);

  if (opt_vars.count("help")) {
    std::cout << desc << std::endl;
    return (0);
  }

  const std::size_t x_order = opt_vars["x-order"].as<TimeType>(),
                    y_order = opt_vars["y-order"].as<TimeType>(),
                    y_delay = opt_vars["y-delay"].as<TimeType>();

  const std::size_t num_series = 1 + y_order + x_order;

  assert(x_order > 0);
  assert(y_order > 0);
  assert(y_delay > 0);

  if (num_series > MAX_XY_ORDER) {
    std::cout << "The combined order of x and y cannot exceed " << MAX_XY_ORDER << std::endl;
    return (0);
  }

  // Parse arguments
  if (!opt_vars.count("in-file") || !opt_vars.count("out-file")) {
    std::cout << "Input and output file paths are required" << std::endl;
    return (0);
  }

  std::string in_file_path = opt_vars["in-file"].as<std::string>(),
              out_file_path = opt_vars["out-file"].as<std::string>();

  arr_index col_start = opt_vars["col-start"].as<arr_index>(),
            cols = opt_vars["cols"].as<arr_index>(),
            row_start = opt_vars["row-start"].as<arr_index>(),
            rows = opt_vars["rows"].as<arr_index>();

  // Read in time series block
  std::vector<TimeSeries> all_series;

  std::ifstream in_file(in_file_path.c_str());
  std::string line;

  getline(in_file, line);
  TimeType duration = boost::lexical_cast<TimeType>(line);

  while (getline(in_file, line)) {

    std::istringstream line_stream(line);
    TimeSeries cur_series;

    // This could be more efficient, but it's fast enough for now
    std::copy(std::istream_iterator<TimeType>(line_stream),
              std::istream_iterator<TimeType>(),
              std::back_inserter(cur_series));

    all_series.push_back(cur_series);
  }

  if (rows == 0) {
    rows = all_series.size();
  }

  if (cols == 0) {
    cols = all_series.size();
  }

  // Calculate TE
  ResultMatrix te_result(boost::extents[rows][cols]);

  transent_ho(all_series, x_order, y_order, y_delay, duration, te_result,
              row_start, rows, col_start, cols);

  // Write results
  std::ofstream out_file(out_file_path.c_str());

  for (arr_index i = 0; i < rows; ++i) {
    for (arr_index j = 0; j < cols; ++j) {
      out_file << te_result[j][i] << " ";
    }

    out_file << std::endl;
  }

  return (0);
}


/*
 Copyright (c) 2016-2017 Paul Lagrée

 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 <queue>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_01.hpp>
#include "common.hpp"

class Policy {
 protected:
  unsigned int n_experts_;                   // Number of experts

  /**
    Get the `k` largest elements of a vector and returns them as unordered_set.
    Trick with negative weights to get the lowest element of the priority_queue.
  */
  template<typename T>
  std::unordered_set<T> get_k_largest_arguments(
        std::vector<float>& vec, unsigned int k) {
    std::priority_queue<std::pair<float, T>> q;
    for (T i = 0; i < k; ++i) {
      q.push(std::pair<float, T>(-vec[i], i));
    }
    for (T i = k; i < vec.size(); ++i) {
      if (q.top().first > -vec[i]) {
        q.pop();
        q.push(std::pair<float, T>(-vec[i], i));
      }
    }
    std::unordered_set<T> result;
    while (!q.empty()) {
      result.insert(q.top().second);
      q.pop();
    }
    return result;
  }

 public:
  Policy(unsigned int n_experts) : n_experts_(n_experts) {}

  virtual std::unordered_set<unsigned int> selectExpert(unsigned int k) = 0;

  /**
    This method does not necessarly need to be overloaded by classes inheriting
    Policy (e.g. RandomPolicy).
  */
  virtual void updateState(unsigned int,
                           const std::unordered_set<unode_int>&) {}

  /**
    Reinitialize the object to start parameters.
  */
  virtual void init() {}
};

/**
  Selects randomly the expert to play at each round.
*/
class RandomPolicy : public Policy {
 private:
  std::mt19937 gen_;
  std::uniform_int_distribution<unsigned int> dst_;
 public:
  RandomPolicy(unsigned int n_experts)
      : Policy(n_experts), gen_(seed_ns()), dst_(0, n_experts_) {}

  std::unordered_set<unsigned int> selectExpert(unsigned int k) {
    std::unordered_set<unsigned int> result;
    while (result.size() < k)
      result.insert(dst_(gen_));
    return result;
  }
};

/**
  Type of spread estimation.
*/
enum Sigma {
  MEAN,       // Replace sigma by the mean of observed spreads
  SAMPLE_STD, // Mean + sampled standard deviation of observed spreads
  INTERSECTING_SUPPORT, // Simple heuristic when intersecting support isn't null
};

/**
  Good-UCB policy for our problem. Each experts maintains a missing mass
  estimator which is used to select the next expert to play. See paper for
  details.
*/
class GoodUcbPolicy : public Policy {
 private:
  std::vector<unode_int>& nb_neighbours_; // Number of reachable nodes for each expert
  unsigned int t_;                            // Number of rounds played
  std::vector<float> n_plays_;                // Number of times experts were played
  // For each expert, hashmap {node : #activations}
  std::vector<std::unordered_map<unode_int, unsigned int>> n_rewards_;
  std::vector<std::vector<double>> spreads_;  // List of sampled spreads for each experts
  Sigma sigma_type_;        // Type of estimation of expert expected diffusion

 public:
  GoodUcbPolicy(unsigned int n_experts, std::vector<unode_int>& nb_neighbours,
                Sigma type=MEAN)
      : Policy(n_experts), nb_neighbours_(nb_neighbours),
        sigma_type_(type) { init(); }

  /**
    Selects `k` experts whose Good-UCB indices are the largest.
  */
  std::unordered_set<unsigned int> selectExpert(unsigned int k) {
    // 1. Test if all experts were played at least once
    std::unordered_set<unsigned int> chosen_experts;
    unsigned int n_selected_experts = 0;
    for (unsigned int i = 0; i < n_experts_; i++) {
      if (n_plays_[i] == 0) {
        chosen_experts.insert(i);
        n_selected_experts++;
        if (n_selected_experts == k)
          break;
      }
    }
    if (chosen_experts.size() > 0) {
      for (unsigned int i = 0; i < n_experts_ && chosen_experts.size() < k;
           i++) {
        chosen_experts.insert(i);
      }
      return chosen_experts;
    }
    // 2. If all experts were played once, use missing mass estimator
    std::vector<float> ucbs(n_experts_, 0);
    for (unsigned int i = 0; i < n_experts_; i++) {
      // 2. (a) Compute missing mass estimator
      float missing_mass_i = (float)std::count_if(
          n_rewards_[i].begin(), n_rewards_[i].end(), [](auto& elt) {
            return elt.second == 1; // Count hapaxes
          }) / n_plays_[i];
      // 2. (b) Compute estimator of expected diffusion from this expert
      float sigma = 0;
      for (auto& elt : n_rewards_[i])
        sigma += elt.second;
      sigma /= n_plays_[i];
      if (sigma_type_ == SAMPLE_STD) {  // If we estimate sum of p(x) by the sample mean + std
        double empirical_std = 0;
        for (auto elt : spreads_[i])
          empirical_std += (elt - sigma) * (elt - sigma);
        if (n_plays_[i] == 1)
          empirical_std = sigma;
        else
          empirical_std = sqrt(empirical_std / (n_plays_[i] - 1));
        sigma += empirical_std;
      } else if (sigma_type_ == INTERSECTING_SUPPORT) {
        missing_mass_i = 0;
        for (auto& elt : n_rewards_[i]) {
          if (elt.second != 1)
            continue;
          float degree_experts = 1; // Number of experts which activated this node
          for (unsigned int j = 0; j < n_experts_; j++) {
            if (i == j)
              continue;
            else if (n_rewards_[j].find(elt.first) == n_rewards_[j].end())
              continue;
            else
              degree_experts += 1;
          }
          missing_mass_i += 1 / degree_experts;
        }
        missing_mass_i /= n_plays_[i];
      }
      ucbs[i] = missing_mass_i + (1 + sqrt(2)) * sqrt(sigma * log(4 * t_) /
          n_plays_[i]) + log(4 * t_) / (3 * n_plays_[i]);
    }
    return get_k_largest_arguments<unsigned int>(ucbs, k);
  }

  /**
    Update statistics on the chosen expert (k == 1).
  */
  void updateState(unsigned int expert,
                   const std::unordered_set<unode_int>& stage_spread) {
    t_++;
    for (auto& activated_node : stage_spread) {
      if (n_rewards_[expert].count(activated_node) == 0)
        n_rewards_[expert][activated_node] = 0;
      n_rewards_[expert][activated_node]++;
    }
    spreads_[expert].push_back(stage_spread.size());
    n_plays_[expert]++;
  }

  /**
    Initialize datastructures required for computing UCB bounds. It is useful
    when restarting the algorithm (to reset).
  */
  void init() {
    t_ = 0;
    n_plays_ = std::vector<float>(n_experts_, 0);
    spreads_ = std::vector<std::vector<double>>(n_experts_);
    for (unsigned int k = 0; k < n_experts_; k++) {
      n_rewards_.push_back(std::unordered_map<unode_int, unsigned int>());
    }
  }
};


//
// $Id$
//
//
// Original author: Matt Chambers <matt.chambers <a.t> vanderbilt.edu>
//
// Copyright 2008 Vanderbilt University - Nashville, TN 37232
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef _SAVITZKYGOLAYSMOOTHER_HPP_
#define _SAVITZKYGOLAYSMOOTHER_HPP_

#include "Smoother.hpp"
#include <vector>
#include <boost/shared_ptr.hpp>

namespace pwiz {
namespace analysis {

struct PWIZ_API_DECL SavitzkyGolaySmoother : public Smoother
{
    SavitzkyGolaySmoother(int polynomialOrder, int windowSize);
    ~SavitzkyGolaySmoother();

    /// smooth y values to existing vectors using Savitzky-Golay algorithm;
    /// preconditions:
    /// - samples within the window must be (approximately) equally spaced
    virtual void smooth(const std::vector<double>& x, const std::vector<double>& y,
                        std::vector<double>& xSmoothed, std::vector<double>& ySmoothed);

    /// smooth y values and copy back to the input vectors using Savitzky-Golay algorithm;
    /// preconditions:
    /// - samples within the window must be (approximately) equally spaced
    virtual void smooth_copy(std::vector<double>& x, std::vector<double>& y);

    private:
    struct Impl;
    boost::shared_ptr<Impl> impl_;
};

} // namespace analysis
} // namespace pwiz

#endif // _SAVITZKYGOLAYSMOOTHER_HPP_