#include "dynet/nodes.h"
#include "dynet/dynet.h"
#include "dynet/training.h"
#include "dynet/timing.h"
#include "dynet/rnn.h"
#include "dynet/gru.h"
#include "dynet/lstm.h"
#include "dynet/fast-lstm.h"
#include "dynet/expr.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <type_traits>
#include <sys/types.h>
#include <sys/stat.h>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/program_options.hpp>

#include "s2s/dynet/dict.h"
#include "s2s/corpus/options.hpp"
#include "s2s/corpus/corpora.hpp"
#include "s2s/corpus/dicts.hpp"
#include "s2s/corpus/batch.hpp"
#include "s2s/nn/encdec.hpp"

#ifndef INCLUDE_GUARD_LSTM_HPP
#define INCLUDE_GUARD_LSTM_HPP

namespace s2s {

class encoder_decoder_lstm : public encoder_decoder {

public:

    dynet::Expression i_h_lookup_cols;

    explicit encoder_decoder_lstm(dynet::ParameterCollection& model, const s2s_options& opts, dicts& d) : encoder_decoder(model,opts,d) {

        unsigned int dec_input_size = 0;
        unsigned int enc_input_size = rev_enc_builder.input_dim;
        flag_drop_out = true;
        // decoder input
        dec_input_size = 3;
        if(opts.flag_bit_feature == true){
            dec_input_size += 3;
        }
        if(opts.flag_par_embed == true){
            dec_input_size += enc_input_size * 2;
        }else{
            dec_input_size += enc_input_size;
        }
        assert(opts.dec_feature_vocab_size.size() == opts.dec_feature_vec_size.size());
        unsigned int cell_ratio = 2; // In GRU, cell_ration = 1;

        p_dec_init_w.resize(opts.num_layers * cell_ratio);
        for(unsigned int i = 0; i < opts.num_layers * cell_ratio; i++){
            p_dec_init_w[i] = model.add_parameters({opts.rnn_size, opts.rnn_size});
        }
        p_dec_init_bias.resize(opts.num_layers * cell_ratio);
        for(unsigned int i = 0; i < opts.num_layers * cell_ratio; i++){
            p_dec_init_bias[i] = model.add_parameters({opts.rnn_size});
        }

        if(opts.additional_output_layer == true){
            p_add_W = model.add_parameters({opts.rnn_size, opts.rnn_size});
            p_add_bias = model.add_parameters({opts.rnn_size});
            p_out_R = model.add_parameters({d.dict_set_trg.d_word.size(), opts.rnn_size});
            p_out_bias = model.add_parameters({d.dict_set_trg.d_word.size()});
        }else{
            p_out_R = model.add_parameters({d.dict_set_trg.d_word.size(), opts.rnn_size});
            p_out_bias = model.add_parameters({d.dict_set_trg.d_word.size()});
        }

        dec_builder = dynet::VanillaLSTMBuilder(
            opts.num_layers,
            dec_input_size,
            opts.rnn_size,
            model
        );
    }

    encoder_decoder_lstm(const encoder_decoder_lstm&) = delete;
    encoder_decoder_lstm& operator=(const encoder_decoder_lstm&) = delete;
    ~encoder_decoder_lstm() = default;

    void encoder(const batch &batch_local, dynet::ComputationGraph& cg) {
        // initialize
        init(batch_local, cg);
        // embedding
        embedding(batch_local, cg);
        i_h_lookup_cols = concatenate_cols(h_lookup);
        // backward encoder
        rev_enc_builder.new_graph(cg);
        rev_enc_builder.start_new_sequence();
        if(flag_drop_out == true && opts.dropout_rate_lstm_word > 0.f){
            rev_enc_builder.set_dropout_masks(batch_size);
        }
        for (unsigned int ind = 0; ind < slen; ++ind) {
            unsigned int t_i = (slen - 1) - ind;
            rev_enc_builder.add_input(h_lookup[t_i]);
        };
        std::vector<dynet::Expression> vec_enc_final_state = rev_enc_builder.final_s();
        if(opts.flag_bi_enc == true){
            // forward encoder
            fwd_enc_builder.new_graph(cg);
            fwd_enc_builder.start_new_sequence();
            if(flag_drop_out == true && opts.dropout_rate_lstm_word > 0.f){
                fwd_enc_builder.set_dropout_masks(batch_size);
            }
            for (unsigned int t_i = 0; t_i < slen; ++t_i) {
                fwd_enc_builder.add_input(h_lookup[t_i]);
            };
            std::vector<dynet::Expression> vec_fwd_enc_final_state = fwd_enc_builder.final_s();
            for (unsigned int lay_i = 0; lay_i < vec_fwd_enc_final_state.size(); lay_i++){
                vec_enc_final_state[lay_i] = vec_enc_final_state[lay_i] + vec_fwd_enc_final_state.at(lay_i);
            }
        }
        // Initialize decoder
        dec_builder.new_graph(cg);
        if(opts.additional_connect_layer){
            std::vector<dynet::Expression> vec_dec_init_state;
            for (unsigned int lay_i = 0; lay_i < vec_enc_final_state.size(); lay_i++){
                dynet::Expression i_dec_init_w = parameter(cg, p_dec_init_w[lay_i]);
                dynet::Expression i_dec_init_bias = parameter(cg, p_dec_init_bias[lay_i]);
                vec_dec_init_state.push_back(tanh(i_dec_init_w * vec_enc_final_state[lay_i] + i_dec_init_bias));
            }
            dec_builder.start_new_sequence(vec_dec_init_state);
        }else{
            dec_builder.start_new_sequence(vec_enc_final_state);
        }
        if(flag_drop_out == true && opts.dropout_rate_lstm_word > 0.f){
            dec_builder.set_dropout_masks(batch_size);
        }
    }

    dynet::Expression decoder_output(dynet::ComputationGraph& cg, const unsigned int position, const std::vector<unsigned int>& head, const std::vector<dynet::real>& bit_features){
        return decoder_output(cg, position, head, bit_features, dec_builder.state());
    }

    dynet::Expression decoder_output(dynet::ComputationGraph& cg, const unsigned int position, const std::vector<unsigned int> &head, const std::vector<dynet::real>& bit_features, const dynet::RNNPointer pointer_prev){
        dynet::Expression i_dec_input = h_lookup.at(position);
        if(opts.flag_bit_feature){
            i_dec_input = concatenate(std::vector<dynet::Expression>({i_dec_input, dynet::input(cg, dynet::Dim({6}, batch_size), bit_features)}));
        }else{
            i_dec_input = concatenate(std::vector<dynet::Expression>({i_dec_input, dynet::input(cg, dynet::Dim({3}, batch_size), bit_features)}));
        }
        if(opts.flag_par_embed){
            i_dec_input = concatenate(std::vector<dynet::Expression>({i_dec_input, pick(transpose(i_h_lookup_cols), head)}));
        }
        dynet::Expression i_h_dec = dec_builder.add_input(pointer_prev, i_dec_input);
        dynet::Expression i_final;
        if(opts.additional_output_layer){
            dynet::Expression i_add_W = parameter(cg, p_add_W);
            dynet::Expression i_add_bias = parameter(cg, p_add_bias);
            i_final = tanh(i_add_W * i_h_dec + i_add_bias);
        }else{
            i_final = i_h_dec;
        }
        dynet::Expression i_out_R = parameter(cg, p_out_R);
        dynet::Expression i_out_bias = parameter(cg, p_out_bias);
        dynet::Expression i_out_pred_t = i_out_R * i_final + i_out_bias;
        return i_out_pred_t;
    }

    std::vector<dynet::real > bit_features(const unsigned int t, const std::vector<std::vector<unsigned int> >& output, const std::vector<unsigned int>& head){
        // t should be always larger than 0.
        assert(t > 0);
        // convert previous label to the bit features.
        std::vector<dynet::real> features;
        const unsigned int start_id = d.dict_set_trg.start_id_word;
        const unsigned int kep_id = d.dict_set_trg.keep_id_word;
        const unsigned int del_id = d.dict_set_trg.delete_id_word;
        for(unsigned int batch_id = 0; batch_id < batch_size; batch_id++){
            // start
            if(t > 0 && output[t-1][batch_id] == start_id){
                features.push_back(opts.bit_size_flt);
            }else{
                features.push_back(0.0f);
            }
            // keep
            if(t > 0 && output[t-1][batch_id] == kep_id){
                features.push_back(opts.bit_size_flt);
            }else{
                features.push_back(0.0f);
            }
            // delete
            if(t > 0 && output[t-1][batch_id] == del_id){
                features.push_back(opts.bit_size_flt);
            }else{
                features.push_back(0.0f);
            }
            if(opts.flag_bit_feature == true){
                // The parent word was kept.
                if(t == 0 || (head[batch_id] < t && output[head[batch_id]][batch_id] == kep_id)){
                    features.push_back(opts.bit_size_flt);
                }else{
                    features.push_back(0.0f);
                }
                // The parent word was deleted.
                if(t != 0 || (head[batch_id] < t && output[head[batch_id]][batch_id] == del_id)){
                    features.push_back(opts.bit_size_flt);
                }else{
                    features.push_back(0.0f);
                }
                // The parent word comes later.
                if(t != 0 || head[batch_id] > t){
                    features.push_back(opts.bit_size_flt);
                }else{
                    features.push_back(0.0f);
                }
            }
        }
        return features;
    }

    void disable_dropout(){
        fwd_char_enc_builder.disable_dropout();
        rev_char_enc_builder.disable_dropout();
        fwd_enc_builder.disable_dropout();
        rev_enc_builder.disable_dropout();
        dec_builder.disable_dropout();
        flag_drop_out = false;
    }

    void enable_dropout(){
        fwd_char_enc_builder.set_dropout(opts.dropout_rate_lstm_char, 0.f);
        rev_char_enc_builder.set_dropout(opts.dropout_rate_lstm_char, 0.f);
        fwd_enc_builder.set_dropout(opts.dropout_rate_lstm_word, 0.f);
        rev_enc_builder.set_dropout(opts.dropout_rate_lstm_word, 0.f);
        dec_builder.set_dropout(opts.dropout_rate_lstm_word, 0.f);
        flag_drop_out = true;
    }

private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) {
        ar & p_word_enc;
        ar & p_char_enc;
        ar & p_feat_enc;
        ar & p_layer_w;
        ar & p_add_W;
        ar & p_add_bias;
        ar & p_dec_init_bias;
        ar & p_dec_init_w;
        ar & p_out_R;
        ar & p_out_bias;
        ar & dec_builder;
        ar & rev_char_enc_builder;
        ar & fwd_char_enc_builder;
        ar & rev_enc_builder;
        ar & fwd_enc_builder;
    }

};

}

#endif


#ifndef BOOST_GEOMETRY_PROJECTIONS_LSAT_HPP
#define BOOST_GEOMETRY_PROJECTIONS_LSAT_HPP

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

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

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

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

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

#include <boost/geometry/extensions/gis/projections/impl/base_static.hpp>
#include <boost/geometry/extensions/gis/projections/impl/base_dynamic.hpp>
#include <boost/geometry/extensions/gis/projections/impl/projects.hpp>
#include <boost/geometry/extensions/gis/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry { namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace lsat{
            static const double TOL = 1e-7;
            static const double PI_HALFPI = 4.71238898038468985766;
            static const double TWOPI_HALFPI = 7.85398163397448309610;

            struct par_lsat
            {
                double a2, a4, b, c1, c3;
                double q, t, u, w, p22, sa, ca, xj, rlm, rlm2;
            };
            /* based upon Snyder and Linck, USGS-NMD */
            template <typename Parameters>
                inline void
            seraz0(double lam, double mult, Parameters& par, par_lsat& proj_parm) {
                double sdsq, h, s, fc, sd, sq, d__1;

                lam *= DEG_TO_RAD;
                sd = sin(lam);
                sdsq = sd * sd;
                s = proj_parm.p22 * proj_parm.sa * cos(lam) * sqrt((1. + proj_parm.t * sdsq) / ((
                    1. + proj_parm.w * sdsq) * (1. + proj_parm.q * sdsq)));
                d__1 = 1. + proj_parm.q * sdsq;
                h = sqrt((1. + proj_parm.q * sdsq) / (1. + proj_parm.w * sdsq)) * ((1. +
                    proj_parm.w * sdsq) / (d__1 * d__1) - proj_parm.p22 * proj_parm.ca);
                sq = sqrt(proj_parm.xj * proj_parm.xj + s * s);
                proj_parm.b += fc = mult * (h * proj_parm.xj - s * s) / sq;
                proj_parm.a2 += fc * cos(lam + lam);
                proj_parm.a4 += fc * cos(lam * 4.);
                fc = mult * s * (h + proj_parm.xj) / sq;
                proj_parm.c1 += fc * cos(lam);
                proj_parm.c3 += fc * cos(lam * 3.);
            }

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

                 typedef double geographic_type;
                 typedef double cartesian_type;

                par_lsat m_proj_parm;

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

                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    int l, nn;
                    double lamt, xlam, sdsq, c, d, s, lamdp, phidp, lampp, tanph,
                        lamtp, cl, sd, sp, fac, sav, tanphi;

                    if (lp_lat > HALFPI)
                        lp_lat = HALFPI;
                    else if (lp_lat < -HALFPI)
                        lp_lat = -HALFPI;
                    lampp = lp_lat >= 0. ? HALFPI : PI_HALFPI;
                    tanphi = tan(lp_lat);
                    for (nn = 0;;) {
                        sav = lampp;
                        lamtp = lp_lon + this->m_proj_parm.p22 * lampp;
                        cl = cos(lamtp);
                        if (fabs(cl) < TOL)
                            lamtp -= TOL;
                        fac = lampp - sin(lampp) * (cl < 0. ? -HALFPI : HALFPI);
                        for (l = 50; l; --l) {
                            lamt = lp_lon + this->m_proj_parm.p22 * sav;
                            if (fabs(c = cos(lamt)) < TOL)
                                lamt -= TOL;
                            xlam = (this->m_par.one_es * tanphi * this->m_proj_parm.sa + sin(lamt) * this->m_proj_parm.ca) / c;
                            lamdp = atan(xlam) + fac;
                            if (fabs(fabs(sav) - fabs(lamdp)) < TOL)
                                break;
                            sav = lamdp;
                        }
                        if (!l || ++nn >= 3 || (lamdp > this->m_proj_parm.rlm && lamdp < this->m_proj_parm.rlm2))
                            break;
                        if (lamdp <= this->m_proj_parm.rlm)
                            lampp = TWOPI_HALFPI;
                        else if (lamdp >= this->m_proj_parm.rlm2)
                            lampp = HALFPI;
                    }
                    if (l) {
                        sp = sin(lp_lat);
                        phidp = aasin((this->m_par.one_es * this->m_proj_parm.ca * sp - this->m_proj_parm.sa * cos(lp_lat) *
                            sin(lamt)) / sqrt(1. - this->m_par.es * sp * sp));
                        tanph = log(tan(FORTPI + .5 * phidp));
                        sd = sin(lamdp);
                        sdsq = sd * sd;
                        s = this->m_proj_parm.p22 * this->m_proj_parm.sa * cos(lamdp) * sqrt((1. + this->m_proj_parm.t * sdsq)
                             / ((1. + this->m_proj_parm.w * sdsq) * (1. + this->m_proj_parm.q * sdsq)));
                        d = sqrt(this->m_proj_parm.xj * this->m_proj_parm.xj + s * s);
                        xy_x = this->m_proj_parm.b * lamdp + this->m_proj_parm.a2 * sin(2. * lamdp) + this->m_proj_parm.a4 *
                            sin(lamdp * 4.) - tanph * s / d;
                        xy_y = this->m_proj_parm.c1 * sd + this->m_proj_parm.c3 * sin(lamdp * 3.) + tanph * this->m_proj_parm.xj / d;
                    } else
                        xy_x = xy_y = HUGE_VAL;
                }

                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    int nn;
                    double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp;

                    lamdp = xy_x / this->m_proj_parm.b;
                    nn = 50;
                    do {
                        sav = lamdp;
                        sd = sin(lamdp);
                        sdsq = sd * sd;
                        s = this->m_proj_parm.p22 * this->m_proj_parm.sa * cos(lamdp) * sqrt((1. + this->m_proj_parm.t * sdsq)
                             / ((1. + this->m_proj_parm.w * sdsq) * (1. + this->m_proj_parm.q * sdsq)));
                        lamdp = xy_x + xy_y * s / this->m_proj_parm.xj - this->m_proj_parm.a2 * sin(
                            2. * lamdp) - this->m_proj_parm.a4 * sin(lamdp * 4.) - s / this->m_proj_parm.xj * (
                            this->m_proj_parm.c1 * sin(lamdp) + this->m_proj_parm.c3 * sin(lamdp * 3.));
                        lamdp /= this->m_proj_parm.b;
                    } while (fabs(lamdp - sav) >= TOL && --nn);
                    sl = sin(lamdp);
                    fac = exp(sqrt(1. + s * s / this->m_proj_parm.xj / this->m_proj_parm.xj) * (xy_y -
                        this->m_proj_parm.c1 * sl - this->m_proj_parm.c3 * sin(lamdp * 3.)));
                    phidp = 2. * (atan(fac) - FORTPI);
                    dd = sl * sl;
                    if (fabs(cos(lamdp)) < TOL)
                        lamdp -= TOL;
                    spp = sin(phidp);
                    sppsq = spp * spp;
                    lamt = atan(((1. - sppsq * this->m_par.rone_es) * tan(lamdp) *
                        this->m_proj_parm.ca - spp * this->m_proj_parm.sa * sqrt((1. + this->m_proj_parm.q * dd) * (
                        1. - sppsq) - sppsq * this->m_proj_parm.u) / cos(lamdp)) / (1. - sppsq
                        * (1. + this->m_proj_parm.u)));
                    sl = lamt >= 0. ? 1. : -1.;
                    scl = cos(lamdp) >= 0. ? 1. : -1;
                    lamt -= HALFPI * (1. - scl) * sl;
                    lp_lon = lamt - this->m_proj_parm.p22 * lamdp;
                    if (fabs(this->m_proj_parm.sa) < TOL)
                        lp_lat = aasin(spp / sqrt(this->m_par.one_es * this->m_par.one_es + this->m_par.es * sppsq));
                    else
                        lp_lat = atan((tan(lamdp) * cos(lamt) - this->m_proj_parm.ca * sin(lamt)) /
                            (this->m_par.one_es * this->m_proj_parm.sa));
                }
            };

            // Space oblique for LANDSAT
            template <typename Parameters>
            void setup_lsat(Parameters& par, par_lsat& proj_parm)
            {
                int land, path;
                double lam, alf, esc, ess;
                land = pj_param(par.params, "ilsat").i;
                if (land <= 0 || land > 5) throw proj_exception(-28);
                path = pj_param(par.params, "ipath").i;
                if (path <= 0 || path > (land <= 3 ? 251 : 233)) throw proj_exception(-29);
                if (land <= 3) {
                    par.lam0 = DEG_TO_RAD * 128.87 - TWOPI / 251. * path;
                    proj_parm.p22 = 103.2669323;
                    alf = DEG_TO_RAD * 99.092;
                } else {
                    par.lam0 = DEG_TO_RAD * 129.3 - TWOPI / 233. * path;
                    proj_parm.p22 = 98.8841202;
                    alf = DEG_TO_RAD * 98.2;
                }
                proj_parm.p22 /= 1440.;
                proj_parm.sa = sin(alf);
                proj_parm.ca = cos(alf);
                if (fabs(proj_parm.ca) < 1e-9)
                    proj_parm.ca = 1e-9;
                esc = par.es * proj_parm.ca * proj_parm.ca;
                ess = par.es * proj_parm.sa * proj_parm.sa;
                proj_parm.w = (1. - esc) * par.rone_es;
                proj_parm.w = proj_parm.w * proj_parm.w - 1.;
                proj_parm.q = ess * par.rone_es;
                proj_parm.t = ess * (2. - par.es) * par.rone_es * par.rone_es;
                proj_parm.u = esc * par.rone_es;
                proj_parm.xj = par.one_es * par.one_es * par.one_es;
                proj_parm.rlm = PI * (1. / 248. + .5161290322580645);
                proj_parm.rlm2 = proj_parm.rlm + TWOPI;
                proj_parm.a2 = proj_parm.a4 = proj_parm.b = proj_parm.c1 = proj_parm.c3 = 0.;
                seraz0(0., 1., par, proj_parm);
                for (lam = 9.;
             lam <= 81.0001;
             lam += 18.)
                    seraz0(lam, 4., par, proj_parm);
                for (lam = 18;
             lam <= 72.0001;
             lam += 18.)
                    seraz0(lam, 2., par, proj_parm);
                seraz0(90., 1., par, proj_parm);
                proj_parm.a2 /= 30.;
                proj_parm.a4 /= 60.;
                proj_parm.b /= 30.;
                proj_parm.c1 /= 15.;
                proj_parm.c3 /= 45.;
                // par.inv = e_inverse;
                // par.fwd = e_forward;
            }

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

    /*!
        \brief Space oblique for LANDSAT projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
         - lsat= path=
        \par Example
        \image html ex_lsat.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct lsat_ellipsoid : public detail::lsat::base_lsat_ellipsoid<Geographic, Cartesian, Parameters>
    {
        inline lsat_ellipsoid(const Parameters& par) : detail::lsat::base_lsat_ellipsoid<Geographic, Cartesian, Parameters>(par)
        {
            detail::lsat::setup_lsat(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

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

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

    } // namespace detail
    #endif // doxygen

}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_LSAT_HPP


/*
 * silo_convert.cpp
 *
 *  Created on: Oct 10, 2019
 *      Author: dmarce1
 */

#include <boost/program_options.hpp>
#include <stdio.h>
#include <iostream>
#include <string>
#include <silo.h>
#include <vector>
#include <map>

#define SILO_DRIVER DB_HDF5

auto split_silo_id(const std::string str) {
	std::pair<std::string, std::string> split;
	bool before = true;
	for (int i = 0; i < str.size(); i++) {
		if (str[i] == ':') {
			before = false;
		} else if (before) {
			split.first.push_back(str[i]);
		} else {
			split.second.push_back(str[i]);
		}
	}
	return split;
}

struct options {
	std::string input;

	int read_options(int argc, char *argv[]) {
		namespace po = boost::program_options;

		po::options_description command_opts("options");

		command_opts.add_options() //
		("input", po::value < std::string > (&input)->default_value(""), "input filename")           //
				;
		boost::program_options::variables_map vm;
		po::store(po::parse_command_line(argc, argv, command_opts), vm);
		po::notify(vm);

		FILE *fp = fopen(input.c_str(), "rb");
		if (input == "") {
			std::cout << command_opts << "\n";
			return -1;
		} else if (fp == NULL) {
			printf("Unable to open %s\n", input.c_str());
			return -1;
		} else {
			fclose(fp);
		}
		po::notify(vm);
		return 0;
	}
};

std::string strip_nonnumeric(std::string &&s) {
	s.erase(std::remove_if(s.begin(), s.end(), [](char c) {
		return c < '0' || c > '9';
	}), s.end());
	return std::move(s);
}

std::string mesh_to_varname(std::string mesh_name, std::string var_name) {
	std::string token = "quadmesh";
	size_t pos = mesh_name.find(token);
	mesh_name.erase(pos, token.length());
	return mesh_name + var_name;
}

std::string mesh_to_dirname(std::string mesh_name) {
	std::string token = "quadmesh";
	size_t pos = mesh_name.find(token);
	mesh_name.erase(pos, token.length());
	return mesh_name;
}

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

	std::set<std::string> var_names;
	std::set<std::string> mesh_names;

	options opts;
	if (opts.read_options(argc, argv) != 0) {
		return 0;
	}

	printf("Opening %s\n", opts.input.c_str());
	printf("Reading table of contents\n");
	auto db = DBOpenReal(opts.input.c_str(), SILO_DRIVER, DB_READ);
	auto toc = DBGetToc(db);

	printf("Variable names:\n");
	for (int i = 0; i < toc->nmultivar; ++i) {
		auto name = std::string(toc->multivar_names[i]);
		var_names.insert(name);
		printf("	%s\n", name.c_str());
	}
	auto mesh = DBGetMultimesh(db, "quadmesh");
	for (int i = 0; i < mesh->nblocks; ++i) {
		auto name = std::string(mesh->meshnames[i]);
		mesh_names.insert(name);
	}

	DBFreeMultimesh(mesh);

	int counter = 0;
	long long int n_species, cycle;

	DBReadVar(db, "n_species", &n_species);

	std::vector<double> atomic_number(n_species);
	std::vector<double> atomic_mass(n_species);
	DBReadVar(db, "atomic_number", atomic_number.data());
	DBReadVar(db, "atomic_mass", atomic_mass.data());
	printf("cycle = %lli\n", cycle);
	printf("n_species = %lli\n", n_species);
	printf("atomic number | atomic mass \n");
	for (int s = 0; s < n_species; s++) {
		printf("%e | %e\n", atomic_number[s], atomic_mass[s]);
	}
	printf("Reading %li meshes\n", mesh_names.size());
	double sum1 = 0.0;
	double sum2 = 0.0;
	double t;
	double dxmin = 1.0e+99;

	double x1_0, y1_0, rho1_max;
	double vx1_0, vy1_0;
	double x2_0, y2_0, rho2_max;
	double vx2_0, vy2_0;
	rho1_max = 0.0;
	rho2_max = 0.0;
	for (const auto &mesh_name : mesh_names) {
		auto split_name = split_silo_id(mesh_name);
		const auto &filename = split_name.first;
		const auto &name = split_name.second;
		auto db = DBOpenReal(filename.c_str(), SILO_DRIVER, DB_READ);
		auto mesh = DBGetQuadmesh(db, name.c_str());
		printf("\r%s                              ", mesh_name.c_str());
		const auto dir = mesh_to_dirname(name);
		t = mesh->time;
		const auto dx = (((double*) mesh->coords[0])[1] - ((double*) mesh->coords[0])[0]);
		dxmin = std::min(dxmin, dx);
		const auto dV = dx * dx * dx;
		auto var_name = mesh_to_varname(name, "rho_1");
		auto rho1 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_2");
		auto rho2 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_3");
		auto rho3 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_4");
		auto rho4 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_5");
		auto rho5 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "sx");
		auto sx = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "sy");
		auto sy = DBGetQuadvar(db, var_name.c_str());
		const int NX = mesh->dims[0] - 1;
		for (int i = 0; i < NX; i++) {
			for (int j = 0; j < NX; j++) {
				for (int k = 0; k < NX; k++) {
					const auto iii = k * NX * NX + j * NX + i;
					const auto this_rho1 = ((double*) rho1->vals[0])[iii];
					const auto this_rho3 = ((double*) rho3->vals[0])[iii];
					if (this_rho1 > rho1_max) {
						const auto rho = ((double*) rho1->vals[0])[iii] + ((double*) rho2->vals[0])[iii] + ((double*) rho3->vals[0])[iii]
								+ ((double*) rho4->vals[0])[iii] + ((double*) rho5->vals[0])[iii];
						const auto vx = ((double*) sx->vals[0])[iii] / rho;
						const auto vy = ((double*) sy->vals[0])[iii] / rho;
						const auto x = (((double*) mesh->coords[0])[i] + ((double*) mesh->coords[0])[i + 1]) / 2.0;
						const auto y = (((double*) mesh->coords[1])[j] + ((double*) mesh->coords[1])[j + 1]) / 2.0;
						rho1_max = this_rho1;
						x1_0 = x;
						y1_0 = y;
						vx1_0 = vx;
						vy1_0 = vy;
					}
					if (this_rho3 > rho2_max) {
						const auto rho = ((double*) rho1->vals[0])[iii] + ((double*) rho2->vals[0])[iii] + ((double*) rho3->vals[0])[iii]
								+ ((double*) rho4->vals[0])[iii] + ((double*) rho5->vals[0])[iii];
						const auto vx = ((double*) sx->vals[0])[iii] / rho;
						const auto vy = ((double*) sy->vals[0])[iii] / rho;
						const auto x = (((double*) mesh->coords[0])[i] + ((double*) mesh->coords[0])[i + 1]) / 2.0;
						const auto y = (((double*) mesh->coords[1])[j] + ((double*) mesh->coords[1])[j + 1]) / 2.0;
						rho2_max = this_rho3;
						x2_0 = x;
						y2_0 = y;
						vx2_0 = vx;
						vy2_0 = vy;
					}
				}
			}
		}
		DBFreeQuadvar(rho1);
		DBFreeQuadvar(rho2);
		DBFreeQuadvar(rho3);
		DBFreeQuadvar(rho4);
		DBFreeQuadvar(rho5);
		DBFreeQuadvar(sx);
		DBFreeQuadvar(sy);
		DBFreeQuadmesh(mesh);
		DBClose(db);
		counter++;
	}

	double rmax = 2e9;
	int NBIN = rmax / dxmin;
	const auto dx = x1_0 - x2_0;
	const auto dy = y1_0 - y2_0;
	const auto dvx = vx1_0 - vx2_0;
	const auto dvy = vy1_0 - vy2_0;
	double omega = (dx * dvy - dy * dvx) / (dx * dx + dy * dy);
	printf("Omega = %e\n", omega);
	printf("NBIN = %i dxmin = %e rho1_max = %e\n", NBIN, dxmin, rho1_max);
	printf("x0 = %e y0 = %e\n", x1_0, y1_0);
	printf("vx0 = %e vy1_0 = %e\n", vx1_0, vy1_0);
	double dR = (rmax) / NBIN;
	std::vector<double> Ibin(NBIN, 0.0);
	std::vector<double> Lbin(NBIN, 0.0);
	std::vector<double> I0bin(NBIN, 0.0);
	std::vector<double> L0bin(NBIN, 0.0);
	std::vector<double> Mbin(NBIN, 0.0);
	std::vector<double> Vbin(NBIN, 0.0);

	for (const auto &mesh_name : mesh_names) {
		auto split_name = split_silo_id(mesh_name);
		const auto &filename = split_name.first;
		const auto &name = split_name.second;
		auto db = DBOpenReal(filename.c_str(), SILO_DRIVER, DB_READ);
		auto mesh = DBGetQuadmesh(db, name.c_str());
		printf("\r%s                              ", mesh_name.c_str());
		const auto dir = mesh_to_dirname(name);
		t = mesh->time;
		const auto dx = (((double*) mesh->coords[0])[1] - ((double*) mesh->coords[0])[0]);
		const auto dV = dx * dx * dx;
		auto var_name = mesh_to_varname(name, "rho_1");
		auto var = DBGetQuadvar(db, var_name.c_str());

		for (int i = 0; i < var->nels; i++) {
			const double rho1 = ((double*) (var->vals[0]))[i];
			if (rho1 < pow(10.0, 5.0)) {
				sum1 += dV * rho1;
			}
			if (rho1 < pow(10.0, 5.2)) {
				sum2 += dV * rho1;
			}

		}

		DBFreeQuadvar(var);

		var_name = mesh_to_varname(name, "rho_1");
		auto rho1 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_2");
		auto rho2 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_3");
		auto rho3 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_4");
		auto rho4 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "rho_5");
		auto rho5 = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "sx");
		auto sx = DBGetQuadvar(db, var_name.c_str());
		var_name = mesh_to_varname(name, "sy");
		auto sy = DBGetQuadvar(db, var_name.c_str());
		const int NX = mesh->dims[0] - 1;
		for (int i = 0; i < NX; i++) {
			for (int j = 0; j < NX; j++) {
				for (int k = 0; k < NX; k++) {
					const auto iii = k * NX * NX + j * NX + i;
					const auto rho = ((double*) rho1->vals[0])[iii] + ((double*) rho2->vals[0])[iii] + ((double*) rho3->vals[0])[iii]
							+ ((double*) rho4->vals[0])[iii] + ((double*) rho5->vals[0])[iii];
					const auto vx = ((double*) sx->vals[0])[iii] / rho - vx1_0;
					const auto vy = ((double*) sy->vals[0])[iii] / rho - vy1_0;
					const auto x = (((double*) mesh->coords[0])[i] + ((double*) mesh->coords[0])[i + 1]) / 2.0 - x1_0;
					const auto y = (((double*) mesh->coords[1])[j] + ((double*) mesh->coords[1])[j + 1]) / 2.0 - y1_0;
					const auto z = (((double*) mesh->coords[2])[k] + ((double*) mesh->coords[2])[k + 1]) / 2.0;
					const auto R = std::sqrt(x * x + y * y);
					const auto r = std::sqrt(x * x + y * y + z * z);
					const auto omega = (-y * vx + x * vy) / (R * R);
					int I = R / dR;
					if (I < NBIN && r < rmax) {
						const double dV = dx * dx * dx;
						Lbin[I] += rho * R * R * omega * dV;
						Ibin[I] += rho * R * R * dV;
						if (std::abs(z) < dxmin) {
							L0bin[I] += rho * R * R * omega * dV;
							I0bin[I] += rho * R * R * dV;
						}
						Mbin[I] += rho * dV;
						Vbin[I] += dV;
					}
				}
			}
		}
		DBFreeQuadvar(rho1);
		DBFreeQuadvar(rho2);
		DBFreeQuadvar(rho3);
		DBFreeQuadvar(rho4);
		DBFreeQuadvar(rho5);
		DBFreeQuadvar(sx);
		DBFreeQuadvar(sy);

		DBFreeQuadmesh(mesh);
		DBClose(db);
		counter++;
	}
	FILE *fp = fopen("omega.dat", "wt");
	double omega_max = 0.0;
	for (int i = 0; i < NBIN; i++) {
		double this_omega = Lbin[i] / Ibin[i] / omega;
		omega_max = std::max(this_omega, omega_max);
		fprintf(fp, "%e %e %e %e\n", (i + 0.5) * dR, this_omega, L0bin[i] / I0bin[i] / omega, Mbin[i] / Vbin[i] * Vbin[0] / Mbin[0]);
	}
	fclose(fp);
	fp = fopen( "omega_max.dat", "at");
	fprintf( fp, "%e\n", omega_max);
	fclose(fp);

	const auto Msol = 1.989e+33;
	fp = fopen("dredge.dat", "at");
	fprintf(fp, "%e %e %e\n", t, sum1 / Msol, sum2 / Msol);
	fclose(fp);
	printf("\rDone!                                                          \n");

	DBClose(db);

	return 0;
}


//   Copyright 2015 Patrick Putnam
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
#ifndef MUTATE_GENERATOR_HPP_
#define MUTATE_GENERATOR_HPP_

#include <boost/property_tree/ptree.hpp>

#include "clotho/cuda/data_spaces/event_space/device_event_space.hpp"
#include "clotho/cuda/mutation/simple_mutation_generator.hpp"
#include "clotho/utility/timer.hpp"

#include <curand.h>
#include <curand_kernel.h>

#include "clotho/cuda/curand_state_pool.hpp"

#include "clotho/cuda/distributions/poisson_distribution.hpp"

#include "clotho/cuda/mutation/generate_mutation_kernel_api.hpp"
#include "clotho/cuda/mutation/generate_mutation_kernel_impl.hpp"

#include "clotho/cuda/mutation/scatter_unordered_impl.hpp"

#include "clotho/cuda/device_state_object.hpp"
#include "clotho/mutation/mutation_rate_parameter.hpp"

template < class RealType, class IntType, class OrderTag >
class MutateGenerator : public clotho::utility::iStateObject {
public:
    typedef RealType  real_type;
    typedef device_event_space< IntType, OrderTag > space_type;

    typedef clotho::cuda::curand_state_pool             state_pool_type;

    typedef poisson_cdf< real_type, 32 >                poisson_type;

    //typedef scaled_mean_helper< real_type, OrderTag >                     scaled_mean_type;
    //typedef event_space_helper< OrderTag >  space_helper_type;

    MutateGenerator( boost::property_tree::ptree & config ) :
        m_mutation_rate( config )
    {
        parse_configuration( config );

        initialize();
    }

    void generate( space_type * space, unsigned int N ) {
        make_event_distribution_kernel<<< 1, 32 >>>( state_pool_type::getInstance()->get_device_space(), space, m_mutation_rate.m_mu, N );
    }

    template < class PopulationType >
    void scatter( PopulationType * pop, space_type * events, unsigned int N ) {
        const unsigned int MAX_BLOCKS = 40000;  // arbitrary limitation (think 65535 is max for any single grid dimension)
        unsigned int offset = 0;

        while( offset < N ) {
            unsigned int bcount = N - offset;
            bcount = (( bcount > MAX_BLOCKS ) ? MAX_BLOCKS : bcount);

            _scatter_mutation_single_thread<<< bcount, 1 >>>( pop->free_space, events, pop->sequences.get_device_space(), offset );
            offset += bcount;
        }

        _generate_mutation_kernel<<< 1, 32 >>>( state_pool_type::getInstance()->get_device_states(), pop->free_space, events, pop->alleles.get_device_space() );
    }

    void get_state( boost::property_tree::ptree & state ) {
    }

    virtual ~MutateGenerator() {
        cudaFree( dPoisCDF );
    }

protected:

    void initialize() {

        assert( cudaMalloc( (void **) &dPoisCDF, sizeof( poisson_type) ) == cudaSuccess );

        initialize_poisson( m_mutation_rate.m_mu );
    }

    void initialize_poisson( real_type mean ) {
        real_type smean = scaled_mean_type::get( mean );
        make_poisson_cdf_maxk32<<< 1, 32 >>>( dPoisCDF, smean );
    }

    void parse_configuration( boost::property_tree::ptree & config ) {
        state_pool_type::getInstance()->initialize(config);
    }

    poisson_type        * dPoisCDF;

    mutation_rate_parameter< real_type > m_mutation_rate;
};

#endif  // MUTATE_GENERATOR_HPP_


#include <bits/stdc++.h>
#include <boost/lexical_cast.hpp>
#define lli long long int
#define ull unsigned long long
#define MX 100000

using std::cin;
using std::cout;
using std::cerr;
using std::floor;
using std::ceil;
using std::vector;
using std::unordered_map;
using std::map;
using std::endl;
using std::stack;
using std::queue;
using std::max;
using std::min;
using std::deque;
using std::priority_queue;
using std::bitset;
using std::set;
using std::sort;
using std::hash;
using std::pair;
using std::make_pair;
using std::string;
using std::swap;
using std::hash;
using std::stoi;
using boost::lexical_cast;
struct Solution
{
    int D[10][10][10][10];
    int findNode[4];
    void initFind(int i,int j,int k,int l)
    {
        findNode[0] = i;
        findNode[1] = j;
        findNode[2] = k;
        findNode[3] = l;
    }
    int calculate(int i,int j,int k,int l)
    {

    }
    int openLock(vector<string> & deadends, string targets)
    {
        int t1,t2,t3,t0;
        for(int i=0;i<10;i++)
        {
            for(int j = 0;j < 10;j++)
            {
                for(int k = 0; k < 10;k++)
                {
                    for(int l = 0;l < 10;l++)
                        D[i][j][k][l] = INT_MAX;
                }
            }
        }
        for(int i=0;i<(int)deadends.size();i++)
        {
            t3 = boost::lexical_cast<int>(deadends[i][3]);
            t2 = boost::lexical_cast<int>(deadends[i][2]);
            t1 = boost::lexical_cast<int>(deadends[i][1]);
            t0 = boost::lexical_cast<int>(deadends[i][0]);
            D[t0][t1][t2][t3] = INT_MAX;
        }
        t3 = boost::lexical_cast<int>(targets[3]);
        t2 = boost::lexical_cast<int>(targets[2]);
        t1 = boost::lexical_cast<int>(targets[1]);
        t0 = boost::lexical_cast<int>(targets[0]);
        initFind(t0,t1,t2,t3);
        return D[t0][t1][t2][t3];
    }
};

int main()
{
    std::ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<string> deadends;
    for(int i=0;i<n;i++)
    {
        string s;
        cin >> s;

    }

    return 0;
}


/**
 * @file
 * @author Jason Lingle
 *
 * @brief Implements src/audio/ship_mixer.hxx
 */

#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <stack>
#include <vector>
#include <utility>
#include <queue>
#include <map>
#include <fstream>
#include <iostream>

#include <SDL.h>
#include <SDL_thread.h>

#include <boost/shared_array.hpp>

#include "audio.hxx"
#include "ship_mixer.hxx"
#include "src/ship/cell/cell.hxx"
#include "src/ship/cell/square_cell.hxx"
#include "src/ship/cell/circle_cell.hxx"
#include "src/ship/ship.hxx"
#include "src/globals.hxx"
#include "src/exit_conditions.hxx"

using namespace std;

//#define DEBUG_WRITE_TO_FILE "debug.raw"
#ifdef DEBUG_WRITE_TO_FILE
#warning "All ship audio will be written to a file!"
#warning "THIS MUST NOT BE ENABLED IN A PRODUCTION BUILD!!!"
#endif

/* The inverse-speed of sound at reinforcement zero.
 * The speed at any other reinforcement is found by
 * dividing this value by (1+sqrt(reinforcement)) and
 * rounding down.
 */
#define PRD_OF_SOUND_AT_RNF0 (AUDIO_FRQ/200)

/* The energy loss per cell at reinforcement zero.
 * The loss at any other reinforcement is found by
 * dividing this value by (1+sqrt(reinforcement)).
 */
#define LOSS_AT_RNF0 0.10f

/* How much audio to remix at a time.
 * Higher values produce higher throughput, but cause
 * poorer responsiveness to mixing abortion (which will stall
 * the main thread when the ship is modified).
 */
#define MIX_SEGMENT_SZ 512

/* The maximum number of elements in the root ship mixer before
 * we begin ignoring dynamic events.
 */
#define MAX_MIXER_SZ 32

namespace audio {
  typedef boost::shared_array<Sint16> Clip;

  /* Set to false by the main thread to indicate that current calculations by
   * the worker thread should be aborted and it is to enter the waiting state.
   */
  static volatile bool abortCurrent;

  /* Set to false by the main thread to indicate that the worker thread is
   * to terminate instead of waiting on the condition, next time it has
   * nothing to do.
   */
  static volatile bool continueRunning;

  /* Set to true if the graph is ready to be used for new things. */
  static volatile bool graphReady;

  /* Used for total synchronisation between the main and worker threads. */
  static SDL_mutex* totalMutex;
  /* Used to ensure that the worker thread is waiting. */
  static SDL_mutex* waitMutex;

  /* The worker waits on this condition when it has nothing to do, locked
   * on totalMutex.
   */
  static SDL_cond* cond;

  /* The worker thread. */
  static SDL_Thread* thread;

  /* See shipMixer definition in ShipMixer section */
  static VMixer*const quietMixer(new VMixer(0x1000, 0x3000));

  /****************************************************************************
   * BEGIN SECTION: SHIP GRAPH                                                *
   ****************************************************************************/

  /* Defines the conduction of sound from a source to the bridge.
   * delay      Delay, in samples, between the sound production and observation
   * Sint16     Volume multiplier
   * l,r        Whether the left and right channels are affected
   */
  struct ConductionElt {
    unsigned delay;
    Sint16 volume;
    bool l, r;
  };

  #define MAX_ELTS 32

  /* Since we do calculations on a separate thread, we need to copy
   * the ship information into a separate structure, which we'll also
   * use to store the conduction tables.
   */
  struct Node {
    Node* neighbours[4];
    unsigned numNeighbours;
    ConductionElt elts[MAX_ELTS];
    unsigned numElts;
  }
  /* The hard cap for ship size is 4094 cells, so hardwire to that. */
  static cells[MAX_CELL_CNT];
  static unsigned numCells;

  /* Map source Cell*s to Node*s, since the outside world has no knowledge
   * of the Node structure.
   */
  static map<Cell*,Node*> cellMap;

  static float shipReinforcement;

  /* Sets the graph for the specified Cell* array up.
   * This assumes that it is already empty.
   */
  static void graphCreate(Cell*const* c, unsigned n) {
    //Start by mapping cells to nodes
    Node* curr = cells;
    for (unsigned i=0; i<n; ++i)
      if (!c[i]->isEmpty)
        cellMap[c[i]] = curr++;

    //Now copy other information
    numCells = cellMap.size();
    for (map<Cell*,Node*>::const_iterator it = cellMap.begin(); it != cellMap.end(); ++it) {
      Cell* cell = it->first;
      Node* node = it->second;
      node->numNeighbours = cell->numNeighbours();
      memset(node->neighbours, 0, sizeof(node->neighbours));
      for (unsigned i=0; i<node->numNeighbours; ++i)
        if (cell->neighbours[i] && !cell->neighbours[i]->isEmpty)
          node->neighbours[i] = cellMap[cell->neighbours[i]];
    }
  }

  /* Updates the graph to compensate for cells no longer present in the provided
   * array. Removed cells are added to the specified vector.
   * Results are undefined if new, non-empty cells are present.
   */
  static void graphUpdate(Cell*const* c, unsigned n, set<Cell*>& removed) {
    //Copy the old cellMap so we can use it for taking the difference
    map<Cell*,Node*> oldMap(cellMap);
    //Make a set so we have Cell*s in the same order as the map
    set<Cell*> cellset;
    for (unsigned i=0; i<n; ++i)
      if (!c[i]->isEmpty)
        cellset.insert(c[i]);

    for (map<Cell*,Node*>::const_iterator it = oldMap.begin(); it != oldMap.end(); ++it) {
      Cell* cell = it->first;
      Node* node = it->second;
      //Does it exist?
      if (cellset.end() == cellset.find(cell)) {
        //Nope, remove from map and nullify respective neighbours
        cellMap.erase(cellMap.find(cell));
        removed.insert(cell);
        for (unsigned i=0; i<4; ++i)
          if (node->neighbours[i])
            for (unsigned j=0; j<4; ++j)
              if (node->neighbours[i]->neighbours[j] == node)
                node->neighbours[i]->neighbours[j] = NULL;
      }
    }
  }

  /* Maps the Cell* vector into the provided empty Node* vector. */
  static void mappingCreate(const vector<Cell*>& c, vector<Node*>& n) {
    for (unsigned i=0; i<c.size(); ++i)
      n.push_back(cellMap[c[i]]);
  }

  /* Updates the given Cell* and Node* vectors, given a list of Cell*s that
   * were removed.
   */
  static void mappingUpdate(vector<Cell*>& c, vector<Node*>& n, const set<Cell*>& removed) {
    for (unsigned i=0; i<c.size(); ++i) {
      set<Cell*>::iterator it = removed.find(c[i]);
      if (it != removed.end()) {
        n.erase(n.begin() + i);
        c.erase(c.begin() + i--);
      }
    }
  }

  /* Returns the index of the first node in the second's neighbour array. */
  static inline unsigned revn(const Node* a, const Node* b) {
    for (unsigned i=0;; ++i) {
      assert(i < b->numNeighbours);
      if (b->neighbours[i] == a)
        return i;
    }
  }

  struct graphop {
    Node* node;
    unsigned from;
    unsigned delay;
    float vol;
    bool l, r;
  };
  /* Calculates the conductions for all nodes in the graph.
   * Returns early if abortCurrent is set to true.
   *
   * The following initialisation is performed:
   * + All nodes have numElts set to 0
   * + Node 0 (the bridge) is given a conduction of 0 delay and 100% volume.
   *
   * After that, a queue is created and the following algorithm performed until
   * the queue is emptied:
   * + The head is removed; it contains the Node* to work on, the neighbour index
   *   from whence it came, as well as the delay, volume values, and the channels.
   * + A ConductionElt is added to that Node* if one of these conditions is met:
   *   - numElts < MAX_ELTS: Add to end of array, increment numElts
   *   - One elt already there has a lower volume: Replace the elt that has the lowest volume
   * + If this elt was not used, go back to the beginning
   * + Otherwise, add new items to the queue for each neighbour, adding the appropriate value
   *   to the delay and multiplying the volume by the energy loss factor and the conductance
   *   defined below. Abort this step for any neighbour that results in a volume less than 0x100.
   *
   * Volume is tracked internally with floats, and starts at a volume higher than 100% (so that
   * sound can be conducted farther).
   *
   * The queue is started by adding an item for each neighbour of the bridge, which is the same as
   * the last step above, except that channels and volume is determined as below: (forward is right)
   *
   * Square/circle:      l/10.0
   *                         |
   *             lr/7.50   --+-- lr/7.50
   *                         |
   *                     r/10.0
   *
   * Equilateral triangle:   l/10.0
   *                          /
   *             lr/7.50   --<
   *                          \
   *                          r/10.0
   * Conductance is determined by the type of the cell and the neighbour arrangement. The table
   * below has the outgoing (since we do the graph in reverse) signal from the left. Each number
   * indicates the volume multiplier for incomming from that direction.
   *                                               0.50
   * Square/circle:                                  |
   *      1.0 --+          0.0 --+-- 1.0      0.50 --+
   *
   *                           0.30             0.30
   *                             |                |
   *     0.50 --+         0.40 --+          0.0 --+-- 0.40
   *            |                |                |
   *          0.50             0.30             0.30
   *
   *                                0.40
   *                                 |
   *      0.0 --+-- 0.60       0.0 --+-- 0.60
   *            |
   *           0.40
   *
   * Equilateral/right:       0.667
   *                            /
   *    1.0 --*         0.33 --*                  0.33 --*
   *                                                      \
   *          0.40                                      0.667
   *           /
   *   0.20 --*
   *          \
   *         0.40
   */
  void calcConductions() {
    graphReady = false;
    //Initialise
    for (unsigned i=0; i<MAX_CELL_CNT; ++i)
      cells[i].numElts = 0;
    cells[0].elts[0].delay = 0;
    cells[0].elts[0].volume = 0x7FFF;
    cells[0].elts[0].l = cells[0].elts[0].r = true;
    cells[0].numElts = 1;

    unsigned transitTime = (unsigned)(PRD_OF_SOUND_AT_RNF0 / (1.0f + sqrt(shipReinforcement)));
    float energyLoss = (1.0f - LOSS_AT_RNF0 / (1.0f + sqrt(shipReinforcement)));

    //Initial queue setup
    queue<graphop> ops;
    if (cells[0].numNeighbours == 4) {
      //Square/circle
      if (cells[0].neighbours[0]) {
        graphop op = { cells[0].neighbours[0], revn(&cells[0], cells[0].neighbours[0]),
                       transitTime, 1.5f, true, true };
        ops.push(op);
      }
      if (cells[0].neighbours[1]) {
        graphop op = { cells[0].neighbours[1], revn(&cells[0], cells[0].neighbours[1]),
                       transitTime, 2.0f, true, false };
        ops.push(op);
      }
      if (cells[0].neighbours[2]) {
        graphop op = { cells[0].neighbours[2], revn(&cells[0], cells[0].neighbours[2]),
                       transitTime, 1.5f, true, true };
        ops.push(op);
      }
      if (cells[0].neighbours[3]) {
        graphop op = { cells[0].neighbours[3], revn(&cells[0], cells[0].neighbours[3]),
                       transitTime, 2.0f, false, true };
        ops.push(op);
      }
    } else {
      //Triangle
      if (cells[0].neighbours[0]) {
        graphop op = { cells[0].neighbours[0], revn(&cells[0], cells[0].neighbours[0]),
                       transitTime, 10.0f, true, false };
        ops.push(op);
      }
      if (cells[0].neighbours[1]) {
        graphop op = { cells[0].neighbours[1], revn(&cells[0], cells[0].neighbours[1]),
                       transitTime, 7.5f, true, true };
        ops.push(op);
      }
      if (cells[0].neighbours[2]) {
        graphop op = { cells[0].neighbours[2], revn(&cells[0], cells[0].neighbours[2]),
                       transitTime, 10.0f, false, true };
        ops.push(op);
      }
    }

    //OK, ready to run
    while (!ops.empty() && !abortCurrent) {
      const graphop& o(ops.front());

      //Do we instert it?
      if (o.node->numElts < MAX_ELTS) {
        //Yes, addition case
        unsigned ix = o.node->numElts ++;
        o.node->elts[ix].delay = o.delay;
        o.node->elts[ix].volume = ((Sint32)(0x7FFF * min(1.0f, o.vol)));
        o.node->elts[ix].l = o.l;
        o.node->elts[ix].r = o.r;
      } else {
        //See if anything is queiter
        unsigned ix = 0;
        for (unsigned i=1; i<MAX_ELTS; ++i)
          if (o.node->elts[i].volume < o.node->elts[ix].volume)
            ix = i;

        if (o.node->elts[ix].volume < o.vol*0x7FFF) {
          //Insert, ix is the minimum
          o.node->elts[ix].delay = o.delay;
          o.node->elts[ix].volume = ((Sint32)(0x7FFF * min(1.0f, o.vol)));
          o.node->elts[ix].l = o.l;
          o.node->elts[ix].r = o.r;
        } else {
          //Can't insert, skip this one
          ops.pop();
          continue;
        }
      }

      /* Generate a "cell code" which we can use in a switch statement.
       * So it is easy to use hex, each item uses one nybble. Neighbours
       * run counterclockwise from the source node. For triangles, the
       * nybbles are
       *   <neighbour+1?><neighbour+2?><numNeighbours><currentNeighbourOff>
       * And for squares
       *   <neighbour+1?><neighbour+2?><neighbour+3><numNeighbours><currentNeighbourOff>
       */
      unsigned code = 0;
      for (unsigned i=1; i<o.node->numNeighbours; ++i) {
        code |= (o.node->neighbours[(i+o.from)%o.node->numNeighbours]? 1:0);
        code <<= 4;
      }
      code |= o.node->numNeighbours;
      code <<= 4;

      for (unsigned i=0; i<o.node->numNeighbours; ++i) {
        Node* n = o.node->neighbours[(i+o.from)%o.node->numNeighbours];
        if (n) {
          graphop op = { n, revn(o.node, n), o.delay+transitTime, o.vol*energyLoss, o.l, o.r };
          switch (code | i) {
            case 0x00040:
            case 0x00030:
              //100% reflection back
              break;
            //Special quad cases
            case 0x10041:
            case 0x00143:
              //Around right-angle
              op.vol *= 0.50f;
              break;
            case 0x10040:
            case 0x00140:
              //Reflected by right-angle
              op.vol *= 0.50f;
              break;
            case 0x01042:
              //Pass through
              break;
            case 0x01040:
            case 0x11040:
            case 0x01140:
            case 0x11140:
              //All passed through, nothing reflected
              op.vol = 0;
              break;
            case 0x10140:
              //T-intersection reflection
              op.vol *= 0.4f;
              break;
            case 0x10141:
            case 0x10143:
              //T-intersection leg
              op.vol *= 0.30f;
              break;
            case 0x11042:
            case 0x01142:
              //Side-T pass-through
              op.vol *= 0.60f;
              break;
            case 0x11041:
            case 0x01143:
              //Side-T dispersion
              op.vol *= 0.40f;
              break;
            case 0x11142:
              //4-way pass-through
              op.vol *= 0.4f;
              break;
            case 0x11141:
            case 0x11143:
              //4-way side
              op.vol *= 0.30f;
              break;

            //Special triangle cases
            case 0x1030:
            case 0x0130:
              //Corner reflection
              op.vol *= 0.3333f;
              break;
            case 0x1031:
            case 0x0132:
              //Corner pass-through
              op.vol *= 0.6667f;
              break;
            case 0x1130:
              //3-way reflection
              op.vol *= 0.2f;
              break;
            case 0x1131:
            case 0x1132:
              //3-way leg
              op.vol *= 0.4f;
              break;
            default:
              cerr << "FATAL: Unexpected code in " __FILE__ ":" << __LINE__ << ": " << code << endl;
              exit(EXIT_PROGRAM_BUG);
          }

          if (op.vol > 1.0f/256.0f)
            ops.push(op);
        }
      } //for loop on neighbours

      //Done with this one
      ops.pop();
    } //While ops

    graphReady = true;
  }
  /****************************************************************************
   * END SECTION: SHIP GRAPH                                                  *
   ****************************************************************************/

  /****************************************************************************
   * BEGIN SECTION: REMIXING                                                  *
   ****************************************************************************/
  static const Sint16 zero[1024] = {0};

  struct remix_data {
    bool wasInit;
    struct remix_data_inner {
      const Sint16* d, *end;
      Sint16 volume;
    } inputs[MAX_ELTS];
    unsigned sam;

    remix_data() : wasInit(false), sam(0) { }
  };
  /* Remixes input data according to the specified ConductionElts.
   * Before each elt's delay, the pre data are used. When pre0 is
   * exhausted, pre1 will be used and looped. After the delay, the
   * post data are used, in the same way as pre.
   * The final mandatory argument is internally-used data used to allow calls
   * on the same data to be broken across invocations.
   * If the first optional argument is true, each input will start at the offset of
   * its delay.
   *
   * This function is fully thread-safe, assuming the writable data are not
   * shared between threads.
   *
   * Returns true if at least one input is not on post1.
   */
  static bool remix(Sint16* dst, unsigned numSamples,
                    const ConductionElt* elts, unsigned numElts,
                    const Sint16* pre0,  unsigned pre0len,
                    const Sint16* pre1,  unsigned pre1len,
                    const Sint16* post0, unsigned post0len,
                    const Sint16* post1, unsigned post1len,
                    remix_data& dat,
                    bool addOffset = false,
                    float volmul = 1) {

    if (!dat.wasInit){
      for (unsigned i=0; i<numElts; ++i) {
        dat.inputs[i].d = pre0 + (addOffset? pre0len - elts[i].delay % pre0len : 0);
        dat.inputs[i].end = pre0+pre0len;
        if (dat.inputs[i].d == dat.inputs[i].end) dat.inputs[i].d = pre0; //happens if elts[i].delay % pre0len == 0
        dat.inputs[i].volume = (Sint16)max(-32768.0f, min(32767.0f, volmul * elts[i].volume));
      }
      dat.wasInit = true;
    }

    for (unsigned sam=0; sam<numSamples; ++sam) {
      signed left=0, right=0;
      for (unsigned i=0; i<numElts; ++i) {
        //Don't reset if we'll be in the same array
        if (sam+dat.sam == elts[i].delay
        &&  !(dat.inputs[i].d >= pre0 && dat.inputs[i].d < pre0+pre0len && pre0==post0)
        &&  !(dat.inputs[i].d >= pre1 && dat.inputs[i].d < pre1+pre1len && pre1==post0)) {
          dat.inputs[i].d = post0;
          dat.inputs[i].end = post0+post0len;
        }
        Sint16 d = *dat.inputs[i].d++;
        muleq(d, dat.inputs[i].volume);
        if (elts[i].l) left += d;
        if (elts[i].r) right += d;

        if (dat.inputs[i].d == dat.inputs[i].end) {
          if (sam+dat.sam >= elts[i].delay) {
            dat.inputs[i].d = post1;
            dat.inputs[i].end = post1+post1len;
          } else {
            dat.inputs[i].d = pre1;
            dat.inputs[i].end = pre1+pre1len;
          }
        }
      }

      if (left  < -0x8000) left  = -0x8000;
      if (left  >  0x7FFF) left  =  0x7FFF;
      if (right < -0x8000) right = -0x8000;
      if (right >  0x7FFF) right =  0x7FFF;
      *dst++ = left;
      *dst++ = right;
    }

    dat.sam += numSamples;

    //Return true if any input is not within post1
    for (unsigned i=0; i<numElts; ++i)
      if (!(dat.inputs[i].d >= post1 && dat.inputs[i].d < post1+post1len))
        return true;
    return false;
  }

  /* Simple AudioSource that passes through the remix function.
   * If abortCurrent becomes true, and abortable is true, its get
   * function will do nothing.
   */
  class ShipRemixer: public AudioSource {
    bool alwaysReturnTrue;

    const ConductionElt* elts;
    unsigned nelts;
    const Sint16* a, *b, *c, *d;
    unsigned al, bl, cl, dl;
    bool addoff, canAbort;
    float volmul;

    remix_data dat;

    public:
    ShipRemixer(bool _alwaysReturnTrue,
                const ConductionElt* e, unsigned ne,
                const Sint16* a_, unsigned al_,
                const Sint16* b_, unsigned bl_,
                const Sint16* c_, unsigned cl_,
                const Sint16* d_, unsigned dl_,
                bool ao = false,
                float vm = 1.0f,
                bool abortable = false)
    : alwaysReturnTrue(_alwaysReturnTrue),
      elts(e), nelts(ne),
      a(a_), b(b_), c(c_), d(d_),
      al(al_), bl(bl_), cl(cl_), dl(dl_),
      addoff(ao), canAbort(abortable), volmul(vm)
    { }

    virtual bool get(Sint16* dst, unsigned numVals) {
      //Output is stereo, so num samples is half the number of values
      return (canAbort && abortCurrent) ||
             remix(dst, numVals/2,
                   elts, nelts,
                   a, al, b, bl,
                   c, cl, d, dl,
                   dat, addoff, volmul) || alwaysReturnTrue;
    }
  };
  /****************************************************************************
   * END SECTION: REMIXING                                                    *
   ****************************************************************************/

  /****************************************************************************
   * BEGIN SECTION: AMBIENT EFFECTS                                           *
   ****************************************************************************/

  /* Simply a collection of all ambient effects that exist.
   * This will be populated at static-load time.
   * Since static initialisation order is not guaranteed
   * between files (much less properly ordered according
   * to dependency), this is an uninitialised pointer.
   * New items must be added with the ShipAmbient_impl
   * constructor, which properly initialises this if
   * necessary.
   */
  static vector<ShipAmbient_impl*>* ambients;

  /* Simple AudioSource to play an ambient effect.
   * Allows altering the volume while playing.
   * Since it depends on the contents of ShipAmbient_impl,
   * its functions are implemented below that class.
   *
   * Modifications to this class will only performed by the
   * main thread by in-game events, so the audio thread will
   * be locked (therefore no further locking is necessary).
   */
  class ShipAmbientPlayer: public AudioSource {
    friend class ShipAmbient_impl;
    ShipAmbient_impl* ambient;

    enum Stage { Init=0, Middle, Final, Terminate } stage;
    unsigned ix;
    Sint16 volume;

    public:
    ShipAmbientPlayer(ShipAmbient_impl* a, Sint16 vol)
    : ambient(a), stage(Init), ix(0), volume(vol)
    { }

    virtual bool get(Sint16* dst, unsigned cnt);

    private:
    bool getsub(Sint16* dst, unsigned cnt, const Clip& init, const Clip& mid, const Clip& fin,
                                           unsigned leninit, unsigned lenmid, unsigned lenfin);
  };

  /* Contains the actual data for ambient effects.
   */
  struct ShipAmbient_impl {
    /* The lengths of the source initial, middle, and final data */
    unsigned inlen, midlen, finlen;
    /* The source initial, middle, and final data */
    Sint16* init, * middle, * final;

    float volmul;

    /* The sound thread must lock this when reading the Clip variables,
     * and the worker when writing them.
     */
    SDL_mutex* lock;
    /* The rendered arrays, or NULL if that isn't done yet. */
    Clip cin, cmid, cfin;
    /* The lengths of the rendered arrays. */
    unsigned cinlen, cmidlen, cfinlen;

    /* The raw Cell* sources. */
    vector<Cell*> sources;
    /* Remapped to internal Node*s for the current graph. */
    vector<Node*> nodesrc;

    /* The current player, or NULL if there is no player.
     * Also set to NULL when transitioning to Final, since
     * a new one playing Init must be stacked on top in
     * this case.
     */
    ShipAmbientPlayer* player;

    ShipAmbient_impl(AudioSource* src, float vm, unsigned il, unsigned ml, unsigned fl)
    : inlen(il != 1? il : ml), midlen(ml), finlen(fl != 1? fl : ml),
      init(inlen? new Sint16[inlen] : NULL),
      middle(new Sint16[midlen]),
      final(finlen? new Sint16[finlen] : NULL),
      volmul(vm),
      lock(SDL_CreateMutex()),
      cinlen(0), cmidlen(0), cfinlen(0),
      player(NULL)
    {
      if (il > 1)
        src->get(init, inlen);
      src->get(middle, midlen);
      if (fl > 1)
        src->get(final, finlen);
      if (il == 1)
        memcpy(init, middle, midlen*2);
      if (fl == 1)
        memcpy(final, middle, midlen*2);

      delete src;

      static bool hasAmbients=false;
      if (!hasAmbients) {
        ambients = new vector<ShipAmbient_impl*>;
        hasAmbients = true;
      }
      ambients->push_back(this);
    }

    virtual ~ShipAmbient_impl() {
      delete[] init;
      delete[] middle;
      delete[] final;
      SDL_DestroyMutex(lock);
    }

    void set(Sint16 vol) {
      if (!cmid) return;

      if (vol) {
        if (sources.empty()) return;
        if (player)
          player->volume = vol;
        else {
          player = new ShipAmbientPlayer(this, vol);
          SDL_LockAudio();
          quietMixer->add(player);
          SDL_UnlockAudio();
        }
      } else {
        if (player) {
          SDL_LockAudio();
          player->stage = player->Final;
          player->ix = 0;
          SDL_UnlockAudio();
          player = NULL;
        }
      }
    }

    void addSource(Cell* src) {
      sources.push_back(src);
    }
  };

  //Implementation of ShipAmbientPlayer
  bool ShipAmbientPlayer::get(Sint16* dst, unsigned cnt) {
    SDL_mutexP(ambient->lock);
    Clip init(ambient->cin), mid(ambient->cmid), fin(ambient->cfin);
    unsigned initl(ambient->cinlen), midl(ambient->cmidlen), finl(ambient->cfinlen);
    SDL_mutexV(ambient->lock);
    return getsub(dst, cnt, init, mid, fin, initl, midl, finl);
  }

  bool ShipAmbientPlayer::getsub(Sint16* dst, unsigned cnt,
                                 const Clip& init, const Clip& mid, const Clip& fin,
                                 unsigned initl, unsigned midl, unsigned finl) {
    while (cnt) {
      Clip src;
      unsigned slen;
      /* Length will be zero when the clip is NULL, so we can
       * safely ignore the special cases.
       */
      switch (stage) {
        case Init:
          src = init;
          slen = initl;
          break;
        case Middle:
          src = mid;
          slen = midl;
          break;
        case Final:
          src = fin;
          slen = finl;
          break;
        default:
          //End of stream
          memset(dst, 0, cnt*2);
          return false;
      }
      //We need to be able to handle the length changing, possibly
      //invalidating ix
      if (ix > slen) ix = slen;
      unsigned num = (cnt <= slen-ix? cnt : slen-ix);
      if (num) memcpy(dst, &src[ix], num*2);
      for (unsigned i=0; i<num; ++i)
        muleq(dst[i], volume);

      cnt -= num;
      dst += num;
      ix += num;
      if (cnt) {
        //Exhausted this stage
        //If in Middle, loop;
        //otherwise, go to next if we still have data
        ix = 0;
        if (stage != Middle || !src || !slen)
          switch (stage) {
            case Init:
              stage = Middle; break;
            case Middle:
              stage = Final; break;
            case Final:
              stage = Terminate; break;
            default: break;
          }
      }
    }

    return true;
  }

  ShipAmbient::ShipAmbient(AudioSource* src, float vm, unsigned midl, unsigned initl, unsigned finl)
  : dat(new ShipAmbient_impl(src, vm, initl, midl, finl))
  { }

  void ShipAmbient::set(Sint16 v) {
    dat->set(v);
  }

  void ShipAmbient::addSource(Cell* c) {
    dat->addSource(c);
  }

  /* Performs all ambient remixing for a single effect.
   * This assumes a number of things:
   * + It is run by the worker thread
   * + All sources are properly populated
   * + The Cell*->Node* internal remapping is complete
   * + All calculations for the ship are complete
   *
   * The length of each segment is the length of the source segement
   * plus the longest delay of any input, except for the middle, which
   * is simply the input length. The remixing pattern
   * for the three cases is:
   *   init:    (silence->silence)->(init->middle)      no offset
   *   middle:  (middle->middle)->(middle->middle)      offset added
   *   final:   (middle->middle)->(final->silence)      offset added
   */
  static void ambientRemixing(ShipAmbient_impl* ambient) {
    Clip init, mid, fin;
    unsigned initl, midl, finl;

    unsigned baselen = 0;
    //Determine base length
    for (unsigned i=0; i<ambient->nodesrc.size(); ++i) {
      Node* n = ambient->nodesrc[i];
      for (unsigned j=0; j<n->numElts; ++j)
        if (n->elts[j].delay > baselen)
          baselen = n->elts[j].delay;
    }

    //Handle init
    if (ambient->init) {
      initl = baselen + ambient->inlen;

      //Allocate (times two since output is stereo)
      init = Clip(new Sint16[initl*2]);
      //Populate a mixer with remixers for each source
      Mixer mixer;
      for (unsigned i=0; i<ambient->nodesrc.size(); ++i)
        mixer.add(new ShipRemixer(true,
                                  ambient->nodesrc[i]->elts, ambient->nodesrc[i]->numElts,
                                  zero, lenof(zero), zero, lenof(zero),
                                  ambient->init, ambient->inlen, ambient->middle, ambient->midlen,
                                  false, ambient->volmul, true));
      //Read it all in
      for (unsigned i=0; i<initl*2 && !abortCurrent; i += MIX_SEGMENT_SZ)
        mixer.get(&init[i], initl*2 - i > MIX_SEGMENT_SZ? MIX_SEGMENT_SZ : initl*2-i);
    } else initl = 0;

    if (abortCurrent) return;

    //Mid
    //Start a new scope so the temporary mixer we create will go out of scope
    {
      midl = ambient->midlen; //Since this is looped, it is simply the input length

      //Allocate
      mid = Clip(new Sint16[midl*2]);
      //Populate the mixer
      Mixer mixer;
      for (unsigned i=0; i<ambient->nodesrc.size(); ++i)
        mixer.add(new ShipRemixer(true,
                                  ambient->nodesrc[i]->elts, ambient->nodesrc[i]->numElts,
                                  ambient->middle, ambient->midlen, ambient->middle, ambient->midlen,
                                  ambient->middle, ambient->midlen, ambient->middle, ambient->midlen,
                                  true, ambient->volmul, true));
      //Read in
      for (unsigned i=0; i<midl*2 && !abortCurrent; i += MIX_SEGMENT_SZ)
        mixer.get(&mid[i], midl*2 - i > MIX_SEGMENT_SZ? MIX_SEGMENT_SZ : midl*2-i);
    }

    if (abortCurrent) return;

    //And the same for final
    if (ambient->final) {
      finl = baselen + ambient->finlen;
      fin = Clip(new Sint16[finl*2]);

      Mixer mixer;
      for (unsigned i=0; i<ambient->nodesrc.size(); ++i)
        mixer.add(new ShipRemixer(false,
                                  ambient->nodesrc[i]->elts, ambient->nodesrc[i]->numElts,
                                  ambient->middle, ambient->midlen, ambient->middle, ambient->midlen,
                                  ambient->final, ambient->finlen, zero, lenof(zero),
                                  true, ambient->volmul, true));
      //Read in
      for (unsigned i=0; i<finl*2 && !abortCurrent; i += MIX_SEGMENT_SZ)
        mixer.get(&fin[i], finl*2 - i > MIX_SEGMENT_SZ? MIX_SEGMENT_SZ : finl*2-i);
    } else finl = 0;

    if (abortCurrent) return; //Don't save, data is not valid

    //Done, write back
    SDL_mutexP(ambient->lock);
    ambient->cin = init;
    ambient->cinlen = initl*2;
    ambient->cmid = mid;
    ambient->cmidlen = midl*2;
    ambient->cfin = fin;
    ambient->cfinlen = finl*2;
    SDL_mutexV(ambient->lock);
  }

  /***************************************************************************
   * END SECTION: AMBIENT EFFECTS                                            *
   ***************************************************************************/

  /***************************************************************************
   * BEGIN SECTION: STATIC EFFECTS                                           *
   ***************************************************************************/

  /* Collection of all static effects that exist.
   * This will be populated at static-load time.
   * Only the constructor of ShipStaticEvent_impl may modify this.
   */
  static vector<ShipStaticEvent_impl*>* statics;

  /* Simple AudioSource to play a static event.
   * It captures the input array when started, removing the need for
   * any synchronisation later.
   */
  class ShipStaticEventPlayer: public AudioSource {
    Clip clip;
    unsigned ix, len;
    Sint16 volume;

    public:
    ShipStaticEventPlayer(ShipStaticEvent_impl*, Sint16);
    virtual bool get(Sint16*, unsigned);
  };

  struct ShipStaticEvent_impl {
    Clip clip;
    unsigned len;

    Sint16* dat;
    unsigned datlen;

    SDL_mutex* lock;

    vector<Cell*> sources;
    vector<Node*> nodesrc;

    float volmul;

    ShipStaticEvent_impl(AudioSource* src, float vm, unsigned length)
    : len(0), dat(new Sint16[length]), datlen(length), lock(SDL_CreateMutex()), volmul(vm)
    {
      src->get(dat, datlen);
      delete src;

      static bool hasStatics = false;
      if (!hasStatics) {
        hasStatics = true;
        statics = new vector<ShipStaticEvent_impl*>;
      }
      statics->push_back(this);
    }

    virtual ~ShipStaticEvent_impl() {
      delete[] dat;
    }

    void play(Sint16 vol) {
      if (!clip) return;

      //We must lock the audio thread since we'll be modifying the
      //mixer, and our lock since we'll be copying clip information
      //in the constructor
      SDL_LockAudio();
      SDL_mutexP(lock);
      quietMixer->add(new ShipStaticEventPlayer(this, vol));
      SDL_mutexV(lock);
      SDL_UnlockAudio();
    }

    void addSource(Cell* c) {
      sources.push_back(c);
    }
  };

  ShipStaticEventPlayer::ShipStaticEventPlayer(ShipStaticEvent_impl* that, Sint16 v)
  : clip(that->clip), ix(0), len(that->len), volume(v)
  { }

  bool ShipStaticEventPlayer::get(Sint16* dst, unsigned cnt) {
    unsigned l = (cnt > (len - ix)? len-ix : cnt);
    memcpy(dst, &clip[ix], l*2);
    ix += l;
    for (unsigned i=0; i<l; ++i)
      muleq(dst[i], volume);

    if (l != cnt) {
      memset(dst+l, 0, (cnt-l)*2);
    }
    return ix < len;
  }

  ShipStaticEvent::ShipStaticEvent(AudioSource* src, float vm, unsigned len)
  : dat(new ShipStaticEvent_impl(src, vm, len))
  { }

  void ShipStaticEvent::play(Sint16 vol) {
    dat->play(vol);
  }

  void ShipStaticEvent::addSource(Cell* c) {
    dat->addSource(c);
  }

  /* Renders a static event's clip. This makes the same assumptions
   * as ambientRemixing.
   */
  static void staticRemixing(ShipStaticEvent_impl* evt) {
    //The length of the result is the length of the input plus the
    //longest delay
    unsigned len = 0;
    for (unsigned i=0; i<evt->nodesrc.size(); ++i)
      for (unsigned j=0; j<evt->nodesrc[i]->numElts; ++j)
        if (evt->nodesrc[i]->elts[j].delay > len)
          len = evt->nodesrc[i]->elts[j].delay;
    len += evt->datlen;

    //Similar procedure as for ambient remixing.
    //The procession for data is
    //  (zero->zero)->(input->zero)     no offset
    Mixer mixer;
    for (unsigned i=0; i<evt->nodesrc.size(); ++i)
      mixer.add(new ShipRemixer(true,
                                evt->nodesrc[i]->elts, evt->nodesrc[i]->numElts,
                                zero, lenof(zero), zero, lenof(zero),
                                evt->dat, evt->datlen, zero, lenof(zero),
                                false, evt->volmul, true));
    Clip clip(new Sint16[len*2]);
    for (unsigned i=0; i<len*2 && !abortCurrent; i+=MIX_SEGMENT_SZ)
      mixer.get(&clip[i], len*2 - i > MIX_SEGMENT_SZ? MIX_SEGMENT_SZ : len*2-i);

    if (abortCurrent) return; //Data is not valid

    //OK, save
    SDL_mutexP(evt->lock);
    evt->clip = clip;
    evt->len = len*2;
    SDL_mutexV(evt->lock);
  }

  /***************************************************************************
   * END SECTION: STATIC EFFECTS                                             *
   ***************************************************************************/

  /***************************************************************************
   * BEGIN SECTION: DYNAMIC EFFECTS                                          *
   ***************************************************************************/
  class ShipDynamicEffectPlayer: public AudioSource {
    remix_data data;
    Sint16* src;
    unsigned ix, len, origlen;

    //We need to make a copy, since we operate on this asynchronously
    //from work done on the original
    ConductionElt elts[MAX_ELTS];
    unsigned numElts;

    float volmul;

    public:
    ShipDynamicEffectPlayer(AudioSource* source, unsigned _origlen, Cell* c, float vm)
    : src(new Sint16[_origlen]), ix(0), origlen(_origlen), volmul(vm)
    {
      Node* n = cellMap[c];
      numElts = n->numElts;
      memcpy(elts, n->elts, sizeof(elts));

      //The length is the original length plus the longest delay
      len = 0;
      for (unsigned i=0; i<numElts; ++i)
        if (elts[i].delay > len)
          len = elts[i].delay;
      len += origlen;

      source->get(src, origlen);
    }

    virtual bool get(Sint16* dst, unsigned cnt) {
      remix(dst, cnt/2, elts, numElts,
            zero, lenof(zero), zero, lenof(zero),
            src, origlen, zero, lenof(zero), data,
            false, volmul);
      ix += cnt/2;
      return ix < len;
    }

    virtual ~ShipDynamicEffectPlayer() {
      delete[] src;
    }
  };

  void shipDynamicEvent(AudioSource* src, unsigned len, Cell* c, float vm) {
    if (graphReady && shipMixer->size() < MAX_MIXER_SZ) {
      SDL_LockAudio();
      SDL_mutexP(totalMutex);
      shipMixer->add(new ShipDynamicEffectPlayer(src, len, c, vm));
      SDL_mutexV(totalMutex);
      SDL_UnlockAudio();
    }
    delete src;
  }
  /***************************************************************************
   * END SECTION: DYNAMIC EFFECTS                                            *
   ***************************************************************************/

  /***************************************************************************
   * BEGIN SECTION: SHIP MIXER AND THREADING                                 *
   ***************************************************************************/
  static int workerThread(void*) throw() {
    SDL_mutexP(waitMutex);
    while (continueRunning) {
      //Begin by waiting for the signal to actually do something
      SDL_CondWait(cond, waitMutex);

      if (!continueRunning) {
        SDL_mutexV(waitMutex);
        return 0;
      }

      //OK, assume the graph is populated and begin working with it
      SDL_mutexP(totalMutex);
      calcConductions();
      SDL_mutexV(totalMutex);

      if (abortCurrent) {
        abortCurrent = false;
        continue;
      }

      //Now render all the effects
      for (unsigned i=0; i<ambients->size() && !abortCurrent; ++i)
        ambientRemixing(ambients->operator[](i));
      for (unsigned i=0; i<statics->size() && !abortCurrent; ++i)
        staticRemixing(statics->operator[](i));

      abortCurrent = false;
    }

    SDL_mutexV(waitMutex);

    return 0;
  }

  bool ShipMixer::get(Sint16* dst, unsigned cnt) {
    SDL_mutexP(totalMutex);
    Mixer::get(dst, cnt);
    SDL_mutexV(totalMutex);
    #ifdef DEBUG_WRITE_TO_FILE
    static ofstream out(DEBUG_WRITE_TO_FILE, ios::binary | ios::out);
    out.write(reinterpret_cast<char*>(dst), cnt*2);
    #endif
    return true;
  }

  void ShipMixer::init() {
    static bool hasGlobals = false;
    continueRunning = true;
    abortCurrent = false;
    if (!hasGlobals) {
      hasGlobals = true;
      cond = SDL_CreateCond();
      totalMutex = SDL_CreateMutex();
      waitMutex = SDL_CreateMutex();
      trueRoot.add(shipMixer);
      shipMixer->add(quietMixer);
    }

    thread = SDL_CreateThread(workerThread, NULL);
  }

  void ShipMixer::end() {
    reset();
    abortCurrent = true;
    SDL_mutexP(totalMutex);
    cellMap.clear();
    SDL_mutexV(totalMutex);
    SDL_mutexP(waitMutex);
    continueRunning = false;
    SDL_mutexV(waitMutex);
    SDL_CondBroadcast(cond);
    SDL_WaitThread(thread,NULL);

    SDL_LockAudio();
    for (unsigned i=0; i<ambients->size(); ++i)
      (*ambients)[i]->set(0);
    SDL_UnlockAudio();
  }

  void ShipMixer::reset() {
    abortCurrent = true;
    SDL_LockAudio();
    SDL_mutexP(totalMutex);
    cellMap.clear();
    for (unsigned i=0; i<ambients->size(); ++i) {
      ShipAmbient_impl* a = (*ambients)[i];
      SDL_mutexP(a->lock);
      a->set(0);
      a->cin = a->cmid = a->cfin = Clip(NULL);
      a->cinlen = a->cmidlen = a->cfinlen = 0;
      a->sources.clear();
      a->nodesrc.clear();
      SDL_mutexV(a->lock);
    }
    for (unsigned i=0; i<statics->size(); ++i) {
      ShipStaticEvent_impl* a = (*statics)[i];
      SDL_mutexP(a->lock);
      a->clip = Clip(NULL);
      a->len = 0;
      a->sources.clear();
      a->nodesrc.clear();
      SDL_mutexV(a->lock);
    }

    SDL_mutexV(totalMutex);
    SDL_UnlockAudio();
  }

  void ShipMixer::setShip(Cell*const* cells, unsigned ncell, float reinforcement) {
    abortCurrent = true;
    SDL_LockAudio();
    SDL_mutexP(totalMutex);
    shipReinforcement = reinforcement;
    if (cellMap.empty()) {
      //Create new maps
      graphCreate(cells, ncell);
      for (unsigned i=0; i<ambients->size(); ++i)
        mappingCreate((*ambients)[i]->sources, (*ambients)[i]->nodesrc);
      for (unsigned i=0; i<statics->size(); ++i)
        mappingCreate((*statics)[i]->sources, (*statics)[i]->nodesrc);
    } else {
      //Update old maps
      set<Cell*> removed;
      graphUpdate(cells, ncell, removed);
      for (unsigned i=0; i<ambients->size(); ++i)
        mappingUpdate((*ambients)[i]->sources, (*ambients)[i]->nodesrc, removed);
      for (unsigned i=0; i<statics->size(); ++i)
        mappingUpdate((*statics)[i]->sources, (*statics)[i]->nodesrc, removed);
    }

    SDL_mutexV(totalMutex);
    SDL_UnlockAudio();

    //Notify worker
    SDL_mutexP(waitMutex);
    SDL_mutexV(waitMutex);
    graphReady = false;
    abortCurrent = false;
    SDL_CondBroadcast(cond);
  }

  /* In order to allow dynamic events to be significantly louder,
   * we use two separate mixers. The root shipMixer performs no
   */
  ShipMixer*SHIP_MIXER_CONST shipMixer(new ShipMixer);
  /***************************************************************************
   * END SECTION: SHIP MIXER AND THREADING                                   *
   ***************************************************************************/
}


// Copyright (c) 2020 Vitaly Chipounov
//
// 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/foreach.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <iomanip>
#include <iostream>
#include <math.h>

#include <gpsmap/gpx.h>

namespace pt = boost::property_tree;

namespace gpsmap {

std::ostream &operator<<(std::ostream &os, TrackItem const &m) {
    os << "TrackItem " << m.OriginalTimestamp << " lat=" << m.Latitude << " lon=" << m.Longitude << " speed=" << m.Speed
       << " alt=" << m.Elevation;
    return os;
}

time_t parse_time(const std::string &iso) {
    std::stringstream ss(iso);
    std::tm t = {};
    if (ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%SZ")) {
        return std::mktime(&t) - timezone;
    }
    return 0;
}

std::string time_to_str(time_t t) {
    char buff[64];
    struct tm *timeinfo;
    timeinfo = localtime(&t);
    strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", timeinfo);
    return buff;
}

// Returns the distance in meters between the given coordinates (in degrees)
static double distance(double lat1, double lon1, double lat2, double lon2) {
    // Radius of Earth in meters, R = 6371 * 1000
    long double R = 6371 * 1000;

    lat1 = to_rad(lat1);
    lon1 = to_rad(lon1);
    lat2 = to_rad(lat2);
    lon2 = to_rad(lon2);

    // Haversine Formula
    long double dlon = lon2 - lon1;
    long double dlat = lat2 - lat1;

    long double ans = pow(sin(dlat / 2), 2) + cos(lat1) * cos(lat2) * pow(sin(dlon / 2), 2);

    return R * 2 * asin(sqrt(ans));
}

static double bearing(double lat1, double lon1, double lat2, double lon2) {
    lat1 = to_rad(lat1);
    lon1 = to_rad(lon1);
    lat2 = to_rad(lat2);
    lon2 = to_rad(lon2);

    auto x = cos(lat2) * sin(lon2 - lon1);
    auto y = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2 - lon1);
    auto bearing = atan2(x, y);
    return to_deg(bearing);
}

static double bearing(const TrackItem &i1, const TrackItem &i2) {
    return bearing(i1.Latitude, i1.Longitude, i2.Latitude, i2.Longitude);
}

static double GetGrade(const std::vector<TrackItem> &items, int i) {
    const double maxDist = 50.0;

    // Get altitude and cumulative distance on the left
    double ldist = 0.0;
    double lelev = 0.0;
    for (auto l = i; l >= 0; --l) {
        ldist += items[l].DistanceDelta;
        lelev = items[l].Elevation;
        if (ldist >= maxDist) {
            break;
        }
    }

    // Get altitude and cumulative distance on the right
    double rdist = 0.0;
    double relev = 0.0;
    for (size_t l = i; l < items.size(); ++l) {
        rdist += items[l].DistanceDelta;
        relev = items[l].Elevation;
        if (rdist >= maxDist) {
            break;
        }
    }

    auto td = ldist + rdist;
    if (td < 0.01) {
        return 0.0;
    }

    return (relev - lelev) / td * 100;
}

std::shared_ptr<GPX> GPX::Create() {
    return std::shared_ptr<GPX>(new GPX());
}

void GPX::LoadFromFile(const std::string &path) {
    pt::ptree tree;
    pt::read_xml(path, tree);

    bool first = true;

    BOOST_FOREACH (pt::ptree::value_type &trkseg, tree.get_child("gpx.trk")) {
        BOOST_FOREACH (pt::ptree::value_type &trkpt, trkseg.second) {
            TrackItem item;
            auto lat = trkpt.second.get_child("<xmlattr>.lat").get_value("");
            auto lon = trkpt.second.get_child("<xmlattr>.lon").get_value("");
            auto speed = trkpt.second.get_child("speed").get_value("");
            auto elevation = trkpt.second.get_child("ele").get_value("");
            auto time = trkpt.second.get_child("time").get_value("");

            item.Speed = strtod(speed.c_str(), nullptr);
            item.Latitude = strtod(lat.c_str(), nullptr);
            item.Longitude = strtod(lon.c_str(), nullptr);
            item.Elevation = strtod(elevation.c_str(), nullptr);
            item.OriginalTimestamp = time;
            item.Timestamp = parse_time(time);
            item.Grade = 0.0;
            item.DistanceDelta = 0.0;
            item.TotalDistance = 0.0;
            item.Bearing = 0.0;
            item.IsTrackStart = first;
            first = false;

            m_trackItems.push_back(item);
        }
    }

    std::sort(m_trackItems.begin(), m_trackItems.end());

    for (size_t i = 1; i < m_trackItems.size(); ++i) {
        auto &ti0 = m_trackItems[i - 1];
        if (i == 1) {
            ti0.TotalDistance = m_initialDistance;
        }

        auto &ti1 = m_trackItems[i];
        ti1.DistanceDelta = distance(ti0.Latitude, ti0.Longitude, ti1.Latitude, ti1.Longitude);
        ti1.TotalDistance = ti0.TotalDistance + ti1.DistanceDelta;
    }

    for (size_t i = 0; i < m_trackItems.size() - 1; ++i) {
        m_trackItems[i].Grade = GetGrade(m_trackItems, i);
        auto b = bearing(m_trackItems[i], m_trackItems[i + 1]);
        if (b == 0) {
            if (i > 1) {
                m_trackItems[i].Bearing = m_trackItems[i - 1].Bearing;
            }
        } else {
            m_trackItems[i].Bearing = b;
        }
    }
}

void GPX::CreateSegments() {
    std::vector<int> boundaries;

    if (m_trackItems.size() == 0) {
        return;
    }

    boundaries.push_back(0);

    for (size_t i = 1; i < m_trackItems.size(); ++i) {
        const auto &prevItem = m_trackItems[i - 1];
        const auto &item = m_trackItems[i];

        if (item.Latitude == prevItem.Latitude && item.Longitude == prevItem.Longitude) {
            boundaries.push_back(i);
        }
    }

    auto bs = boundaries.size();
    for (size_t i = 0; i < bs; ++i) {
        int start = boundaries[i];
        int end = i == bs - 1 ? m_trackItems.size() - 1 : boundaries[i + 1];

        if (i == bs - 1) {
            if (start < end) {
                m_segments.push_back(Segment(start, end));
            }
        } else {
            assert(start < end);
            m_segments.push_back(Segment(start, end));
        }
    }
}

bool GPX::GetItem(size_t index, TrackItem &item) {
    if (index >= m_trackItems.size() || index < 0) {
        return false;
    }

    item = m_trackItems[index];
    return true;
}

// Return the first element i such that i.timestamp <= timestamp < (i+1).timestamp
bool GPX::GetClosestItem(time_t timestamp, size_t &nextItem, TrackItem &item) {
    if (nextItem >= m_trackItems.size() || nextItem < 0) {
        return false;
    }

    if (timestamp < m_trackItems[nextItem].Timestamp) {
        return false;
    }

    for (auto i = nextItem; i + 1 < m_trackItems.size(); ++i) {
        const auto &i1 = m_trackItems[i];
        const auto &i2 = m_trackItems[i + 1];
        if (i1.Timestamp <= timestamp && timestamp < i2.Timestamp) {
            item = i1;
            nextItem = i;
            return true;
        }
    }

    return false;
}

} // namespace gpsmap


#include <ripple/basics/safe_cast.h>
#include <ripple/protocol/tokens.h>
#include <ripple/protocol/digest.h>
#include <boost/container/small_vector.hpp>
#include <cassert>
#include <cstring>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>

namespace ripple {

static char rippleAlphabet[] =
    "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";

static char bitcoinAlphabet[] =
    "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

template <class Hasher>
static
typename Hasher::result_type
digest (void const* data, std::size_t size) noexcept
{
    Hasher h;
    h(data, size);
    return static_cast<
        typename Hasher::result_type>(h);
}

template <class Hasher, class T, std::size_t N,
    class = std::enable_if_t<sizeof(T) == 1>>
static
typename Hasher::result_type
digest (std::array<T, N> const& v)
{
    return digest<Hasher>(v.data(), v.size());
}

template <class Hasher, class... Args>
static
typename Hasher::result_type
digest2 (Args const&... args)
{
    return digest<Hasher>(
        digest<Hasher>(args...));
}

void
checksum (void* out,
    void const* message,
        std::size_t size)
{
    auto const h =
        digest2<sha256_hasher>(message, size);
    std::memcpy(out, h.data(), 4);
}

static
std::string
encodeBase58(
    void const* message, std::size_t size,
        void *temp, std::size_t temp_size,
            char const* const alphabet)
{
    auto pbegin = reinterpret_cast<unsigned char const*>(message);
    auto const pend = pbegin + size;

    int zeroes = 0;
    while (pbegin != pend && *pbegin == 0)
    {
        pbegin++;
        zeroes++;
    }

    auto const b58begin = reinterpret_cast<unsigned char*>(temp);
    auto const b58end = b58begin + temp_size;

    std::fill(b58begin, b58end, 0);

    while (pbegin != pend)
    {
        int carry = *pbegin;
        for (auto iter = b58end; iter != b58begin; --iter)
        {
            carry += 256 * (iter[-1]);
            iter[-1] = carry % 58;
            carry /= 58;
        }
        assert(carry == 0);
        pbegin++;
    }

    auto iter = b58begin;
    while (iter != b58end && *iter == 0)
        ++iter;

    std::string str;
    str.reserve(zeroes + (b58end - iter));
    str.assign(zeroes, alphabet[0]);
    while (iter != b58end)
        str += alphabet[*(iter++)];
    return str;
}

static
std::string
encodeToken (TokenType type,
    void const* token, std::size_t size, char const* const alphabet)
{
    auto const expanded = 1 + size + 4;

    auto const bufsize = expanded * 3;

    boost::container::small_vector<std::uint8_t, 1024> buf (bufsize);

    buf[0] = safe_cast<std::underlying_type_t <TokenType>>(type);
    if (size)
        std::memcpy(buf.data() + 1, token, size);
    checksum(buf.data() + 1 + size, buf.data(), 1 + size);

    return encodeBase58(
        buf.data(), expanded,
        buf.data() + expanded, bufsize - expanded,
        alphabet);
}

std::string
base58EncodeToken (TokenType type,
    void const* token, std::size_t size)
{
    return encodeToken(type, token, size, rippleAlphabet);
}

std::string
base58EncodeTokenBitcoin (TokenType type,
    void const* token, std::size_t size)
{
    return encodeToken(type, token, size, bitcoinAlphabet);
}

template <class InverseArray>
static
std::string
decodeBase58 (std::string const& s,
    InverseArray const& inv)
{
    auto psz = s.c_str();
    auto remain = s.size();
    int zeroes = 0;
    while (remain > 0 && inv[*psz] == 0)
    {
        ++zeroes;
        ++psz;
        --remain;
    }
    std::vector<unsigned char> b256(
        remain * 733 / 1000 + 1);
    while (remain > 0)
    {
        auto carry = inv[*psz];
        if (carry == -1)
            return {};
        for (auto iter = b256.rbegin();
            iter != b256.rend(); ++iter)
        {
            carry += 58 * *iter;
            *iter = carry % 256;
            carry /= 256;
        }
        assert(carry == 0);
        ++psz;
        --remain;
    }
    auto iter = std::find_if(
        b256.begin(), b256.end(),[](unsigned char c)
            { return c != 0; });
    std::string result;
    result.reserve (zeroes + (b256.end() - iter));
    result.assign (zeroes, 0x00);
    while (iter != b256.end())
        result.push_back(*(iter++));
    return result;
}

template <class InverseArray>
static
std::string
decodeBase58Token (std::string const& s,
    TokenType type, InverseArray const& inv)
{
    std::string const ret = decodeBase58(s, inv);

    if (ret.size() < 6)
        return {};

    if (type != safe_cast<TokenType>(static_cast<std::uint8_t>(ret[0])))
        return {};

    std::array<char, 4> guard;
    checksum(guard.data(), ret.data(), ret.size() - guard.size());
    if (!std::equal (guard.rbegin(), guard.rend(), ret.rbegin()))
        return {};

    return ret.substr(1, ret.size() - 1 - guard.size());
}

class InverseAlphabet
{
private:
    std::array<int, 256> map_;

public:
    explicit
    InverseAlphabet(std::string const& digits)
    {
        map_.fill(-1);
        int i = 0;
        for(auto const c : digits)
            map_[static_cast<
                unsigned char>(c)] = i++;
    }

    int
    operator[](char c) const
    {
        return map_[static_cast<
            unsigned char>(c)];
    }
};

static InverseAlphabet rippleInverse(rippleAlphabet);

static InverseAlphabet bitcoinInverse(bitcoinAlphabet);

std::string
decodeBase58Token(
    std::string const& s, TokenType type)
{
    return decodeBase58Token(s, type, rippleInverse);
}

std::string
decodeBase58TokenBitcoin(
    std::string const& s, TokenType type)
{
    return decodeBase58Token(s, type, bitcoinInverse);
}

}