#include <iostream>

#include "World.h"
#include "Camera.h"
#include <Eigen>
#include "Ray.h"
#include "Sphere.h"
#include "Triangle.h"
#include "Shape.h"
#include <vector>

using namespace std;
using namespace Eigen;

int main()
{

    World * sampl = new World();
    Vector3f lookat;
    lookat << 0, 0, -1;
    Vector3f up;
    up << 0, 1, 0;
    Vector3f pos;
    pos << 3, 0, 1;
    Camera * cam = new Camera(lookat, up, pos);
    cam->captureworld(sampl);

    /*
    Vector3f a,b,c;

    a << 0,0,10;
    b << 100, 0, 10;
    c << 100, 100, 10;

    Vector3f l,d;

    l << 1,0, 0;
    d << -1,0, 10;

    Ray * test = new Ray(l,d);
    Sphere * tr = new Sphere(1, a);

    if (tr->checkcollision(test))
        cout << "blah";

    */
    return 0;
}


#ifndef BOOST_GEOMETRY_PROJECTIONS_AEQD_HPP
#define BOOST_GEOMETRY_PROJECTIONS_AEQD_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// 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: 4.9.1

// Original copyright notice:

// Purpose:  Implementation of the aeqd (Azimuthal Equidistant) projection.
// Author:   Gerald Evenden
// Copyright (c) 1995, Gerald Evenden

// 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/config.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/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/aasincos.hpp>
#include <boost/geometry/srs/projections/impl/pj_mlfn.hpp>

#include <boost/geometry/srs/projections/par4.hpp>

#include <boost/type_traits/is_same.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct aeqd {};
    //struct aeqd_guam {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace aeqd
    {

            static const double EPS10 = 1.e-10;
            static const double TOL = 1.e-14;
            static const int N_POLE = 0;
            static const int S_POLE = 1;
            static const int EQUIT = 2;
            static const int OBLIQ = 3;

            template <typename T>
            struct par_aeqd
            {
                T    sinph0;
                T    cosph0;
                T    en[EN_SIZE];
                T    M1;
                T    N1;
                T    Mp;
                T    He;
                T    G;
                int        mode;
            };

            template <typename T, typename Par, typename ProjParm>
            inline void e_forward(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y, Par const& par, ProjParm const& proj_parm)
            {
                T  coslam, cosphi, sinphi, rho, s, H, H2, c, Az, t, ct, st, cA, sA;

                coslam = cos(lp_lon);
                cosphi = cos(lp_lat);
                sinphi = sin(lp_lat);
                switch (proj_parm.mode) {
                case N_POLE:
                    coslam = - coslam;
                    BOOST_FALLTHROUGH;
                case S_POLE:
                    xy_x = (rho = fabs(proj_parm.Mp - pj_mlfn(lp_lat, sinphi, cosphi, proj_parm.en))) *
                        sin(lp_lon);
                    xy_y = rho * coslam;
                    break;
                case EQUIT:
                case OBLIQ:
                    if (fabs(lp_lon) < EPS10 && fabs(lp_lat - par.phi0) < EPS10) {
                        xy_x = xy_y = 0.;
                        break;
                    }
                    t = atan2(par.one_es * sinphi + par.es * proj_parm.N1 * proj_parm.sinph0 *
                        sqrt(1. - par.es * sinphi * sinphi), cosphi);
                    ct = cos(t); st = sin(t);
                    Az = atan2(sin(lp_lon) * ct, proj_parm.cosph0 * st - proj_parm.sinph0 * coslam * ct);
                    cA = cos(Az); sA = sin(Az);
                    s = aasin(fabs(sA) < TOL ?
                        (proj_parm.cosph0 * st - proj_parm.sinph0 * coslam * ct) / cA :
                        sin(lp_lon) * ct / sA );
                    H = proj_parm.He * cA;
                    H2 = H * H;
                    c = proj_parm.N1 * s * (1. + s * s * (- H2 * (1. - H2)/6. +
                        s * ( proj_parm.G * H * (1. - 2. * H2 * H2) / 8. +
                        s * ((H2 * (4. - 7. * H2) - 3. * proj_parm.G * proj_parm.G * (1. - 7. * H2)) /
                        120. - s * proj_parm.G * H / 48.))));
                    xy_x = c * sA;
                    xy_y = c * cA;
                    break;
                }
            }

            template <typename T, typename Par, typename ProjParm>
            inline void e_inverse(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat, Par const& par, ProjParm const& proj_parm)
            {
                static const T HALFPI = detail::HALFPI<T>();

                T c, Az, cosAz, A, B, D, E, F, psi, t;

                if ((c = boost::math::hypot(xy_x, xy_y)) < EPS10) {
                    lp_lat = par.phi0;
                    lp_lon = 0.;
                        return;
                }
                if (proj_parm.mode == OBLIQ || proj_parm.mode == EQUIT) {
                    cosAz = cos(Az = atan2(xy_x, xy_y));
                    t = proj_parm.cosph0 * cosAz;
                    B = par.es * t / par.one_es;
                    A = - B * t;
                    B *= 3. * (1. - A) * proj_parm.sinph0;
                    D = c / proj_parm.N1;
                    E = D * (1. - D * D * (A * (1. + A) / 6. + B * (1. + 3.*A) * D / 24.));
                    F = 1. - E * E * (A / 2. + B * E / 6.);
                    psi = aasin(proj_parm.sinph0 * cos(E) + t * sin(E));
                    lp_lon = aasin(sin(Az) * sin(E) / cos(psi));
                    if ((t = fabs(psi)) < EPS10)
                        lp_lat = 0.;
                    else if (fabs(t - HALFPI) < 0.)
                        lp_lat = HALFPI;
                    else
                        lp_lat = atan((1. - par.es * F * proj_parm.sinph0 / sin(psi)) * tan(psi) /
                            par.one_es);
                } else { /* Polar */
                    lp_lat = pj_inv_mlfn(proj_parm.mode == N_POLE ? proj_parm.Mp - c : proj_parm.Mp + c,
                        par.es, proj_parm.en);
                    lp_lon = atan2(xy_x, proj_parm.mode == N_POLE ? -xy_y : xy_y);
                }
            }

            template <typename T, typename Par, typename ProjParm>
            inline void e_guam_fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y, Par const& par, ProjParm const& proj_parm)
            {
                T cosphi, sinphi, t;

                cosphi = cos(lp_lat);
                sinphi = sin(lp_lat);
                t = 1. / sqrt(1. - par.es * sinphi * sinphi);
                xy_x = lp_lon * cosphi * t;
                xy_y = pj_mlfn(lp_lat, sinphi, cosphi, proj_parm.en) - proj_parm.M1 +
                    .5 * lp_lon * lp_lon * cosphi * sinphi * t;
            }

            template <typename T, typename Par, typename ProjParm>
            inline void e_guam_inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat, Par const& par, ProjParm const& proj_parm)
            {
                T x2, t;
                int i;

                x2 = 0.5 * xy_x * xy_x;
                lp_lat = par.phi0;
                for (i = 0; i < 3; ++i) {
                    t = par.e * sin(lp_lat);
                    lp_lat = pj_inv_mlfn(proj_parm.M1 + xy_y -
                        x2 * tan(lp_lat) * (t = sqrt(1. - t * t)), par.es, proj_parm.en);
                }
                lp_lon = xy_x * t / cos(lp_lat);
            }

            template <typename T, typename Par, typename ProjParm>
            inline void s_forward(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y, Par const& /*par*/, ProjParm const& proj_parm)
            {
                static const T HALFPI = detail::HALFPI<T>();

                T coslam, cosphi, sinphi;

                sinphi = sin(lp_lat);
                cosphi = cos(lp_lat);
                coslam = cos(lp_lon);
                switch (proj_parm.mode) {
                case EQUIT:
                    xy_y = cosphi * coslam;
                    goto oblcon;
                case OBLIQ:
                    xy_y = proj_parm.sinph0 * sinphi + proj_parm.cosph0 * cosphi * coslam;
            oblcon:
                    if (fabs(fabs(xy_y) - 1.) < TOL)
                        if (xy_y < 0.)
                            BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        else
                            xy_x = xy_y = 0.;
                    else {
                        xy_y = acos(xy_y);
                        xy_y /= sin(xy_y);
                        xy_x = xy_y * cosphi * sin(lp_lon);
                        xy_y *= (proj_parm.mode == EQUIT) ? sinphi :
                                proj_parm.cosph0 * sinphi - proj_parm.sinph0 * cosphi * coslam;
                    }
                    break;
                case N_POLE:
                    lp_lat = -lp_lat;
                    coslam = -coslam;
                    BOOST_FALLTHROUGH;
                case S_POLE:
                    if (fabs(lp_lat - HALFPI) < EPS10)
                        BOOST_THROW_EXCEPTION( projection_exception(-20) );
                    xy_x = (xy_y = (HALFPI + lp_lat)) * sin(lp_lon);
                    xy_y *= coslam;
                    break;
                }
            }

            template <typename T, typename Par, typename ProjParm>
            inline void s_inverse(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat, Par const& par, ProjParm const& proj_parm)
            {
                static const T ONEPI = detail::ONEPI<T>();
                static const T HALFPI = detail::HALFPI<T>();

                T cosc, c_rh, sinc;

                if ((c_rh = boost::math::hypot(xy_x, xy_y)) > ONEPI) {
                    if (c_rh - EPS10 > ONEPI)
                        BOOST_THROW_EXCEPTION( projection_exception(-20) );
                    c_rh = ONEPI;
                } else if (c_rh < EPS10) {
                    lp_lat = par.phi0;
                    lp_lon = 0.;
                        return;
                }
                if (proj_parm.mode == OBLIQ || proj_parm.mode == EQUIT) {
                    sinc = sin(c_rh);
                    cosc = cos(c_rh);
                    if (proj_parm.mode == EQUIT) {
                                    lp_lat = aasin(xy_y * sinc / c_rh);
                        xy_x *= sinc;
                        xy_y = cosc * c_rh;
                    } else {
                        lp_lat = aasin(cosc * proj_parm.sinph0 + xy_y * sinc * proj_parm.cosph0 /
                            c_rh);
                        xy_y = (cosc - proj_parm.sinph0 * sin(lp_lat)) * c_rh;
                        xy_x *= sinc * proj_parm.cosph0;
                    }
                    lp_lon = atan2(xy_x, xy_y);
                } else if (proj_parm.mode == N_POLE) {
                    lp_lat = HALFPI - c_rh;
                    lp_lon = atan2(xy_x, -xy_y);
                } else {
                    lp_lat = c_rh - HALFPI;
                    lp_lon = atan2(xy_x, xy_y);
                }
            }

            // Azimuthal Equidistant
            template <typename Parameters, typename T>
            inline void setup_aeqd(Parameters& par, par_aeqd<T>& proj_parm, bool is_sphere, bool is_guam)
            {
                static const T HALFPI = detail::HALFPI<T>();

                par.phi0 = pj_param(par.params, "rlat_0").f;
                if (fabs(fabs(par.phi0) - HALFPI) < EPS10) {
                    proj_parm.mode = par.phi0 < 0. ? S_POLE : N_POLE;
                    proj_parm.sinph0 = par.phi0 < 0. ? -1. : 1.;
                    proj_parm.cosph0 = 0.;
                } else if (fabs(par.phi0) < EPS10) {
                    proj_parm.mode = EQUIT;
                    proj_parm.sinph0 = 0.;
                    proj_parm.cosph0 = 1.;
                } else {
                    proj_parm.mode = OBLIQ;
                    proj_parm.sinph0 = sin(par.phi0);
                    proj_parm.cosph0 = cos(par.phi0);
                }
                if (is_sphere) {
                } else {
                    if (!pj_enfn(par.es, proj_parm.en))
                        BOOST_THROW_EXCEPTION( projection_exception(0) );
                    if (is_guam) {
                        proj_parm.M1 = pj_mlfn(par.phi0, proj_parm.sinph0, proj_parm.cosph0, proj_parm.en);
                    } else {
                        switch (proj_parm.mode) {
                        case N_POLE:
                            proj_parm.Mp = pj_mlfn<T>(HALFPI, 1., 0., proj_parm.en);
                            break;
                        case S_POLE:
                            proj_parm.Mp = pj_mlfn<T>(-HALFPI, -1., 0., proj_parm.en);
                            break;
                        case EQUIT:
                        case OBLIQ:
                            proj_parm.N1 = 1. / sqrt(1. - par.es * proj_parm.sinph0 * proj_parm.sinph0);
                            proj_parm.G = proj_parm.sinph0 * (proj_parm.He = par.e / sqrt(par.one_es));
                            proj_parm.He *= proj_parm.cosph0;
                            break;
                        }
                    }
                }
            }

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_aeqd_e : public base_t_fi<base_aeqd_e<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_aeqd<CalculationType> m_proj_parm;

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

                // FORWARD(e_forward)  elliptical
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    e_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_par, this->m_proj_parm);
                }

                // INVERSE(e_inverse)  elliptical
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    e_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_par, this->m_proj_parm);
                }

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

            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_aeqd_e_guam : public base_t_fi<base_aeqd_e_guam<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_aeqd<CalculationType> m_proj_parm;

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

                // FORWARD(e_guam_fwd)  Guam elliptical
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    e_guam_fwd(lp_lon, lp_lat, xy_x, xy_y, this->m_par, this->m_proj_parm);
                }

                // INVERSE(e_guam_inv)  Guam elliptical
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    e_guam_inv(xy_x, xy_y, lp_lon, lp_lat, this->m_par, this->m_proj_parm);
                }

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

            };

            // template class, using CRTP to implement forward/inverse
            template <typename BGParameters, typename CalculationType, typename Parameters>
            struct base_aeqd_e_static : public base_t_fi<base_aeqd_e_static<BGParameters, CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_aeqd<CalculationType> m_proj_parm;

                static const bool is_guam = ! boost::is_same
                    <
                        typename srs::par4::detail::tuples_find_if
                            <
                                BGParameters,
                                //srs::par4::detail::is_guam
                                srs::par4::detail::is_param<srs::par4::guam>::pred
                            >::type,
                        void
                    >::value;

                inline base_aeqd_e_static(const Parameters& par)
                    : base_t_fi<base_aeqd_e_static<BGParameters, CalculationType, Parameters>,
                     CalculationType, Parameters>(*this, par)
                {}

                // FORWARD(e_forward or e_guam_fwd)  elliptical
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    if (is_guam)
                        e_guam_fwd(lp_lon, lp_lat, xy_x, xy_y, this->m_par, this->m_proj_parm);
                    else
                        e_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_par, this->m_proj_parm);
                }

                // INVERSE(e_inverse or e_guam_inv)  elliptical
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    if (is_guam)
                        e_guam_inv(xy_x, xy_y, lp_lon, lp_lat, this->m_par, this->m_proj_parm);
                    else
                        e_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_par, this->m_proj_parm);
                }

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

            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_aeqd_s : public base_t_fi<base_aeqd_s<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_aeqd<CalculationType> m_proj_parm;

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

                // FORWARD(s_forward)  spherical
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    s_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_par, this->m_proj_parm);
                }

                // INVERSE(s_inverse)  spherical
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    s_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_par, this->m_proj_parm);
                }

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

            };

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

    /*!
        \brief Azimuthal Equidistant projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_0: Latitude of origin (degrees)
         - guam (boolean)
        \par Example
        \image html ex_aeqd.gif
    */
    template <typename CalculationType, typename Parameters>
    struct aeqd_e : public detail::aeqd::base_aeqd_e<CalculationType, Parameters>
    {
        inline aeqd_e(const Parameters& par) : detail::aeqd::base_aeqd_e<CalculationType, Parameters>(par)
        {
            detail::aeqd::setup_aeqd(this->m_par, this->m_proj_parm, false, false);
        }
    };

    /*!
        \brief Azimuthal Equidistant projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_0: Latitude of origin (degrees)
         - guam (boolean)
        \par Example
        \image html ex_aeqd.gif
    */
    template <typename CalculationType, typename Parameters>
    struct aeqd_e_guam : public detail::aeqd::base_aeqd_e_guam<CalculationType, Parameters>
    {
        inline aeqd_e_guam(const Parameters& par) : detail::aeqd::base_aeqd_e_guam<CalculationType, Parameters>(par)
        {
            detail::aeqd::setup_aeqd(this->m_par, this->m_proj_parm, false, true);
        }
    };

    /*!
        \brief Azimuthal Equidistant projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_0: Latitude of origin (degrees)
         - guam (boolean)
        \par Example
        \image html ex_aeqd.gif
    */
    template <typename BGParameters, typename CalculationType, typename Parameters>
    struct aeqd_e_static : public detail::aeqd::base_aeqd_e_static<BGParameters, CalculationType, Parameters>
    {
        inline aeqd_e_static(const Parameters& par) : detail::aeqd::base_aeqd_e_static<BGParameters, CalculationType, Parameters>(par)
        {
            detail::aeqd::setup_aeqd(this->m_par, this->m_proj_parm,
                                     false,
                                     detail::aeqd::base_aeqd_e_static<BGParameters, CalculationType, Parameters>::is_guam);
        }
    };

    /*!
        \brief Azimuthal Equidistant projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_0: Latitude of origin (degrees)
         - guam (boolean)
        \par Example
        \image html ex_aeqd.gif
    */
    template <typename CalculationType, typename Parameters>
    struct aeqd_s : public detail::aeqd::base_aeqd_s<CalculationType, Parameters>
    {
        inline aeqd_s(const Parameters& par) : detail::aeqd::base_aeqd_s<CalculationType, Parameters>(par)
        {
            detail::aeqd::setup_aeqd(this->m_par, this->m_proj_parm, true, false);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        template <typename BGP, typename CT, typename P>
        struct static_projection_type<srs::par4::aeqd, srs_sphere_tag, BGP, CT, P>
        {
            typedef aeqd_s<CT, P> type;
        };
        template <typename BGP, typename CT, typename P>
        struct static_projection_type<srs::par4::aeqd, srs_spheroid_tag, BGP, CT, P>
        {
            typedef aeqd_e_static<BGP, CT, P> type;
        };
        //BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::aeqd, aeqd_s, aeqd_e_static)
        //BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::aeqd_guam, aeqd_guam, aeqd_guam)

        // Factory entry(s)
        template <typename CalculationType, typename Parameters>
        class aeqd_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    bool const guam = pj_param(par.params, "bguam").i != 0;

                    if (par.es && ! guam)
                        return new base_v_fi<aeqd_e<CalculationType, Parameters>, CalculationType, Parameters>(par);
                    else if (par.es && guam)
                        return new base_v_fi<aeqd_e_guam<CalculationType, Parameters>, CalculationType, Parameters>(par);
                    else
                        return new base_v_fi<aeqd_s<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void aeqd_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("aeqd", new aeqd_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_AEQD_HPP


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_DEFINITION_RSQRT_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_DEFINITION_RSQRT_HPP_INCLUDED

#include <boost/simd/config.hpp>
#include <boost/simd/detail/dispatch/function/make_callable.hpp>
#include <boost/simd/detail/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    BOOST_DISPATCH_MAKE_TAG(ext, rsqrt_, boost::dispatch::elementwise_<rsqrt_>);
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, rsqrt_)
  }

  BOOST_DISPATCH_CALLABLE_DEFINITION(tag::rsqrt_,rsqrt);

} }

#endif


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <cmath>
#include <string>
#include <iostream>
#include <vector>
#include <exception>
#include <GeographicLib/Geodesic.hpp>
#include <GeographicLib/Constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
#include "bilinearInterpolation.hpp"
#include "hypoStation.hpp"
#include "h5io.hpp"
#include "blackList.hpp"

struct TravelTimeInterpolator
{
    double getTime(const double offset, const double depth)
    {
        bilin.interpolate(1, &offset, &depth);
        auto ptr = bilin.getInterpolatedFunctionPointer();
        return ptr[0];
    }
    std::vector<double> getTimes(const std::vector<double> &offsets,
                                 const std::vector<double> &depths)
    {
        if (offsets.size() != depths.size())
        {
            throw std::invalid_argument("offsets.size() != depths.size()");
        }
        bilin.interpolate(offsets.size(), offsets.data(), depths.data());
        return bilin.getInterpolatedFunction();
    }
    BilinearInterpolation<double> bilin;
    std::string phaseLabel;
    std::vector<double> travelTimes;
    std::vector<double> depths;
    std::vector<double> offsets;
};

struct Receiver
{
    std::string network;
    std::string station;
    double latitude = 0;
    double longitude = 0;
    double depth = 0;
};

bool operator==(const Receiver &a, const Receiver &b)
{
    return (a.network == b.network && a.station == b.station);
}

std::vector<Receiver> getReceiverListFromCSV(const std::string &fileName,
                                             const bool haveHeader = true)
{
    std::ifstream infl(fileName);
    if (!infl){throw std::runtime_error("Could not open: " + fileName);}
    std::vector<Receiver> receivers;
    std::string line;
    std::vector<std::string> split;
    int lineNumber = 0;
    while (std::getline(infl, line))
    {
        lineNumber = lineNumber + 1;
        if (lineNumber == 1 && haveHeader){continue;}
        //UU,ALT,ENE,ENN,ENZ,01,40.59028,-111.6375,2635.0
        boost::split(split, line, boost::is_any_of(",\n\t"),
                     boost::token_compress_on);
        Receiver receiver;
        receiver.network = split[0];
        receiver.station = split[1];
        receiver.latitude = std::stod(split[6]);
        receiver.longitude = std::stod(split[7]);
        receiver.depth = std::stod(split[8]);
        bool lnew = true;
        for (const auto &r : receivers)
        {
            if (receiver == r)
            {
                lnew = false;
                break;
            }
        }
        if (lnew){receivers.push_back(receiver);}
    }
    infl.close();
    std::cout << "Number of receivers: " << receivers.size() << std::endl;
    return receivers;
}

std::vector<Receiver> getReceiverListFromNodalFile(const std::string &fileName)
{
    std::ifstream infl(fileName);
    if (!infl){throw std::runtime_error("Could not open: " + fileName);}
    std::vector<Receiver> receivers;
    std::vector<std::string> split;
    std::string line;
    while (std::getline(infl, line))
    {
        // -112.02516440 40.75396926 1272.3 001 001 5462 UU001
        boost::split(split, line, boost::is_any_of(" ,\n\t"),
                     boost::token_compress_on);
        Receiver receiver;
        receiver.network = "UU";
        receiver.station = split[3];
        receiver.latitude = std::stod(split[1]);
        receiver.longitude = std::stod(split[0]);
        receiver.depth = std::round(std::stod(split[2]));
//std::cout << receiver.network << "," << receiver.station << "," << receiver.latitude << "," << receiver.longitude << "," << receiver.depth << std::endl;
        if (isBlackListed(receiver.network, receiver.station)){continue;}
        bool lnew = true;
        for (const auto &r : receivers)
        {
            if (receiver == r)
            {
                lnew = false;
                break;
            }
        }
        if (lnew){receivers.push_back(receiver);}
    }
    infl.close();
    std::cout << "Number of nodes: " << receivers.size() << std::endl;
    return receivers;
}

/// @brief Creates a travel time table interpolation structure from the
///        travel time tables computed by GrowClust.
/// @param[in] fileName   The file name with the travel time table.
/// @param[in] phaseLabel The phase label (e.g., P or S).
/// @param[out] interp    This is the travel time with a travel time
///                       bilinear interpolation method.
void createGrowClustTravelTimeTable(const std::string &fileName,
                                    const std::string &phaseLabel,
                                    TravelTimeInterpolator *interp)
{
    std::ifstream infl(fileName);
    if (!infl)
    {
        throw std::runtime_error("Could not open: " + fileName);
    }
    int lineNumber = 0;
    std::string line;
    std::vector<std::string> split;
    int nDepths = 0;
    int nDistances = 0;
    std::vector<double> depths;
    std::vector<double> offsets;
    std::vector<double> travelTimes;
    while (std::getline(infl, line))
    {
        lineNumber = lineNumber + 1;
        if (lineNumber == 1){continue;} // Header
        // Remove leading blanks and parse
        boost::algorithm::trim_left(line);
        boost::split(split, line, boost::is_any_of("\n\t "),
                     boost::token_compress_on);
        // Read sizes
        if (lineNumber == 2)
        {
            nDistances = std::stoi(split[0]);
            nDepths = std::stoi(split[1]);
            offsets.reserve(nDistances);
            depths.resize(nDepths);
            travelTimes.resize(nDistances*nDepths, -1);
            continue;
        }
        // Read depths
        if (lineNumber == 3)
        {
            for (int id=0; id<nDepths; ++id)
            {
                depths[id] = std::stod(split[id]);
            }
            continue;
       }
       offsets.push_back(std::stod(split[0]));
       if (static_cast<int> (split.size() - 1) != nDepths)
       {
           throw std::runtime_error("Invalid size");
       }
       for (int i=0; i<nDepths; ++i)
       {
           auto indx = i*nDistances + offsets.size() - 1;
           travelTimes.at(indx) = std::stod(split[i+1]);
       }
    }
    infl.close();
    auto [tmin, tmax]
        = std::minmax_element(travelTimes.begin(), travelTimes.end());
    if (*tmin < 0){throw std::runtime_error("Failed to unpack ttimes");}
    // Create linearly interpolation
    interp->phaseLabel = phaseLabel;
    interp->offsets = offsets;
    interp->depths = depths;
    interp->travelTimes = travelTimes;
    interp->bilin.initialize(offsets.size(), offsets.data(),
                             depths.size(), depths.data(),
                             travelTimes.size(), travelTimes.data());
    // Check travel time interpolator works:
    //std::cout << travelTimes[10*nDistances+12] << " "  << interp->getTime(offsets[12], depths[10]) << std::endl;
}

/// @brief Create a grid in spherical coordinates.
void createGrid(const double lat0, const double lat1, const int nLat,
                const double lon0, const double lon1, const int nLon,
                const double z0,   const double z1,   const int nDep,
                std::vector<double> *lons,
                std::vector<double> *lats,
                std::vector<double> *depths)
{
    if (nDep < 2 || nLon < 2 || nLat < 2)
    {
        throw std::invalid_argument("need more than 2 grid points in x, y, z");
    }
    int nPts = nLat*nLon*nDep;
    std::cout << "Number of points in grid: " << nPts << std::endl;
    auto dLat = (lat1 - lat0)/(nLat - 1);
    auto dLon = (lon1 - lon0)/(nLon - 1);
    auto dDep = (z1 - z0)/(nDep - 1);
    lons->resize(nPts, 0);
    lats->resize(nPts, 0);
    depths->resize(nPts, -10);
    auto lonPtr = lons->data();
    auto latPtr = lats->data();
    auto depPtr = depths->data();
    // x
    #pragma omp simd collapse(3)
    for (int iLon=0; iLon<nLon; ++iLon)
    {
        // y
        for (int iLat=0; iLat<nLat; ++iLat)
        {
            // z
            for (int id=0; id<nDep; ++id)
            {
                auto indx = iLon*nDep*nLat + iLat*nDep + id;
                lonPtr[indx] = lon0 + iLon*dLon;
                latPtr[indx] = lat0 + iLat*dLat;
                depPtr[indx] = z0   + id*dDep;
            }
        }
    }
    auto dmin = std::min_element(depths->begin(), depths->end());
    if (*dmin <=-10){throw std::runtime_error("algorithm failure");}
}

/// @brief Creates the candidate source points in the grid search.
/// @param[out] lons     The longitudes in degrees.
/// @param[out] lats     The latitudes in degrees.
/// @param[out] depths   The depths in kilometers.
void createSourcePoints(std::vector<double> *lats,
                        std::vector<double> *lons,
                        std::vector<double> *depths)
{
    double kmPerDeg = 111.195;
    double deltaCoarse = 4; // 5 km spacing
    double lat0Coarse = 40.5;
    double lat1Coarse = 41.0;
    double lon0Coarse =-112.24;
    double lon1Coarse =-111.76;
    double z0Coarse = 1.5; // 1.5 km is sea-level
    double z1Coarse = 21.5;
    int nLatCoarse = static_cast<int> (std::round( (lat1Coarse - lat0Coarse)
                                                  /deltaCoarse*kmPerDeg) );
    int nLonCoarse = static_cast<int> (std::round( (lon1Coarse - lon0Coarse)
                                                  /deltaCoarse*kmPerDeg) );
    int nDepCoarse = static_cast<int> (std::round( (z1Coarse - z0Coarse)
                                                  /deltaCoarse ));

    double deltaFine = 1.25; // 1 km spacing
    double lon0Fine =-112.2;
    double lon1Fine =-111.8;
    double lat0Fine = 40.6;
    double lat1Fine = 40.85;
    double z0Fine = 0;
    double z1Fine = 18;
    int nLatFine = static_cast<int> (std::round( (lat1Fine - lat0Fine)
                                                /deltaFine*kmPerDeg) );
    int nLonFine = static_cast<int> (std::round( (lon1Fine - lon0Fine)
                                                /deltaFine*kmPerDeg) );
    int nDepFine = static_cast<int> (std::round( (z1Fine - z0Fine)
                                                /deltaFine ));

    std::vector<double> latsCoarse, lonsCoarse, depthsCoarse;
    createGrid(lat0Coarse, lat1Coarse, nLatCoarse,
               lon0Coarse, lon1Coarse, nLonCoarse,
               z0Coarse, z1Coarse, nDepCoarse,
               &lonsCoarse, &latsCoarse, &depthsCoarse);
    // Add in Bingham
    lonsCoarse.push_back(-112.1453);
    latsCoarse.push_back(40.5162);
    depthsCoarse.push_back(0);
    //std::cout << depthsCoarse.size() << std::endl;
    // Create the fine grid
    std::vector<double> latsFine, lonsFine, depthsFine;
    createGrid(lat0Fine, lat1Fine, nLatFine,
               lon0Fine, lon1Fine, nLonFine,
               z0Fine,   z1Fine, nDepFine,
               &lonsFine, &latsFine, &depthsFine);
    lonsCoarse.insert(std::end(lonsCoarse),
                      std::begin(lonsFine), std::end(lonsFine));
    latsCoarse.insert(std::end(latsCoarse),
                      std::begin(latsFine), std::end(latsFine));
    depthsCoarse.insert(std::end(depthsCoarse),
                        std::begin(depthsFine), std::end(depthsFine));
    //std::cout << depthsCoarse.size() << std::endl;
    *lons = lonsCoarse;
    *lats = latsCoarse;
    *depths = depthsCoarse;
}

// @brief Compute the source-to-receiver distances.
/// @param[in] receiverLatitude   The receiver's latitude in degrees.
/// @param[in] receiverLongitude  The receiver's longitude in degrees.
/// @param[in] sourceLatitudes    The source latitudes in degrees.
/// @param[in] sourceLongitudes   The source longitudes in degrees.
/// @param[out] distances         The source receiver distances in kilometers.
void computeDistances(const double receiverLatitude,
                      const double receiverLongitude,
                      const std::vector<double> &sourceLatitudes,
                      const std::vector<double> &sourceLongitudes,
                      std::vector<double> *distances)
{
    GeographicLib::Geodesic geodesic{GeographicLib::Constants::WGS84_a(),
                                     GeographicLib::Constants::WGS84_f()};
    distances->resize(sourceLatitudes.size());
    auto dPtr = distances->data();
    for (int i=0; i<static_cast<int> (sourceLatitudes.size()); ++i)
    {
        geodesic.Inverse(sourceLatitudes[i], sourceLongitudes[i],
                         receiverLatitude,   receiverLongitude,
                         dPtr[i]);
        dPtr[i] = dPtr[i]*1.e-3; // km
    }
}

int main()
{
    struct TravelTimeInterpolator pTimes;
    struct TravelTimeInterpolator sTimes;
/*
    auto receivers = getReceiverListFromCSV("../magna/magna_3c_stations.csv", true);
    auto nodes = getReceiverListFromNodalFile("../magna/Magna-Locs_2020-SN.txt");
    receivers.insert(std::end(receivers), std::begin(nodes), std::end(nodes));
*/
    HypoStation stations;
    stations.read("../magna/locate/magna.sta");
    std::cout << "Total number of receivers: " << stations.networks.size() << std::endl;
//    std::cout << "Total number of receivers: " << receivers.size() << std::endl;
    createGrowClustTravelTimeTable("../magna/TT.wasatch.pg", "P", &pTimes);
    createGrowClustTravelTimeTable("../magna/TT.wasatch.sg", "S", &sTimes);
    std::vector<double> lons, lats, depths, distances;
    createSourcePoints(&lats, &lons, &depths);
    H5IO h5io;
    h5io.openFileForWriting("../magna/travelTimeTables.h5");
    h5io.setGeometry(lats.size(), lats.data(), lons.data(), depths.data());
std::ofstream locFile("locations.txt");
std::ofstream staFile("stations.txt");

    for (int irec=0; irec<static_cast<int> (stations.networks.size()); ++irec)
    {
        computeDistances(stations.latitudes[irec], stations.longitudes[irec],
                         lats, lons, &distances);
        auto dmax = std::max_element(distances.begin(), distances.end());
        auto pTravelTimes = pTimes.getTimes(distances, depths);
        auto sTravelTimes = sTimes.getTimes(distances, depths);
        auto pmax = std::max_element(pTravelTimes.begin(), pTravelTimes.end());
        auto smax = std::max_element(sTravelTimes.begin(), sTravelTimes.end());
        if (irec == 0)
        {
           for (int j=0; j<static_cast<int> (lats.size()); ++j)
           {
               locFile << lons[j] << " " << lats[j] << " " << depths[j] << " " << distances[j]
                       << " " << pTravelTimes[j] << std::endl;
           }
        }
        staFile << stations.longitudes[irec] << " " << stations.latitudes[irec] << std::endl;
       std::cout << stations.stations[irec] << " " << stations.latitudes[irec] << " "
                 << stations.longitudes[irec]
                 << " " << *dmax << " " << *pmax << " " << *smax << std::endl;
        h5io.addTravelTimeTable(stations.networks[irec],
                                stations.stations[irec],
                                "P",
                                pTravelTimes.size(), pTravelTimes.data());
        h5io.addTravelTimeTable(stations.networks[irec],
                                stations.stations[irec],
                                "S",
                                sTravelTimes.size(), sTravelTimes.data());
    }
/*
    for (int irec=0; irec<static_cast<int> (receivers.size()); ++irec)
    {
        computeDistances(receivers[irec].latitude, receivers[irec].longitude,
                         lats, lons, &distances);
        auto dmax = std::max_element(distances.begin(), distances.end());
        auto pTravelTimes = pTimes.getTimes(distances, depths);
        auto sTravelTimes = sTimes.getTimes(distances, depths);
        auto pmax = std::max_element(pTravelTimes.begin(), pTravelTimes.end());
        auto smax = std::max_element(sTravelTimes.begin(), sTravelTimes.end());
if (irec == 0)
{
for (int j=0; j<lats.size(); ++j)
{
   locFile << lons[j] << " " << lats[j] << " " << distances[j] << " " << pTravelTimes[j] << std::endl;
}
}
staFile << receivers[irec].longitude << " " << receivers[irec].latitude << std::endl;
std::cout << receivers[irec].station << " " << receivers[irec].latitude << " " << receivers[irec].longitude << " " << *dmax << " " << *pmax << " " << *smax << std::endl;
        h5io.addTravelTimeTable(receivers[irec].network,
                                receivers[irec].station,
                                "P",
                                pTravelTimes.size(), pTravelTimes.data());
        h5io.addTravelTimeTable(receivers[irec].network,
                                receivers[irec].station,
                                "S",
                                sTravelTimes.size(), sTravelTimes.data());
    }
*/
}


// TODO:
// 1. init state from file
// 2. many algo runs
// 3. save metadata (score)
// 4. optimize moderator
#include <iostream>
#include <fstream>
#include <sstream>
#include <ctime>
#include <iomanip>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <math.h>
#include <random>
#include <boost/functional/hash.hpp>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>

#include "schedule.hh"
#include "params.hh"
#include "utils.hh"
#include "scorer.hh"

using namespace std;
namespace po = boost::program_options;

void usage(const string& name, const string& errmsg) {
  if (!errmsg.empty()) {
    cout << "Error: " << errmsg << endl << endl;
  }
  cout << "USAGE:" << endl;
  cout << "    " << name << " rankings_file" << endl;
}

bool parseArgs(int argc, char** argv, Params& params) {
  po::options_description desc("Allowed options");
  desc.add_options()
    ("help,h", "Show help message and exit")
    ("ranking_file,r", po::value<string>(), "Rankings CSV file")
    ("results_dir", po::value<string>()->default_value("results"), "Directory for saving results")
    ("iterations,i", po::value<u64>()->default_value(100000), "Number of iterations")
    ("init_temp", po::value<double>()->default_value(10.0), "Initial temperature")
    ("final_temp", po::value<double>()->default_value(0.00001), "Final temperature")
    ("timeslots", po::value<int>()->default_value(18), "Number of timeslots")
    ("rooms", po::value<int>()->default_value(9), "Number of rooms")
    ("room_size", po::value<int>()->default_value(12), "Room capacity including speaker")
    ("person_id_col", po::value<string>()->default_value("person_id"), "Name of person_id column")
    ("abstract_id_col", po::value<string>()->default_value("abstract_id"), "Name of abstract_id column")
    ("score_col", po::value<string>()->default_value("rating"), "Name of score column")
    ("input_delimiter", po::value<string>()->default_value(","), "Delimiter character of input file")
    ("default_score", po::value<Score>()->default_value(0), "Value of empty score")
    ("max_score", po::value<Score>()->default_value(5), "Minimum value for single score")
    ("min_score", po::value<Score>()->default_value(0), "Maximum value for single score")
    ("score_delta", po::value<Score>()->default_value(1), "Delta added per score (to avoid 0 score)")
    ("participation_range", po::value<u32>()->default_value(2), "Allowed deviation from mean number of participations per person")
    ("max_presentations", po::value<u32>()->default_value(3), "Max number of presentations per abstract")
    ("seed", po::value<int>(), "Algorithm random seed (for debugging)")
    ("verbose,v", "Verbose mode (for debugging)")
    ;

  try {
    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);
    if (vm.count("help")) {
      cout << desc << "\n";
      return false;
    }
    setVerboseMode(vm.count("verbose") > 0);
    params.nTimeslots = vm["timeslots"].as<int>();
    params.nRooms = vm["rooms"].as<int>();
    params.roomSize = vm["room_size"].as<int>();
    params.maxIterations = vm["iterations"].as<u64>();
    params.initTemp = vm["init_temp"].as<double>();
    params.finalTemp = vm["final_temp"].as<double>();
    params.resultsDir = vm["results_dir"].as<string>();
    params.personIdCol = vm["person_id_col"].as<string>();
    params.abstractIdCol = vm["abstract_id_col"].as<string>();
    params.scoreCol = vm["score_col"].as<string>();
    params.defaultScore = vm["default_score"].as<Score>();
    params.maxScore = vm["max_score"].as<Score>();
    params.minScore = vm["min_score"].as<Score>();
    params.scoreDelta = vm["score_delta"].as<Score>();
    params.participationRange = vm["participation_range"].as<u32>();
    params.maxPresentations = vm["max_presentations"].as<u32>();
    params.maxNormScore = params.scoreDelta + params.maxScore;
    params.minNormScore = params.scoreDelta + params.minScore;
    string strDelim = vm["input_delimiter"].as<string>();
    if (strDelim.size() != 1) {
      err() << "input_delimiter should be a single character. Got: " << strDelim.size() << endl;
      return false;
    }
    params.inputDelimiter = strDelim[0];
    if (vm.count("seed")) {
      params.seed = vm["seed"].as<int>();
    } else {
      std::random_device rd;
      params.seed = rd();
    }

    if (!readRankings(vm["ranking_file"].as<string>(), params))
      return false;

    params.avgParticipations = round(double(params.nTimeslots * params.nRooms * (params.roomSize - 1)) / params.nPeople);
    params.minParticipations = ceil(params.avgParticipations - params.participationRange);
    params.maxParticipations = floor(params.avgParticipations + params.participationRange);

  } catch(po::error& e) {
    cout << "Error parsing command line: " << e.what();
    return false;
  }

  return true;
}

Score maxPotentialScore(const Rankings& rankings) {
  Score sumScore = 0;
  for (auto const& x : rankings)
    sumScore += x;
  return sumScore;
}

class SimAnnealing {
public:
  SimAnnealing(Schedule& sched, const Params& params, Scorer& scorer) :
    m_sched(sched), m_scorer(scorer), m_params(params), m_iter(0),
    m_timeslotCapacity(m_params.nRooms * m_params.roomSize), m_bestSched(sched) {}

  bool run() {
    m_startTime = chrono::system_clock::now();
    Score maxScore = maxPotentialScore(m_params.rankings);
    m_bestScore = 0;
    dbg() << "maxScore: " << maxScore << endl;
    s32 nextOutputSec = 0;
    m_temperature = m_params.initTemp;
    for (m_iter = 0; m_iter < m_params.maxIterations; ++m_iter) {
      if (m_iter % 10000 == 0) {
        double tempRatio = m_params.finalTemp / m_params.initTemp;
        m_temperature = m_params.initTemp *
          (exp(std::log(tempRatio) * (double(m_iter) / m_params.maxIterations)));
        if (elapsedSecs(m_startTime) >= nextOutputSec) {
          if (!outputStatus(dbg()))
            return false;
          ++nextOutputSec;
        }
      }
      try {
        oneIteration();
        if (m_scorer.score() > m_bestScore) {
          if (!handleNewBest())
            return false;
          m_bestScore = m_scorer.score();
        }
      } catch(std::exception& e) {
        cout << "Error in iter " << m_iter << ": " << e.what();
        return false;
      }
    }
    outputStatus(info());
    return true;
  }

  void outputSchedSummary(ostream& s) {
    s << endl;
    for (s32 t = 0; t < m_params.nTimeslots; ++t) {
      Score tScore = 0;
      s << setw(2) << (t + 1) << " || ";
      for (s32 r = 0; r < m_params.nRooms; ++r) {
        Score rScore = m_scorer.calcRoomScore(t, r);
        s << setw(3) << m_sched.getAbstractID(t, r) << " " << setw(4) << rScore << " | ";
        tScore += rScore;
      }
      s << tScore << endl;
    }
  }

  void outputSchedStats(ostream& s, const Schedule& sched) {
    // nPeople per number of rated abstracts they got
    // Score quantiles
    vector<s32> personParticipations(m_params.nPeople, 0);
    vector<s32> ratedAbstractsGotPerPerson(m_params.nPeople, 0);
    vector<s32> abstractPresentations(m_params.nAbstracts, 0);
    vector<s32> ratingsGotPerAbstract(m_params.nAbstracts, 0);
    for (s32 t = 0; t < m_params.nTimeslots; ++t) {
      for (s32 r = 0; r < m_params.nRooms; ++r) {
        ID abstractID = sched.getAbstractID(t, r);
        ++abstractPresentations[abstractID];
        for (s32 s = 1; s < m_params.roomSize; ++s) {
          ID personID = sched.getID(t, r, s);
          ++personParticipations[personID];
          if (getRankingOrig(personID, abstractID, m_params) > 0) {
            ++ratedAbstractsGotPerPerson[personID];
            ++ratingsGotPerAbstract[abstractID];
          }
        }
      }
    }
    vector<s32> ratedAbstractsPerPerson(m_params.nPeople, 0);
    vector<s32> ratingsPerAbstract(m_params.nAbstracts, 0);
    for (ID personID = 0; personID < m_params.nPeople; ++personID) {
      for (ID abstractID = 0; abstractID < m_params.nAbstracts; ++abstractID) {
        if (getRankingOrig(personID, abstractID, m_params) > 0) {
          ++ratedAbstractsPerPerson[personID];
          ++ratingsPerAbstract[abstractID];
        }
      }
    }
    s32 unmatchableAbstracts = 0, unmatchablePeople = 0;
    vector<s32> ratedGotPercentPerPerson(102, 0);
    vector<s32> ratedGotPercentOfMaxPerPerson(102, 0);
    for (ID personID = 0; personID < m_params.nPeople; ++personID) {
      s32 percent = (ratedAbstractsPerPerson[personID] == 0) ? 101 : (
        (20 * ratedAbstractsGotPerPerson[personID]) / ratedAbstractsPerPerson[personID]) * 5;
      s32 percentOfMax = (ratedAbstractsPerPerson[personID] == 0) ? 101 : (
        (20 * ratedAbstractsGotPerPerson[personID]) /
        min(ratedAbstractsPerPerson[personID], s32(m_params.maxParticipations))) * 5;
      ++ratedGotPercentPerPerson[percent];
      ++ratedGotPercentOfMaxPerPerson[percentOfMax];
      unmatchablePeople += max(0, s32(m_params.minParticipations) - ratedAbstractsPerPerson[personID]);
    }
    vector<s32> ratedGotPercentPerAbstract(102, 0);
    for (ID abstractID = 0; abstractID < m_params.nAbstracts; ++abstractID) {
      s32 percent = (ratingsPerAbstract[abstractID] == 0) ? 101 : (
        (20 * ratingsGotPerAbstract[abstractID]) / ratingsPerAbstract[abstractID]) * 5;
      ++ratedGotPercentPerAbstract[percent];
      unmatchableAbstracts += max(0, m_params.roomSize - 1 - ratingsPerAbstract[abstractID]);
    }

    vector<s32> nPeoplePerNParticipations = vectorCount(personParticipations);
    vector<s32> nPeoplePerNRatedAbstractsGot = vectorCount(ratedAbstractsGotPerPerson);
    vector<s32> nPeoplePerNRatedAbstracts = vectorCount(ratedAbstractsPerPerson);
    vector<s32> nAbstractsPerNPresentations = vectorCount(abstractPresentations);
    vector<s32> nAbstractsPerNRatingsGot = vectorCount(ratingsGotPerAbstract);
    vector<s32> nAbstractsPerNRatings = vectorCount(ratingsPerAbstract);
    s32 totalParticipations = m_params.nTimeslots * m_params.nRooms * (m_params.roomSize-1);

    s << "nPeople:" << m_params.nPeople;
    s << " nAbstracts:" << m_params.nAbstracts;
    s << " nRooms:" << m_params.nRooms;
    s << " nTimeslots:" << m_params.nTimeslots;
    s << " Room size:" << m_params.roomSize << endl;
    s << "nAbstracts ratings:" << vectorSum(ratedAbstractsPerPerson) << endl;
    s << "nAbstracts rated and got:" << vectorSum(ratedAbstractsGotPerPerson) << endl;
    s << "Total participations (excl. presenters): " << totalParticipations << endl;
    s << "Total forced unrated participations (people with less than " << m_params.minParticipations
      << " rankings): " << unmatchablePeople << " (max matches: "
      << (totalParticipations - unmatchablePeople) << ")" << endl;
    s << "Total forced unrated participations (abstracts with less than " << (m_params.roomSize - 1)
      << " rankings): " << unmatchableAbstracts << " (max matches: "
      << (totalParticipations - unmatchableAbstracts) << ")" << endl;
    s << "nPeople per number of participations:";
    outputVectorCount(s, nPeoplePerNParticipations) << endl;
    s << "nPeople per abstracts rated:";
    outputVectorCount(s, nPeoplePerNRatedAbstracts) << endl;
    s << "nPeople per abstracts rated got:";
    outputVectorCount(s, nPeoplePerNRatedAbstractsGot) << endl;
    s << "nAbstracts per number of presentations:";
    outputVectorCount(s, nAbstractsPerNPresentations) << endl;
    s << "nAbstracts per times rated:";
    outputVectorCount(s, nAbstractsPerNRatings) << endl;
    s << "nAbstracts per times rated and got:";
    outputVectorCount(s, nAbstractsPerNRatingsGot) << endl;
    s << "nAbstracts per ratings got percent: N/A:" << ratedGotPercentPerAbstract[101];
    ratedGotPercentPerAbstract[101] = 0;
    outputVectorCount(s, ratedGotPercentPerAbstract, "%") << endl;
    s << "nPeople per ratings got percent: N/A:" << ratedGotPercentPerPerson[101];
    ratedGotPercentPerPerson[101] = 0;
    outputVectorCount(s, ratedGotPercentPerPerson, "%") << endl;
    s << "nPeople per ratings got of their max percent: N/A:" << ratedGotPercentOfMaxPerPerson[101];
    ratedGotPercentOfMaxPerPerson[101] = 0;
    outputVectorCount(s, ratedGotPercentOfMaxPerPerson, "%") << endl;
  }

  const Schedule& bestSchedule() {
    m_bestSched.setAllIDs(m_bestSchedule);
    return m_bestSched;
  }

  const Schedule& curSchedule() {
    return m_sched;
  }

protected:
  Schedule& m_sched;
  Scorer& m_scorer;
  const Params m_params;
  u64 m_iter;
  const s32 m_timeslotCapacity;
  double m_temperature;
  Score m_bestScore;
  vector<ID> m_bestSchedule;
  Schedule m_bestSched;
  time_point m_startTime;

  std::string inResultsDir(std::string name) {
    return (boost::filesystem::path(m_params.resultsDir) / name).c_str();
  }

  bool handleNewBest() {
    m_sched.getAllIDs(m_bestSchedule);
    return true;

  }

  bool saveBest() {
    if (m_bestSchedule.empty())
      return true;
    string schedPath = inResultsDir("best_schedule.csv");
    ofstream schedFile(schedPath);
    if (schedFile.bad() || schedFile.fail()) {
      err() << "Error opening file '" << schedPath << "': " << strerror(errno) << endl;
      return false;
    }
    m_sched.outputIDs(schedFile, m_bestSchedule);

    string metadataPath = inResultsDir("best_schedule.metadata");
    ofstream metadataFile(metadataPath);
    if (metadataFile.bad() || metadataFile.fail()) {
      err() << "Error opening file '" << metadataPath << "': " << strerror(errno) << endl;
      return false;
    }
    metadataFile << "Score: " << m_scorer.score() << endl;
    metadataFile << "Iter: " << m_iter << endl;
    metadataFile << "Temperature: " << m_temperature << endl;
    metadataFile << "Elapsed seconds: " << elapsedSecs(m_startTime) << endl;
    outputParams(m_params, metadataFile);
    outputSchedSummary(metadataFile);
    outputSchedStats(metadataFile, bestSchedule());
    return true;
  }

  bool oneIteration() {
    Score curScore = m_scorer.score();
    s32 t = randInt(m_params.nTimeslots);
    s32 i1 = randInt(m_params.nPeople), i2 = randInt(m_params.nPeople);
    if (i2 < i1)
      swap(i1, i2);
    if (i1 >= m_timeslotCapacity || i1 == i2)
      return false;
    s32 room1 = i1 / m_params.roomSize;
    s32 seat1 = i1 % m_params.roomSize;
    ID id1 = m_sched.getID(t, room1, seat1);
    if (i2 < m_timeslotCapacity) { // Change type 1: swap two seats in time slot
      s32 room2 = i2 / m_params.roomSize;
      s32 seat2 = i2 % m_params.roomSize;
      ID id2 = m_sched.getID(t, room2, seat2);
      if (room1 == room2 && seat1 > 0 && seat2 > 0)
        return false;
      if ((seat1 == 0 && id2 >= m_params.nAbstracts) ||
          (seat2 == 0 && id1 >= m_params.nAbstracts)) {
        return false;
      }
      m_scorer.prepareSwapChange(t, room1, seat1, t, room2, seat2);
      if (!swapIfLegal(t, room1, seat1, room2, seat2)) {
        ASSERT(m_scorer.score() == curScore);
        return false;
      }
      m_scorer.tryChange();
      // m_scorer.recalcScore();
      Score newScore = m_scorer.score();
      if (!shouldAcceptStep(curScore, newScore, m_temperature)) {
        m_sched.setIDUnsafe(t, room2, seat2, INVALID_ID);
        m_sched.setIDUnsafe(t, room1, seat1, id1);
        m_sched.setIDUnsafe(t, room2, seat2, id2);
        m_scorer.undoChange();
        // m_scorer.recalcScore();
        ASSERT(abs(m_scorer.score() - curScore) < (m_params.minNormScore / 1000));
      }
    }
    else {  // Change type 2: Swap seat with a free person in time slot
      ID id2 = m_sched.getRandomFreePerson(t);
      if (seat1 == 0 && id2 >= m_params.nAbstracts) {
        return false;
      }
      m_scorer.prepareSetChange(t, room1, seat1, id2);
      if (!m_sched.setIDIfLegal(t, room1, seat1, id2))
        return false;
      m_scorer.tryChange();
      Score newScore = m_scorer.score();
      if (!shouldAcceptStep(curScore, newScore, m_temperature)) {
        m_sched.setIDUnsafe(t, room1, seat1, id1);
        m_scorer.undoChange();
        // m_scorer.recalcScore();
        ASSERT(abs(m_scorer.score() - curScore) < (m_params.minNormScore / 1000));
      }
    }
    return true;
  }

  bool swapIfLegal(s32 timeslot, s32 room1, s32 seat1, s32 room2, s32 seat2) {
    ID id1 = m_sched.getID(timeslot, room1, seat1);
    ID id2 = m_sched.getID(timeslot, room2, seat2);
    if (!m_sched.setIDIfLegal(timeslot, room2, seat2, INVALID_ID))
      return false;
    if (!m_sched.setIDIfLegal(timeslot, room1, seat1, id2)) {
      m_sched.setIDUnsafe(timeslot, room2, seat2, id2);
      return false;
    }
    if (!m_sched.setIDIfLegal(timeslot, room2, seat2, id1)) {
      m_sched.setIDUnsafe(timeslot, room1, seat1, id1);
      m_sched.setIDUnsafe(timeslot, room2, seat2, id2);
      return false;
    }
    return true;
  }

  vector<s32> vectorCount(const vector<s32>& data) {
    vector<s32> count(*max_element(begin(data), end(data)) + 1, 0);
    for (s32 d : data) {
      ++count[d];
    }
    return count;
  }

  s32 vectorSum(const vector<s32>& data) {
    s32 sum = 0;
    for (s32 d : data)
      sum += d;
    return sum;
  }

  ostream& outputVectorCount(ostream& s, const vector<s32>& v, string countSuffix="") {
    for (s32 i = 0; i < v.size(); ++i) {
      if (v[i] > 0)
        s << " " << i << countSuffix << ":" << v[i];
    }
    return s;
  }

  bool outputStatus(ostream& s) {
    s << "Iter " << double(m_iter) << "/" << double(m_params.maxIterations)
      << " (" << setprecision(4)
      << left << (100.0 * m_iter / m_params.maxIterations) << right << "%) temperature: "
      << m_temperature << " score: " << m_scorer.score() << " (dbg:" << m_scorer.calcScore()
      << ") best so far:" << m_bestScore << endl;
    //outputSchedSummary(s << endl);
    ASSERT(abs(m_scorer.score() - m_scorer.calcScore()) < (m_params.minNormScore / 1000));
    return saveBest();
  }

  bool shouldAcceptStep(Score curScore, Score newScore, double temperature) {
    if (newScore >= curScore)
      return true;
    double normDelta = double(newScore - curScore);
    return randProb() < exp(normDelta / temperature);
  }
};

void findSchedule(const Params& params) {
    outputParams(params, info());
    dbg() << "Creating empty schedule" << endl;
    Schedule sched = Schedule(params);
    dbg() << "Initializing schedule" << endl;
    sched.initState();
    dbg() << "Initializing scorer" << endl;
    SumHappinessScorer scorer(sched, params);

    dbg() << "Initializing algorithm" << endl;
    SimAnnealing sa(sched, params, scorer);

    dbg() << "Stats:" << endl;
    auto& s = dbg();
    sa.outputSchedStats(s, sa.curSchedule());
    sa.outputSchedSummary(s);
    s << "Score:" << scorer.score() << endl;

    dbg() << "Optimizing schedule" << endl;
    sa.run();
//    auto& s = dbg();
    sa.outputSchedSummary(s);

    SumHappinessScorer scorer2(sched, params);
    MinHappinessBonusScorer minScorer(sched, params);

    sa.outputSchedStats(s, sa.bestSchedule());
    s << "Score:" << scorer.score() << endl;

    SumScorers sumScorers(scorer2, minScorer);
    SimAnnealing sa2(sched, params, sumScorers);
    sa2.run();
    sa.outputSchedSummary(s);
    MinHappinessBonusScorer scorer3(sched, params);
    sa.outputSchedStats(s, sa.bestSchedule());
    dbg() << "min person ID: " << minScorer.calcMinPersonScoreID() << endl;
    s << "Score:" << SumHappinessScorer(sched, params).score() << endl;
}

int main(int argc, char** argv) {
  Params params;
  if (!parseArgs(argc, argv, params))
    return 2;
  randSetSeed(params.seed);
  try {
    findSchedule(params);
  } catch (const std::exception& e) {
    err() << e.what() << '\n';
  }
  return 0;
}


#ifndef STRING_HPP
#define STRING_HPP

#include <boost/mpl/int.hpp>

namespace detail_ {

template < int ... Nums >
struct indices
{
    template < int I >
    static constexpr auto append(boost::mpl::int_<I>)
    {
        return indices<Nums...,I>();
    }
};

constexpr indices<> make_indices(boost::mpl::int_<0>)
{
    return indices<>();
}

template < int I >
constexpr auto make_indices(boost::mpl::int_<I>)
{
    using prev = boost::mpl::int_<I-1>;
    return make_indices(prev()).append(prev());
}

}

template < int Size >
constexpr auto make_string(char const (&) [Size]);

template < int Size >
struct string
{
    using data_type = char const (&) [Size+1];

    constexpr int size() const
    {
        return Size;
    }

    constexpr data_type c_str() const
    {
        return arr;
    }

    constexpr string<Size+1> push_back(char c) const
    {
        return push_back(c, detail_::make_indices(boost::mpl::int_<Size>()));
    }

    constexpr string<Size+1> push_front(char c) const
    {
        return push_front(c, detail_::make_indices(boost::mpl::int_<Size>()));
    }

    template < int I >
    constexpr string<Size+I-1> append(char const (&cstr)[I]) const
    {
        return append( detail_::make_indices(boost::mpl::int_<Size>())
                     , detail_::make_indices(boost::mpl::int_<I-1>())
                     , cstr );
    }

    template < int I >
    constexpr string<Size+I> append(string<I> const& s) const
    {
        return append( detail_::make_indices(boost::mpl::int_<Size>())
                     , detail_::make_indices(boost::mpl::int_<I>())
                     , s.c_str() );
    }

    constexpr string(char const (&str) [Size+1])
        : string(str, detail_::make_indices(boost::mpl::int_<Size>()))
    {}

    constexpr string() : arr{} {}

private:
    char arr[Size+1];

    template < int I, int ... Indices >
    constexpr string(char const (&str) [I], detail_::indices<Indices...>)
        : arr{str[Indices]..., '\0'}
    {}

    template < int ... Indices >
    constexpr string<Size+1> push_back(char c, detail_::indices<Indices...>) const
    {
        char const newArr[] = {arr[Indices]..., c, '\0'};
        return string<Size+1>(newArr);
    }

    template < int ... Indices >
    constexpr string<Size+1> push_front(char c, detail_::indices<Indices...>) const
    {
        char const newArr[] = {c, arr[Indices]..., '\0'};
        return string<Size+1>(newArr);
    }

    template < int ... ThisIndices
             , int ... ThatIndices
             , int I >
    constexpr string<Size+I-1> append( detail_::indices<ThisIndices...>
                                     , detail_::indices<ThatIndices...>
                                     , char const (&cstr) [I] ) const
    {
        char const newArr[] = {arr[ThisIndices]..., cstr[ThatIndices]..., '\0'};
        return string<Size+I-1>(newArr);
    }
};

template < int Size >
constexpr auto make_string(char const (&cstr) [Size])
{
    return string<Size-1>(cstr);
}

template < int I >
constexpr string<I + 1> operator + (string<I> const& s, char c)
{
    return s.push_back(c);
}

template < int I >
constexpr string<I + 1> operator + (char c, string<I> const& s)
{
    return s.push_front(c);
}

template < int I0, int I1>
constexpr string<I0+I1> operator + (string<I0> const& s0, string<I1> const& s1)
{
    return s0.append(s1);
}

#endif


#include <iostream>
#include <fstream>
#include <vector>
#include <cassert>

#include <boost/program_options.hpp>
#include <boost/program_options/variables_map.hpp>

#include "filelib.h"
#include "fdict.h"
#include "weights.h"
#include "sparse_vector.h"

using namespace std;
namespace po = boost::program_options;

void InitCommandLine(int argc, char** argv, po::variables_map* conf) {
  po::options_description opts("Configuration options");
  opts.add_options()
        ("input_format,f",po::value<string>()->default_value("b64"),"Encoding of the input (b64 or text)")
        ("input,i",po::value<string>()->default_value("-"),"Read file from")
        ("output,o",po::value<string>()->default_value("-"),"Write weights to");
  po::options_description clo("Command line options");
  clo.add_options()
        ("config", po::value<string>(), "Configuration file")
        ("help,h", "Print this help message and exit");
  po::options_description dconfig_options, dcmdline_options;
  dconfig_options.add(opts);
  dcmdline_options.add(opts).add(clo);

  po::store(parse_command_line(argc, argv, dcmdline_options), *conf);
  if (conf->count("config")) {
    ifstream config((*conf)["config"].as<string>().c_str());
    po::store(po::parse_config_file(config, dconfig_options), *conf);
  }
  po::notify(*conf);

  if (conf->count("help")) {
    cerr << dcmdline_options << endl;
    exit(1);
  }
}

void WriteWeights(const SparseVector<double>& weights, ostream* out) {
  for (SparseVector<double>::const_iterator it = weights.begin();
       it != weights.end(); ++it) {
    (*out) << FD::Convert(it->first) << " " << it->second << endl;
  }
}

int main(int argc, char** argv) {
  po::variables_map conf;
  InitCommandLine(argc, argv, &conf);

  const bool use_b64 = conf["input_format"].as<string>() == "b64";

  const string s_obj = "**OBJ**";
  // E-step
  ReadFile rf(conf["input"].as<string>());
  istream* in = rf.stream();
  assert(*in);
  WriteFile wf(conf["output"].as<string>());
  ostream* out = wf.stream();
  out->precision(17);
  while(*in) {
    string line;
    getline(*in, line);
    if (line.empty()) continue;
    int feat;
    double val;
    size_t i = line.find("\t");
    assert(i != string::npos);
    ++i;
    if (use_b64) {
      SparseVector<double> g;
      double obj;
      if (!B64::Decode(&obj, &g, &line[i], line.size() - i)) {
        cerr << "B64 decoder returned error, skipping!\n";
        continue;
      }
      WriteWeights(g, out);
    } else {       // text encoding - your counts will not be accurate!
      SparseVector<double> weights;
      while (i < line.size()) {
        size_t start = i;
        while (line[i] != '=' && i < line.size()) ++i;
        if (i == line.size()) { cerr << "FORMAT ERROR\n"; break; }
        string fname = line.substr(start, i - start);
        if (fname == s_obj) {
          feat = -1;
        } else {
          feat = FD::Convert(line.substr(start, i - start));
        }
        ++i;
        start = i;
        while (line[i] != ';' && i < line.size()) ++i;
        if (i - start == 0) continue;
        val = atof(line.substr(start, i - start).c_str());
        ++i;
        if (feat != -1) {
          weights.set_value(feat, val);
        }
      }
      WriteWeights(weights, out);
    }
  }

  return 0;
}


#include <boost/numeric/odeint/iterator/times_time_iterator.hpp>


//
// Copyright 2020 Mateusz Loskot <mateusz at loskot dot net>
//
// Distributed under 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
//
#include <boost/gil/cmyk.hpp>
#include <boost/gil/gray.hpp>
#include <boost/gil/pixel.hpp>
#include <boost/gil/rgb.hpp>

#include <boost/mp11.hpp>
#include <boost/core/lightweight_test.hpp>

#include "test_fixture.hpp"
#include "core/channel/test_fixture.hpp"

namespace gil = boost::gil;
namespace fixture = boost::gil::test::fixture;
namespace mp11 = boost::mp11;

// Test color_convert from any pixel to CMYK pixel types

template <typename SrcPixel>
struct test_cmyk_from_gray
{
    template<typename DstPixel>
    void operator()(DstPixel const&)
    {
        using pixel_src_t = SrcPixel;
        using pixel_dst_t = DstPixel;
        fixture::channel_value<typename gil::channel_type<pixel_src_t>::type> f_src;
        fixture::channel_value<typename gil::channel_type<pixel_dst_t>::type> f_dst;

        // FIXME: Where does this calculation come from? Shouldn't gray be inverted?
        //        Currently, white becomes black and black becomes white.
        {
            pixel_src_t const src{f_src.min_v_};
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
        }
        {
            pixel_src_t const src{f_src.max_v_};
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.max_v_);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each
        <
            mp11::mp_list
            <
                gil::cmyk8_pixel_t,
                gil::cmyk8s_pixel_t,
                gil::cmyk16_pixel_t,
                gil::cmyk16s_pixel_t,
                gil::cmyk32_pixel_t,
                gil::cmyk32s_pixel_t,
                gil::cmyk32f_pixel_t
            >
        >(test_cmyk_from_gray<SrcPixel>{});
    }
};

struct test_cmyk_from_rgb
{
    template <typename SrcPixel, typename DstPixel>
    void operator()(mp11::mp_list<SrcPixel, DstPixel> const&) const
    {
        using pixel_src_t = SrcPixel;
        using pixel_dst_t = DstPixel;
        fixture::channel_value<typename gil::channel_type<pixel_src_t>::type> f_src;
        fixture::channel_value<typename gil::channel_type<pixel_dst_t>::type> f_dst;

        // black
        {
            pixel_src_t const src(f_src.min_v_, f_src.min_v_, f_src.min_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.max_v_);
        }
        // white
        {
            pixel_src_t const src(f_src.max_v_, f_src.max_v_, f_src.max_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
        // red
        {
            pixel_src_t const src(f_src.max_v_, f_src.min_v_, f_src.min_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
        // green
        {
            pixel_src_t const src(f_src.min_v_, f_src.max_v_, f_src.min_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
        // blue
        {
            pixel_src_t const src(f_src.min_v_, f_src.min_v_, f_src.max_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
        // yellow
        {
            pixel_src_t const src(f_src.max_v_, f_src.max_v_, f_src.min_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
        // cyan
        {
            pixel_src_t const src(f_src.min_v_, f_src.max_v_, f_src.max_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
        // magenta
        {
            pixel_src_t const src(f_src.max_v_, f_src.min_v_, f_src.max_v_);
            pixel_dst_t dst;
            gil::color_convert(src, dst);

            BOOST_TEST_EQ(gil::get_color(dst, gil::cyan_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::magenta_t{}), f_dst.max_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::yellow_t{}), f_dst.min_v_);
            BOOST_TEST_EQ(gil::get_color(dst, gil::black_t{}), f_dst.min_v_);
        }
    }
    static void run()
    {
        boost::mp11::mp_for_each
        <
            mp11::mp_product
            <
                mp11::mp_list,
                mp11::mp_list
                <
                    gil::rgb8_pixel_t,
                    gil::rgb8s_pixel_t,
                    gil::rgb16_pixel_t,
                    gil::rgb16s_pixel_t,
                    gil::rgb32_pixel_t,
                    gil::rgb32s_pixel_t,
                    gil::rgb32f_pixel_t
                >,
                mp11::mp_list
                <
                    gil::cmyk8_pixel_t,
                    gil::cmyk16_pixel_t,
                    gil::cmyk32_pixel_t,
                    gil::cmyk32f_pixel_t
                    // FIXME: Conversion not handle properly signed CMYK pixels as destination
                >
            >
        >(test_cmyk_from_rgb{});
    }
};

int main()
{
    test_cmyk_from_gray<gil::gray8_pixel_t>::run();
    test_cmyk_from_gray<gil::gray8s_pixel_t>::run();
    test_cmyk_from_gray<gil::gray16_pixel_t>::run();
    test_cmyk_from_gray<gil::gray16s_pixel_t>::run();
    test_cmyk_from_gray<gil::gray32_pixel_t>::run();
    test_cmyk_from_gray<gil::gray32s_pixel_t>::run();
    test_cmyk_from_gray<gil::gray32f_pixel_t>::run();

    test_cmyk_from_rgb::run();

    return ::boost::report_errors();
}


//          Copyright Carl Philipp Reh 2009 - 2016.
// Distributed under 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)

#ifndef FCPPT_MATH_DETAIL_NARROW_CAST_HPP_INCLUDED
#define FCPPT_MATH_DETAIL_NARROW_CAST_HPP_INCLUDED

#include <fcppt/math/detail/init.hpp>
#include <fcppt/config/external_begin.hpp>
#include <boost/mpl/less.hpp>
#include <type_traits>
#include <fcppt/config/external_end.hpp>

namespace fcppt
{
namespace math
{
namespace detail
{

template<
	typename T,
	typename U
>
inline
T
narrow_cast(
	U const &_other
)
{
	static_assert(
		std::is_same<
			typename T::value_type,
			typename U::value_type
		>::value,
		"narrow_cast can only be used on the same value_types"
	);

	static_assert(
		boost::mpl::less<
			typename T::dim_wrapper,
			typename U::dim_wrapper
		>::value,
		"narrow_cast can only cast to types with lesser dimensions"
	);

	return
		fcppt::math::detail::init<
			T
		>(
			[
				&_other
			](
				typename T::size_type const _index
			)
			{
				return
					_other[
						_index
					];
			}
		);
}

}
}
}

#endif


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_TOUINT_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_TOUINT_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/touint.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( touint_, tag::cpu_
                                    , (A0)
                                    , (scalar_< integer_<A0> >)
                                    )
  {
    typedef typename dispatch::meta::as_integer<A0, unsigned> ::type result_type;

    BOOST_FORCEINLINE  result_type operator()(A0 const& a0) const
    {
      return a0;
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( touint_, tag::cpu_
                                    , (A0)
                                    , (scalar_< floating_<A0> >)
                                    )
  {
    typedef typename dispatch::meta::as_integer<A0, unsigned> ::type result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      return result_type(a0);
    }
  };
} } }

#endif


#include <algorithm>
#include <glm/gtc/matrix_transform.hpp>
#if defined(_MSC_VER)
#include <boost/math/special_functions/round.hpp>
#define bmround	boost::math::round
#else
#define bmround	round
#endif

#include "camera.hpp"
#include "preferences.hpp"
#include "profile_timer.hpp"
#include "variant_utils.hpp"

camera_callable::camera_callable()
	: fov_(45.0f), horizontal_angle_(float(M_PI)), vertical_angle_(0.0f),
	speed_(0.1f), mouse_speed_(0.005f), near_clip_(0.1f), far_clip_(300.0f),
	mode_(MODE_AUTO), type_(PERSPECTIVE_CAMERA), ortho_left_(0), ortho_bottom_(0),
	ortho_top_(preferences::actual_screen_height()), ortho_right_(preferences::actual_screen_width()),
	clip_planes_set_(false)
{
	up_ = glm::vec3(0.0f, 1.0f, 0.0f);
	position_ = glm::vec3(0.0f, 0.0f, 10.0f);
	aspect_ = float(preferences::actual_screen_width())/float(preferences::actual_screen_height());

	compute_view();
	compute_projection();
}

camera_callable::camera_callable(const variant& node)
	: fov_(45.0f), horizontal_angle_(float(M_PI)), vertical_angle_(0.0f),
	speed_(0.1f), mouse_speed_(0.005f), near_clip_(0.1f), far_clip_(300.0f),
	mode_(MODE_AUTO), type_(PERSPECTIVE_CAMERA), ortho_left_(0), ortho_bottom_(0),
	ortho_top_(preferences::actual_screen_height()), ortho_right_(preferences::actual_screen_width()),
	clip_planes_set_(false)
{
	position_ = glm::vec3(0.0f, 0.0f, 10.0f);
	if(node.has_key("fov")) {
		fov_ = std::min(90.0f, std::max(15.0f, float(node["fov"].as_decimal().as_float())));
	}
	if(node.has_key("horizontal_angle")) {
		horizontal_angle_ = float(node["horizontal_angle"].as_decimal().as_float());
	}
	if(node.has_key("vertical_angle")) {
		vertical_angle_ = float(node["vertical_angle"].as_decimal().as_float());
	}
	if(node.has_key("speed")) {
		speed_ = float(node["speed"].as_decimal().as_float());
	}
	if(node.has_key("mouse_speed")) {
		mouse_speed_ = float(node["mouse_speed"].as_decimal().as_float());
	}
	if(node.has_key("aspect")) {
		aspect_ = float(node["aspect"].as_decimal().as_float());
	} else {
		aspect_ = float(preferences::actual_screen_width())/float(preferences::actual_screen_height());
	}

	if(node.has_key("position")) {
		ASSERT_LOG(node["position"].is_list() && node["position"].num_elements() == 3,
			"position must be a list of 3 decimals.");
		position_ = glm::vec3(float(node["position"][0].as_decimal().as_float()),
			float(node["position"][1].as_decimal().as_float()),
			float(node["position"][2].as_decimal().as_float()));
	}

	if(node.has_key("type")) {
		if(node["type"].as_string() == "orthogonal") {
			type_ = ORTHOGONAL_CAMERA;
		}
	}
	if(node.has_key("ortho_window")) {
		ASSERT_LOG(node["ortho_window"].is_list() && node["ortho_window"].num_elements() == 4, "Attribute 'ortho_window' must be a 4 element list. left,right,top,bottom");
		ortho_left_ = node["ortho_window"][0].as_int();
		ortho_right_ = node["ortho_window"][1].as_int();
		ortho_top_ = node["ortho_window"][2].as_int();
		ortho_bottom_ = node["ortho_window"][3].as_int();
	}

	// If lookat key is specified it overrides the normal compute.
	if(node.has_key("lookat")) {
		const variant& la = node["lookat"];
		ASSERT_LOG(la.has_key("position") && la.has_key("target") && la.has_key("up"),
			"lookat must be a map having 'position', 'target' and 'up' as tuples");
		glm::vec3 position(la["position"][0].as_decimal().as_float(),
			la["position"][1].as_decimal().as_float(),
			la["position"][2].as_decimal().as_float());
		glm::vec3 target(la["target"][0].as_decimal().as_float(),
			la["target"][1].as_decimal().as_float(),
			la["target"][2].as_decimal().as_float());
		glm::vec3 up(la["up"][0].as_decimal().as_float(),
			la["up"][1].as_decimal().as_float(),
			la["up"][2].as_decimal().as_float());
		look_at(position, target, up);
		mode_ = MODE_MANUAL;
	} else {
		compute_view();
	}
	compute_projection();
}

camera_callable::camera_callable(CAMERA_TYPE type, int left, int right, int top, int bottom)
	: fov_(45.0f), horizontal_angle_(float(M_PI)), vertical_angle_(0.0f),
	speed_(0.1f), mouse_speed_(0.005f), near_clip_(0.1f), far_clip_(300.0f),
	mode_(MODE_AUTO), type_(type), ortho_left_(left), ortho_bottom_(bottom),
	ortho_top_(top), ortho_right_(right), clip_planes_set_(false),
	view_(1.0f)
{
	up_ = glm::vec3(0.0f, 1.0f, 0.0f);
	position_ = glm::vec3(0.0f, 0.0f, 10.0f);
	aspect_ = float(right - left)/float(top - bottom);

	compute_projection();
}

camera_callable::camera_callable(CAMERA_TYPE type, float fov, float aspect, float near_clip, float far_clip)
	: fov_(fov), horizontal_angle_(float(M_PI)), vertical_angle_(0.0f),
	speed_(0.1f), mouse_speed_(0.005f), near_clip_(near_clip), far_clip_(far_clip),
	aspect_(aspect), mode_(MODE_AUTO), type_(type), ortho_left_(0), ortho_bottom_(0),
	ortho_top_(preferences::actual_screen_height()), ortho_right_(preferences::actual_screen_width()),
	clip_planes_set_(true)
{
	up_ = glm::vec3(0.0f, 1.0f, 0.0f);
	position_ = glm::vec3(0.0f, 0.0f, 10.0f);

	compute_view();
	compute_projection();
}

camera_callable::~camera_callable()
{
}

variant camera_callable::write()
{
	variant_builder res;
	if(fov_ != 45.0) {
		res.add("fov", fov_);
	}
	if(horizontal_angle_ != float(M_PI)) {
		res.add("horizontal_angle", horizontal_angle_);
	}
	if(vertical_angle_ != 0.0f) {
		res.add("vertical_angle", vertical_angle_);
	}
	if(speed_ != 0.1f) {
		res.add("speed", speed_);
	}
	if(mouse_speed_ != 0.005f) {
		res.add("mouse_speed", mouse_speed_);
	}
	res.add("position", position_.x);
	res.add("position", position_.y);
	res.add("position", position_.z);
	return res.build();
}

void camera_callable::compute_view()
{
	mode_ = MODE_AUTO;
	direction_ = glm::vec3(
		cos(vertical_angle_) * sin(horizontal_angle_),
		sin(vertical_angle_),
		cos(vertical_angle_) * cos(horizontal_angle_)
	);
	right_ = glm::vec3(
		sin(horizontal_angle_ - float(M_PI)/2.0f),
		0,
		cos(horizontal_angle_ - float(M_PI)/2.0f)
	);

	// Up vector
	up_ = glm::cross(right_, direction_);
	target_ = position_ + direction_;

	view_ = glm::lookAt(position_, target_, up_);
	frustum_.update_matrices(projection_, view_);
}

void camera_callable::set_type(CAMERA_TYPE type)
{
	type_ = type;
	compute_projection();
}

void camera_callable::set_ortho_window(int left, int right, int top, int bottom)
{
	ortho_left_ = left;
	ortho_right_ = right;
	ortho_top_ = top;
	ortho_bottom_ = bottom;

	if(type_ == ORTHOGONAL_CAMERA) {
		compute_projection();
	}
}

BEGIN_DEFINE_CALLABLE_NOBASE(camera_callable)
BEGIN_DEFINE_FN(screen_to_world, "(int,int,int=0,int=0) -> [decimal,decimal,decimal]")
	int wx = preferences::actual_screen_width();
	int wy = preferences::actual_screen_height();
	if(NUM_FN_ARGS > 2) {
		wx = FN_ARG(2).as_int();
		if(NUM_FN_ARGS > 3) {
			wy = FN_ARG(3).as_int();
		}
	}
	return vec3_to_variant(obj.screen_to_world(FN_ARG(0).as_int(), FN_ARG(1).as_int(),wx,wy));
END_DEFINE_FN

DEFINE_FIELD(position, "[decimal,decimal,decimal]")
	std::vector<variant> v;
	v.push_back(variant(obj.position().x));
	v.push_back(variant(obj.position().y));
	v.push_back(variant(obj.position().z));
	return variant(&v);
DEFINE_SET_FIELD
	ASSERT_LOG(value.is_list() && value.num_elements() == 3, "position must be a list of 3 elements");
	obj.set_position(glm::vec3(float(value[0].as_decimal().as_float()),
		float(value[1].as_decimal().as_float()),
		float(value[2].as_decimal().as_float())));
	if(obj.mode_ == MODE_MANUAL) {
		obj.view_ = glm::lookAt(obj.position_, obj.target_, obj.up_);
		obj.frustum_.update_matrices(obj.projection_, obj.view_);
	} else {
		obj.compute_view();
	}

DEFINE_FIELD(speed, "decimal")
	return variant(obj.speed());
DEFINE_SET_FIELD
	obj.set_speed(value.as_decimal().as_float());

DEFINE_FIELD(right, "[decimal,decimal,decimal]")
	std::vector<variant> v;
	v.push_back(variant(obj.right().x));
	v.push_back(variant(obj.right().y));
	v.push_back(variant(obj.right().z));
	return variant(&v);

DEFINE_FIELD(direction, "[decimal,decimal,decimal]")
	std::vector<variant> v;
	v.push_back(variant(obj.direction().x));
	v.push_back(variant(obj.direction().y));
	v.push_back(variant(obj.direction().z));
	return variant(&v);

DEFINE_FIELD(horizontal_angle, "decimal")
	return variant(obj.hangle());
DEFINE_SET_FIELD
	obj.set_hangle(value.as_decimal().as_float());
	obj.compute_view();

DEFINE_FIELD(hangle, "decimal")
	return variant(obj.hangle());
DEFINE_SET_FIELD
	obj.set_hangle(value.as_decimal().as_float());
	obj.compute_view();

DEFINE_FIELD(vertical_angle, "decimal")
	return variant(obj.vangle());
DEFINE_SET_FIELD
	obj.set_vangle(value.as_decimal().as_float());
	obj.compute_view();

DEFINE_FIELD(vangle, "decimal")
	return variant(obj.vangle());
DEFINE_SET_FIELD
	obj.set_vangle(value.as_decimal().as_float());
	obj.compute_view();

DEFINE_FIELD(mouse_speed, "decimal")
	return variant(obj.mousespeed());
DEFINE_SET_FIELD
	obj.set_mousespeed(value.as_decimal().as_float());

DEFINE_FIELD(target, "[decimal,decimal,decimal]")
	std::vector<variant> v;
	v.push_back(variant(obj.target_.x));
	v.push_back(variant(obj.target_.y));
	v.push_back(variant(obj.target_.z));
	return variant(&v);

DEFINE_FIELD(up, "[decimal,decimal,decimal]")
	std::vector<variant> v;
	v.push_back(variant(obj.up_.x));
	v.push_back(variant(obj.up_.y));
	v.push_back(variant(obj.up_.z));
	return variant(&v);

DEFINE_FIELD(fov, "decimal")
	return variant(obj.fov());
DEFINE_SET_FIELD
	obj.set_fov(value.as_decimal().as_float());

DEFINE_FIELD(aspect, "decimal")
	return variant(obj.aspect());
DEFINE_SET_FIELD
	obj.set_aspect(value.as_decimal().as_float());

DEFINE_FIELD(clip_planes, "[decimal,decimal]")
	std::vector<variant> v;
	v.push_back(variant(obj.near_clip_));
	v.push_back(variant(obj.far_clip_));
	return variant(&v);
DEFINE_SET_FIELD
	ASSERT_LOG(value.is_list() && value.num_elements() == 2, "clip_planes takes a tuple of two decimals");
	obj.set_clip_planes(value[0].as_decimal().as_float(), value[1].as_decimal().as_float());

DEFINE_FIELD(type, "string")
	if(obj.type() == ORTHOGONAL_CAMERA) {
		return variant("orthogonal");
	}
	return variant("perspective");
DEFINE_SET_FIELD
	if(value.as_string() == "orthogonal") {
		obj.set_type(ORTHOGONAL_CAMERA);
	} else {
		obj.set_type(PERSPECTIVE_CAMERA);
	}

DEFINE_FIELD(ortho_window, "[int,int,int,int]")
	std::vector<variant> v;
	v.push_back(variant(obj.ortho_left()));
	v.push_back(variant(obj.ortho_right()));
	v.push_back(variant(obj.ortho_top()));
	v.push_back(variant(obj.ortho_bottom()));
	return variant(&v);
DEFINE_SET_FIELD
	ASSERT_LOG(value.is_list() && value.num_elements() == 4, "Attribute 'ortho_window' must be a 4 element list. left,right,top,bottom");
	obj.set_ortho_window(value[0].as_int(), value[1].as_int(), value[2].as_int(), obj.ortho_bottom_ = value[3].as_int());

END_DEFINE_CALLABLE(camera_callable)

void camera_callable::look_at(glm::vec3 position, glm::vec3 target, glm::vec3 up)
{
	mode_ = MODE_MANUAL;
	position_ = position;
	target_ = target;
	up_ = up;
	direction_ = target_ - position_;
	view_ = glm::lookAt(position_, target_, up_);
	frustum_.update_matrices(projection_, view_);
}

void camera_callable::set_fov(float fov)
{
	fov_ = fov;
	compute_projection();
}

void camera_callable::set_clip_planes(float z_near, float z_far)
{
	near_clip_ = z_near;
	far_clip_ = z_far;
	clip_planes_set_ = true;
	compute_projection();
}

void camera_callable::set_aspect(float aspect)
{
	aspect_ = aspect;
	compute_projection();
}

void camera_callable::compute_projection()
{
	if(type_ == ORTHOGONAL_CAMERA) {
		if(clip_planes_set_) {
			projection_ = glm::frustum(float(ortho_left_), float(ortho_right_), float(ortho_bottom_), float(ortho_top_), near_clip(), far_clip());
		} else {
			projection_ = glm::ortho(float(ortho_left_), float(ortho_right_), float(ortho_bottom_), float(ortho_top_));
		}
	} else {
		projection_ = glm::perspective(fov(), aspect_, near_clip(), far_clip());
	}
	frustum_.update_matrices(projection_, view_);
}

// Convert from a screen position (assume +ve x to right, +ve y down) to world space.
// Assumes the depth buffer was enabled.
glm::vec3 camera_callable::screen_to_world(int x, int y, int wx, int wy) const
{
	glm::vec4 view_port(0, 0, wx, wy);

	GLfloat depth;
	glReadPixels(x, wy - y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
	glm::vec3 screen(x, wy - y, depth);

	return glm::unProject(screen, view_, projection_, view_port);
}

namespace
{
	float dti(float val)
	{
		return abs(val - bmround(val));
	}
}

glm::ivec3 camera_callable::get_facing(const glm::vec3& coords) const
{
	if(dti(coords.x) < dti(coords.y)) {
		if(dti(coords.x) < dti(coords.z)) {
			if(direction_.x > 0) {
				return glm::ivec3(-1,0,0);
			} else {
				return glm::ivec3(1,0,0);
			}
		} else {
			if(direction_.z > 0) {
				return glm::ivec3(0,0,-1);
			} else {
				return glm::ivec3(0,0,1);
			}
		}
	} else {
		if(dti(coords.y) < dti(coords.z)) {
			if(direction_.y > 0) {
				return glm::ivec3(0,-1,0);
			} else {
				return glm::ivec3(0,1,0);
			}
		} else {
			if(direction_.z > 0) {
				return glm::ivec3(0,0,-1);
			} else {
				return glm::ivec3(0,0,1);
			}
		}
	}

}