/*  $Id$
 *
 *  Copyright 2010 Anders Wallin (anders.e.e.wallin "at" gmail.com)
 *
 *  This file is part of OpenCAMlib.
 *
 *  OpenCAMlib is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpenCAMlib is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenCAMlib.  If not, see <http://www.gnu.org/licenses/>.
*/

// this is mostly a translation to c++ of the earlier c# code
// http://code.google.com/p/monocam/source/browse/trunk/Project2/monocam_console/monocam_console/kdtree.cs

#include <boost/foreach.hpp>

#include "millingcutter.h"
#include "point.h"
#include "triangle.h"
#include "numeric.h"
#include "kdtree.h"

namespace ocl
{

//#define DEBUG_KD

//********   KDNode ********************** */

KDNode::KDNode(int d, double cv, KDNode *parent, KDNode *hi_c, KDNode *lo_c,
            const std::list<Triangle> *tlist, int lev)
{
    dim = d;
    cutval = cv;
    up = parent;
    hi = hi_c;
    lo = lo_c;
    tris = tlist;
    level = lev;
}

/// given a list of triangles, build and return the root node of a kd-tree with the triangles
KDNode* KDNode::build_kdtree(const std::list<Triangle> *tris,
                             unsigned int bucketSize,   // defaults to 1
                             int level,                 // defualts to 0 == root
                             KDNode *parent)            // defaults to NULL
{

    if (tris->size() == 0) { //this is a fatal error.
        std::cout << "kdtree.cpp ERROR: build_kdtree() called with tris->size()==0 ! \n";
        assert(0);
        return 0;
    }

    // calculate spread in order to know how to cut
    //static int spreadstat[4];
    Spread* spr = KDNode::spread(tris);
    //spreadstat[ spr->d ]++;
    // calculate cut value
    double cutvalue = spr->start + spr->val/2; // cut in the middle

    // if spr.val==0 (no need/possibility to subdivide anymore)
    // OR number of triangles is smaller than bucketSize
    // then return a bucket node
    if ( (tris->size() <= bucketSize) || (spr->val == 0.0)) {
        //std::cout << "Bucket with len(tris)=" << tris->size() << "\n";
        KDNode *bucket;
        if ( (spr->d == 0) || (spr->d == 2) )  {// maxx or maxy cut
            // does the node spread value make a difference here??
            bucket = new KDNode(spr->d, spr->start+spr->val, parent , NULL, NULL, tris, level);
            //bucket = new KDNode(spr->d, 0.0 , parent , NULL, NULL, tris, level);
        }
        else {// the min cut case:
            // does the node spread value make a difference here??
             bucket = new KDNode(spr->d, spr->start, parent , NULL, NULL, tris, level);
            //bucket = new KDNode(spr->d, 0.0 , parent , NULL, NULL, tris, level);
        }
        return bucket;
    }

    // build lists of triangles for hi and lo child nodes
    std::list<Triangle> *lolist = new std::list<Triangle>();
    std::list<Triangle> *hilist = new std::list<Triangle>();
    BOOST_FOREACH(Triangle t, *tris) { // loop through each triangle and put it in either lolist or hilist
        #ifdef DEBUG_KD
            std::cout << "adding tri=" << t;
            std::cout << " dim=" << spr->d << "\n";
        #endif
        if (spr->d == 0) { // cut along maxx
            if (t.maxx > cutvalue) {
                hilist->push_back(t);
            }
            else {
                lolist->push_back(t);
            }
        } else if (spr->d == 1) { // cut along minx
            if (t.minx > cutvalue) {
                hilist->push_back(t);
            }
            else {
                lolist->push_back(t);
            }
        } else if (spr->d == 2) { // cut along maxy
            if (t.maxy > cutvalue) {
                hilist->push_back(t);
            }
            else {
                lolist->push_back(t);
            }
        } else if (spr->d == 3) { // cut along miny
            if (t.miny > cutvalue) {
                hilist->push_back(t);
            }
            else {
                lolist->push_back(t);
            }
        }

    } // end loop through each triangle

    if (hilist->size() == 0) {// an error ??
        std::cout << "kdtree.cpp: hilist.size()==0!\n";
        assert(0);
    }
    if (lolist->size() == 0) {
        std::cout << "kdtree.cpp: lolist.size()==0!\n";
        assert(0);
    }

    /*
    std::cout << "hilist.size()=" << hilist->size() << "\n";
    std::cout << "lolist.size()=" << lolist->size() << "\n";
    std::cout << "sum: " << hilist->size()+lolist->size() << "== " << tris->size() << " ? \n";
    char c;
    std::cin >> c;
    */

    //                         dim     value    parent  hi   lo   trilist  level
    KDNode *node = new KDNode(spr->d, cutvalue, parent, NULL,NULL,NULL, level);

    // recursion:                   list    bucketsize   level   parent
    node->hi = KDNode::build_kdtree(hilist, bucketSize, level+1, node);
    node->lo = KDNode::build_kdtree(lolist, bucketSize, level+1, node);

    /*
    for (int m=0;m<4;m++)
        std::cout << m << " : " << spreadstat[ m ] << "\n";
    */
    // return a new node
    return node;
}

bool Spread::spread_compare(Spread *x, Spread *y) {
    if (x->val > y->val)
        return true;
    else
        return false;
}

int KDNode::cutcount=0;

/// find the maximum 'extent' of Triangle list tris along dimension d
Spread* KDNode::spread(const std::list<Triangle> *tris) {
    double max_xplus, min_xplus, max_xminus, min_xminus;
    double max_yplus, min_yplus, max_yminus, min_yminus;

    double spr_xplus, spr_xminus, spr_yplus, spr_yminus;

    if (tris->size() == 0) {
        std::cout << "kdtree.cpp ERROR, spread() called with tris->size()==0 ! \n";
        assert( 0 );
        return NULL;
    } else {
        bool first=true;
        BOOST_FOREACH(Triangle t, *tris) {
            // t.calcBB(); //ugly...
            if (first) { // initialize things on the first run
                max_xplus  = t.maxx;
                min_xplus  = t.maxx;
                max_xminus = t.minx;
                min_xminus = t.minx;

                max_yplus  = t.maxy;
                min_yplus  = t.maxy;
                max_yminus = t.miny;
                min_yminus = t.miny;
                first=false;
            } // end initialize
            else { // FIXME madness really, but for only 4 dimensions this works...

                // compute spread in xplus
                if (t.maxx > max_xplus)
                    max_xplus = t.maxx;
                if (t.maxx < min_xplus)
                    min_xplus = t.maxx;

                // compute spread in xminus
                if (t.minx > max_xminus)
                    max_xminus = t.minx;
                if (t.minx < min_xminus)
                    min_xminus = t.minx;

                // compute spread in yplus
                if (t.maxy > max_yplus)
                    max_yplus = t.maxy;
                if (t.maxy < min_yplus)
                    min_yplus = t.maxy;

                // compute spread in yminus
                if (t.miny > max_yminus)
                    max_yminus = t.miny;
                if (t.miny < min_yminus)
                    min_yminus = t.miny;
            }

        } // end FOREACH triangle in tris

        // calculate the spread along each dimension
        spr_xplus  =  max_xplus  - min_xplus;
        spr_xminus =  max_xminus - min_xminus;
        spr_yplus  =  max_yplus  - min_yplus;
        spr_yminus =  max_yminus - min_yminus;

        // spreads are distances, they should be zero or positive.
        assert(  spr_xplus  >= 0.0 );
        assert(  spr_xminus >= 0.0 );
        assert(  spr_yplus  >= 0.0 );
        assert(  spr_yminus >= 0.0 );

        // put the spreads in a list
        std::vector<Spread*> spreads;
        spreads.push_back( new Spread(0, spr_xplus , min_xplus)   );  // dim=0  is maxx
        spreads.push_back( new Spread(1, spr_xminus, min_xminus) );   // dim=1  is minx
        spreads.push_back( new Spread(2, spr_yplus , min_yplus)   );  // dim=2  is maxy
        spreads.push_back( new Spread(3, spr_yminus, min_yminus) );   // dim=3  is miny
        std::sort(spreads.begin(), spreads.end(), Spread::spread_compare); // sort the list
        // priority-queue could also be used ??

        /*
        std::cout << "\n";
        std::cout <<"spreads for " << tris->size() << " triangles:\n";
        std::cout <<"0: spread=" << spr_xplus << "\n";
        std::cout <<"1: spread=" << spr_xminus << "\n";
        std::cout <<"2: spread=" << spr_yplus << "\n";
        std::cout <<"3: spread=" << spr_yminus << "\n";
        std::cout << " selecting " << (spreads[0])->d << " with s="<< (spreads[0])->val << "\n";
        char c;
        std::cin >> c;
        */

        cutcount++;
        if (cutcount == 4)
            cutcount = 0;

        // select each dim in turn
        //return spreads[ cutcount ];

        // select the biggest spread and return
        return spreads[ 0 ];

    } // end tris->size != 0

} // end spread()

bool KDNode::overlap(const KDNode *node, const CLPoint &cl, const MillingCutter &cutter)
{
    switch(node->dim) {
        case 0: // cut along xplus
            if ( node->cutval <= cl.x - cutter.getRadius() )
                return false;
            else
                return true;
            break;
        case 1: // cut along xminus
            if ( node->cutval >= cl.x + cutter.getRadius() )
                return false;
            else
                return true;
            break;
        case 2: // cut along yplus
            if ( node->cutval <= cl.y - cutter.getRadius() )
                return false;
            else
                return true;
            break;
        case 3: // cut along yminus
            if ( node->cutval >= cl.y + cutter.getRadius() )
                return false;
            else
                return true;
            break;
        default:
            assert(0);
    } // end of switch(dim)

    return false;
}

/// returns all triangles under KDNode node in the tree
void KDNode::getTriangles( std::list<Triangle> *tris, KDNode *node)
{
    if (node->tris != NULL) {
        // found a bucket node, add all triangles
        BOOST_FOREACH(Triangle t, *(node->tris)) {
            tris->push_back(t);
        }
        return;
    }
    // not a bucket node, so search recursively high and low:
    KDNode::getTriangles(tris, node->hi);
    KDNode::getTriangles(tris, node->lo);
    return;
}

#define DEBUG_KD_SEARCH

/// search kd-tree starting at KDNode node for triangles.
/// find the ones which overlap (in the xy-plane)
/// with the MillingCutter cutter positioned at  Point cl
/// these triangles are added to the tris list.
void KDNode::search_kdtree( std::list<Triangle>* tris,      // found triangles added to tris
                            const CLPoint &cl,              // cutter positioned at cl
                            const MillingCutter &cutter,    // cutter
                            KDNode *node)                   // start search here and recurse into tree
{
    // we found a bucket node, so add all triangles and return.
    if (node->tris != NULL) {
        //std::cout << "bucket: cl=" << cl << "r=" << cutter.getRadius()
        //          << " len(tris)=" << node->tris->size() << " KDNode:" << *node << "\n";

        //if ( KDNode::overlap(node,cl,cutter) ) {  // check if node overlaps
            #ifdef DEBUG_KD_SEARCH
                 //std::cout << " returning bucket node with len(tris)="<< (*(node->tris)).size() << "\n";
                 //char c;
                 //std::cin >> c;
            #endif
            BOOST_FOREACH( Triangle t, *(node->tris) ) {
                //std::cout << t << "\n";
                //double r = cutter.getRadius();
                //std::cout << "T: " << t.minx << "\t" << t.maxx << "\t" << t.miny << "\t" << t.maxy << "\n";
                //std::cout << "C: " << cl.x-r << "\t" << cl.x+r << "\t" << cl.y-r << "\t" << cl.y+r << "\n";
                //std::cout << "overlap?:" << KDTree::overlap(node,cl,cutter) << "\n";

                // THIS IS PROBABLY WRONG, should not have to do an explicit overlap check here
                // if ( cutter.overlaps(cl,t) ) { // explicit cutter-overlap check
                    tris->push_back(t);
                //}

            } // loop through triangles

        //} // node overlap check

        return;
    } // end bucket-node

    // not a bucket node, so recursevily seach hi/lo branches of KDNode

    #ifdef DEBUG_KD_SEARCH
        //std::cout << "dim=" << node->dim << " cv=" << node->cutval << "\n";
    #endif
    //std::cout << "cl=" << cl << "r=" << cutter.getRadius()
    //              << " Internal KDNode:" << *node << "\n";
    switch(node->dim) { // ugly, solve with polymorphism?
        case 0: // cut along xplus
            // if one child node is not overlapping, search only the other
            if ( node->cutval < ( cl.x - cutter.getRadius() ) ) {
                #ifdef DEBUG_KD_SEARCH
                    //std::cout << " dim=" << node->dim << " branch hi\n";
                    //std::cout << "NO triangles with xmax < " << node-> cutval << "\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->hi); //hi
            }
            else { // else we need to search both child-nodes
                #ifdef DEBUG_KD_SEARCH
                    //std::cout << " dim=" << node->dim << " branch hi AND lo\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->hi);
                KDNode::search_kdtree(tris, cl, cutter, node->lo);
            }
            break;
        case 1: // cut along xminus
            if ( node->cutval > ( cl.x + cutter.getRadius() ) ) {
                #ifdef DEBUG_KD_SEARCH
                    //std::cout << " dim=" << node->dim << " branch lo\n";
                    //std::cout << "NO triangles with xmin < " << node-> cutval << "\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->lo);
            }
            else {
                KDNode::search_kdtree(tris, cl, cutter, node->hi);
                KDNode::search_kdtree(tris, cl, cutter, node->lo);
            }
            break;
        case 2: // cut along yplus
            if ( node->cutval < ( cl.y - cutter.getRadius() ) ) {
                #ifdef DEBUG_KD_SEARCH
                    // std::cout << " dim=" << node->dim << " branch hi\n";
                    //std::cout << "NO triangles with ymax < " << node-> cutval << "\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->hi);
            }
            else {
                #ifdef DEBUG_KD_SEARCH
                    // std::cout << " dim=" << node->dim << " branch hi AND lo\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->hi);
                KDNode::search_kdtree(tris, cl, cutter, node->lo);
            }
            break;
        case 3: // cut along yminus
            if ( node->cutval > ( cl.y + cutter.getRadius() ) ) {
                #ifdef DEBUG_KD_SEARCH
                    //std::cout << " dim=" << node->dim << " branch lo\n";
                    //std::cout << "NO triangles with ymin > " << node-> cutval << "\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->lo);
            }
            else {
                #ifdef DEBUG_KD_SEARCH
                    //std::cout << " dim=" << node->dim << " branch hi AND lo\n";
                #endif
                KDNode::search_kdtree(tris, cl, cutter, node->lo);
                KDNode::search_kdtree(tris, cl, cutter, node->hi);
            }
            break;
        default:
            std::cout << "kdtree.cpp ERROR!\n"; // error
            assert(0);
    } // end of switch(dim)

    // we get here after all the recursive calls above.
    return;

} // end search_kdtree()

//*********** Spread ****************

Spread::Spread(int dim, double v, double s)
{
    d = dim;
    val = v;
    start = s;
}

//********  string output ********************** */
std::string KDNode::str() const
{
    std::ostringstream o;
    o << *this;
    return o.str();
}

std::ostream& operator<<(std::ostream &stream, const KDNode root)
{
    stream << "KDNode d:" << root.dim << " cv:" << root.cutval;
    return stream;
}

} // end namespace
// end file kdtree.cpp


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

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

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

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

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

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Copyright (c) 2003, 2006   Gerald I. Evenden

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

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

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

#ifndef BOOST_GEOMETRY_PROJECTIONS_OMERC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_OMERC_HPP

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

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

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace omerc
    {
            template <typename T>
            struct par_omerc
            {
                T   A, B, E, AB, ArB, BrA, rB, singam, cosgam, sinrot, cosrot;
                T   v_pole_n, v_pole_s, u_0;
                int no_rot;
            };

            static const double tolerance = 1.e-7;
            static const double epsilon = 1.e-10;

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

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

                // FORWARD(e_forward)  ellipsoid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    static const T half_pi = detail::half_pi<T>();

                    T  s, t, U, V, W, temp, u, v;

                    if (fabs(fabs(lp_lat) - half_pi) > epsilon) {
                        W = this->m_proj_parm.E / math::pow(pj_tsfn(lp_lat, sin(lp_lat), this->m_par.e), this->m_proj_parm.B);
                        temp = 1. / W;
                        s = .5 * (W - temp);
                        t = .5 * (W + temp);
                        V = sin(this->m_proj_parm.B * lp_lon);
                        U = (s * this->m_proj_parm.singam - V * this->m_proj_parm.cosgam) / t;
                        if (fabs(fabs(U) - 1.0) < epsilon) {
                            BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                        }
                        v = 0.5 * this->m_proj_parm.ArB * log((1. - U)/(1. + U));
                        temp = cos(this->m_proj_parm.B * lp_lon);
                        if(fabs(temp) < tolerance) {
                            u = this->m_proj_parm.A * lp_lon;
                        } else {
                            u = this->m_proj_parm.ArB * atan2((s * this->m_proj_parm.cosgam + V * this->m_proj_parm.singam), temp);
                        }
                    } else {
                        v = lp_lat > 0 ? this->m_proj_parm.v_pole_n : this->m_proj_parm.v_pole_s;
                        u = this->m_proj_parm.ArB * lp_lat;
                    }
                    if (this->m_proj_parm.no_rot) {
                        xy_x = u;
                        xy_y = v;
                    } else {
                        u -= this->m_proj_parm.u_0;
                        xy_x = v * this->m_proj_parm.cosrot + u * this->m_proj_parm.sinrot;
                        xy_y = u * this->m_proj_parm.cosrot - v * this->m_proj_parm.sinrot;
                    }
                }

                // INVERSE(e_inverse)  ellipsoid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T half_pi = detail::half_pi<T>();

                    T  u, v, Qp, Sp, Tp, Vp, Up;

                    if (this->m_proj_parm.no_rot) {
                        v = xy_y;
                        u = xy_x;
                    } else {
                        v = xy_x * this->m_proj_parm.cosrot - xy_y * this->m_proj_parm.sinrot;
                        u = xy_y * this->m_proj_parm.cosrot + xy_x * this->m_proj_parm.sinrot + this->m_proj_parm.u_0;
                    }
                    Qp = exp(- this->m_proj_parm.BrA * v);
                    Sp = .5 * (Qp - 1. / Qp);
                    Tp = .5 * (Qp + 1. / Qp);
                    Vp = sin(this->m_proj_parm.BrA * u);
                    Up = (Vp * this->m_proj_parm.cosgam + Sp * this->m_proj_parm.singam) / Tp;
                    if (fabs(fabs(Up) - 1.) < epsilon) {
                        lp_lon = 0.;
                        lp_lat = Up < 0. ? -half_pi : half_pi;
                    } else {
                        lp_lat = this->m_proj_parm.E / sqrt((1. + Up) / (1. - Up));
                        if ((lp_lat = pj_phi2(math::pow(lp_lat, T(1) / this->m_proj_parm.B), this->m_par.e)) == HUGE_VAL) {
                            BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                        }
                        lp_lon = - this->m_proj_parm.rB * atan2((Sp * this->m_proj_parm.cosgam -
                            Vp * this->m_proj_parm.singam), cos(this->m_proj_parm.BrA * u));
                    }
                }

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

            };

            // Oblique Mercator
            template <typename Params, typename Parameters, typename T>
            inline void setup_omerc(Params const& params, Parameters& par, par_omerc<T>& proj_parm)
            {
                static const T fourth_pi = detail::fourth_pi<T>();
                static const T half_pi = detail::half_pi<T>();
                static const T pi = detail::pi<T>();
                static const T two_pi = detail::two_pi<T>();

                T con, com, cosph0, D, F, H, L, sinph0, p, J, gamma=0,
                  gamma0, lamc=0, lam1=0, lam2=0, phi1=0, phi2=0, alpha_c=0;
                int alp, gam, no_off = 0;

                proj_parm.no_rot = pj_get_param_b<srs::spar::no_rot>(params, "no_rot", srs::dpar::no_rot);
                alp = pj_param_r<srs::spar::alpha>(params, "alpha", srs::dpar::alpha, alpha_c);
                gam = pj_param_r<srs::spar::gamma>(params, "gamma", srs::dpar::gamma, gamma);
                if (alp || gam) {
                    lamc = pj_get_param_r<T, srs::spar::lonc>(params, "lonc", srs::dpar::lonc);
                    // NOTE: This is not needed in Boost.Geometry
                    //no_off =
                    //            /* For libproj4 compatability */
                    //            pj_param_exists(par.params, "no_off")
                    //            /* for backward compatibility */
                    //            || pj_param_exists(par.params, "no_uoff");
                    //if( no_off )
                    //{
                    //    /* Mark the parameter as used, so that the pj_get_def() return them */
                    //    pj_get_param_s(par.params, "no_uoff");
                    //    pj_get_param_s(par.params, "no_off");
                    //}
                } else {
                    lam1 = pj_get_param_r<T, srs::spar::lon_1>(params, "lon_1", srs::dpar::lon_1);
                    phi1 = pj_get_param_r<T, srs::spar::lat_1>(params, "lat_1", srs::dpar::lat_1);
                    lam2 = pj_get_param_r<T, srs::spar::lon_2>(params, "lon_2", srs::dpar::lon_2);
                    phi2 = pj_get_param_r<T, srs::spar::lat_2>(params, "lat_2", srs::dpar::lat_2);
                    if (fabs(phi1 - phi2) <= tolerance ||
                        (con = fabs(phi1)) <= tolerance ||
                        fabs(con - half_pi) <= tolerance ||
                        fabs(fabs(par.phi0) - half_pi) <= tolerance ||
                        fabs(fabs(phi2) - half_pi) <= tolerance)
                        BOOST_THROW_EXCEPTION( projection_exception(error_lat_0_or_alpha_eq_90) );
                }
                com = sqrt(par.one_es);
                if (fabs(par.phi0) > epsilon) {
                    sinph0 = sin(par.phi0);
                    cosph0 = cos(par.phi0);
                    con = 1. - par.es * sinph0 * sinph0;
                    proj_parm.B = cosph0 * cosph0;
                    proj_parm.B = sqrt(1. + par.es * proj_parm.B * proj_parm.B / par.one_es);
                    proj_parm.A = proj_parm.B * par.k0 * com / con;
                    D = proj_parm.B * com / (cosph0 * sqrt(con));
                    if ((F = D * D - 1.) <= 0.)
                        F = 0.;
                    else {
                        F = sqrt(F);
                        if (par.phi0 < 0.)
                            F = -F;
                    }
                    proj_parm.E = F += D;
                    proj_parm.E *= math::pow(pj_tsfn(par.phi0, sinph0, par.e), proj_parm.B);
                } else {
                    proj_parm.B = 1. / com;
                    proj_parm.A = par.k0;
                    proj_parm.E = D = F = 1.;
                }
                if (alp || gam) {
                    if (alp) {
                        gamma0 = aasin(sin(alpha_c) / D);
                        if (!gam)
                            gamma = alpha_c;
                    } else
                        alpha_c = aasin(D*sin(gamma0 = gamma));
                    par.lam0 = lamc - aasin(.5 * (F - 1. / F) *
                       tan(gamma0)) / proj_parm.B;
                } else {
                    H = math::pow(pj_tsfn(phi1, sin(phi1), par.e), proj_parm.B);
                    L = math::pow(pj_tsfn(phi2, sin(phi2), par.e), proj_parm.B);
                    F = proj_parm.E / H;
                    p = (L - H) / (L + H);
                    J = proj_parm.E * proj_parm.E;
                    J = (J - L * H) / (J + L * H);
                    if ((con = lam1 - lam2) < -pi)
                        lam2 -= two_pi;
                    else if (con > pi)
                        lam2 += two_pi;
                    par.lam0 = adjlon(.5 * (lam1 + lam2) - atan(
                       J * tan(.5 * proj_parm.B * (lam1 - lam2)) / p) / proj_parm.B);
                    gamma0 = atan(2. * sin(proj_parm.B * adjlon(lam1 - par.lam0)) /
                       (F - 1. / F));
                    gamma = alpha_c = aasin(D * sin(gamma0));
                }
                proj_parm.singam = sin(gamma0);
                proj_parm.cosgam = cos(gamma0);
                proj_parm.sinrot = sin(gamma);
                proj_parm.cosrot = cos(gamma);
                proj_parm.BrA = 1. / (proj_parm.ArB = proj_parm.A * (proj_parm.rB = 1. / proj_parm.B));
                proj_parm.AB = proj_parm.A * proj_parm.B;
                if (no_off)
                    proj_parm.u_0 = 0;
                else {
                    proj_parm.u_0 = fabs(proj_parm.ArB * atan(sqrt(D * D - 1.) / cos(alpha_c)));
                    if (par.phi0 < 0.)
                        proj_parm.u_0 = - proj_parm.u_0;
                }
                F = 0.5 * gamma0;
                proj_parm.v_pole_n = proj_parm.ArB * log(tan(fourth_pi - F));
                proj_parm.v_pole_s = proj_parm.ArB * log(tan(fourth_pi + F));
            }

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

    /*!
        \brief Oblique Mercator projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - no_rot: No rotation
         - alpha: Alpha (degrees)
         - gamma: Gamma (degrees)
         - no_off: Only for compatibility with libproj, proj4 (string)
         - lonc: Longitude (only used if alpha (or gamma) is specified) (degrees)
         - lon_1 (degrees)
         - lat_1: Latitude of first standard parallel (degrees)
         - lon_2 (degrees)
         - lat_2: Latitude of second standard parallel (degrees)
         - no_uoff (string)
        \par Example
        \image html ex_omerc.gif
    */
    template <typename T, typename Parameters>
    struct omerc_ellipsoid : public detail::omerc::base_omerc_ellipsoid<T, Parameters>
    {
        template <typename Params>
        inline omerc_ellipsoid(Params const& params, Parameters const& par)
            : detail::omerc::base_omerc_ellipsoid<T, Parameters>(par)
        {
            detail::omerc::setup_omerc(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_omerc, omerc_ellipsoid, omerc_ellipsoid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(omerc_entry, omerc_ellipsoid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(omerc_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(omerc, omerc_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_OMERC_HPP


/*
 * This file belongs to the Galois project, a C++ library for exploiting parallelism.
 * The code is being released under the terms of the 3-Clause BSD License (a
 * copy is located in LICENSE.txt at the top-level directory).
 *
 * Copyright (C) 2018, The University of Texas at Austin. All rights reserved.
 * UNIVERSITY EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES CONCERNING THIS
 * SOFTWARE AND DOCUMENTATION, INCLUDING ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR ANY PARTICULAR PURPOSE, NON-INFRINGEMENT AND WARRANTIES OF
 * PERFORMANCE, AND ANY WARRANTY THAT MIGHT OTHERWISE ARISE FROM COURSE OF
 * DEALING OR USAGE OF TRADE.  NO WARRANTY IS EITHER EXPRESS OR IMPLIED WITH
 * RESPECT TO THE USE OF THE SOFTWARE OR DOCUMENTATION. Under no circumstances
 * shall University be liable for incidental, special, indirect, direct or
 * consequential damages or loss of profits, interruption of business, or
 * related expenses which may arise from use of Software or Documentation,
 * including but not limited to those resulting from defects in Software and/or
 * Documentation, or loss or inaccuracy of data of any kind.
 */

#include "galois/Reduction.h"
#include "galois/Bag.h"
#include "galois/Galois.h"
#include "galois/Timer.h"
#include "galois/graphs/LCGraph.h"
#include "llvm/Support/CommandLine.h"

#include "Lonestar/BoilerPlate.h"

#include <boost/iterator/iterator_adaptor.hpp>

#include <fstream>
#include <iostream>

namespace cll = llvm::cl;

const char* name = "Preflow Push";
const char* desc =
    "Finds the maximum flow in a network using the preflow push technique";
const char* url = "preflow_push";

enum DetAlgo { nondet = 0, detBase, detDisjoint };

static cll::opt<std::string> filename(cll::Positional,
                                      cll::desc("<input file>"), cll::Required);
static cll::opt<uint32_t> sourceId(cll::Positional, cll::desc("sourceID"),
                                   cll::Required);
static cll::opt<uint32_t> sinkId(cll::Positional, cll::desc("sinkID"),
                                 cll::Required);
static cll::opt<bool> useHLOrder("useHLOrder",
                                 cll::desc("Use HL ordering heuristic"),
                                 cll::init(false));
static cll::opt<bool>
    useUnitCapacity("useUnitCapacity",
                    cll::desc("Assume all capacities are unit"),
                    cll::init(false));
static cll::opt<bool> useSymmetricDirectly(
    "useSymmetricDirectly",
    cll::desc("Assume input graph is symmetric and has unit capacities"),
    cll::init(false));
static cll::opt<int>
    relabelInt("relabel",
               cll::desc("relabel interval X: relabel every X iterations "
                         "(default 0 uses default interval)"),
               cll::init(0));
static cll::opt<DetAlgo> detAlgo(
    cll::desc("Deterministic algorithm:"),
    cll::values(clEnumVal(nondet, "Non-deterministic (default)"),
                clEnumVal(detBase, "Base execution"),
                clEnumVal(detDisjoint, "Disjoint execution")),
    cll::init(nondet));

/**
 * Alpha parameter the original Goldberg algorithm to control when global
 * relabeling occurs. For comparison purposes, we keep them the same as
 * before, but it is possible to achieve much better performance by adjusting
 * the global relabel frequency.
 */
static const int ALPHA = 6;

/**
 * Beta parameter the original Goldberg algorithm to control when global
 * relabeling occurs. For comparison purposes, we keep them the same as
 * before, but it is possible to achieve much better performance by adjusting
 * the global relabel frequency.
 */
static const int BETA = 12;

struct Node {
  uint32_t id;
  int64_t excess;
  int height;
  int current;

  Node() : excess(0), height(1), current(0) {}
};

std::ostream& operator<<(std::ostream& os, const Node& n) {
  os << "("
     << "id: " << n.id << ", excess: " << n.excess << ", height: " << n.height
     << ", current: " << n.current << ")";
  return os;
}

using Graph =
    galois::graphs::LC_CSR_Graph<Node, int32_t>::with_numa_alloc<false>::type;
using GNode   = Graph::GraphNode;
using Counter = galois::GAccumulator<int>;

struct PreflowPush {

  Graph graph;
  GNode sink;
  GNode source;
  int global_relabel_interval;
  bool should_global_relabel = false;
  galois::LargeArray<Graph::edge_iterator>
      reverseDirectionEdgeIterator; // ideally should be on the graph as
                                    // graph.getReverseEdgeIterator()

  void reduceCapacity(const Graph::edge_iterator& ii, const GNode& src,
                      const GNode& dst, int64_t amount) {
    Graph::edge_data_type& cap1 = graph.getEdgeData(ii);
    Graph::edge_data_type& cap2 =
        graph.getEdgeData(reverseDirectionEdgeIterator[*ii]);
    cap1 -= amount;
    cap2 += amount;
  }

  Graph::edge_iterator findEdge(GNode src, GNode dst) {

    auto i     = graph.edge_begin(src, galois::MethodFlag::UNPROTECTED);
    auto end_i = graph.edge_end(src, galois::MethodFlag::UNPROTECTED);

    if ((end_i - i) < 32) {
      return findEdgeLinear(dst, i, end_i);

    } else {
      return findEdgeLog2(dst, i, end_i);
    }
  }

  Graph::edge_iterator findEdgeLinear(GNode dst, Graph::edge_iterator beg_e,
                                      Graph::edge_iterator end_e) {

    auto ii = beg_e;
    for (; ii != end_e; ++ii) {
      if (graph.getEdgeDst(ii) == dst)
        break;
    }
    assert(ii != end_e); // Never return the end iterator
    return ii;
  }

  Graph::edge_iterator findEdgeLog2(GNode dst, Graph::edge_iterator i,
                                    Graph::edge_iterator end_i) {

    struct EdgeDstIter
        : public boost::iterator_facade<
              EdgeDstIter, GNode, boost::random_access_traversal_tag, GNode> {
      Graph* g;
      Graph::edge_iterator ei;

      EdgeDstIter(void) : g(nullptr) {}

      EdgeDstIter(Graph* g, Graph::edge_iterator ei) : g(g), ei(ei) {}

    private:
      friend boost::iterator_core_access;

      GNode dereference(void) const { return g->getEdgeDst(ei); }

      void increment(void) { ++ei; }

      void decrement(void) { --ei; }

      bool equal(const EdgeDstIter& that) const {
        assert(this->g == that.g);
        return this->ei == that.ei;
      }

      void advance(ptrdiff_t n) { ei += n; }

      ptrdiff_t distance_to(const EdgeDstIter& that) const {
        assert(this->g == that.g);

        return that.ei - this->ei;
      }
    };

    EdgeDstIter ai(&graph, i);
    EdgeDstIter end_ai(&graph, end_i);

    auto ret = std::lower_bound(ai, end_ai, dst);

    assert(ret != end_ai);
    assert(*ret == dst);

    return ret.ei;
  }

  void acquire(const GNode& src) {
    // LC Graphs have a different idea of locking
    for (auto ii : graph.edges(src, galois::MethodFlag::WRITE)) {
      GNode dst = graph.getEdgeDst(ii);
      graph.getData(dst, galois::MethodFlag::WRITE);
    }
  }

  void relabel(const GNode& src) {
    int minHeight = std::numeric_limits<int>::max();
    int minEdge   = 0;

    int current = 0;
    for (auto ii : graph.edges(src, galois::MethodFlag::UNPROTECTED)) {
      GNode dst   = graph.getEdgeDst(ii);
      int64_t cap = graph.getEdgeData(ii);
      if (cap > 0) {
        const Node& dnode = graph.getData(dst, galois::MethodFlag::UNPROTECTED);
        if (dnode.height < minHeight) {
          minHeight = dnode.height;
          minEdge   = current;
        }
      }
      ++current;
    }

    assert(minHeight != std::numeric_limits<int>::max());
    ++minHeight;

    Node& node = graph.getData(src, galois::MethodFlag::UNPROTECTED);
    if (minHeight < (int)graph.size()) {
      node.height  = minHeight;
      node.current = minEdge;
    } else {
      node.height = graph.size();
    }
  }

  template <typename C>
  bool discharge(const GNode& src, C& ctx) {
    // Node& node = graph.getData(src, galois::MethodFlag::WRITE);
    Node& node = graph.getData(src, galois::MethodFlag::UNPROTECTED);
    // int prevHeight = node.height;
    bool relabeled = false;

    if (node.excess == 0 || node.height >= (int)graph.size()) {
      return false;
    }

    while (true) {
      // galois::MethodFlag flag = relabeled ? galois::MethodFlag::UNPROTECTED :
      // galois::MethodFlag::WRITE;
      galois::MethodFlag flag = galois::MethodFlag::UNPROTECTED;
      bool finished           = false;
      int current             = node.current;

      auto ii = graph.edge_begin(src, flag);
      auto ee = graph.edge_end(src, flag);

      std::advance(ii, node.current);

      for (; ii != ee; ++ii, ++current) {
        GNode dst   = graph.getEdgeDst(ii);
        int64_t cap = graph.getEdgeData(ii);
        if (cap == 0) // || current < node.current)
          continue;

        Node& dnode = graph.getData(dst, galois::MethodFlag::UNPROTECTED);
        if (node.height - 1 != dnode.height)
          continue;

        // Push flow
        int64_t amount = std::min(node.excess, cap);
        reduceCapacity(ii, src, dst, amount);

        // Only add once
        if (dst != sink && dst != source && dnode.excess == 0)
          ctx.push(dst);

        assert(node.excess >= amount);
        node.excess -= amount;
        dnode.excess += amount;

        if (node.excess == 0) {
          finished     = true;
          node.current = current;
          break;
        }
      }

      if (finished)
        break;

      relabel(src);
      relabeled = true;

      if (node.height == (int)graph.size())
        break;

      // prevHeight = node.height;
    }

    return relabeled;
  }

  template <DetAlgo version>
  void detDischarge(galois::InsertBag<GNode>& initial, Counter& counter) {
    typedef galois::worklists::Deterministic<> DWL;

    auto detIDfn = [this](const GNode& item) -> uint32_t {
      return graph.getData(item, galois::MethodFlag::UNPROTECTED).id;
    };

    const int relabel_interval =
        global_relabel_interval / galois::getActiveThreads();

    auto detBreakFn = [&, this](void) -> bool {
      if (this->global_relabel_interval > 0 &&
          counter.peekLocal() >= relabel_interval) {
        this->should_global_relabel = true;
        return true;
      } else {
        return false;
      }
    };

    galois::for_each(
        galois::iterate(initial),
        [&, this](GNode& src, auto& ctx) {
          if (version != nondet) {
            if (ctx.isFirstPass()) {
              this->acquire(src);
            }
            if (version == detDisjoint && ctx.isFirstPass()) {
              return;
            } else {
              this->graph.getData(src, galois::MethodFlag::WRITE);
              ctx.cautiousPoint();
            }
          }

          int increment = 1;
          if (this->discharge(src, ctx)) {
            increment += BETA;
          }

          counter += increment;
        },
        galois::loopname("detDischarge"), galois::wl<DWL>(),
        galois::per_iter_alloc(), galois::det_id<decltype(detIDfn)>(detIDfn),
        galois::det_parallel_break<decltype(detBreakFn)>(detBreakFn));
  }

  template <typename W>
  void nonDetDischarge(galois::InsertBag<GNode>& initial, Counter& counter,
                       const W& wl_opt) {

    // per thread
    const int relabel_interval =
        global_relabel_interval / galois::getActiveThreads();

    galois::for_each(
        galois::iterate(initial),
        [&counter, relabel_interval, this](GNode& src, auto& ctx) {
          int increment = 1;
          this->acquire(src);
          if (this->discharge(src, ctx)) {
            increment += BETA;
          }

          counter += increment;
          if (this->global_relabel_interval > 0 &&
              counter.peekLocal() >= relabel_interval) { // local check

            this->should_global_relabel = true;
            ctx.breakLoop();
            return;
          }
        },
        galois::loopname("nonDetDischarge"), galois::parallel_break(), wl_opt);
  }

  /**
   * Do reverse BFS on residual graph.
   */
  template <DetAlgo version, typename WL, bool useCAS = true>
  void updateHeights() {

    galois::for_each(
        galois::iterate({sink}),
        [&, this](const GNode& src, auto& ctx) {
          if (version != nondet) {

            if (ctx.isFirstPass()) {
              for (auto ii :
                   this->graph.edges(src, galois::MethodFlag::WRITE)) {
                GNode dst = this->graph.getEdgeDst(ii);
                int64_t rdata =
                    this->graph.getEdgeData(reverseDirectionEdgeIterator[*ii]);
                if (rdata > 0) {
                  this->graph.getData(dst, galois::MethodFlag::WRITE);
                }
              }
            }

            if (version == detDisjoint && ctx.isFirstPass()) {
              return;
            } else {
              this->graph.getData(src, galois::MethodFlag::WRITE);
              ctx.cautiousPoint();
            }
          }

          for (auto ii :
               this->graph.edges(src, useCAS ? galois::MethodFlag::UNPROTECTED
                                             : galois::MethodFlag::WRITE)) {
            GNode dst = this->graph.getEdgeDst(ii);
            int64_t rdata =
                this->graph.getEdgeData(reverseDirectionEdgeIterator[*ii]);
            if (rdata > 0) {
              Node& node =
                  this->graph.getData(dst, galois::MethodFlag::UNPROTECTED);
              int newHeight =
                  this->graph.getData(src, galois::MethodFlag::UNPROTECTED)
                      .height +
                  1;
              if (useCAS) {
                int oldHeight;
                while (newHeight < (oldHeight = node.height)) {
                  if (__sync_bool_compare_and_swap(&node.height, oldHeight,
                                                   newHeight)) {
                    ctx.push(dst);
                    break;
                  }
                }
              } else {
                if (newHeight < node.height) {
                  node.height = newHeight;
                  ctx.push(dst);
                }
              }
            }
          } // end for
        },
        galois::wl<WL>(), galois::no_conflicts(),
        galois::loopname("updateHeights"));
  }

  template <typename IncomingWL>
  void globalRelabel(IncomingWL& incoming) {

    galois::do_all(galois::iterate(graph),
                   [&](const GNode& src) {
                     Node& node =
                         graph.getData(src, galois::MethodFlag::UNPROTECTED);
                     node.height  = graph.size();
                     node.current = 0;
                     if (src == sink)
                       node.height = 0;
                   },
                   galois::loopname("ResetHeights"));

    using BSWL = galois::worklists::BulkSynchronous<>;
    using DWL  = galois::worklists::Deterministic<>;
    switch (detAlgo) {
    case nondet:
      updateHeights<nondet, BSWL>();
      break;
    case detBase:
      updateHeights<detBase, DWL>();
      break;
    case detDisjoint:
      updateHeights<detDisjoint, DWL>();
      break;
    default:
      std::cerr << "Unknown algorithm" << detAlgo << "\n";
      abort();
    }

    galois::do_all(galois::iterate(graph),
                   [&incoming, this](const GNode& src) {
                     Node& node = this->graph.getData(
                         src, galois::MethodFlag::UNPROTECTED);
                     if (src == this->sink || src == this->source ||
                         node.height >= (int)this->graph.size())
                       return;
                     if (node.excess > 0)
                       incoming.push_back(src);
                   },
                   galois::loopname("FindWork"));
  }

  template <typename C>
  void initializePreflow(C& initial) {
    for (auto ii : graph.edges(source)) {
      GNode dst   = graph.getEdgeDst(ii);
      int64_t cap = graph.getEdgeData(ii);
      reduceCapacity(ii, source, dst, cap);
      Node& node = graph.getData(dst);
      node.excess += cap;
      if (cap > 0)
        initial.push_back(dst);
    }
  }

  void run() {
    Graph *captured_graph = &graph;
    auto obimIndexer = [=](const GNode& n) {
      return -captured_graph->getData(n, galois::MethodFlag::UNPROTECTED).height;
    };

    typedef galois::worklists::PerSocketChunkFIFO<16> Chunk;
    typedef galois::worklists::OrderedByIntegerMetric<decltype(obimIndexer),
                                                      Chunk>
        OBIM;

    galois::InsertBag<GNode> initial;
    initializePreflow(initial);

    while (initial.begin() != initial.end()) {
      galois::StatTimer T_discharge("DischargeTime");
      T_discharge.start();
      Counter counter;
      switch (detAlgo) {
      case nondet:
        if (useHLOrder) {
          nonDetDischarge(initial, counter, galois::wl<OBIM>(obimIndexer));
        } else {
          nonDetDischarge(initial, counter, galois::wl<Chunk>());
        }
        break;
      case detBase:
        detDischarge<detBase>(initial, counter);
        break;
      case detDisjoint:
        detDischarge<detDisjoint>(initial, counter);
        break;
      default:
        std::cerr << "Unknown algorithm" << detAlgo << "\n";
        abort();
      }
      T_discharge.stop();

      if (should_global_relabel) {
        galois::StatTimer T_global_relabel("GlobalRelabelTime");
        T_global_relabel.start();
        initial.clear();
        globalRelabel(initial);
        should_global_relabel = false;
        std::cout << " Flow after global relabel: "
                  << graph.getData(sink).excess << "\n";
        T_global_relabel.stop();
      } else {
        break;
      }
    }
  }

  template <typename EdgeTy>
  static void writePfpGraph(const std::string& inputFile,
                            const std::string& outputFile) {
    typedef galois::graphs::FileGraph ReaderGraph;
    typedef ReaderGraph::GraphNode ReaderGNode;

    ReaderGraph reader;
    reader.fromFile(inputFile);

    typedef galois::graphs::FileGraphWriter Writer;
    typedef galois::LargeArray<EdgeTy> EdgeData;
    typedef typename EdgeData::value_type edge_value_type;

    Writer p;
    EdgeData edgeData;

    // Count edges
    size_t numEdges = 0;
    for (ReaderGraph::iterator ii = reader.begin(), ei = reader.end(); ii != ei;
         ++ii) {
      ReaderGNode rsrc = *ii;
      for (auto jj : reader.edges(rsrc)) {
        ReaderGNode rdst = reader.getEdgeDst(jj);
        if (rsrc == rdst)
          continue;
        if (!reader.hasNeighbor(rdst, rsrc))
          ++numEdges;
        ++numEdges;
      }
    }

    p.setNumNodes(reader.size());
    p.setNumEdges(numEdges);
    p.setSizeofEdgeData(sizeof(edge_value_type));

    p.phase1();
    for (ReaderGraph::iterator ii = reader.begin(), ei = reader.end(); ii != ei;
         ++ii) {
      ReaderGNode rsrc = *ii;
      for (auto jj : reader.edges(rsrc)) {
        ReaderGNode rdst = reader.getEdgeDst(jj);
        if (rsrc == rdst)
          continue;
        if (!reader.hasNeighbor(rdst, rsrc))
          p.incrementDegree(rdst);
        p.incrementDegree(rsrc);
      }
    }

    EdgeTy one = 1;
    static_assert(sizeof(one) == sizeof(uint32_t), "Unexpected edge data size");
    one = galois::convert_le32toh(one);

    p.phase2();
    edgeData.create(numEdges);
    for (ReaderGraph::iterator ii = reader.begin(), ei = reader.end(); ii != ei;
         ++ii) {
      ReaderGNode rsrc = *ii;
      for (auto jj : reader.edges(rsrc)) {
        ReaderGNode rdst = reader.getEdgeDst(jj);
        if (rsrc == rdst)
          continue;
        if (!reader.hasNeighbor(rdst, rsrc))
          edgeData.set(p.addNeighbor(rdst, rsrc), 0);
        EdgeTy cap = useUnitCapacity ? one : reader.getEdgeData<EdgeTy>(jj);
        edgeData.set(p.addNeighbor(rsrc, rdst), cap);
      }
    }

    edge_value_type* rawEdgeData = p.finish<edge_value_type>();
    std::uninitialized_copy(std::make_move_iterator(edgeData.begin()),
                            std::make_move_iterator(edgeData.end()),
                            rawEdgeData);

    using Wnode = Writer::GraphNode;

    struct IdLess {
      bool operator()(
          const galois::graphs::EdgeSortValue<Wnode, edge_value_type>& e1,
          const galois::graphs::EdgeSortValue<Wnode, edge_value_type>& e2)
          const {
        return e1.dst < e2.dst;
      }
    };

    for (Writer::iterator i = p.begin(), end_i = p.end(); i != end_i; ++i) {
      p.sortEdges<edge_value_type>(*i, IdLess());
    }

    p.toFile(outputFile);
  }

  void initializeGraph(std::string inputFile, uint32_t sourceId,
                       uint32_t sinkId) {
    if (useSymmetricDirectly) {
      galois::graphs::readGraph(graph, inputFile);
      for (auto ss : graph)
        for (auto ii : graph.edges(ss))
          graph.getEdgeData(ii) = 1;
    } else {
      if (inputFile.find(".gr.pfp") != inputFile.size() - strlen(".gr.pfp")) {
        std::string pfpName = inputFile + ".pfp";
        std::ifstream pfpFile(pfpName.c_str());
        if (!pfpFile.good()) {
          galois::gPrint("Writing new input file: ", pfpName, "\n");
          writePfpGraph<Graph::edge_data_type>(inputFile, pfpName);
        }
        inputFile = pfpName;
      }
      galois::gPrint("Reading graph: ", inputFile, "\n");
      galois::graphs::readGraph(graph, inputFile);

      // Assume that input edge data has already been converted instead
#if 0 // def HAVE_BIG_ENDIAN
      // Convert edge data to host ordering
      for (auto ss : newApp->graph) {
        for (auto ii : newApp->graph.edges(ss)) {
          Graph::edge_data_type& cap = newApp->graph.getEdgeData(ii);
          static_assert(sizeof(cap) == sizeof(uint32_t), "Unexpected edge data size");
          cap = galois::convert_le32toh(cap);
        }
      }
#endif
    }

    if (sourceId == sinkId || sourceId >= graph.size() ||
        sinkId >= graph.size()) {
      std::cerr << "invalid source or sink id\n";
      abort();
    }

    uint32_t id = 0;
    for (Graph::iterator ii = graph.begin(), ei = graph.end(); ii != ei;
         ++ii, ++id) {
      if (id == sourceId) {
        source                       = *ii;
        graph.getData(source).height = graph.size();
      } else if (id == sinkId) {
        sink = *ii;
      }
      graph.getData(*ii).id = id;
    }

    reverseDirectionEdgeIterator.allocateInterleaved(graph.sizeEdges());
    // memoize the reverse direction edge-iterators
    galois::do_all(galois::iterate(graph.begin(), graph.end()),
                   [&, this](const GNode& src) {
                     for (auto ii : this->graph.edges(
                              src, galois::MethodFlag::UNPROTECTED)) {
                       GNode dst = this->graph.getEdgeDst(ii);
                       reverseDirectionEdgeIterator[*ii] =
                           this->findEdge(dst, src);
                     }
                   },
                   galois::loopname("FindReverseDirectionEdges"));
  }

  void checkSorting(void) {
    for (auto n : graph) {
      galois::optional<GNode> prevDst;
      for (auto e : graph.edges(n, galois::MethodFlag::UNPROTECTED)) {
        GNode dst = graph.getEdgeDst(e);
        if (prevDst.is_initialized()) {
          Node& prevNode =
              graph.getData(*prevDst, galois::MethodFlag::UNPROTECTED);
          Node& currNode = graph.getData(dst, galois::MethodFlag::UNPROTECTED);
          GALOIS_ASSERT(prevNode.id != currNode.id,
                        "Adjacency list cannot have duplicates");
          GALOIS_ASSERT(prevNode.id <= currNode.id, "Adjacency list unsorted");
        }
        prevDst = dst;
      }
    }
  }

  void checkAugmentingPath() {
    // Use id field as visited flag
    for (Graph::iterator ii = graph.begin(), ee = graph.end(); ii != ee; ++ii) {
      GNode src             = *ii;
      graph.getData(src).id = 0;
    }

    std::deque<GNode> queue;

    graph.getData(source).id = 1;
    queue.push_back(source);

    while (!queue.empty()) {
      GNode& src = queue.front();
      queue.pop_front();
      for (auto ii : graph.edges(src)) {
        GNode dst = graph.getEdgeDst(ii);
        if (graph.getData(dst).id == 0 && graph.getEdgeData(ii) > 0) {
          graph.getData(dst).id = 1;
          queue.push_back(dst);
        }
      }
    }

    if (graph.getData(sink).id != 0) {
      assert(false && "Augmenting path exisits");
      abort();
    }
  }

  void checkHeights() {
    for (Graph::iterator ii = graph.begin(), ei = graph.end(); ii != ei; ++ii) {
      GNode src = *ii;
      int sh    = graph.getData(src).height;
      for (auto jj : graph.edges(src)) {
        GNode dst   = graph.getEdgeDst(jj);
        int64_t cap = graph.getEdgeData(jj);
        int dh      = graph.getData(dst).height;
        if (cap > 0 && sh > dh + 1) {
          std::cerr << "height violated at " << graph.getData(src) << "\n";
          abort();
        }
      }
    }
  }

  void checkConservation(PreflowPush& orig) {
    std::vector<GNode> map;
    map.resize(graph.size());

    // Setup ids assuming same iteration order in both graphs
    uint32_t id = 0;
    for (Graph::iterator ii = graph.begin(), ei = graph.end(); ii != ei;
         ++ii, ++id) {
      graph.getData(*ii).id = id;
    }
    id = 0;
    for (Graph::iterator ii = orig.graph.begin(), ei = orig.graph.end();
         ii != ei; ++ii, ++id) {
      orig.graph.getData(*ii).id = id;
      map[id]                    = *ii;
    }

    // Now do some checking
    for (Graph::iterator ii = graph.begin(), ei = graph.end(); ii != ei; ++ii) {
      GNode src        = *ii;
      const Node& node = graph.getData(src);
      uint32_t srcId   = node.id;

      if (src == source || src == sink)
        continue;

      if (node.excess != 0 && node.height != (int)graph.size()) {
        std::cerr << "Non-zero excess at " << node << "\n";
        abort();
      }

      int64_t sum = 0;
      for (auto jj : graph.edges(src)) {
        GNode dst      = graph.getEdgeDst(jj);
        uint32_t dstId = graph.getData(dst).id;
        int64_t ocap =
            orig.graph.getEdgeData(orig.findEdge(map[srcId], map[dstId]));
        int64_t delta = 0;
        if (ocap > 0)
          delta -= (ocap - graph.getEdgeData(jj));
        else
          delta += graph.getEdgeData(jj);
        sum += delta;
      }

      if (node.excess != sum) {
        std::cerr << "Not pseudoflow: " << node.excess << " != " << sum
                  << " at " << node << "\n";
        abort();
      }
    }
  }

  void verify(PreflowPush& orig) {
    // FIXME: doesn't fully check result
    checkHeights();
    checkConservation(orig);
    checkAugmentingPath();
  }
};

int main(int argc, char** argv) {
  galois::SharedMemSys G;
  LonestarStart(argc, argv, name, desc, url);

  PreflowPush app;
  app.initializeGraph(filename, sourceId, sinkId);

  app.checkSorting();

  if (relabelInt == 0) {
    app.global_relabel_interval =
        app.graph.size() * ALPHA + app.graph.sizeEdges() / 3;
  } else {
    app.global_relabel_interval = relabelInt;
  }
  std::cout << "Number of nodes: " << app.graph.size() << "\n";
  std::cout << "Global relabel interval: " << app.global_relabel_interval
            << "\n";

  galois::StatTimer T;
  galois::preAlloc(numThreads * app.graph.size() /
                   galois::runtime::pagePoolSize());
  galois::reportPageAlloc("MeminfoPre");
  T.start();
  app.run();
  T.stop();
  galois::reportPageAlloc("MeminfoPost");

  std::cout << "Flow is " << app.graph.getData(app.sink).excess << "\n";

  if (!skipVerify) {
    PreflowPush orig;
    orig.initializeGraph(filename, sourceId, sinkId);
    app.verify(orig);
    std::cout << "(Partially) Verified\n";
  }

  return 0;
}


/*
 * This file is part of the statismo library.
 *
 * Author: Marcel Luethi (marcel.luethi@unibas.ch)
 *
 * Copyright (c) 2011 University of Basel
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the project's author nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

// This example shows how a partial shape can be reconstructed using a statistical shape model.
//
// WARNING: This example assumes that the closest point of the partial shape to the model mean is also its
// corresponding point. This is only true if the partial shape is close to the model mean. If this is not the
// case, a more sophisticated method for establishing correspondence needs to be used.
//
#include <iostream>
#include <boost/scoped_ptr.hpp>

#include <vtkPolyData.h>
#include <vtkPolyDataReader.h>
#include <vtkPolyDataWriter.h>
#include <vtkVersion.h>

#include "DataManager.h"
#include "PosteriorModelBuilder.h"
#include "StatisticalModel.h"
#include "StatismoIO.h"

#include "vtkStandardMeshRepresenter.h"

typedef statismo::VectorType VectorType;
typedef statismo::MatrixType MatrixType;
typedef statismo::vtkStandardMeshRepresenter RepresenterType;
typedef statismo::StatisticalModel<vtkPolyData> StatisticalModelType;
typedef statismo::PosteriorModelBuilder<vtkPolyData> PosteriorModelBuilderType;
typedef StatisticalModelType::DomainType DomainType;
typedef DomainType::DomainPointsListType::const_iterator DomainPointsConstIterator;

vtkPolyData* loadVTKPolyData(const std::string& filename) {
    vtkPolyDataReader* reader = vtkPolyDataReader::New();
    reader->SetFileName(filename.c_str());
    reader->Update();
    vtkPolyData* pd = vtkPolyData::New();
    pd->DeepCopy(reader->GetOutput());
    reader->Delete();
    return pd;
}

/**
  * Computes the mahalanobis distance of the targetPt, to the model point with the given pointId.
  */
double mahalanobisDistance(const StatisticalModelType* model, unsigned ptId, const statismo::vtkPoint& targetPt) {
    statismo::MatrixType cov = model->GetCovarianceAtPoint(ptId, ptId);
    statismo::vtkPoint meanPt = model->DrawMeanAtPoint(ptId);
    unsigned pointDim = model->GetRepresenter()->GetDimensions();
    assert(pointDim <= 3);

    VectorType x = VectorType::Zero(pointDim);
    for (unsigned d = 0; d < pointDim; d++) {
        x(d) = targetPt[d] - meanPt[d];
    }
    return x.transpose() * cov.inverse() * x;
}

/**
  * Given the inputModel and a partialMesh, the program outputs the posterior model (a statistical model) and
  * its mean, which is the best reconstruction of the partial shape given the model.
 */
int main(int argc, char** argv) {

    if (argc < 5) {
        std::cout << "Usage " << argv[0] << " inputModel  partialShapeMesh posteriorModel reconstructedShape" << std::endl;
        exit(-1);
    }

    std::string inputModelName(argv[1]);
    std::string partialShapeMeshName(argv[2]);
    std::string posteriorModelName(argv[3]);
    std::string reconstructedShapeName(argv[4]);

    try {

        vtkPolyData* partialShape = loadVTKPolyData(partialShapeMeshName);

        RepresenterType* representer = RepresenterType::Create();
        boost::scoped_ptr<StatisticalModelType> inputModel(
                statismo::IO<vtkPolyData>::LoadStatisticalModel(representer, inputModelName));
        vtkPolyData* refPd = const_cast<vtkPolyData*>(inputModel->GetRepresenter()->GetReference());

        StatisticalModelType::PointValueListType constraints;

        // for each point we get the closest point and check how far it is away (in mahalanobis distance).
        // If it is close, we add it as a constraint, otherwise we ignore the remaining points.
        const DomainType::DomainPointsListType& domainPoints = inputModel->GetDomain().GetDomainPoints();
        for (unsigned ptId = 0; ptId < domainPoints.size(); ptId++) {
            statismo::vtkPoint domainPoint = domainPoints[ptId];

            unsigned closestPointId = ptId;
            statismo::vtkPoint closestPointOnPartialShape = partialShape->GetPoint(closestPointId);
            double mhdist = mahalanobisDistance(inputModel.get(), ptId, closestPointOnPartialShape);
            if (mhdist < 5) {
                StatisticalModelType::PointValuePairType ptWithTargetPt(domainPoint, closestPointOnPartialShape);
                constraints.push_back(ptWithTargetPt);
            }
        }

        // build the new model. In addition to the input model and the constraints, we also specify
        // the inaccuracy of our value (variance of the error).

        boost::scoped_ptr<PosteriorModelBuilderType> posteriorModelBuilder(PosteriorModelBuilderType::Create());
        boost::scoped_ptr<StatisticalModelType> constraintModel(posteriorModelBuilder->BuildNewModelFromModel(inputModel.get(), constraints, 0.5));

        // The resulting model is a normal statistical model, from which we could for example sample examples.
        // Here we simply  save it to disk for later use.
        statismo::IO<vtkPolyData>::SaveStatisticalModel(constraintModel.get(), posteriorModelName);
        std::cout << "successfully saved the model to " << posteriorModelName << std::endl;

        // The mean of the constraint model is the optimal reconstruction
        vtkPolyData* pmean = constraintModel->DrawMean();
        vtkPolyDataWriter* writer = vtkPolyDataWriter::New();
#if (VTK_MAJOR_VERSION == 5 )
        writer->SetInput(pmean);
#else
        writer->SetInputData(pmean);
#endif
        writer->SetFileName(reconstructedShapeName.c_str());
        writer->Update();
    } catch (statismo::StatisticalModelException& e) {
        std::cout << "Exception occured while building the intenisity model" << std::endl;
        std::cout << e.what() << std::endl;
    }
}


#pragma once

#include <armadillo>

#include "heattransfer/radial.hpp"

class Pipeline;
class PipeWall;
class BurialMedium;
class AmbientFluid;

/*!
 * \brief Class that implements steady state heat transfer between gas and
 * pipeline surroundings.
 *
 * This is well documented in Jan Fredrik Helgaker's PhD thesis.
 */
class SteadyStateHeatTransfer : public RadialHeatTransfer
{
public:
    /*!
     * \brief Construct from full description of pipeline.
     * \param diameter Inner diameter [m]
     * \param pipeWall PipeWall instance
     * \param burialDepth Distance from top of pipe to top of burial medium [m]
     * \param burialMedium BurialMedium instace
     * \param ambientFluid AmbientFluid instance
     */
    SteadyStateHeatTransfer(
            const double diameter,
            const PipeWall& pipeWall,
            const double burialDepth,
            const BurialMedium& burialMedium,
            const AmbientFluid& ambientFluid);

    /*!
     * \brief Constructor with default pipe wall, burial medium and ambient medium.
     * \param diameter Inner diameter [m]
     * \param burialDepth Distance from top of pipe to top of burial medium [m]
     */
    SteadyStateHeatTransfer(
            const double diameter = 1.0,
            const double burialDepth = 1.0);

    /*!
     * \brief Evaluate 1d radial steady state heat transfer.
     *
     * Operates on a HeatTransferState and returns a new HeatTransferState,
     * but does not require discretization temperature.
     *
     * \param current Current HeatTransferState
     * \param timeStep Time step [s]
     * \param ambientTemperature Ambient temperature [K]
     * \param gasPressure Gas pressure [Pa]
     * \param gasTemperature Gas temperature [K]
     * \param gasReynoldsNumber Reynolds number of gas [-]
     * \param gasHeatCapacity Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return HeatTransferState with new heat flux.
     */
    virtual HeatTransferState evaluate(
            const HeatTransferState& current,
            const double timeStep,
            const double ambientTemperature,
            const double gasPressure,
            const double gasTemperature,
            const double gasReynoldsNumber,
            const double gasHeatCapacity,
            const double gasViscosity) const override;

    /*!
     * \brief Internal method used for evaluating steady state heat transfer.
     *
     * This is exposed for testing purposes. We typically use pointers anyway,
     * so this is not accessible without casting to SteadyStateHeatTransfer.
     *
     * \param ambientTemperature Ambient temperature [K]
     * \param gasPressure Gas pressure [Pa]
     * \param gasTemperature Gas temperature [K]
     * \param gasReynoldsNumber Reynolds number of gas [-]
     * \param gasHeatCapacity Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return HeatTransferState with new heat flux.
     */
    HeatTransferState evaluateInternal(
            const double ambientTemperature,
            const double gasPressure,
            const double gasTemperature,
            const double gasReynoldsNumber,
            const double gasHeatCapacity,
            const double gasViscosity) const;

    /*!
     * \brief Calculate the total heat transfer coefficient U.
     *
     * This is exposed for testing purposes. We typically use pointers anyway,
     * so this is not accessible without casting to SteadyStateHeatTransfer.
     *
     * \param gasPressure Gas pressure [Pa]
     * \param gasReynoldsNumber Gas Reynolds number [-]
     * \param gasHeatCapacityConstantPressure Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return Total heat transfer coefficient U [W/(m2 K)]
     */
    double calculateHeatTransferCoefficient(
            const double gasPressure,
            const double gasReynoldsNumber,
            const double gasHeatCapacityConstantPressure,
            const double gasViscosity) const;

    //! Get the total heat transfer coefficient of all radial discretization shells.
    //! Does not include the inner film coefficient.
    double getOverallHeatTransferCoefficient() const { return m_overallHeatTransferCoefficient; }
    //! Get the total thermal resistance of all radial discretization shells.
    //! Does not include the inner film coefficient.
    double getOverallThermalResistance() const { return m_overallThermalResistance; }

private:
    //! Total heat transfer coefficient of all radial discretization shells.
    //! Does not include the inner film coefficient.
    double m_overallHeatTransferCoefficient;

    //! Total thermal resistance of all radial discretization shells.
    //! Does not include the inner film coefficient.
    double m_overallThermalResistance;
};


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

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

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

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

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

// Last updated version of proj: 5.0.0

// Original copyright notice:

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

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

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

#ifndef BOOST_GEOMETRY_PROJECTIONS_OEA_HPP
#define BOOST_GEOMETRY_PROJECTIONS_OEA_HPP

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

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

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace oea
    {
            template <typename T>
            struct par_oea
            {
                T    theta;
                T    m, n;
                T    two_r_m, two_r_n, rm, rn, hm, hn;
                T    cp0, sp0;
            };

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

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

                // FORWARD(s_forward)  sphere
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T Az, M, N, cp, sp, cl, shz;

                    cp = cos(lp_lat);
                    sp = sin(lp_lat);
                    cl = cos(lp_lon);
                    Az = aatan2(cp * sin(lp_lon), this->m_proj_parm.cp0 * sp - this->m_proj_parm.sp0 * cp * cl) + this->m_proj_parm.theta;
                    shz = sin(0.5 * aacos(this->m_proj_parm.sp0 * sp + this->m_proj_parm.cp0 * cp * cl));
                    M = aasin(shz * sin(Az));
                    N = aasin(shz * cos(Az) * cos(M) / cos(M * this->m_proj_parm.two_r_m));
                    xy_y = this->m_proj_parm.n * sin(N * this->m_proj_parm.two_r_n);
                    xy_x = this->m_proj_parm.m * sin(M * this->m_proj_parm.two_r_m) * cos(N) / cos(N * this->m_proj_parm.two_r_n);
                }

                // INVERSE(s_inverse)  sphere
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    T N, M, xp, yp, z, Az, cz, sz, cAz;

                    N = this->m_proj_parm.hn * aasin(xy_y * this->m_proj_parm.rn);
                    M = this->m_proj_parm.hm * aasin(xy_x * this->m_proj_parm.rm * cos(N * this->m_proj_parm.two_r_n) / cos(N));
                    xp = 2. * sin(M);
                    yp = 2. * sin(N) * cos(M * this->m_proj_parm.two_r_m) / cos(M);
                    cAz = cos(Az = aatan2(xp, yp) - this->m_proj_parm.theta);
                    z = 2. * aasin(0.5 * boost::math::hypot(xp, yp));
                    sz = sin(z);
                    cz = cos(z);
                    lp_lat = aasin(this->m_proj_parm.sp0 * cz + this->m_proj_parm.cp0 * sz * cAz);
                    lp_lon = aatan2(sz * sin(Az),
                        this->m_proj_parm.cp0 * cz - this->m_proj_parm.sp0 * sz * cAz);
                }

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

            };

            // Oblated Equal Area
            template <typename Params, typename Parameters, typename T>
            inline void setup_oea(Params const& params, Parameters& par, par_oea<T>& proj_parm)
            {
                if (((proj_parm.n = pj_get_param_f<T, srs::spar::n>(params, "n", srs::dpar::n)) <= 0.) ||
                    ((proj_parm.m = pj_get_param_f<T, srs::spar::m>(params, "m", srs::dpar::m)) <= 0.)) {
                    BOOST_THROW_EXCEPTION( projection_exception(error_invalid_m_or_n) );
                } else {
                    proj_parm.theta = pj_get_param_r<T, srs::spar::theta>(params, "theta", srs::dpar::theta);
                    proj_parm.sp0 = sin(par.phi0);
                    proj_parm.cp0 = cos(par.phi0);
                    proj_parm.rn = 1./ proj_parm.n;
                    proj_parm.rm = 1./ proj_parm.m;
                    proj_parm.two_r_n = 2. * proj_parm.rn;
                    proj_parm.two_r_m = 2. * proj_parm.rm;
                    proj_parm.hm = 0.5 * proj_parm.m;
                    proj_parm.hn = 0.5 * proj_parm.n;
                    par.es = 0.;
                }
            }

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

    /*!
        \brief Oblated Equal Area projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
        \par Projection parameters
         - n (real)
         - m (real)
         - theta: Theta (degrees)
        \par Example
        \image html ex_oea.gif
    */
    template <typename T, typename Parameters>
    struct oea_spheroid : public detail::oea::base_oea_spheroid<T, Parameters>
    {
        template <typename Params>
        inline oea_spheroid(Params const& params, Parameters const& par)
            : detail::oea::base_oea_spheroid<T, Parameters>(par)
        {
            detail::oea::setup_oea(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_oea, oea_spheroid, oea_spheroid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(oea_entry, oea_spheroid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(oea_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(oea, oea_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_OEA_HPP


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

  @copyright 2016 NumScale SAS

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

#include <boost/simd/arch/common/detail/generic/trig_reduction.hpp>
#include <boost/simd/function/rem_pio2_medium.hpp>
#include <boost/simd/function/rem_pio2_cephes.hpp>
#include <boost/simd/function/rem_pio2.hpp>
#include <boost/simd/function/logical_and.hpp>
#include <boost/simd/function/split.hpp>
#include <boost/simd/function/group.hpp>
#include <boost/simd/detail/dispatch/meta/upgrade.hpp>

#include <boost/simd/function/all.hpp>
#include <boost/simd/function/toint.hpp>
#include <boost/simd/function/nearbyint.hpp>
#include <boost/simd/function/if_else_nan.hpp>
#include <boost/simd/function/is_not_greater.hpp>
#include <boost/simd/function/is_nez.hpp>
#include <boost/simd/function/is_flint.hpp>
#include <boost/simd/function/all.hpp>
#include <boost/simd/function/inrad.hpp>
#include <boost/simd/function/multiplies.hpp>
#include <boost/simd/function/divides.hpp>
#include <boost/simd/function/minus.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/constant/pi.hpp>
#include <boost/simd/constant/pio_2.hpp>
#include <boost/simd/constant/pio_4.hpp>
#include <boost/simd/constant/half.hpp>
#include <boost/simd/constant/two.hpp>
#include <boost/simd/detail/constant/medium_pi.hpp>
#include <boost/simd/constant/false.hpp>
#include <boost/simd/constant/ratio.hpp>
#include <boost/simd/constant/constant.hpp>
#include <boost/simd/constant/real.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>
#include <boost/simd/detail/dispatch/meta/scalar_of.hpp>
#include <utility>

namespace boost { namespace simd
{
  namespace detail
  {
    template<class A0, class mode>
    struct trig_reduction < A0, tag::radian_tag, tag::simd_type, mode>
    {
      using i_t = bd::as_integer_t<A0, signed>;
      using l_t = bs::as_logical_t<A0>;
      using conversion_allowed_t = bd::is_upgradable<A0>;

      static BOOST_FORCEINLINE auto is_0_pio4_reduced(const A0&a0) BOOST_NOEXCEPT
      ->  decltype(is_not_greater(a0, Pio_4<A0>()))
      {
        return is_not_greater(a0, Pio_4<A0>());
      }
      static BOOST_FORCEINLINE auto is_0_pio2_reduced(const A0&a0) BOOST_NOEXCEPT
      ->  decltype(is_not_greater(a0, Pio_2<A0>()))
      {
        return is_not_greater(a0, Pio_2<A0>());
      }
      static BOOST_FORCEINLINE auto is_0_20pi_reduced(const A0&a0) BOOST_NOEXCEPT
      ->  decltype(is_not_greater(a0, Real<A0, 0X404F6A7A2955385EULL, 0X427B53D1UL>()))
      {
        return is_not_greater(a0, Real<A0, 0X404F6A7A2955385EULL, 0X427B53D1UL>()); //20 pi;
      }
      static BOOST_FORCEINLINE auto is_0_mpi_reduced (const A0&a0) BOOST_NOEXCEPT
      ->  decltype(is_not_greater(a0, Medium_pi<A0>()))
      {
        return is_not_greater(a0, Medium_pi<A0>()); //2^6 pi
      }
      static BOOST_FORCEINLINE auto is_0_dmpi_reduced(const A0&a0) BOOST_NOEXCEPT
      ->  decltype(is_not_greater(a0,Constant<A0,262144>()))
      {
        return is_not_greater(a0, Ratio<A0,262144>()); //2^18 pi
      }

      static BOOST_FORCEINLINE l_t cot_invalid(const A0& )  BOOST_NOEXCEPT
      {
        return False<l_t>();
      }
      static BOOST_FORCEINLINE l_t tan_invalid(const A0& )  BOOST_NOEXCEPT
      {
        return False<l_t>();
      }

      static BOOST_FORCEINLINE i_t reduce(const A0& x, A0& xr)  BOOST_NOEXCEPT
      {
        return inner_reduce(x, xr);
      }

      static BOOST_FORCEINLINE i_t inner_reduce(const A0& x, A0& xr) BOOST_NOEXCEPT
      {
        A0 xx =  preliminary<mode>::clip(x);
        return select_mode(xx, xr, boost::mpl::int_<mode::start>());
      }

      template < class Mode, bool clipped = Mode::clipped>
      struct preliminary
      {
        static BOOST_FORCEINLINE A0 const& clip(const A0& x) BOOST_NOEXCEPT  { return x; }
      };

      template < class Mode>
      struct preliminary<Mode, true>
      {
        static BOOST_FORCEINLINE A0 clip(const A0& x) BOOST_NOEXCEPT
        {
          return clipto(x, boost::mpl::int_<Mode::range>());
        }
      private :
        static BOOST_FORCEINLINE A0 clipto(const A0& x
                                          , boost::mpl::int_<tag::r_0_pio4> const&) BOOST_NOEXCEPT
        {
          return if_else_nan(is_0_pio4_reduced(x), x);
        }
        static BOOST_FORCEINLINE A0 clipto(const A0& x
                                          , boost::mpl::int_<tag::r_0_20pi> const&) BOOST_NOEXCEPT
        {
          return if_else_nan(is_0_20pi_reduced(x), x);
        }
        static BOOST_FORCEINLINE A0 clipto(const A0& x
                                          , boost::mpl::int_<tag::r_0_mpi> const&) BOOST_NOEXCEPT
        {
          return if_else_nan(is_0_mpi_reduced(x), x);
        }
        static BOOST_FORCEINLINE A0 clipto(const A0& x
                                          , boost::mpl::int_<tag::r_0_dmpi> const&) BOOST_NOEXCEPT
        {
          return if_else_nan(is_0_dmpi_reduced(x), x);
        }
      };

      static BOOST_FORCEINLINE i_t
      select_range( const A0& xx, A0& xr
                  , boost::mpl::true_ const&
                  , boost::mpl::int_<tag::r_0_pio4> const&
                  ) BOOST_NOEXCEPT
      {
        xr = xx;
        return Zero<i_t>();
      }

      static BOOST_FORCEINLINE i_t
      select_range( const A0& xx, A0& xr
                  , boost::mpl::false_ const&
                  , boost::mpl::int_<tag::r_0_pio4> const& r
                  ) BOOST_NOEXCEPT
      {
        if(all(is_0_pio4_reduced(xx)))
        {
          return select_range(xx,xr,boost::mpl::true_(), r);
        }

        return select_mode(xx,xr,boost::mpl::int_<tag::r_0_pio2>());
      }

      static BOOST_FORCEINLINE i_t
      select_mode(const A0& xx, A0& xr
                 , boost::mpl::int_<tag::r_0_pio4> const& r) BOOST_NOEXCEPT
      {
        return select_range(xx,xr,boost::mpl::bool_<mode::range == tag::r_0_pio4>(),r);
      }

      static BOOST_FORCEINLINE i_t
      select_mode(const A0& xx, A0& xr
                 , boost::mpl::int_<tag::r_0_pio2> const&) BOOST_NOEXCEPT
      {
        if(all(is_0_pio2_reduced(xx)))
        {
          auto test = is_greater(xx, Pio_4<A0>());
          xr = xx-Pio2_1<A0>();
          xr -= Pio2_2<A0>();
          xr -= Pio2_3<A0>();
          xr = if_else(test, xr, xx);
          return -bitwise_cast<i_t>(genmask(test));
        }

        return select_mode(xx,xr,boost::mpl::int_<tag::r_0_20pi>());
      }

      static BOOST_FORCEINLINE i_t
      select_range( const A0& xx, A0& xr
                  , boost::mpl::true_ const&
                  , boost::mpl::int_<tag::r_0_20pi> const&
                  ) BOOST_NOEXCEPT
      {
        i_t n;
        std::tie(n, xr) = rem_pio2_cephes(xx);
        return n;
      }

      static BOOST_FORCEINLINE i_t
      select_range( const A0& xx, A0& xr
                  , boost::mpl::false_ const&
                  , boost::mpl::int_<tag::r_0_20pi> const& r
                  ) BOOST_NOEXCEPT
      {
        if(all(is_0_20pi_reduced(xx)))
          return select_range(xx,xr,boost::mpl::true_(), r);

        return select_mode(xx,xr,boost::mpl::int_<tag::r_0_mpi>());
      }

      static BOOST_FORCEINLINE i_t
      select_mode(const A0& xx, A0& xr
                 , boost::mpl::int_< tag::r_0_20pi> const& r) BOOST_NOEXCEPT
      {
        return select_range(xx,xr,boost::mpl::bool_<mode::range == tag::r_0_20pi>(),r);
      }

      static BOOST_FORCEINLINE i_t
      select_range( const A0& xx, A0& xr
                  , boost::mpl::true_ const&
                  , boost::mpl::int_<tag::r_0_mpi> const&
                  ) BOOST_NOEXCEPT
      {
        i_t n;
        std::tie(n, xr) = rem_pio2_medium(xx);
        return n;
      }

      static BOOST_FORCEINLINE i_t
      select_range( const A0& xx, A0& xr
                  , boost::mpl::false_ const&
                  , boost::mpl::int_<tag::r_0_mpi> const& r
                  ) BOOST_NOEXCEPT
      {
        if(all(is_0_mpi_reduced(xx)))
          return select_range(xx,xr,boost::mpl::true_(), r);

        return select_mode(xx,xr,boost::mpl::int_<tag::r_0_dmpi>());
      }

      static BOOST_FORCEINLINE i_t
      select_mode(const A0& xx, A0& xr
                 , boost::mpl::int_< tag::r_0_mpi> const& r) BOOST_NOEXCEPT
      {
        return select_range(xx,xr,boost::mpl::bool_<mode::range == tag::r_0_mpi>(),r);
      }

      static BOOST_FORCEINLINE i_t
      select_mode(const A0& xx, A0& xr
                 , boost::mpl::int_< tag::r_0_dmpi> const&) BOOST_NOEXCEPT
      {
        if(all(is_0_dmpi_reduced(xx)))
           return use_conversion(xx, xr, conversion_allowed_t());
        i_t n;
        std::tie(n, xr) = rem_pio2(xx);
        return n;
      }

      static BOOST_FORCEINLINE i_t
      use_conversion(const A0 & xx,  A0& xr
                    , std::false_type) BOOST_NOEXCEPT
      {
        i_t n;
        std::tie(n, xr) = rem_pio2(xx);
        return n;
      }

      static BOOST_FORCEINLINE i_t
      use_conversion(const A0 & x,  A0& xr
                    ,std::true_type) BOOST_NOEXCEPT
      {
        // all of x are in [0, 2^18*pi],  conversion to double is used to reduce
        using uA0 = bd::upgrade_t<A0>;
        using aux_reduc_t = trig_reduction< uA0, tag::radian_tag,  tag::simd_type, mode, double>;

        auto uxs = split(x);
        uA0  uxr1, uxr2;

        auto n1 = aux_reduc_t::reduce(uxs[0], uxr1);
        auto n2 = aux_reduc_t::reduce(uxs[1], uxr2);

          xr = group(uxr1, uxr2);
        return group(n1, n2);
      }
    };

  }
} }

#endif


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_DETAIL_EXPLODER_HPP
#define CRYPTO3_DETAIL_EXPLODER_HPP

#include <nil/crypto3/detail/stream_endian.hpp>
#include <nil/crypto3/detail/unbounded_shift.hpp>

#include <boost/integer.hpp>
#include <boost/static_assert.hpp>

#include <iterator>

#include <climits>
#include <cstring>

namespace nil {
    namespace crypto3 {
        namespace detail {

            // By definition, for all exploders, InputBits > OutputBits,
            // so we're taking one value and splitting it into many smaller values

            template<typename OutIter, int OutBits, typename T = typename std::iterator_traits<OutIter>::value_type>
            struct outvalue_helper {
                typedef T type;
            };
            template<typename OutIter, int OutBits>
            struct outvalue_helper<OutIter, OutBits, void> {
                typedef typename boost::uint_t<OutBits>::least type;
            };

            template<typename Endianness, int InputBits, int OutputBits, int k>
            struct exploder_step;

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_step<stream_endian::big_unit_big_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutIter>
                static void step(InputValue const &x, OutIter &out) {
                    int const shift = InputBits - (OutputBits + k);
                    typedef typename outvalue_helper<OutIter, OutputBits>::type OutValue;
                    InputValue y = unbounded_shr<shift>(x);
                    *out++ = OutValue(low_bits<OutputBits>(y));
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_step<stream_endian::little_unit_big_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutIter>
                static void step(InputValue const &x, OutIter &out) {
                    int const kb = (k % UnitBits);
                    int const ku = k - kb;
                    int const shift = OutputBits >= UnitBits ? k :
                                      InputBits >= UnitBits  ? ku + (UnitBits - (OutputBits + kb)) :
                                                               InputBits - (OutputBits + kb);
                    typedef typename outvalue_helper<OutIter, OutputBits>::type OutValue;
                    InputValue y = unbounded_shr<shift>(x);
                    *out++ = OutValue(low_bits<OutputBits>(y));
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_step<stream_endian::big_unit_little_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutIter>
                static void step(InputValue const &x, OutIter &out) {
                    int const kb = (k % UnitBits);
                    int const ku = k - kb;
                    int const shift = OutputBits >= UnitBits ? InputBits - (OutputBits + k) :
                                      InputBits >= UnitBits  ? InputBits - (UnitBits + ku) + kb :
                                                               kb;
                    typedef typename outvalue_helper<OutIter, OutputBits>::type OutValue;
                    InputValue y = unbounded_shr<shift>(x);
                    *out++ = OutValue(low_bits<OutputBits>(y));
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_step<stream_endian::little_unit_little_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutIter>
                static void step(InputValue const &x, OutIter &out) {
                    int const shift = k;
                    typedef typename outvalue_helper<OutIter, OutputBits>::type OutValue;
                    InputValue y = unbounded_shr<shift>(x);
                    *out++ = OutValue(low_bits<OutputBits>(y));
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_step<stream_endian::host_unit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutIter>
                static void step(InputValue const &x, OutIter &out) {
                    typedef typename outvalue_helper<OutIter, OutputBits>::type OutValue;
                    BOOST_STATIC_ASSERT(sizeof(InputValue) * CHAR_BIT == InputBits);
                    BOOST_STATIC_ASSERT(sizeof(OutValue) * CHAR_BIT == OutputBits);
                    OutValue value;
                    std::memcpy(&value, (char *)&x + k / CHAR_BIT, OutputBits / CHAR_BIT);
                    *out++ = value;
                }
            };

            template<typename Endianness, int InputBits, int OutputBits, int k = 0>
            struct exploder;

            template<template<int> class Endian, int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder<Endian<UnitBits>, InputBits, OutputBits, k> {

                // To keep the implementation managable, input and output sizes must
                // be multiples or factors of the unit size.
                // If one of these is firing, you may want a bit-only stream_endian
                // rather than one that mentions bytes or octets.
                BOOST_STATIC_ASSERT(!(InputBits % UnitBits && UnitBits % InputBits));
                BOOST_STATIC_ASSERT(!(OutputBits % UnitBits && UnitBits % OutputBits));

                typedef Endian<UnitBits> Endianness;
                typedef exploder_step<Endianness, InputBits, OutputBits, k> step_type;
                typedef exploder<Endianness, InputBits, OutputBits, k + OutputBits> next_type;

                template<typename InputValue, typename OutIter>
                static void explode(InputValue const &x, OutIter &out) {
                    step_type::step(x, out);
                    next_type::explode(x, out);
                }
            };

            template<template<int> class Endian, int UnitBits, int InputBits, int OutputBits>
            struct exploder<Endian<UnitBits>, InputBits, OutputBits, InputBits> {
                template<typename InputValue, typename OutIter>
                static void explode(InputValue const &, OutIter &) {
                }
            };

        }    // namespace detail
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_BLOCK_DETAIL_EXPLODER_HPP