/*
 *  Copyright 2010-2011 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/>.
*/

#include <boost/foreach.hpp>
#include <boost/progress.hpp>

#ifdef _OPENMP
    #include <omp.h>
#endif

#include "millingcutter.hpp"
#include "point.hpp"
#include "triangle.hpp"
#include "batchpushcutter.hpp"

namespace ocl
{

//********   ********************** */

BatchPushCutter::BatchPushCutter() {
    fibers = new std::vector<Fiber>();
    nCalls = 0;
    nthreads = 1;
#ifdef _OPENMP
    nthreads = omp_get_num_procs(); // figure out how many cores we have
#endif
    cutter = NULL;
    bucketSize = 1;
    root = new KDTree<Triangle>();
}

BatchPushCutter::~BatchPushCutter() {
    delete fibers;
    delete root;
}

void BatchPushCutter::setSTL(const STLSurf &s) {
    surf = &s;
    std::cout << "BPC::setSTL() Building kd-tree... bucketSize=" << bucketSize << "..";
    root->setBucketSize( bucketSize );
    if (x_direction)
        root->setYZDimensions(); // we search for triangles in the XY plane, don't care about Z-coordinate
    else if (y_direction)
        root->setXZDimensions();
    else {
        std::cout << " ERROR: setXDirection() or setYDirection() must be called before setSTL() \n";
        assert(0);
    }
    std::cout << "BPC::setSTL() root->build()...";
    root->build(s.tris);
    std::cout << "done.\n";
}

void BatchPushCutter::appendFiber(Fiber& f) {
    fibers->push_back(f);
}

void BatchPushCutter::reset() {
    fibers->clear();
}

/// very simple batch push-cutter
/// each fiber is tested against all triangles of surface
void BatchPushCutter::pushCutter1() {
    std::cout << "BatchPushCutter1 with " << fibers->size() <<
              " fibers and " << surf->tris.size() << " triangles..." << std::endl;
    nCalls = 0;
    boost::progress_display show_progress( fibers->size() );
    BOOST_FOREACH(Fiber& f, *fibers) {
        BOOST_FOREACH( const Triangle& t, surf->tris) {// test against all triangles in s
            Interval i;
            cutter->pushCutter(f,i,t);
            f.addInterval(i);
            ++nCalls;
        }
        ++show_progress;
    }
    std::cout << "BatchPushCutter done." << std::endl;
    return;
}

/// push-cutter which uses KDNode2 kd-tree search to find triangles
/// overlapping with the cutter.
void BatchPushCutter::pushCutter2() {
    std::cout << "BatchPushCutter2 with " << fibers->size() <<
              " fibers and " << surf->tris.size() << " triangles..." << std::endl;
    nCalls = 0;
    std::list<Triangle>* overlap_triangles;
    boost::progress_display show_progress( fibers->size() );
    BOOST_FOREACH(Fiber& f, *fibers) {
        CLPoint cl;
        if (x_direction) {
            cl.x = 0;
            cl.y = f.p1.y;
            cl.z = f.p1.z;
        } else if (y_direction) {
            cl.x = f.p1.x;
            cl.y = 0;
            cl.z = f.p1.z;
        } else {
            assert(0);
        }
        overlap_triangles = root->search_cutter_overlap(cutter, &cl);
        assert( overlap_triangles->size() <= surf->size() ); // can't possibly find more triangles than in the STLSurf
        BOOST_FOREACH( const Triangle& t, *overlap_triangles) {
            //if ( bb->overlaps( t.bb ) ) {
                Interval i;
                cutter->pushCutter(f,i,t);
                f.addInterval(i);
                ++nCalls;
            //}
        }
        delete( overlap_triangles );
        ++show_progress;
    }
    std::cout << "BatchPushCutter2 done." << std::endl;
    return;
}

/// use kd-tree search to find overlapping triangles
/// use OpenMP for multi-threading
void BatchPushCutter::pushCutter3() {
    std::cout << "BatchPushCutter3 with " << fibers->size() <<
              " fibers and " << surf->tris.size() << " triangles." << std::endl;
    std::cout << " cutter = " << cutter->str() << "\n";
    nCalls = 0;
    boost::progress_display show_progress( fibers->size() );
#ifdef _OPENMP
    omp_set_num_threads(nthreads);
    //omp_set_nested(1);
#endif
    unsigned int Nmax = fibers->size();         // the number of fibers to process
    std::list<Triangle>::iterator it,it_end;    // for looping over found triabgles
    Interval* i;
    std::list<Triangle>* tris;
    std::vector<Fiber>& fiberr = *fibers;
    unsigned int n; // loop variable
    unsigned int calls=0;

    #pragma omp parallel for schedule(dynamic) shared(calls, fiberr) private(n,i,tris,it,it_end)
    //#pragma omp parallel for shared( calls, fiberr) private(n,i,tris,it,it_end)
    for (n=0; n<Nmax; ++n) { // loop through all fibers
#ifdef _OPENMP
        if ( n== 0 ) { // first iteration
            if (omp_get_thread_num() == 0 )
                std::cout << "Number of OpenMP threads = "<< omp_get_num_threads() << "\n";
        }
#endif
        CLPoint cl; // cl-point on the fiber
        if ( x_direction ) {
            cl.x=0;
            cl.y=fiberr[n].p1.y;
            cl.z=fiberr[n].p1.z;
        } else if (y_direction ) {
            cl.x=fiberr[n].p1.x;
            cl.y=0;
            cl.z=fiberr[n].p1.z;
        }
        tris = root->search_cutter_overlap(cutter, &cl);
        it_end = tris->end();
        for ( it=tris->begin() ; it!=it_end ; ++it) { // loop through the found overlapping triangles
            //if ( bb->overlaps( it->bb ) ) {
                // todo: optimization where method-calls are skipped if triangle bbox already in the fiber
                i = new Interval();
                cutter->pushCutter(fiberr[n],*i,*it);
                fiberr[n].addInterval(*i);
                ++calls;
                delete i;
            //}
        }
        delete( tris );
        ++show_progress;
    } // OpenMP parallel region ends here

    this->nCalls = calls;
    std::cout << "\nBatchPushCutter3 done." << std::endl;
    return;
}

}// end namespace
// end file batchpushcutter.cpp


/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */

#include <boost/bind/bind.hpp>
using namespace boost::placeholders;

#include "tudat/simulation/environment_setup/createRadiationPressureInterface.h"
#include "tudat/astro/basic_astro/sphericalBodyShapeModel.h"
#include "tudat/astro/reference_frames/referenceFrameTransformations.h"

namespace tudat
{

namespace simulation_setup
{

//! Function to obtain (by reference) the position functions and radii of occulting bodies
void getOccultingBodiesInformation(
        const SystemOfBodies& bodies, const std::vector< std::string >& occultingBodies,
        std::vector< std::function< Eigen::Vector3d( ) > >& occultingBodyPositions,
        std::vector< double >& occultingBodyRadii )
{
    // Iterate over occulring bodies and retrieve radius and position function.
    for( unsigned int i = 0; i < occultingBodies.size( ); i++ )
    {
        if( bodies.count( occultingBodies[ i ] ) == 0 )
        {
            throw std::runtime_error( "Error, could not find body " + occultingBodies[ i ] +
                                      " in system of bodies when making occulting body settings" );
        }
        else
        {
            std::shared_ptr< basic_astrodynamics::BodyShapeModel > shapeModel =
                    bodies.at( occultingBodies.at( i ) )->getShapeModel( );
            if( shapeModel == nullptr )
            {
                throw std::runtime_error( "Error, no shape model for " + occultingBodies[ i ] +
                                          " when making occulting body settings" );
            }
            occultingBodyPositions.push_back(
                        std::bind( &Body::getPosition, bodies.at( occultingBodies[ i ] ) ) );
            occultingBodyRadii.push_back( shapeModel->getAverageRadius( ) );
        }
    }
}

//! Function to obtain (by reference) the position and velocity functions of the central body.
void getCentralBodyInformation(
    const SystemOfBodies& bodies, const std::string& centralBody,
    std::function< Eigen::Vector3d( ) >& centralBodyPosition,
    std::function< Eigen::Vector3d( ) >& centralBodyVelocity )
{

    // Check that the central body is defined.
    if( bodies.count( centralBody ) == 0 )
    {
        throw std::runtime_error( "Error, could not find body " + centralBody +
                                 " in system of bodies when making central body body settings" );
    }
    // Retrieve position and velocity functions.
    else
    {
        centralBodyPosition = std::bind( &Body::getPosition, bodies.at( centralBody ) );
        centralBodyVelocity = std::bind( &Body::getVelocity, bodies.at( centralBody ) );
    }
}

//! Function to create a radiation pressure interface.
std::shared_ptr< electromagnetism::RadiationPressureInterface > createRadiationPressureInterface(
        const std::shared_ptr< RadiationPressureInterfaceSettings > radiationPressureInterfaceSettings,
        const std::string& bodyName, const SystemOfBodies& bodies )
{
    std::shared_ptr< electromagnetism::RadiationPressureInterface > radiationPressureInterface;

    // Check type of radiation pressure interface
    switch( radiationPressureInterfaceSettings->getRadiationPressureType( ) )
    {
    case cannon_ball_radiation_pressure_interface:
    {
        // Check type consistency.
        std::shared_ptr< CannonBallRadiationPressureInterfaceSettings > cannonBallSettings =
                std::dynamic_pointer_cast< CannonBallRadiationPressureInterfaceSettings >(
                    radiationPressureInterfaceSettings );
        if( cannonBallSettings == nullptr )
        {
            throw std::runtime_error( "Error when making cannon ball radiation interface, type does not match object" );
        }

        // Retrieve source body and check consistency.
        if( bodies.count( radiationPressureInterfaceSettings->getSourceBody( ) ) == 0 )
        {
            throw std::runtime_error( "Error when making cannon ball radiation interface, source not found.");
        }

        std::shared_ptr< Body > sourceBody =
                bodies.at( radiationPressureInterfaceSettings->getSourceBody( ) );

        // Get reqruied data for occulting bodies.
        std::vector< std::string > occultingBodies = cannonBallSettings->getOccultingBodies( );
        std::vector< std::function< Eigen::Vector3d( ) > > occultingBodyPositions;
        std::vector< double > occultingBodyRadii;
        getOccultingBodiesInformation(
                    bodies, occultingBodies, occultingBodyPositions, occultingBodyRadii );

        // Retrive radius of source if occultations are used.
        double sourceRadius;
        if( occultingBodyPositions.size( ) > 0 )
        {
            std::shared_ptr< basic_astrodynamics::BodyShapeModel > sourceShapeModel =
                    sourceBody->getShapeModel( );

            if( sourceShapeModel == nullptr )
            {
                throw std::runtime_error( "Error when making occulted body, source body " +
                                          radiationPressureInterfaceSettings->getSourceBody( ) +
                                          " does not have a shape" );
            }
            else
            {
                sourceRadius = sourceShapeModel->getAverageRadius( );
            }
        }
        else
        {
            sourceRadius = 0.0;
        }

        // Create function returning radiated power.
        std::function< double( ) > radiatedPowerFunction;
        if( defaultRadiatedPowerValues.count(
                    radiationPressureInterfaceSettings->getSourceBody( ) ) == 0 )
        {
            throw std::runtime_error( "Error, no radiated power found for " +
                                      radiationPressureInterfaceSettings->getSourceBody( ) );
        }
        else
        {
            radiatedPowerFunction = [ = ]( ){ return
                        defaultRadiatedPowerValues.at(
                            radiationPressureInterfaceSettings->getSourceBody( ) ); };
        }

        if( cannonBallSettings->getRadiationPressureCoefficient( ) !=
                cannonBallSettings->getRadiationPressureCoefficient( ) )
        {
        // Create radiation pressure interface.
        radiationPressureInterface =
                std::make_shared< electromagnetism::RadiationPressureInterface >(
                    radiatedPowerFunction,
                    std::bind( &Body::getPosition, sourceBody ),
                    std::bind( &Body::getPosition, bodies.at( bodyName ) ),
                    cannonBallSettings->getRadiationPressureCoefficientFunction( ),
                    cannonBallSettings->getArea( ), occultingBodyPositions, occultingBodyRadii,
                    sourceRadius );
        }
        else
        {
            // Create radiation pressure interface.
            radiationPressureInterface =
                    std::make_shared< electromagnetism::RadiationPressureInterface >(
                        radiatedPowerFunction,
                        std::bind( &Body::getPosition, sourceBody ),
                        std::bind( &Body::getPosition, bodies.at( bodyName ) ),
                        cannonBallSettings->getRadiationPressureCoefficient( ),
                        cannonBallSettings->getArea( ), occultingBodyPositions, occultingBodyRadii,
                        sourceRadius );
        }
        break;
    }
    case panelled_radiation_pressure_interface:
    {
        // Check type consistency.
        std::shared_ptr< PanelledRadiationPressureInterfaceSettings > panelledSettings =
                std::dynamic_pointer_cast< PanelledRadiationPressureInterfaceSettings >(
                    radiationPressureInterfaceSettings );
        if( panelledSettings == NULL )
        {
            throw std::runtime_error( "Error when making panelled radiation interface, type does not match object" );
        }

        // Retrieve source body and check consistency.
        if( bodies.count( radiationPressureInterfaceSettings->getSourceBody( ) ) == 0 )
        {
            throw std::runtime_error( "Error when making panelled radiation interface, source not found.");
        }

        std::shared_ptr< Body > sourceBody =
                bodies.at( radiationPressureInterfaceSettings->getSourceBody( ) );

        // Get required data for occulting bodies.
        std::vector< std::string > occultingBodies = panelledSettings->getOccultingBodies( );
        std::vector< std::function< Eigen::Vector3d( ) > > occultingBodyPositions;
        std::vector< double > occultingBodyRadii;
        getOccultingBodiesInformation( bodies, occultingBodies, occultingBodyPositions, occultingBodyRadii );

        // Retrieve radius of source if occultations are used.
        double sourceRadius;
        if( occultingBodyPositions.size( ) > 0 )
        {
            std::shared_ptr< basic_astrodynamics::BodyShapeModel > sourceShapeModel =
                    sourceBody->getShapeModel( );

            if( sourceShapeModel == NULL )
            {
                throw std::runtime_error(
                            "Error when making occulted body, source body " + radiationPressureInterfaceSettings->getSourceBody( ) +
                            " does not have a shape" );
            }
            else
            {
                sourceRadius = sourceShapeModel->getAverageRadius( );
            }
        }
        else
        {
            sourceRadius = 0.0;
        }

        // Create function returning radiated power.
        std::function< double( ) > radiatedPowerFunction;
        if( defaultRadiatedPowerValues.count(
                    radiationPressureInterfaceSettings->getSourceBody( ) ) == 0 )
        {
            throw std::runtime_error( "Error, no radiated power found for " +
                                      radiationPressureInterfaceSettings->getSourceBody( ) );
        }
        else
        {
            radiatedPowerFunction = [ = ]( ){ return defaultRadiatedPowerValues.at(
                            radiationPressureInterfaceSettings->getSourceBody( ) ); };
        }

        std::vector< std::function< Eigen::Vector3d( const double ) > > localFrameSurfaceNormalFunctions = panelledSettings->getSurfaceNormalsInBodyFixedFrameFunctions();

        // Create radiation pressure interface.
        radiationPressureInterface =
                std::make_shared< electromagnetism::PanelledRadiationPressureInterface >(
                    radiatedPowerFunction,
                    std::bind( &Body::getPosition, sourceBody ),
                    std::bind( &Body::getPosition, bodies.at( bodyName ) ),
                    localFrameSurfaceNormalFunctions,
                    panelledSettings->getEmissivities( ),
                    panelledSettings->getAreas( ),
                    panelledSettings->getDiffusionCoefficients( ),
                    std::bind( &Body::getCurrentRotationToGlobalFrame, bodies.at( bodyName ) ),
                    occultingBodyPositions, occultingBodyRadii,
                    sourceRadius );
        break;
    }
    case solar_sailing_radiation_pressure_interface:
    {
        // Check type consistency.
        std::shared_ptr< SolarSailRadiationInterfaceSettings > solarSailRadiationSettings =
                std::dynamic_pointer_cast< SolarSailRadiationInterfaceSettings >( radiationPressureInterfaceSettings );

        if( solarSailRadiationSettings == nullptr )
        {
            throw std::runtime_error( "Error when making solar sail radiation interface, type does not match object" );
        }

        // Retrieve source body and check consistency.
        if( bodies.count( radiationPressureInterfaceSettings->getSourceBody( ) ) == 0 )
        {
            throw std::runtime_error( "Error when making solar sail radiation interface, source not found.");
        }
        std::shared_ptr< Body > sourceBody = bodies.at( radiationPressureInterfaceSettings->getSourceBody( ) );

        // Get required data for occulting bodies.
        std::vector< std::string > occultingBodies = solarSailRadiationSettings->getOccultingBodies( );
        std::vector< std::function< Eigen::Vector3d( ) > > occultingBodyPositions;
        std::vector< double > occultingBodyRadii;
        getOccultingBodiesInformation(
            bodies, occultingBodies, occultingBodyPositions, occultingBodyRadii );

        // Retrieve radius of source if occultations are used.
        double sourceRadius;
        if( occultingBodyPositions.size( ) > 0 )
        {
            std::shared_ptr< basic_astrodynamics::BodyShapeModel > sourceShapeModel =
                sourceBody->getShapeModel( );

            if( sourceShapeModel == nullptr )
            {
                throw std::runtime_error( "Error when making occulted body, source body " +
                                         radiationPressureInterfaceSettings->getSourceBody( ) +
                                         " does not have a shape" );
            }
            else
            {
                sourceRadius = sourceShapeModel->getAverageRadius( );
            }
        }
        else
        {
            sourceRadius = 0.0;
        }

        // Get required data for central bodies.
        std::string centralBody = solarSailRadiationSettings->getCentralBody();
        std::function< Eigen::Vector3d( ) > centralBodyPosition;
        std::function< Eigen::Vector3d( ) > centralBodyVelocity;
        getCentralBodyInformation( bodies, centralBody, centralBodyPosition, centralBodyVelocity );

        // Create function returning radiated power.
        std::function< double( ) > radiatedPowerFunction;
        if( defaultRadiatedPowerValues.count(
                radiationPressureInterfaceSettings->getSourceBody( ) ) == 0 )
        {
            throw std::runtime_error( "Error, no radiated power found for " +
                                     radiationPressureInterfaceSettings->getSourceBody( ) );
        }
        else
        {
            radiatedPowerFunction = [ = ]( ){ return defaultRadiatedPowerValues.at( radiationPressureInterfaceSettings->getSourceBody( ) );};
        }

        // Create solar sailing radiation pressure interface.
        radiationPressureInterface =
            std::make_shared< electromagnetism::SolarSailingRadiationPressureInterface >(
                radiatedPowerFunction,
                std::bind( &Body::getPosition, sourceBody ),
                std::bind( &Body::getPosition, bodies.at( bodyName ) ),
                std::bind( &Body::getVelocity, bodies.at( bodyName ) ),
                solarSailRadiationSettings->getArea( ),
                solarSailRadiationSettings->getConeAngle(),
                solarSailRadiationSettings->getClockAngle(),
                solarSailRadiationSettings->getFrontEmissivityCoefficient(),
                solarSailRadiationSettings->getBackEmissivityCoefficient(),
                solarSailRadiationSettings->getFrontLambertianCoefficient(),
                solarSailRadiationSettings->getBackLambertianCoefficient(),
                solarSailRadiationSettings->getReflectivityCoefficient(),
                solarSailRadiationSettings->getSpecularReflectionCoefficient(),
                occultingBodyPositions, centralBodyVelocity,
                occultingBodyRadii, sourceRadius);
        break;
    }
    default:
        throw std::runtime_error(
                    "Error, radiation pressure type" + std::to_string(
                        radiationPressureInterfaceSettings->getRadiationPressureType( ) ) +
                    "not recognized for body" + bodyName );
    }

    return radiationPressureInterface;
}

std::function< double( const double ) > getOccultationFunction(
        const SystemOfBodies& bodyMap,
        const std::string& sourceBody,
        const std::string& occultingBody,
        const std::string& shadowedBody )
{
    std::function< Eigen::Vector3d( ) > sourceBodyPositionFunction =
            std::bind( &Body::getPosition, bodyMap.at( sourceBody ) );
    std::function< Eigen::Vector3d( ) > occultingBodyPositionFunction =
            std::bind( &Body::getPosition, bodyMap.at( occultingBody ) );
    std::function< Eigen::Vector3d( ) > shadowedBodyPositionFunction =
            std::bind( &Body::getPosition, bodyMap.at( shadowedBody ) );
    double sourceBodyRadius = bodyMap.at( sourceBody )->getShapeModel( )->getAverageRadius( );
    double occultingBodyRadius = bodyMap.at( occultingBody )->getShapeModel( )->getAverageRadius( );

    return [=]( const double ){
        return mission_geometry::computeShadowFunction(
                    sourceBodyPositionFunction( ), sourceBodyRadius,
                    occultingBodyPositionFunction( ), occultingBodyRadius,
                    shadowedBodyPositionFunction( ) ); };
}

} // namespace simulation_setup

} // namespace tudat


/*!
@file
Defines several `constexpr` algorithms.

@copyright Louis Dionne 2013-2017
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_DETAIL_ALGORITHM_HPP
#define BOOST_HANA_DETAIL_ALGORITHM_HPP

#include <boost/hana/functional/placeholder.hpp>

#include <boost/hana/config.hpp>

#include <cstddef>
#include <utility>

BOOST_HANA_NAMESPACE_BEGIN namespace detail {
    // Do not call this swap, otherwise it can get picked up by ADL and conflict
    // with std::swap (see https://github.com/boostorg/hana/issues/297).
    template <typename T>
    constexpr void constexpr_swap(T& x, T& y) {
        auto tmp = x;
        x = y;
        y = std::move(tmp);
    }

    template <typename BidirIter>
    constexpr void reverse(BidirIter first, BidirIter last) {
        while (first != last) {
            if (first == --last)
                break;
            detail::constexpr_swap(*first, *last);
            ++first;
        }
    }

    template <typename BidirIter, typename BinaryPred>
    constexpr bool next_permutation(BidirIter first, BidirIter last,
                                    BinaryPred pred)
    {
        BidirIter i = last;
        if (first == last || first == --i)
            return false;
        while (true) {
            BidirIter ip1 = i;
            if (pred(*--i, *ip1)) {
                BidirIter j = last;
                while (!pred(*i, *--j))
                    ;
                detail::constexpr_swap(*i, *j);
                detail::reverse(ip1, last);
                return true;
            }
            if (i == first) {
                detail::reverse(first, last);
                return false;
            }
        }
    }

    template <typename BidirIter>
    constexpr bool next_permutation(BidirIter first, BidirIter last)
    { return detail::next_permutation(first, last, hana::_ < hana::_); }

    template <typename InputIter1, typename InputIter2, typename BinaryPred>
    constexpr bool lexicographical_compare(InputIter1 first1, InputIter1 last1,
                                           InputIter2 first2, InputIter2 last2,
                                           BinaryPred pred)
    {
        for (; first2 != last2; ++first1, ++first2) {
            if (first1 == last1 || pred(*first1, *first2))
                return true;
            else if (pred(*first2, *first1))
                return false;
        }
        return false;
    }

    template <typename InputIter1, typename InputIter2>
    constexpr bool lexicographical_compare(InputIter1 first1, InputIter1 last1,
                                           InputIter2 first2, InputIter2 last2)
    { return detail::lexicographical_compare(first1, last1, first2, last2, hana::_ < hana::_); }

    template <typename InputIter1, typename InputIter2, typename BinaryPred>
    constexpr bool equal(InputIter1 first1, InputIter1 last1,
                         InputIter2 first2, InputIter2 last2,
                         BinaryPred pred)
    {
        for (; first1 != last1 && first2 != last2; ++first1, ++first2)
            if (!pred(*first1, *first2))
                return false;
        return first1 == last1 && first2 == last2;
    }

    template <typename InputIter1, typename InputIter2>
    constexpr bool equal(InputIter1 first1, InputIter1 last1,
                         InputIter2 first2, InputIter2 last2)
    { return detail::equal(first1, last1, first2, last2, hana::_ == hana::_); }

    template <typename BidirIter, typename BinaryPred>
    constexpr void sort(BidirIter first, BidirIter last, BinaryPred pred) {
        if (first == last) return;

        BidirIter i = first;
        for (++i; i != last; ++i) {
            BidirIter j = i;
            auto t = *j;
            for (BidirIter k = i; k != first && pred(t,  *--k); --j)
                *j = *k;
            *j = t;
        }
    }

    template <typename BidirIter>
    constexpr void sort(BidirIter first, BidirIter last)
    { detail::sort(first, last, hana::_ < hana::_); }

    template <typename InputIter, typename T>
    constexpr InputIter find(InputIter first, InputIter last, T const& value) {
        for (; first != last; ++first)
            if (*first == value)
                return first;
        return last;
    }

    template <typename InputIter, typename UnaryPred>
    constexpr InputIter find_if(InputIter first, InputIter last, UnaryPred pred) {
        for (; first != last; ++first)
            if (pred(*first))
                return first;
        return last;
    }

    template <typename ForwardIter, typename T>
    constexpr void iota(ForwardIter first, ForwardIter last, T value) {
        while (first != last) {
            *first++ = value;
            ++value;
        }
    }

    template <typename InputIt, typename T>
    constexpr std::size_t
    count(InputIt first, InputIt last, T const& value) {
        std::size_t n = 0;
        for (; first != last; ++first)
            if (*first == value)
                ++n;
        return n;
    }

    template <typename InputIt, typename T, typename F>
    constexpr T accumulate(InputIt first, InputIt last, T init, F f) {
        for (; first != last; ++first)
            init = f(init, *first);
        return init;
    }

    template <typename InputIt, typename T>
    constexpr T accumulate(InputIt first, InputIt last, T init) {
        return detail::accumulate(first, last, init, hana::_ + hana::_);
    }

    template <typename ForwardIt>
    constexpr ForwardIt min_element(ForwardIt first, ForwardIt last) {
        if (first == last)
            return last;

        ForwardIt smallest = first;
        ++first;
        for (; first != last; ++first)
            if (*first < *smallest)
                smallest = first;
        return smallest;
    }
} BOOST_HANA_NAMESPACE_END

#endif // !BOOST_HANA_DETAIL_ALGORITHM_HPP


/*! file qubo.hpp
 *
 * @copyright Licensed under MIT license by hyperQ – Ewa Hendzel
 *
 */
#include "helpers/hash.hpp"
#include "helpers/insert.hpp"
#include <iostream>
#include <ostream>
#include <string>
#include <unordered_map>

#include <boost/bind.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iostream>

namespace qi = boost::spirit::qi;

#ifndef QUBO_MODEL_HPP__
#define QUBO_MODEL_HPP__

namespace qubo {

using boost::fusion::at_c;
using qi::double_;
using qi::int_;
using qi::phrase_parse;
/**
 * @brief Storage class for linear coefficients of the QUBO model.
 *
 * @tparam NodeType integer number type used to index nodes of the QUBO model.
 * @tparam CoefType real type used to store values of the QUBO model.
 */
template <class NodeType, class CoefType>
using LinearCoef = std::unordered_map<NodeType, CoefType>;

/**
 * @brief Storage class for quadratic coefficients of the QUBO model.
 *
 * @tparam NodeType integer number type used to index nodes of the QUBO model.
 * @tparam CoefType real type used to store values of the QUBO model.
 */
template <class NodeType, class CoefType>
using QuadraticCoef = std::unordered_map<std::pair<NodeType, NodeType>,
                                         CoefType, helpers::hash_pair>;
/**
 * @brief Container class for storage of the QUBO model.
 *
 * @tparam NodeType integer number type used to index nodes of the QUBO model.
 * @tparam CoefType real type used to store values of the QUBO model.
 */
template <class NodeType, class CoefType> class QUBOModel {
protected:
 /**
  * @brief QUBO model linear coefficients store.
  *
  */
  LinearCoef<NodeType, CoefType> linear;
  /**
   * @brief QUBO model quadratic coefficients store.
   *
   */
  QuadraticCoef<NodeType, CoefType> quadratic;
  /**
   * @brief Number of nodes (variables) in the QUBO model.
   *
   */
  ulong num_nodes = 0;

public:
  /**
   * @brief Construct a new QUBOModel object.
   *
   * @param c_linear Linear coefficients of the QUBO model.
   * @param c_quadratic Quadratic coefficients of the QUBO model.
   */
  QUBOModel(LinearCoef<NodeType, CoefType> &c_linear,
            QuadraticCoef<NodeType, CoefType> &c_quadratic);

  /**
   * @brief Returns a textual representation of the QUBO model.
   *
   * @return std::string Textual representation of the stored QUBO model.
   */
  std::string str() const;

  /**
   * @brief Add a binary variable (node) to the QUBO model.
   *
   * @param vi Variable addres.
   * @param hi Value of the variable coefficient.
   */
  void add_variable(const NodeType &vi, const CoefType &hi);

  /**
   * @brief Add a connection (coupling) between binaray variables.
   *
   * @param connection Addresses of the variables to connect (couple).
   * @param Ji Strength of the connection (coupling).
   */
  void add_connection(const std::pair<NodeType, NodeType> &connection,
                      const CoefType &Ji);

  /**
   * @brief Get the variable object.
   *
   * @param vi Address of the variable.
   * @return const CoefType Variable coefficient.
   */
  const CoefType get_variable(const NodeType &vi) const;

  /**
   * @brief Get the connection object.
   *
   * @param connection Addresses of the variables.
   * @return const CoefType Connection (coupling) strength between variables.
   */
  const CoefType
  get_connection(const std::pair<NodeType, NodeType> &connection) const;

  /**
   * @brief Set the number of nodes (variables) in the QUBO model.
   *
   * @param num_nodes Number of nodes to set.
   */
  void set_nodes(int num_nodes);

  /**
   * @brief Get number of nodes (variables) in the QUBO model.
   *
   * @return ulong Number of nodes.
   */
  ulong get_nodes() const;

  /**
   * @brief Print textual representation of the QUBO model to a stream.
   *
   * @param os Ouput stream reference.
   * @param qubos QUBO model.
   * @return std::ostream& Modified ouput stream reference.
   */
  friend std::ostream &operator<<(std::ostream &os,
                                  qubo::QUBOModel<NodeType, CoefType> &qubos);

  /**
   * @brief Create a QUBO model from the input stream.
   *
   * @param is Input stream reference.
   * @param qubos QUBO model.
   * @return std::istream& Modified input stream reference.
   */
  friend std::istream &operator>>(std::istream &is,
                                  qubo::QUBOModel<NodeType, CoefType> &qubos);

  /**
   * @brief Load a QUBO model for an input stream.
   *
   * @param stream Input stream refernce.
   * @return QUBOModel<int, double> Constructed QUBO model object.
   */
  static QUBOModel<int, double> load(std::istream &stream);
};

template <class NodeType, class CoefType>
QUBOModel<NodeType, CoefType>::QUBOModel(
    qubo::LinearCoef<NodeType, CoefType> &c_linear,
    qubo::QuadraticCoef<NodeType, CoefType> &c_quadratic) {
  linear = c_linear;
  quadratic = c_quadratic;
}

template <class NodeType, class CoefType>
void QUBOModel<NodeType, CoefType>::set_nodes(int number) {
  QUBOModel::num_nodes = number;
}
template <class NodeType, class CoefType>
ulong QUBOModel<NodeType, CoefType>::get_nodes() const {
  return num_nodes;
}

template <class NodeType, class CoefType>
std::string QUBOModel<NodeType, CoefType>::str() const {

  std::string s;

  s.append("QUBO model");

  for (auto &qii : linear) {
    s.append(" " + std::to_string(qii.first) + "--" +
             std::to_string(qii.first) + ":" + std::to_string(qii.second) +
             " ");
  }

  for (auto &qij : quadratic) {
    s.append(std::to_string(qij.first.first) + "--" +
             std::to_string(qij.first.second) + ":" +
             std::to_string(qij.second));
  }

  return s;
}

template <class NodeType, class CoefType>
void QUBOModel<NodeType, CoefType>::add_variable(const NodeType &vi,
                                                 const CoefType &hi) {
  helpers::insert_model(linear, vi, hi);
}

template <class NodeType, class CoefType>
void QUBOModel<NodeType, CoefType>::add_connection(
    const std::pair<NodeType, NodeType> &connection, const CoefType &Ji) {
  helpers::insert_model(quadratic, connection, Ji);
}

template <class NodeType, class CoefType>
const CoefType QUBOModel<NodeType, CoefType>::get_connection(
    const std::pair<NodeType, NodeType> &connection) const {
  if (quadratic.count(connection) == 0) {

    return 0;

  } else {

    return quadratic.at(connection);
  }
}

template <class NodeType, class CoefType>
const CoefType
QUBOModel<NodeType, CoefType>::get_variable(const NodeType &vi) const {
  if (linear.count(vi) == 0) {

    return 0;

  } else {

    return linear.at(vi);
  }
}

/**
 * @brief Print textual representation of the QUBO model to a stream.
 *
 * @param os Ouput stream reference.
 * @param qubos QUBO model.
 * @return std::ostream& Modified ouput stream reference.
 */
template <class NodeType, class CoefType>
std::ostream &operator<<(std::ostream &os,
                         const qubo::QUBOModel<NodeType, CoefType> &qubos) {
  os << qubos.str();
  return os;
}

/**
 * @brief Create a QUBO model from the input stream.
 *
 * @param is Input stream reference.
 * @param qubos QUBO model.
 * @return std::istream& Modified input stream reference.
 */
template <class NodeType, class CoefType>
std::istream &operator>>(std::istream &is,
                         const qubo::QUBOModel<NodeType, CoefType> &qubos) {
  NodeType J1;
  NodeType J2;
  CoefType value;
  is >> J1 >> J2 >> value;
  if (J1 == J2) {
    qubos.add_variable(J1, value);
  } else {
    qubos.add_connection(std::make_pair(J1, J2), value);
  }

  return is;
}

  /*!
  * @brief Builder for QUBOModel.
  */
  struct QUBOBuilder {
  private:
    qubo::LinearCoef<int, double> linear_c{};
    qubo::QuadraticCoef<int, double> quadratic_c{};
    int num_quadratic = -1;
    int num_linear = -1;
    int max_nodes = -1;
    std::set<int> used_nodes;

  public:
    /*!
    * @brief Add new QUBO Model coefficient.
    *
    * Methods of this class are meant to be used in conjunction with
    * appropriate boost spirit based parser which should call them
    * as actions after parsing respective portions of the input file.
    *
    * @param element a triple (i, j, q_ij) defining coefficient to
    *                be added. The indices pair has to be ordered such
    *                that i <= j.
    * @throw std::invalid_argument if i > j.
    */
    void add_element(boost::fusion::vector<int, int, double> element) {
      auto i = at_c<0>(element);
      auto j = at_c<1>(element);
      auto coef = at_c<2>(element);
      if (i == j) {
        linear_c.insert({i, coef});
      } else if (i < j) {
        quadratic_c.insert({{i, j}, coef});
      } else {
        throw std::invalid_argument("Incorrect file, encountered coefficient "
                                    "from lower triangle of QUBO matrix.");
      }
      used_nodes.insert(i);
      used_nodes.insert(j);
    }

    /*!
    * @brief Set number of quadratic coefficients in the model.
    *
    * @param value number of quadratic coefficients.
    */
    void set_num_quadratic(int value) { num_quadratic = value; }

    /*!
    * @brief Set number of linear coefficients in the model.
    *
    * @param value number of linear coefficients.
    */
    void set_num_linear(int value) { num_linear = value; }

    /*!
    * @brief Set maximum number of nodes in the model.
    *
    * @param value maximm number of nodes in the model.
    */
    void set_max_nodes(int value) { max_nodes = value; }

    /*!
    * @brief Build QUBO Model based on information provided this far
    *        to the builder.
    *
    * @throw std::invalid_argument if one of the following conditions
    *        is met:
    *        - number of quadratic and/or linear coefficients is different
    *          from the declared one.
    *        - number of quadratic/linear coefficients or maximum number
    *          of nodes was not provided.
    *        - there are more variables than the declard maximum number.
    * @returns Constructed QUBO model.
    */
    qubo::QUBOModel<int, double> build_qubo() {
      if (linear_c.size() == 0 && quadratic_c.size() == 0) {
        throw std::invalid_argument("An empty input, no coefficients defined.");
      }
      if (num_quadratic == -1 || num_linear == -1 || max_nodes == -1) {
        throw std::invalid_argument(
            "No header line or header line misformatted.");
      }
      if (num_linear > max_nodes) {
        throw std::invalid_argument(
            "Number of linear terms is greater than num nodes.");
      }
      if (quadratic_c.size() != num_quadratic) {
        throw std::invalid_argument(
            "Number of quadratic terms is not equal to the declared one.");
      }
      if (linear_c.size() != num_linear) {
        throw std::invalid_argument(
            "Number of linear terms is not equal to the declared one.");
      }

      qubo::QUBOModel q(linear_c, quadratic_c);
      q.set_nodes(*used_nodes.rbegin() + 1);
      return q;
    }
  };

  /*!
  * @brief Parse QUBO using boost spirit based parser.
  *
  * @param first Iterator to the start of the input stream containing
  *              definition of QUBO.
  * @param last Iterator to the end of the input stream containing
  *              definition of QUBO.
  * @throw std::invalid_argument if parsing failed, either from syntactic
  *        or semantic reasons.
  * @returns Constructed QUBO.
  */
  template <typename Iterator>
  qubo::QUBOModel<int, double> parse_qubo(Iterator first, Iterator last) {
    QUBOBuilder builder;
    auto real = qi::lexeme[double_];
    auto index = qi::lexeme[qi::uint_ >> " "];
    auto coefficients =
        (index >> index >>
        real)[boost::bind(&QUBOBuilder::add_element, &builder, _1)] >>
        (qi::eol | qi::eoi);
    auto comment = qi::lit('c') >> *(qi::print) >> (qi::eol | qi::eoi);
    auto header =
        (qi::lit("p qubo") >> qi::uint_ >>
        qi::uint_[boost::bind(&QUBOBuilder::set_max_nodes, &builder, _1)] >>
        qi::uint_[boost::bind(&QUBOBuilder::set_num_linear, &builder, _1)] >>
        qi::uint_[boost::bind(&QUBOBuilder::set_num_quadratic, &builder, _1)] >>
        (qi::eol | qi::eoi));

    auto result = phrase_parse(first, last,
                              (*comment >> -header >> *(comment | coefficients)),
                              qi::blank);

    if (!result || first != last) {
      throw std::invalid_argument("Parsing failed. Incorrect file format.");
    }
    return builder.build_qubo();
  }

  /*!
  * @brief Load QUBO from an input stream.
  *
  * @param stream an input stream containing definition of QUBO.
  * @returns A loaded instance.
  */
  template <class NodeType, class CoefType>
  qubo::QUBOModel<int, double>
  qubo::QUBOModel<NodeType, CoefType>::load(std::istream &stream) {
    boost::spirit::istream_iterator first(stream), last;
    return parse_qubo(first, last);
  }

}; // namespace qubo
#endif


/*
 Copyright (C) 2016 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/algorithm/string.hpp>
#include <boost/make_shared.hpp>
#include <map>
#include <ored/marketdata/marketdatumparser.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/parsers.hpp>
#include <ql/time/calendars/weekendsonly.hpp>

using namespace std;
using QuantLib::WeekendsOnly;
using QuantLib::Currency;

namespace ore {
namespace data {

static MarketDatum::InstrumentType parseInstrumentType(const string& s) {

    static map<string, MarketDatum::InstrumentType> b = {
        {"ZERO", MarketDatum::InstrumentType::ZERO},
        {"DISCOUNT", MarketDatum::InstrumentType::DISCOUNT},
        {"MM", MarketDatum::InstrumentType::MM},
        {"MM_FUTURE", MarketDatum::InstrumentType::MM_FUTURE},
        {"FRA", MarketDatum::InstrumentType::FRA},
        {"IMM_FRA", MarketDatum::InstrumentType::IMM_FRA},
        {"IR_SWAP", MarketDatum::InstrumentType::IR_SWAP},
        {"BASIS_SWAP", MarketDatum::InstrumentType::BASIS_SWAP},
        {"CC_BASIS_SWAP", MarketDatum::InstrumentType::CC_BASIS_SWAP},
        {"CC_FIX_FLOAT_SWAP", MarketDatum::InstrumentType::CC_FIX_FLOAT_SWAP},
        {"BMA_SWAP", MarketDatum::InstrumentType::BMA_SWAP},
        {"CDS", MarketDatum::InstrumentType::CDS},
        {"CDS_INDEX", MarketDatum::InstrumentType::CDS_INDEX},
        {"FX", MarketDatum::InstrumentType::FX_SPOT},
        {"FX_SPOT", MarketDatum::InstrumentType::FX_SPOT},
        {"FXFWD", MarketDatum::InstrumentType::FX_FWD},
        {"FX_FWD", MarketDatum::InstrumentType::FX_FWD},
        {"HAZARD_RATE", MarketDatum::InstrumentType::HAZARD_RATE},
        {"RECOVERY_RATE", MarketDatum::InstrumentType::RECOVERY_RATE},
        {"FX_FWD", MarketDatum::InstrumentType::FX_FWD},
        {"SWAPTION", MarketDatum::InstrumentType::SWAPTION},
        {"CAPFLOOR", MarketDatum::InstrumentType::CAPFLOOR},
        {"FX_OPTION", MarketDatum::InstrumentType::FX_OPTION},
        {"EQUITY", MarketDatum::InstrumentType::EQUITY_SPOT},
        {"EQUITY_FWD", MarketDatum::InstrumentType::EQUITY_FWD},
        {"EQUITY_DIVIDEND", MarketDatum::InstrumentType::EQUITY_DIVIDEND},
        {"EQUITY_OPTION", MarketDatum::InstrumentType::EQUITY_OPTION},
        {"BOND", MarketDatum::InstrumentType::BOND},
        {"BOND_OPTION", MarketDatum::InstrumentType::BOND_OPTION},
        {"ZC_INFLATIONSWAP", MarketDatum::InstrumentType::ZC_INFLATIONSWAP},
        {"ZC_INFLATIONCAPFLOOR", MarketDatum::InstrumentType::ZC_INFLATIONCAPFLOOR},
        {"YY_INFLATIONSWAP", MarketDatum::InstrumentType::YY_INFLATIONSWAP},
        {"YY_INFLATIONCAPFLOOR", MarketDatum::InstrumentType::YY_INFLATIONCAPFLOOR},
        {"SEASONALITY", MarketDatum::InstrumentType::SEASONALITY},
        {"INDEX_CDS_OPTION", MarketDatum::InstrumentType::INDEX_CDS_OPTION},
        {"COMMODITY", MarketDatum::InstrumentType::COMMODITY_SPOT},
        {"COMMODITY_FWD", MarketDatum::InstrumentType::COMMODITY_FWD},
        {"CORRELATION", MarketDatum::InstrumentType::CORRELATION},
        {"COMMODITY_OPTION", MarketDatum::InstrumentType::COMMODITY_OPTION},
        {"CPR", MarketDatum::InstrumentType::CPR}};

    auto it = b.find(s);
    if (it != b.end()) {
        return it->second;
    } else {
        QL_FAIL("Cannot convert " << s << " to InstrumentType");
    }
}

static MarketDatum::QuoteType parseQuoteType(const string& s) {
    static map<string, MarketDatum::QuoteType> b = {
        {"BASIS_SPREAD", MarketDatum::QuoteType::BASIS_SPREAD},
        {"CREDIT_SPREAD", MarketDatum::QuoteType::CREDIT_SPREAD},
        {"YIELD_SPREAD", MarketDatum::QuoteType::YIELD_SPREAD},
        {"RATE", MarketDatum::QuoteType::RATE},
        {"RATIO", MarketDatum::QuoteType::RATIO},
        {"PRICE", MarketDatum::QuoteType::PRICE},
        {"RATE_LNVOL", MarketDatum::QuoteType::RATE_LNVOL},
        {"RATE_GVOL", MarketDatum::QuoteType::RATE_LNVOL}, // deprecated
        {"RATE_NVOL", MarketDatum::QuoteType::RATE_NVOL},
        {"RATE_SLNVOL", MarketDatum::QuoteType::RATE_SLNVOL},
        {"BASE_CORRELATION", MarketDatum::QuoteType::BASE_CORRELATION},
        {"SHIFT", MarketDatum::QuoteType::SHIFT},
    };

    if (s == "RATE_GVOL")
        LOG("Use of deprecated quote type RATE_GVOL");

    auto it = b.find(s);
    if (it != b.end()) {
        return it->second;
    } else {
        QL_FAIL("Cannot convert " << s << " to QuoteType");
    }
}

// calls parseDateOrPeriod and returns a Date (either the supplied date or asof+period)
static Date getDateFromDateOrPeriod(const string& token, Date asof) {
    Period term;                                           // gets populated by parseDateOrPeriod
    Date expiryDate;                                       // gets populated by parseDateOrPeriod
    bool tmpIsDate;                                        // gets populated by parseDateOrPeriod
    parseDateOrPeriod(token, expiryDate, term, tmpIsDate); // checks if the market string contains a date or a period
    if (!tmpIsDate)
        expiryDate =
            WeekendsOnly().adjust(asof + term); // we have no calendar information here, so we use a generic calendar
    return expiryDate;
}

//! Function to parse a market datum
boost::shared_ptr<MarketDatum> parseMarketDatum(const Date& asof, const string& datumName, const Real& value) {

    vector<string> tokens;
    boost::split(tokens, datumName, boost::is_any_of("/"));
    QL_REQUIRE(tokens.size() > 2, "more than 2 tokens expected in " << datumName);

    MarketDatum::InstrumentType instrumentType = parseInstrumentType(tokens[0]);
    MarketDatum::QuoteType quoteType = parseQuoteType(tokens[1]);

    switch (instrumentType) {

    case MarketDatum::InstrumentType::ZERO: {
        // ZERO/RATE/EUR/EUR1D/A365/1Y
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE || quoteType == MarketDatum::QuoteType::YIELD_SPREAD,
                   "Invalid quote type for " << datumName);
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        DayCounter dc = parseDayCounter(tokens[4]);
        // token 5 can be a date, or tenor
        Date date = Date();
        Period tenor = Period();
        bool isDate;
        parseDateOrPeriod(tokens[5], date, tenor, isDate);
        return boost::make_shared<ZeroQuote>(value, asof, datumName, quoteType, ccy, date, dc, tenor);
    }

    case MarketDatum::InstrumentType::DISCOUNT: {
        // DISCOUNT/RATE/EUR/EUR1D/1Y
        // DISCOUNT/RATE/EUR/EUR1D/2016-12-15
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        // token 4 can be a date, or tenor
        Date date = Date();
        Period tenor = Period();
        bool isDate;
        parseDateOrPeriod(tokens[4], date, tenor, isDate);
        if (!isDate) {
            // we can't assume any calendar here, so we do the minimal adjustment with a weekend only calendar
            QL_REQUIRE(tenor != Period(), "neither date nor tenor recognised");
            date = WeekendsOnly().adjust(asof + tenor);
        }
        return boost::make_shared<DiscountQuote>(value, asof, datumName, quoteType, ccy, date);
    }

    case MarketDatum::InstrumentType::MM: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        Period fwdStart = parsePeriod(tokens[3]);
        Period term = parsePeriod(tokens[4]);
        return boost::make_shared<MoneyMarketQuote>(value, asof, datumName, quoteType, ccy, fwdStart, term);
    }

    case MarketDatum::InstrumentType::MM_FUTURE: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        const string& expiry = tokens[3];
        const string& contract = tokens[4];
        Period term = parsePeriod(tokens[5]);
        return boost::make_shared<MMFutureQuote>(value, asof, datumName, quoteType, ccy, expiry, contract, term);
    }

    case MarketDatum::InstrumentType::FRA: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        Period fwdStart = parsePeriod(tokens[3]);
        Period term = parsePeriod(tokens[4]);
        return boost::make_shared<FRAQuote>(value, asof, datumName, quoteType, ccy, fwdStart, term);
    }

    case MarketDatum::InstrumentType::IMM_FRA: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        string imm1 = tokens[3];
        string imm2 = tokens[4];
        unsigned int m1 = parseInteger(imm1);
        unsigned int m2 = parseInteger(imm2);
        QL_REQUIRE(m2 > m1, "Second IMM date must be after the first in " << datumName);
        return boost::make_shared<ImmFraQuote>(value, asof, datumName, quoteType, ccy, m1, m2);
    }

    case MarketDatum::InstrumentType::IR_SWAP: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        Period fwdStart = parsePeriod(tokens[3]);
        Period tenor = parsePeriod(tokens[4]);
        Period term = parsePeriod(tokens[5]);
        return boost::make_shared<SwapQuote>(value, asof, datumName, quoteType, ccy, fwdStart, term, tenor);
    }

    case MarketDatum::InstrumentType::BASIS_SWAP: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        Period flatTerm = parsePeriod(tokens[2]);
        Period term = parsePeriod(tokens[3]);
        const string& ccy = tokens[4];
        Period maturity = parsePeriod(tokens[5]);
        return boost::make_shared<BasisSwapQuote>(value, asof, datumName, quoteType, flatTerm, term, ccy, maturity);
    }

    case MarketDatum::InstrumentType::BMA_SWAP: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        Period term = parsePeriod(tokens[3]);
        Period maturity = parsePeriod(tokens[4]);
        return boost::make_shared<BMASwapQuote>(value, asof, datumName, quoteType, term, ccy, maturity);
    }

    case MarketDatum::InstrumentType::CC_BASIS_SWAP: {
        QL_REQUIRE(tokens.size() == 7, "7 tokens expected in " << datumName);
        const string& flatCcy = tokens[2];
        Period flatTerm = parsePeriod(tokens[3]);
        const string& ccy = tokens[4];
        Period term = parsePeriod(tokens[5]);
        Period maturity = parsePeriod(tokens[6]);
        return boost::make_shared<CrossCcyBasisSwapQuote>(value, asof, datumName, quoteType, flatCcy, flatTerm, ccy,
                                                          term, maturity);
    }

    case MarketDatum::InstrumentType::CC_FIX_FLOAT_SWAP: {
        // CC_FIX_FLOAT_SWAP/RATE/USD/3M/TRY/1Y/5Y
        QL_REQUIRE(tokens.size() == 7, "7 tokens expected in " << datumName);
        Currency floatCurrency = parseCurrency(tokens[2]);
        Period floatTenor = parsePeriod(tokens[3]);
        Currency fixedCurrency = parseCurrency(tokens[4]);
        Period fixedTenor = parsePeriod(tokens[5]);
        Period maturity = parsePeriod(tokens[6]);
        return boost::make_shared<CrossCcyFixFloatSwapQuote>(value, asof, datumName, quoteType, floatCurrency,
                                                             floatTenor, fixedCurrency, fixedTenor, maturity);
    }

    case MarketDatum::InstrumentType::CDS: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& underlyingName = tokens[2];
        const string& seniority = tokens[3];
        const string& ccy = tokens[4];
        Period term = parsePeriod(tokens[5]);
        return boost::make_shared<CdsSpreadQuote>(value, asof, datumName, underlyingName, seniority, ccy, term);
    }

    case MarketDatum::InstrumentType::HAZARD_RATE: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& underlyingName = tokens[2];
        const string& seniority = tokens[3];
        const string& ccy = tokens[4];
        Period term = parsePeriod(tokens[5]);
        return boost::make_shared<HazardRateQuote>(value, asof, datumName, underlyingName, seniority, ccy, term);
    }

    case MarketDatum::InstrumentType::RECOVERY_RATE: {
        QL_REQUIRE(tokens.size() == 3 || tokens.size() == 5, "3 or 5 tokens expected in " << datumName);
        const string& underlyingName = tokens[2]; // issuer name for CDS, security ID for bond specific RRs
        string seniority = "";
        string ccy = "";
        if (tokens.size() == 5) {
            // CDS
            seniority = tokens[3];
            ccy = tokens[4];
        }
        return boost::make_shared<RecoveryRateQuote>(value, asof, datumName, underlyingName, seniority, ccy);
    }

    case MarketDatum::InstrumentType::CAPFLOOR: {
        QL_REQUIRE(tokens.size() == 8 || tokens.size() == 4, "Either 4 or 8 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        if (tokens.size() == 8) {
            Period term = parsePeriod(tokens[3]);
            Period tenor = parsePeriod(tokens[4]);
            bool atm = parseBool(tokens[5].c_str());
            bool relative = parseBool(tokens[6].c_str());
            Real strike = parseReal(tokens[7]);
            return boost::make_shared<CapFloorQuote>(value, asof, datumName, quoteType, ccy, term, tenor, atm, relative,
                                                     strike);
        } else {
            Period indexTenor = parsePeriod(tokens[3]);
            return boost::make_shared<CapFloorShiftQuote>(value, asof, datumName, quoteType, ccy, indexTenor);
        }
    }

    case MarketDatum::InstrumentType::SWAPTION: {
        QL_REQUIRE(tokens.size() == 4 || tokens.size() == 6 || tokens.size() == 7,
                   "4, 6 or 7 tokens expected in " << datumName);
        const string& ccy = tokens[2];
        Period expiry = tokens.size() >= 6 ? parsePeriod(tokens[3]) : Period(0 * QuantLib::Days);
        Period term = tokens.size() >= 6 ? parsePeriod(tokens[4]) : parsePeriod(tokens[3]);
        if (tokens.size() >= 6) { // volatility
            const string& dimension = tokens[5];
            Real strike = 0.0;
            if (dimension == "ATM")
                QL_REQUIRE(tokens.size() == 6, "6 tokens expected in ATM quote " << datumName);
            else if (dimension == "Smile") {
                QL_REQUIRE(tokens.size() == 7, "7 tokens expected in Smile quote " << datumName);
                strike = parseReal(tokens[6]);
            } else
                QL_FAIL("Swaption vol quote dimension " << dimension << " not recognised");
            return boost::make_shared<SwaptionQuote>(value, asof, datumName, quoteType, ccy, expiry, term, dimension,
                                                     strike);
        } else { // SLN volatility shift
            return boost::make_shared<SwaptionShiftQuote>(value, asof, datumName, quoteType, ccy, term);
        }
    }

    case MarketDatum::InstrumentType::BOND_OPTION: {
        QL_REQUIRE(tokens.size() == 4 || tokens.size() == 6, "4 or 6 tokens expected in " << datumName);
        const string& qualifier = tokens[2];
        Period expiry = tokens.size() == 6 ? parsePeriod(tokens[3]) : Period(0 * QuantLib::Days);
        Period term = tokens.size() == 6 ? parsePeriod(tokens[4]) : parsePeriod(tokens[3]);
        if (tokens.size() == 6) { // volatility
            QL_REQUIRE(tokens[5] == "ATM", "only ATM allowed for bond option quotes");
            return boost::make_shared<BondOptionQuote>(value, asof, datumName, quoteType, qualifier, expiry, term);
        }
        else { // SLN volatility shift
            return boost::make_shared<BondOptionShiftQuote>(value, asof, datumName, quoteType, qualifier, term);
        }
    }

    case MarketDatum::InstrumentType::FX_SPOT: {
        QL_REQUIRE(tokens.size() == 4, "4 tokens expected in " << datumName);
        const string& unitCcy = tokens[2];
        const string& ccy = tokens[3];
        return boost::make_shared<FXSpotQuote>(value, asof, datumName, quoteType, unitCcy, ccy);
    }

    case MarketDatum::InstrumentType::FX_FWD: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& unitCcy = tokens[2];
        const string& ccy = tokens[3];
        Period term = parsePeriod(tokens[4]);
        return boost::make_shared<FXForwardQuote>(value, asof, datumName, quoteType, unitCcy, ccy, term);
    }

    case MarketDatum::InstrumentType::FX_OPTION: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& unitCcy = tokens[2];
        const string& ccy = tokens[3];
        Period expiry = parsePeriod(tokens[4]);
        const string& strike = tokens[5];
        return boost::make_shared<FXOptionQuote>(value, asof, datumName, quoteType, unitCcy, ccy, expiry, strike);
    }

    case MarketDatum::InstrumentType::ZC_INFLATIONSWAP: {
        QL_REQUIRE(tokens.size() == 4, "4 tokens expected in " << datumName);
        const string& index = tokens[2];
        Period term = parsePeriod(tokens[3]);
        return boost::make_shared<ZcInflationSwapQuote>(value, asof, datumName, index, term);
    }

    case MarketDatum::InstrumentType::YY_INFLATIONSWAP: {
        QL_REQUIRE(tokens.size() == 4, "4 tokens expected in " << datumName);
        const string& index = tokens[2];
        Period term = parsePeriod(tokens[3]);
        return boost::make_shared<YoYInflationSwapQuote>(value, asof, datumName, index, term);
    }

    case MarketDatum::InstrumentType::ZC_INFLATIONCAPFLOOR: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& index = tokens[2];
        Period term = parsePeriod(tokens[3]);
        QL_REQUIRE(tokens[4] == "C" || tokens[4] == "F",
                   "excepted C or F for Cap or Floor at position 5 in " << datumName);
        bool isCap = tokens[4] == "C";
        string strike = tokens[5];
        return boost::make_shared<ZcInflationCapFloorQuote>(value, asof, datumName, quoteType, index, term, isCap,
                                                            strike);
    }

    case MarketDatum::InstrumentType::YY_INFLATIONCAPFLOOR: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        const string& index = tokens[2];
        Period term = parsePeriod(tokens[3]);
        QL_REQUIRE(tokens[4] == "C" || tokens[4] == "F",
                   "excepted C or F for Cap or Floor at position 5 in " << datumName);
        bool isCap = tokens[4] == "C";
        string strike = tokens[5];
        return boost::make_shared<YyInflationCapFloorQuote>(value, asof, datumName, quoteType, index, term, isCap,
                                                            strike);
    }

    case MarketDatum::InstrumentType::SEASONALITY: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        const string& index = tokens[3];
        const string& type = tokens[2];
        const string& month = tokens[4];
        return boost::make_shared<SeasonalityQuote>(value, asof, datumName, index, type, month);
    }
    case MarketDatum::InstrumentType::EQUITY_SPOT: {
        QL_REQUIRE(tokens.size() == 4, "4 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::PRICE, "Invalid quote type for " << datumName);
        const string& equityName = tokens[2];
        const string& ccy = tokens[3];
        return boost::make_shared<EquitySpotQuote>(value, asof, datumName, quoteType, equityName, ccy);
    }

    case MarketDatum::InstrumentType::EQUITY_FWD: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::PRICE, "Invalid quote type for " << datumName);
        const string& equityName = tokens[2];
        const string& ccy = tokens[3];
        Date expiryDate = getDateFromDateOrPeriod(tokens[4], asof);
        return boost::make_shared<EquityForwardQuote>(value, asof, datumName, quoteType, equityName, ccy, expiryDate);
    }

    case MarketDatum::InstrumentType::EQUITY_DIVIDEND: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE, "Invalid quote type for " << datumName);
        const string& equityName = tokens[2];
        const string& ccy = tokens[3];
        Date tenorDate = getDateFromDateOrPeriod(tokens[4], asof);
        return boost::make_shared<EquityDividendYieldQuote>(value, asof, datumName, quoteType, equityName, ccy,
                                                            tenorDate);
    }

    case MarketDatum::InstrumentType::EQUITY_OPTION: {
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE_LNVOL, "Invalid quote type for " << datumName);
        const string& equityName = tokens[2];
        const string& ccy = tokens[3];
        string expiryString = tokens[4];
        const string& strike = tokens[5];
        // note how we only store the expiry string - to ensure we can support both Periods and Dates being specified in
        // the vol curve-config.
        return boost::make_shared<EquityOptionQuote>(value, asof, datumName, quoteType, equityName, ccy, expiryString,
                                                     strike);
    }

    case MarketDatum::InstrumentType::BOND: {
        QL_REQUIRE(tokens.size() == 3, "3 tokens expected in " << datumName);
        const string& securityID = tokens[2];
        return boost::make_shared<SecuritySpreadQuote>(value, asof, datumName, securityID);
    }

    case MarketDatum::InstrumentType::CDS_INDEX: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::BASE_CORRELATION, "Invalid quote type for " << datumName);
        const string& cdsIndexName = tokens[2];
        Period term = parsePeriod(tokens[3]);
        Real detachmentPoint = parseReal(tokens[4]);
        return boost::make_shared<BaseCorrelationQuote>(value, asof, datumName, quoteType, cdsIndexName, term,
                                                        detachmentPoint);
    }

    case MarketDatum::InstrumentType::INDEX_CDS_OPTION: {
        QL_REQUIRE(tokens.size() == 4, "4 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE_LNVOL, "Invalid quote type for " << datumName);
        const string& indexName = tokens[2];
        const string& expiry = tokens[3];
        return boost::make_shared<IndexCDSOptionQuote>(value, asof, datumName, indexName, expiry);
    }

    case MarketDatum::InstrumentType::COMMODITY_SPOT: {
        QL_REQUIRE(tokens.size() == 4, "4 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::PRICE, "Invalid quote type for " << datumName);

        return boost::make_shared<CommoditySpotQuote>(value, asof, datumName, quoteType, tokens[2], tokens[3]);
    }

    case MarketDatum::InstrumentType::COMMODITY_FWD: {
        QL_REQUIRE(tokens.size() == 5, "5 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::PRICE, "Invalid quote type for " << datumName);

        Date expiryDate = getDateFromDateOrPeriod(tokens[4], asof);
        return boost::make_shared<CommodityForwardQuote>(value, asof, datumName, quoteType, tokens[2], tokens[3],
                                                         expiryDate);
    }

    case MarketDatum::InstrumentType::COMMODITY_OPTION: {
        // Expects the following form:
        // COMMODITY_OPTION/RATE_LNVOL/<COMDTY_NAME>/<CCY>/<DATE/TENOR>/<STRIKE>
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE_LNVOL,
                   "Quote type for " << datumName << " should be 'RATE_LNVOL'");

        return boost::make_shared<CommodityOptionQuote>(value, asof, datumName, quoteType, tokens[2], tokens[3],
                                                        tokens[4], tokens[5]);
    }

    case MarketDatum::InstrumentType::CORRELATION: {
        // Expects the following form:
        // CORRELATION/RATE/<INDEX1>/<INDEX2>/<TENOR>/<STRIKE>
        QL_REQUIRE(tokens.size() == 6, "6 tokens expected in " << datumName);
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE || quoteType == MarketDatum::QuoteType::PRICE,
                   "Quote type for " << datumName << " should be 'CORRELATION' or 'PRICE'");

        return boost::make_shared<CorrelationQuote>(value, asof, datumName, quoteType, tokens[2], tokens[3], tokens[4],
                                                    tokens[5]);
    }

    case MarketDatum::InstrumentType::CPR: {
        QL_REQUIRE(tokens.size() == 3, "3 tokens expected in " << datumName);
        const string& securityID = tokens[2];
        QL_REQUIRE(quoteType == MarketDatum::QuoteType::RATE, "Invalid quote type for " << datumName);
        return boost::make_shared<CPRQuote>(value, asof, datumName, securityID);
    }

    default:
        QL_FAIL("Cannot convert \"" << datumName << "\" to MarketDatum");
    } // switch instrument type
} // parseMarketDatum
} // namespace data
} // namespace ore


// 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_OB_TRAN_HPP
#define BOOST_GEOMETRY_PROJECTIONS_OB_TRAN_HPP

#include <boost/geometry/util/math.hpp>
#include <boost/shared_ptr.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 srs { namespace par4
{
    //struct ob_tran_oblique {};
    //struct ob_tran_transverse {};
    struct ob_tran {}; // General Oblique Transformation

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail {

        // fwd declaration needed below
        template <typename T>
        inline detail::base_v<T, parameters<T> >*
            create_new(parameters<T> const& parameters);

    } // namespace detail

    namespace detail { namespace ob_tran
    {

            static const double tolerance = 1e-10;

            template <typename Parameters>
            inline Parameters o_proj_parameters(Parameters const& par)
            {
                /* copy existing header into new */
                Parameters pj = par;

                /* get name of projection to be translated */
                pj.name = pj_get_param_s(par.params, "o_proj");
                if (pj.name.empty())
                    BOOST_THROW_EXCEPTION( projection_exception(error_no_rotation_proj) );

                /* avoid endless recursion */
                if( pj.name == "ob_tran")
                    BOOST_THROW_EXCEPTION( projection_exception(error_failed_to_find_proj) );

                /* force spherical earth */
                pj.one_es = pj.rone_es = 1.;
                pj.es = pj.e = 0.;

                return pj;
            }

            template <typename T, typename Parameters>
            struct par_ob_tran
            {
                par_ob_tran(Parameters const& par)
                    : link(projections::detail::create_new(o_proj_parameters(par)))
                {
                    if (! link.get())
                        BOOST_THROW_EXCEPTION( projection_exception(error_unknown_projection_id) );
                }

                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    link->fwd(lp_lon, lp_lat, xy_x, xy_y);
                }

                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    link->inv(xy_x, xy_y, lp_lon, lp_lat);
                }

                boost::shared_ptr<base_v<T, Parameters> > link;
                T lamp;
                T cphip, sphip;
            };

            template <typename StaticParameters, typename T, typename Parameters>
            struct par_ob_tran_static
            {
                // this metafunction handles static error handling
                typedef typename srs::par4::detail::pick_o_proj_tag
                    <
                        StaticParameters
                    >::type o_proj_tag;

                /* avoid endless recursion */
                static const bool is_o_proj_not_ob_tran = ! boost::is_same<o_proj_tag, srs::par4::ob_tran>::value;
                BOOST_MPL_ASSERT_MSG((is_o_proj_not_ob_tran), INVALID_O_PROJ_PARAMETER, (StaticParameters));

                typedef typename projections::detail::static_projection_type
                    <
                        o_proj_tag,
                        srs_sphere_tag, // force spherical
                        StaticParameters,
                        T,
                        Parameters
                    >::type projection_type;

                par_ob_tran_static(Parameters const& par)
                    : link(o_proj_parameters(par))
                {}

                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    link.fwd(lp_lon, lp_lat, xy_x, xy_y);
                }

                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    link.inv(xy_x, xy_y, lp_lon, lp_lat);
                }

                projection_type link;
                T lamp;
                T cphip, sphip;
            };

            template <typename T, typename Par>
            inline void o_forward(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y, Par const& proj_parm)
            {
                T coslam, sinphi, cosphi;

                coslam = cos(lp_lon);
                sinphi = sin(lp_lat);
                cosphi = cos(lp_lat);
                lp_lon = adjlon(aatan2(cosphi * sin(lp_lon), proj_parm.sphip * cosphi * coslam +
                    proj_parm.cphip * sinphi) + proj_parm.lamp);
                lp_lat = aasin(proj_parm.sphip * sinphi - proj_parm.cphip * cosphi * coslam);

                proj_parm.fwd(lp_lon, lp_lat, xy_x, xy_y);
            }

            template <typename T, typename Par>
            inline void o_inverse(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat, Par const& proj_parm)
            {
                T coslam, sinphi, cosphi;

                proj_parm.inv(xy_x, xy_y, lp_lon, lp_lat);
                if (lp_lon != HUGE_VAL) {
                    coslam = cos(lp_lon -= proj_parm.lamp);
                    sinphi = sin(lp_lat);
                    cosphi = cos(lp_lat);
                    lp_lat = aasin(proj_parm.sphip * sinphi + proj_parm.cphip * cosphi * coslam);
                    lp_lon = aatan2(cosphi * sin(lp_lon), proj_parm.sphip * cosphi * coslam -
                        proj_parm.cphip * sinphi);
                }
            }

            template <typename T, typename Par>
            inline void t_forward(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y, Par const& proj_parm)
            {
                T cosphi, coslam;

                cosphi = cos(lp_lat);
                coslam = cos(lp_lon);
                lp_lon = adjlon(aatan2(cosphi * sin(lp_lon), sin(lp_lat)) + proj_parm.lamp);
                lp_lat = aasin(- cosphi * coslam);

                proj_parm.fwd(lp_lon, lp_lat, xy_x, xy_y);
            }

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

                proj_parm.inv(xy_x, xy_y, lp_lon, lp_lat);
                if (lp_lon != HUGE_VAL) {
                    cosphi = cos(lp_lat);
                    t = lp_lon - proj_parm.lamp;
                    lp_lon = aatan2(cosphi * sin(t), - sin(lp_lat));
                    lp_lat = aasin(cosphi * cos(t));
                }
            }

            // General Oblique Transformation
            template <typename T, typename Parameters, typename ProjParameters>
            inline T setup_ob_tran(Parameters & par, ProjParameters& proj_parm)
            {
                static const T half_pi = detail::half_pi<T>();

                T phip, alpha;

                par.es = 0.; /* force to spherical */

                // proj_parm.link should be created at this point

                if (pj_param_r(par.params, "o_alpha", alpha)) {
                    T lamc, phic;

                    lamc    = pj_get_param_r(par.params, "o_lon_c");
                    phic    = pj_get_param_r(par.params, "o_lat_c");
                    //alpha   = pj_get_param_r(par.params, "o_alpha");

                    if (fabs(fabs(phic) - half_pi) <= tolerance)
                        BOOST_THROW_EXCEPTION( projection_exception(error_lat_0_or_alpha_eq_90) );

                    proj_parm.lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic));
                    phip = aasin(cos(phic) * sin(alpha));
                } else if (pj_param_r(par.params, "o_lat_p", phip)) { /* specified new pole */
                    proj_parm.lamp = pj_get_param_r(par.params, "o_lon_p");
                    //phip = pj_param_r(par.params, "o_lat_p");
                } else { /* specified new "equator" points */
                    T lam1, lam2, phi1, phi2, con;

                    lam1 = pj_get_param_r(par.params, "o_lon_1");
                    phi1 = pj_get_param_r(par.params, "o_lat_1");
                    lam2 = pj_get_param_r(par.params, "o_lon_2");
                    phi2 = pj_get_param_r(par.params, "o_lat_2");
                    if (fabs(phi1 - phi2) <= tolerance || (con = fabs(phi1)) <= tolerance ||
                        fabs(con - half_pi) <= tolerance || fabs(fabs(phi2) - half_pi) <= tolerance)
                        BOOST_THROW_EXCEPTION( projection_exception(error_lat_1_or_2_zero_or_90) );

                    proj_parm.lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) -
                        sin(phi1) * cos(phi2) * cos(lam2),
                        sin(phi1) * cos(phi2) * sin(lam2) -
                        cos(phi1) * sin(phi2) * sin(lam1));
                    phip = atan(-cos(proj_parm.lamp - lam1) / tan(phi1));
                }

                if (fabs(phip) > tolerance) { /* oblique */
                    proj_parm.cphip = cos(phip);
                    proj_parm.sphip = sin(phip);
                } else { /* transverse */
                }

                // TODO:
                /* Support some rather speculative test cases, where the rotated projection */
                /* is actually latlong. We do not want scaling in that case... */
                //if (proj_parm.link...mutable_parameters().right==PJ_IO_UNITS_ANGULAR)
                //    par.right = PJ_IO_UNITS_PROJECTED;

                // return phip to choose model
                return phip;
            }

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

                inline base_ob_tran_oblique(Parameters const& par,
                                            par_ob_tran<T, Parameters> const& proj_parm)
                    : base_t_fi
                        <
                            base_ob_tran_oblique<T, Parameters>, T, Parameters
                        >(*this, par)
                    , m_proj_parm(proj_parm)
                {}

                // FORWARD(o_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    o_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_proj_parm);
                }

                // INVERSE(o_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    o_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_proj_parm);
                }

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

            };

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

                inline base_ob_tran_transverse(Parameters const& par,
                                               par_ob_tran<T, Parameters> const& proj_parm)
                    : base_t_fi
                        <
                            base_ob_tran_transverse<T, Parameters>, T, Parameters
                        >(*this, par)
                    , m_proj_parm(proj_parm)
                {}

                // FORWARD(t_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    t_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_proj_parm);
                }

                // INVERSE(t_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    t_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_proj_parm);
                }

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

            };

            // template class, using CRTP to implement forward/inverse
            template <typename StaticParameters, typename T, typename Parameters>
            struct base_ob_tran_static
                : public base_t_fi<base_ob_tran_static<StaticParameters, T, Parameters>, T, Parameters>
            {
                par_ob_tran_static<StaticParameters, T, Parameters> m_proj_parm;
                bool m_is_oblique;

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

                // FORWARD(o_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    if (m_is_oblique) {
                        o_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_proj_parm);
                    } else {
                        t_forward(lp_lon, lp_lat, xy_x, xy_y, this->m_proj_parm);
                    }
                }

                // INVERSE(o_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    if (m_is_oblique) {
                        o_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_proj_parm);
                    } else {
                        t_inverse(xy_x, xy_y, lp_lon, lp_lat, this->m_proj_parm);
                    }
                }

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

            };

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

    /*!
        \brief General Oblique Transformation 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
         - o_proj (string)
         - Plus projection parameters
         - o_lat_p (degrees)
         - o_lon_p (degrees)
         - New pole
         - o_alpha: Alpha (degrees)
         - o_lon_c (degrees)
         - o_lat_c (degrees)
         - o_lon_1 (degrees)
         - o_lat_1: Latitude of first standard parallel (degrees)
         - o_lon_2 (degrees)
         - o_lat_2: Latitude of second standard parallel (degrees)
        \par Example
        \image html ex_ob_tran.gif
    */
    template <typename T, typename Parameters>
    struct ob_tran_oblique : public detail::ob_tran::base_ob_tran_oblique<T, Parameters>
    {
        inline ob_tran_oblique(Parameters const& par,
                               detail::ob_tran::par_ob_tran<T, Parameters> const& proj_parm)
            : detail::ob_tran::base_ob_tran_oblique<T, Parameters>(par, proj_parm)
        {
            // already done
            //detail::ob_tran::setup_ob_tran(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief General Oblique Transformation 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
         - o_proj (string)
         - Plus projection parameters
         - o_lat_p (degrees)
         - o_lon_p (degrees)
         - New pole
         - o_alpha: Alpha (degrees)
         - o_lon_c (degrees)
         - o_lat_c (degrees)
         - o_lon_1 (degrees)
         - o_lat_1: Latitude of first standard parallel (degrees)
         - o_lon_2 (degrees)
         - o_lat_2: Latitude of second standard parallel (degrees)
        \par Example
        \image html ex_ob_tran.gif
    */
    template <typename T, typename Parameters>
    struct ob_tran_transverse : public detail::ob_tran::base_ob_tran_transverse<T, Parameters>
    {
        inline ob_tran_transverse(Parameters const& par,
                                  detail::ob_tran::par_ob_tran<T, Parameters> const& proj_parm)
            : detail::ob_tran::base_ob_tran_transverse<T, Parameters>(par, proj_parm)
        {
            // already done
            //detail::ob_tran::setup_ob_tran(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief General Oblique Transformation 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
         - o_proj (string)
         - Plus projection parameters
         - o_lat_p (degrees)
         - o_lon_p (degrees)
         - New pole
         - o_alpha: Alpha (degrees)
         - o_lon_c (degrees)
         - o_lat_c (degrees)
         - o_lon_1 (degrees)
         - o_lat_1: Latitude of first standard parallel (degrees)
         - o_lon_2 (degrees)
         - o_lat_2: Latitude of second standard parallel (degrees)
        \par Example
        \image html ex_ob_tran.gif
    */
    template <typename StaticParameters, typename T, typename Parameters>
    struct ob_tran_static : public detail::ob_tran::base_ob_tran_static<StaticParameters, T, Parameters>
    {
        inline ob_tran_static(const Parameters& par)
            : detail::ob_tran::base_ob_tran_static<StaticParameters, T, Parameters>(par)
        {
            T phip = detail::ob_tran::setup_ob_tran<T>(this->m_par, this->m_proj_parm);
            this->m_is_oblique = fabs(phip) > detail::ob_tran::tolerance;
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        template <typename BGP, typename CT, typename P>
        struct static_projection_type<srs::par4::ob_tran, srs_sphere_tag, BGP, CT, P>
        {
            typedef ob_tran_static<BGP, CT, P> type;
        };
        template <typename BGP, typename CT, typename P>
        struct static_projection_type<srs::par4::ob_tran, srs_spheroid_tag, BGP, CT, P>
        {
            typedef ob_tran_static<BGP, CT, P> type;
        };

        // Factory entry(s)
        template <typename T, typename Parameters>
        class ob_tran_entry : public detail::factory_entry<T, Parameters>
        {
            public :
                virtual base_v<T, Parameters>* create_new(const Parameters& par) const
                {
                    Parameters params = par;
                    detail::ob_tran::par_ob_tran<T, Parameters> proj_parm(params);
                    T phip = detail::ob_tran::setup_ob_tran<T>(params, proj_parm);

                    if (fabs(phip) > detail::ob_tran::tolerance)
                        return new base_v_fi<ob_tran_oblique<T, Parameters>, T, Parameters>(params, proj_parm);
                    else
                        return new base_v_fi<ob_tran_transverse<T, Parameters>, T, Parameters>(params, proj_parm);
                }
        };

        template <typename T, typename Parameters>
        inline void ob_tran_init(detail::base_factory<T, Parameters>& factory)
        {
            factory.add_to_factory("ob_tran", new ob_tran_entry<T, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_OB_TRAN_HPP


//projection1d.cpp
/*
* the projection1d class impl
* (C) 2004 olegabr. All rights reserved.
*/
#include "projection1d.h"
#include "ro_filtrate.h"

#include <iostream>
using std::cout; using std::endl;

#include <algorithm>
#include <numeric>
#include <cmath>
#include <cassert>

#include <boost/lambda/lambda.hpp>

projection1d::projection1d(size1d_t size, point1d point_size)
	: size_(size), point_size_(point_size)
{
	//cout << "projection1d(size)" << endl;
	data_.reserve(size);
	std::fill_n
		(
			std::back_inserter(data_),
			size,
			0.
		);

	//data_.resize(size_);
	//clear();
}

projection1d::projection1d(const projection1d& other)
	: size_(other.size_), point_size_(other.point_size_)
{
	//cout << "projection1d(projection1d)" << endl;
	if (&other != this)
	{
		if (data_.size() < other.data_.size())
		{
			//cout << "projection1d(projection1d): data_.resize\n";
				//<< "data_.size() = " << data_.size() << endl
				//<< "other.data_.size() = " << other.data_.size() << endl;
			data_.resize(size_);
		}
		std::copy(other.data_.begin(), other.data_.end(), data_.begin());
	}
}
projection1d& projection1d::operator =(const projection1d& other)
{
	//cout << "projection1d::operator =(const projection1d& other)" << endl;
	if (&other != this)
	{
		point_size_ = other.point_size_;
		size_ = other.size_;

		if (data_.size() < other.data_.size())
		{
			//cout << "projection1d::operator =(projection1d): data_.resize\n";
				//<< "data_.size() = " << data_.size() << endl
				//<< "other.data_.size() = " << other.data_.size() << endl;
			data_.resize(size_);
		}
		std::copy(other.data_.begin(), other.data_.end(), data_.begin());
	}
	return *this;
}

//g = f-g
projection1d& projection1d::assign_difference(const projection1d& f_)
{
	assert(f_.point_size_ == point_size_);

	projection1d::data_type::difference_type
		w = (f_.size_ - size_)/2;
	projection1d::iterator
		p  =    data_.begin(),  e =    data_.end();
	projection1d::const_iterator
		fp = f_.data_.begin(), fe = f_.data_.end();
	if (w > 0)      std::advance(fp,  w);
	else if (w < 0) std::advance( p, -w);
	for(; p != e && fp != fe; ++p, ++fp) *p = *fp - *p;

	return *this;
}

std::ostream& operator <<(std::ostream& os, const projection1d& sh)
{
	std::copy
	(
		sh.data_.begin(), sh.data_.end(),
		std::ostream_iterator<projection1d::value_type>(os, "\t")
	);
	return os;
}

void projection1d::clear()
{
	std::fill(data_.begin(), data_.end(), 0.0);
}

void projection1d::ro_filtrate()
{
	util::ro_filtrate(data_);
	if (point_size_ != 1)
		std::transform
		(
			data_.begin(), data_.end(),
			data_.begin(),
			boost::lambda::_1/point_size_
		);
}

point1d projection1d::get_point_gt(point1d l) const
{
	return get_Rmin_() + point_size_*std::ceil((l-get_Rmin_())/point_size_);
}

pixel1d projection1d::point2index_(point1d R) const
{
	return static_cast<pixel1d>(util::round((R - get_Rmin_())/point_size_));
}

void projection1d::add_value(pixel1d i, projection1d::value_type value)
{
	if (in_grid(i, size_)) data_[i] += value;
}

void projection1d::add_value(point1d R, projection1d::value_type value)
{
	add_value(point2index_(R), value);
}

void projection1d::set_value(pixel1d i, projection1d::value_type value)
{
	if (in_grid(i, size_)) data_[i] = value;
}

void projection1d::set_value(point1d R, projection1d::value_type value)
{
	set_value(point2index_(R), value);
}

projection1d::value_type projection1d::get_value(pixel1d i) const
{
	assert(in_grid(i, size_));
	return data_[i];
}

projection1d::value_type
operator *(const projection1d& sh, const projection1d& fsh)
{
	assert(sh.get_size()       == fsh.get_size()      );
	assert(sh.get_point_size() == fsh.get_point_size());

	projection1d::const_iterator
		is = sh.data_.begin(), e = sh.data_.end(), ifs = fsh.data_.begin();
	return
		(
			.5*(*is * *ifs + *sh.data_.rbegin() * *fsh.data_.rbegin()) +
			std::inner_product (++is, --e, ++ifs, 0.)
		)*sh.get_point_size();
}

std::pair<projection1d::value_type, projection1d::value_type>
projection1d::get_min_max_() const
{
	/* named return value optimization :) */
	std::pair<projection1d::value_type, projection1d::value_type>
		result(*std::min_element(data_.begin(), data_.end()), *std::max_element(data_.begin(), data_.end()));
	return result;
}