//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2005-2009. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/container for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef BOOST_CONTAINER_DETAIL_ADAPTIVE_NODE_POOL_IMPL_HPP
#define BOOST_CONTAINER_DETAIL_ADAPTIVE_NODE_POOL_IMPL_HPP

#if (defined _MSC_VER) && (_MSC_VER >= 1200)
#  pragma once
#endif

#include "config_begin.hpp"
#include INCLUDE_BOOST_CONTAINER_CONTAINER_FWD_HPP
#include INCLUDE_BOOST_CONTAINER_DETAIL_WORKAROUND_HPP
#include INCLUDE_BOOST_CONTAINER_DETAIL_UTILITIES_HPP
#include <boost/pointer_to_other.hpp>
#include <boost/intrusive/set.hpp>
#include <boost/intrusive/slist.hpp>
#include INCLUDE_BOOST_CONTAINER_DETAIL_TYPE_TRAITS_HPP
#include INCLUDE_BOOST_CONTAINER_DETAIL_MATH_FUNCTIONS_HPP
#include INCLUDE_BOOST_CONTAINER_DETAIL_MPL_HPP
#include INCLUDE_BOOST_CONTAINER_DETAIL_POOL_COMMON_HPP
#include <boost/assert.hpp>
#include <cstddef>

namespace boost {
namespace container {
namespace containers_detail {

struct hdr_offset_holder
{
   hdr_offset_holder(std::size_t offset = 0)
      : hdr_offset(offset)
   {}
   std::size_t hdr_offset;
};

template<class VoidPointer>
struct adaptive_pool_types
{
   typedef VoidPointer void_pointer;
   typedef typename bi::make_set_base_hook
      < bi::void_pointer<void_pointer>
      , bi::optimize_size<true>
      , bi::constant_time_size<false>
      , bi::link_mode<bi::normal_link> >::type multiset_hook_t;

   struct block_info_t
      :
         public hdr_offset_holder,
         public multiset_hook_t
   {
      typedef typename node_slist<void_pointer>::node_slist_t free_nodes_t;
      //An intrusive list of free node from this block
      free_nodes_t free_nodes;
      friend bool operator <(const block_info_t &l, const block_info_t &r)
      {
//      {  return l.free_nodes.size() < r.free_nodes.size();   }
         //Let's order blocks first by free nodes and then by address
         //so that highest address fully free blocks are deallocated.
         //This improves returning memory to the OS (trimming).
         const bool is_less  = l.free_nodes.size() < r.free_nodes.size();
         const bool is_equal = l.free_nodes.size() == r.free_nodes.size();
         return is_less || (is_equal && (&l < &r));
      }

      friend bool operator ==(const block_info_t &l, const block_info_t &r)
      {  return &l == &r;  }
   };
   typedef typename bi::make_multiset
      <block_info_t, bi::base_hook<multiset_hook_t> >::type  block_multiset_t;
};

inline std::size_t calculate_alignment
   ( std::size_t overhead_percent, std::size_t real_node_size
   , std::size_t hdr_size, std::size_t hdr_offset_size, std::size_t payload_per_allocation)
{
   //to-do: handle real_node_size != node_size
   const std::size_t divisor  = overhead_percent*real_node_size;
   const std::size_t dividend = hdr_offset_size*100;
   std::size_t elements_per_subblock = (dividend - 1)/divisor + 1;
   std::size_t candidate_power_of_2 =
      upper_power_of_2(elements_per_subblock*real_node_size + hdr_offset_size);
   bool overhead_satisfied = false;
   //Now calculate the wors-case overhead for a subblock
   const std::size_t max_subblock_overhead  = hdr_size + payload_per_allocation;
   while(!overhead_satisfied){
      elements_per_subblock = (candidate_power_of_2 - max_subblock_overhead)/real_node_size;
      const std::size_t overhead_size = candidate_power_of_2 - elements_per_subblock*real_node_size;
      if(overhead_size*100/candidate_power_of_2 < overhead_percent){
         overhead_satisfied = true;
      }
      else{
         candidate_power_of_2 <<= 1;
      }
   }
   return candidate_power_of_2;
}

inline void calculate_num_subblocks
   (std::size_t alignment, std::size_t real_node_size, std::size_t elements_per_block
   , std::size_t &num_subblocks, std::size_t &real_num_node, std::size_t overhead_percent
   , std::size_t hdr_size, std::size_t hdr_offset_size, std::size_t payload_per_allocation)
{
   std::size_t elements_per_subblock = (alignment - hdr_offset_size)/real_node_size;
   std::size_t possible_num_subblock = (elements_per_block - 1)/elements_per_subblock + 1;
   std::size_t hdr_subblock_elements = (alignment - hdr_size - payload_per_allocation)/real_node_size;
   while(((possible_num_subblock-1)*elements_per_subblock + hdr_subblock_elements) < elements_per_block){
      ++possible_num_subblock;
   }
   elements_per_subblock = (alignment - hdr_offset_size)/real_node_size;
   bool overhead_satisfied = false;
   while(!overhead_satisfied){
      const std::size_t total_data = (elements_per_subblock*(possible_num_subblock-1) + hdr_subblock_elements)*real_node_size;
      const std::size_t total_size = alignment*possible_num_subblock;
      if((total_size - total_data)*100/total_size < overhead_percent){
         overhead_satisfied = true;
      }
      else{
         ++possible_num_subblock;
      }
   }
   num_subblocks = possible_num_subblock;
   real_num_node = (possible_num_subblock-1)*elements_per_subblock + hdr_subblock_elements;
}

template<class SegmentManagerBase, bool AlignOnly = false>
class private_adaptive_node_pool_impl
{
   //Non-copyable
   private_adaptive_node_pool_impl();
   private_adaptive_node_pool_impl(const private_adaptive_node_pool_impl &);
   private_adaptive_node_pool_impl &operator=(const private_adaptive_node_pool_impl &);
   typedef private_adaptive_node_pool_impl this_type;

   typedef typename SegmentManagerBase::void_pointer void_pointer;
   static const std::size_t PayloadPerAllocation = SegmentManagerBase::PayloadPerAllocation;
   typedef bool_<AlignOnly>            IsAlignOnly;
   typedef true_                       AlignOnlyTrue;
   typedef false_                      AlignOnlyFalse;

   public:
   typedef typename node_slist<void_pointer>::node_t node_t;
   typedef typename node_slist<void_pointer>::node_slist_t free_nodes_t;
   typedef typename SegmentManagerBase::multiallocation_chain     multiallocation_chain;

   private:
   typedef typename adaptive_pool_types<void_pointer>::block_info_t     block_info_t;
   typedef typename adaptive_pool_types<void_pointer>::block_multiset_t block_multiset_t;
   typedef typename block_multiset_t::iterator               block_iterator;

   static const std::size_t MaxAlign = alignment_of<node_t>::value;
   static const std::size_t HdrSize  = ((sizeof(block_info_t)-1)/MaxAlign+1)*MaxAlign;
   static const std::size_t HdrOffsetSize = ((sizeof(hdr_offset_holder)-1)/MaxAlign+1)*MaxAlign;

   public:
   //!Segment manager typedef
   typedef SegmentManagerBase                 segment_manager_base_type;

   //!Constructor from a segment manager. Never throws
   private_adaptive_node_pool_impl
      ( segment_manager_base_type *segment_mngr_base
      , std::size_t node_size
      , std::size_t nodes_per_block
      , std::size_t max_free_blocks
      , unsigned char overhead_percent
      )
   :  m_max_free_blocks(max_free_blocks)
   ,  m_real_node_size(lcm(node_size, std::size_t(alignment_of<node_t>::value)))
      //Round the size to a power of two value.
      //This is the total memory size (including payload) that we want to
      //allocate from the general-purpose allocator
   ,  m_real_block_alignment
         (AlignOnly ?
            upper_power_of_2(HdrSize + m_real_node_size*nodes_per_block) :
            calculate_alignment( overhead_percent, m_real_node_size
                               , HdrSize, HdrOffsetSize, PayloadPerAllocation))
      //This is the real number of nodes per block
   ,  m_num_subblocks(0)
   ,  m_real_num_node(AlignOnly ? (m_real_block_alignment - PayloadPerAllocation - HdrSize)/m_real_node_size : 0)
      //General purpose allocator
   ,  mp_segment_mngr_base(segment_mngr_base)
   ,  m_block_multiset()
   ,  m_totally_free_blocks(0)
   {
      if(!AlignOnly){
         calculate_num_subblocks
            ( m_real_block_alignment
            , m_real_node_size
            , nodes_per_block
            , m_num_subblocks
            , m_real_num_node
            , overhead_percent
            , HdrSize
            , HdrOffsetSize
            , PayloadPerAllocation);
      }
   }

   //!Destructor. Deallocates all allocated blocks. Never throws
   ~private_adaptive_node_pool_impl()
   {  priv_clear();  }

   std::size_t get_real_num_node() const
   {  return m_real_num_node; }

   //!Returns the segment manager. Never throws
   segment_manager_base_type* get_segment_manager_base()const
   {  return containers_detail::get_pointer(mp_segment_mngr_base);  }

   //!Allocates array of count elements. Can throw
   void *allocate_node()
   {
      priv_invariants();
      //If there are no free nodes we allocate a new block
      if (m_block_multiset.empty()){
         priv_alloc_block(1);
      }
      //We take the first free node the multiset can't be empty
      return priv_take_first_node();
   }

   //!Deallocates an array pointed by ptr. Never throws
   void deallocate_node(void *pElem)
   {
      multiallocation_chain chain;
      chain.push_front(void_pointer(pElem));
      this->priv_reinsert_nodes_in_block(chain, 1);
      //Update free block count<
      if(m_totally_free_blocks > m_max_free_blocks){
         this->priv_deallocate_free_blocks(m_max_free_blocks);
      }
      priv_invariants();
   }

   //!Allocates n nodes.
   //!Can throw
   multiallocation_chain allocate_nodes(const std::size_t n)
   {
      multiallocation_chain chain;
      std::size_t i = 0;
      try{
         priv_invariants();
         while(i != n){
            //If there are no free nodes we allocate all needed blocks
            if (m_block_multiset.empty()){
               priv_alloc_block(((n - i) - 1)/m_real_num_node + 1);
            }
            free_nodes_t &free_nodes = m_block_multiset.begin()->free_nodes;
            const std::size_t free_nodes_count_before = free_nodes.size();
            if(free_nodes_count_before == m_real_num_node){
               --m_totally_free_blocks;
            }
            const std::size_t num_elems = ((n-i) < free_nodes_count_before) ? (n-i) : free_nodes_count_before;
            for(std::size_t j = 0; j != num_elems; ++j){
               void *new_node = &free_nodes.front();
               free_nodes.pop_front();
               chain.push_back(new_node);
            }

            if(free_nodes.empty()){
               m_block_multiset.erase(m_block_multiset.begin());
            }
            i += num_elems;
         }
      }
      catch(...){
         this->deallocate_nodes(BOOST_CONTAINER_MOVE_NAMESPACE::move(chain));
         throw;
      }
      priv_invariants();
      return BOOST_CONTAINER_MOVE_NAMESPACE::move(chain);
   }

   //!Deallocates a linked list of nodes. Never throws
   void deallocate_nodes(multiallocation_chain nodes)
   {
      this->priv_reinsert_nodes_in_block(nodes, nodes.size());
      if(m_totally_free_blocks > m_max_free_blocks){
         this->priv_deallocate_free_blocks(m_max_free_blocks);
      }
   }

   void deallocate_free_blocks()
   {  this->priv_deallocate_free_blocks(0);   }

   std::size_t num_free_nodes()
   {
      typedef typename block_multiset_t::const_iterator citerator;
      std::size_t count = 0;
      citerator it (m_block_multiset.begin()), itend(m_block_multiset.end());
      for(; it != itend; ++it){
         count += it->free_nodes.size();
      }
      return count;
   }

   void swap(private_adaptive_node_pool_impl &other)
   {
      BOOST_ASSERT(m_max_free_blocks == other.m_max_free_blocks);
      BOOST_ASSERT(m_real_node_size == other.m_real_node_size);
      BOOST_ASSERT(m_real_block_alignment == other.m_real_block_alignment);
      BOOST_ASSERT(m_real_num_node == other.m_real_num_node);
      std::swap(mp_segment_mngr_base, other.mp_segment_mngr_base);
      std::swap(m_totally_free_blocks, other.m_totally_free_blocks);
      m_block_multiset.swap(other.m_block_multiset);
   }

   //Deprecated, use deallocate_free_blocks
   void deallocate_free_chunks()
   {  this->priv_deallocate_free_blocks(0);   }

   private:
   void priv_deallocate_free_blocks(std::size_t max_free_blocks)
   {
      priv_invariants();
      //Now check if we've reached the free nodes limit
      //and check if we have free blocks. If so, deallocate as much
      //as we can to stay below the limit
      for( block_iterator itend = m_block_multiset.end()
         ; m_totally_free_blocks > max_free_blocks
         ; --m_totally_free_blocks
         ){
         BOOST_ASSERT(!m_block_multiset.empty());
         block_iterator it = itend;
         --it;
         BOOST_ASSERT(it->free_nodes.size() == m_real_num_node);
         m_block_multiset.erase_and_dispose(it, block_destroyer(this));
      }
   }

   void priv_reinsert_nodes_in_block(multiallocation_chain &chain, std::size_t n)
   {
      block_iterator block_it(m_block_multiset.end());
      while(n--){
         void *pElem = containers_detail::get_pointer(chain.front());
         chain.pop_front();
         priv_invariants();
         block_info_t *block_info = this->priv_block_from_node(pElem);
         BOOST_ASSERT(block_info->free_nodes.size() < m_real_num_node);
         //We put the node at the beginning of the free node list
         node_t * to_deallocate = static_cast<node_t*>(pElem);
         block_info->free_nodes.push_front(*to_deallocate);

         block_iterator this_block(block_multiset_t::s_iterator_to(*block_info));
         block_iterator next_block(this_block);
         ++next_block;

         //Cache the free nodes from the block
         std::size_t this_block_free_nodes = this_block->free_nodes.size();

         if(this_block_free_nodes == 1){
            m_block_multiset.insert(m_block_multiset.begin(), *block_info);
         }
         else{
            block_iterator next_block(this_block);
            ++next_block;
            if(next_block != block_it){
               std::size_t next_free_nodes = next_block->free_nodes.size();
               if(this_block_free_nodes > next_free_nodes){
                  //Now move the block to the new position
                  m_block_multiset.erase(this_block);
                  m_block_multiset.insert(*block_info);
               }
            }
         }
         //Update free block count
         if(this_block_free_nodes == m_real_num_node){
            ++m_totally_free_blocks;
         }
         priv_invariants();
      }
   }

   node_t *priv_take_first_node()
   {
      BOOST_ASSERT(m_block_multiset.begin() != m_block_multiset.end());
      //We take the first free node the multiset can't be empty
      free_nodes_t &free_nodes = m_block_multiset.begin()->free_nodes;
      node_t *first_node = &free_nodes.front();
      const std::size_t free_nodes_count = free_nodes.size();
      BOOST_ASSERT(0 != free_nodes_count);
      free_nodes.pop_front();
      if(free_nodes_count == 1){
         m_block_multiset.erase(m_block_multiset.begin());
      }
      else if(free_nodes_count == m_real_num_node){
         --m_totally_free_blocks;
      }
      priv_invariants();
      return first_node;
   }

   class block_destroyer;
   friend class block_destroyer;

   class block_destroyer
   {
      public:
      block_destroyer(const this_type *impl)
         :  mp_impl(impl)
      {}

      void operator()(typename block_multiset_t::pointer to_deallocate)
      {  return this->do_destroy(to_deallocate, IsAlignOnly()); }

      private:
      void do_destroy(typename block_multiset_t::pointer to_deallocate, AlignOnlyTrue)
      {
         std::size_t free_nodes = to_deallocate->free_nodes.size();
         (void)free_nodes;
         BOOST_ASSERT(free_nodes == mp_impl->m_real_num_node);
         mp_impl->mp_segment_mngr_base->deallocate(to_deallocate);
      }

      void do_destroy(typename block_multiset_t::pointer to_deallocate, AlignOnlyFalse)
      {
         std::size_t free_nodes = to_deallocate->free_nodes.size();
         (void)free_nodes;
         BOOST_ASSERT(free_nodes == mp_impl->m_real_num_node);
         BOOST_ASSERT(0 == to_deallocate->hdr_offset);
         hdr_offset_holder *hdr_off_holder = mp_impl->priv_first_subblock_from_block(containers_detail::get_pointer(to_deallocate));
         mp_impl->mp_segment_mngr_base->deallocate(hdr_off_holder);
      }

      const this_type *mp_impl;
   };

   //This macro will activate invariant checking. Slow, but helpful for debugging the code.
   //#define BOOST_CONTAINER_ADAPTIVE_NODE_POOL_CHECK_INVARIANTS
   void priv_invariants()
   #ifdef BOOST_CONTAINER_ADAPTIVE_NODE_POOL_CHECK_INVARIANTS
   #undef BOOST_CONTAINER_ADAPTIVE_NODE_POOL_CHECK_INVARIANTS
   {
      //We iterate through the block tree to free the memory
      block_iterator it(m_block_multiset.begin()),
                     itend(m_block_multiset.end()), to_deallocate;
      if(it != itend){
         for(++it; it != itend; ++it){
            block_iterator prev(it);
            --prev;
            std::size_t sp = prev->free_nodes.size(),
                        si = it->free_nodes.size();
            BOOST_ASSERT(sp <= si);
            (void)sp;   (void)si;
         }
      }
      //Check that the total free nodes are correct
      it    = m_block_multiset.begin();
      itend = m_block_multiset.end();
      std::size_t total_free_nodes = 0;
      for(; it != itend; ++it){
         total_free_nodes += it->free_nodes.size();
      }
      BOOST_ASSERT(total_free_nodes >= m_totally_free_blocks*m_real_num_node);

      //Check that the total totally free blocks are correct
      it    = m_block_multiset.begin();
      itend = m_block_multiset.end();
      total_free = 0;
      for(; it != itend; ++it){
         total_free += it->free_nodes.size() == m_real_num_node;
      }
      BOOST_ASSERT(total_free >= m_totally_free_blocks);

      if(!AlignOnly){
         //Check that header offsets are correct
         it = m_block_multiset.begin();
         for(; it != itend; ++it){
            hdr_offset_holder *hdr_off_holder = priv_first_subblock_from_block(&*it);
            for(std::size_t i = 0, max = m_num_subblocks; i < max; ++i){
               BOOST_ASSERT(hdr_off_holder->hdr_offset == std::size_t(reinterpret_cast<char*>(&*it)- reinterpret_cast<char*>(hdr_off_holder)));
               BOOST_ASSERT(0 == ((std::size_t)hdr_off_holder & (m_real_block_alignment - 1)));
               BOOST_ASSERT(0 == (hdr_off_holder->hdr_offset & (m_real_block_alignment - 1)));
               hdr_off_holder = reinterpret_cast<hdr_offset_holder *>(reinterpret_cast<char*>(hdr_off_holder) + m_real_block_alignment);
            }
         }
      }
   }
   #else
   {} //empty
   #endif

   //!Deallocates all used memory. Never throws
   void priv_clear()
   {
      #ifndef NDEBUG
      block_iterator it    = m_block_multiset.begin();
      block_iterator itend = m_block_multiset.end();
      std::size_t num_free_nodes = 0;
      for(; it != itend; ++it){
         //Check for memory leak
         BOOST_ASSERT(it->free_nodes.size() == m_real_num_node);
         ++num_free_nodes;
      }
      BOOST_ASSERT(num_free_nodes == m_totally_free_blocks);
      #endif
      //Check for memory leaks
      priv_invariants();
      m_block_multiset.clear_and_dispose(block_destroyer(this));
      m_totally_free_blocks = 0;
   }

   block_info_t *priv_block_from_node(void *node, AlignOnlyFalse) const
   {
      hdr_offset_holder *hdr_off_holder =
         reinterpret_cast<hdr_offset_holder*>((std::size_t)node & std::size_t(~(m_real_block_alignment - 1)));
      BOOST_ASSERT(0 == ((std::size_t)hdr_off_holder & (m_real_block_alignment - 1)));
      BOOST_ASSERT(0 == (hdr_off_holder->hdr_offset & (m_real_block_alignment - 1)));
      block_info_t *block = reinterpret_cast<block_info_t *>
         (reinterpret_cast<char*>(hdr_off_holder) + hdr_off_holder->hdr_offset);
      BOOST_ASSERT(block->hdr_offset == 0);
      return block;
   }

   block_info_t *priv_block_from_node(void *node, AlignOnlyTrue) const
   {
      return (block_info_t *)((std::size_t)node & std::size_t(~(m_real_block_alignment - 1)));
   }

   block_info_t *priv_block_from_node(void *node) const
   {  return priv_block_from_node(node, IsAlignOnly());   }

   hdr_offset_holder *priv_first_subblock_from_block(block_info_t *block) const
   {
      hdr_offset_holder *hdr_off_holder = reinterpret_cast<hdr_offset_holder*>
            (reinterpret_cast<char*>(block) - (m_num_subblocks-1)*m_real_block_alignment);
      BOOST_ASSERT(hdr_off_holder->hdr_offset == std::size_t(reinterpret_cast<char*>(block) - reinterpret_cast<char*>(hdr_off_holder)));
      BOOST_ASSERT(0 == ((std::size_t)hdr_off_holder & (m_real_block_alignment - 1)));
      BOOST_ASSERT(0 == (hdr_off_holder->hdr_offset & (m_real_block_alignment - 1)));
      return hdr_off_holder;
   }

   //!Allocates a several blocks of nodes. Can throw
   void priv_alloc_block(std::size_t n, AlignOnlyTrue)
   {
      std::size_t real_block_size = m_real_block_alignment - PayloadPerAllocation;
      for(std::size_t i = 0; i != n; ++i){
         //We allocate a new NodeBlock and put it the last
         //element of the tree
         char *mem_address = static_cast<char*>
            (mp_segment_mngr_base->allocate_aligned(real_block_size, m_real_block_alignment));
         if(!mem_address)   throw std::bad_alloc();
         ++m_totally_free_blocks;
         block_info_t *c_info = new(mem_address)block_info_t;
         m_block_multiset.insert(m_block_multiset.end(), *c_info);

         mem_address += HdrSize;
         //We initialize all Nodes in Node Block to insert
         //them in the free Node list
         typename free_nodes_t::iterator prev_insert_pos = c_info->free_nodes.before_begin();
         for(std::size_t i = 0; i < m_real_num_node; ++i){
            prev_insert_pos = c_info->free_nodes.insert_after(prev_insert_pos, *(node_t*)mem_address);
            mem_address   += m_real_node_size;
         }
      }
   }

   void priv_alloc_block(std::size_t n, AlignOnlyFalse)
   {
      std::size_t real_block_size = m_real_block_alignment*m_num_subblocks - PayloadPerAllocation;
      std::size_t elements_per_subblock = (m_real_block_alignment - HdrOffsetSize)/m_real_node_size;
      std::size_t hdr_subblock_elements = (m_real_block_alignment - HdrSize - PayloadPerAllocation)/m_real_node_size;

      for(std::size_t i = 0; i != n; ++i){
         //We allocate a new NodeBlock and put it the last
         //element of the tree
         char *mem_address = static_cast<char*>
            (mp_segment_mngr_base->allocate_aligned(real_block_size, m_real_block_alignment));
         if(!mem_address)   throw std::bad_alloc();
         ++m_totally_free_blocks;

         //First initialize header information on the last subblock
         char *hdr_addr = mem_address + m_real_block_alignment*(m_num_subblocks-1);
         block_info_t *c_info = new(hdr_addr)block_info_t;
         //Some structural checks
         BOOST_ASSERT(static_cast<void*>(&static_cast<hdr_offset_holder*>(c_info)->hdr_offset) ==
                static_cast<void*>(c_info));
         typename free_nodes_t::iterator prev_insert_pos = c_info->free_nodes.before_begin();
         for( std::size_t subblock = 0, maxsubblock = m_num_subblocks - 1
            ; subblock < maxsubblock
            ; ++subblock, mem_address += m_real_block_alignment){
            //Initialize header offset mark
            new(mem_address) hdr_offset_holder(std::size_t(hdr_addr - mem_address));
            char *pNode = mem_address + HdrOffsetSize;
            for(std::size_t i = 0; i < elements_per_subblock; ++i){
               prev_insert_pos = c_info->free_nodes.insert_after(prev_insert_pos, *new (pNode) node_t);
               pNode   += m_real_node_size;
            }
         }
         {
            char *pNode = hdr_addr + HdrSize;
            //We initialize all Nodes in Node Block to insert
            //them in the free Node list
            for(std::size_t i = 0; i < hdr_subblock_elements; ++i){
               prev_insert_pos = c_info->free_nodes.insert_after(prev_insert_pos, *new (pNode) node_t);
               pNode   += m_real_node_size;
            }
         }
         //Insert the block after the free node list is full
         m_block_multiset.insert(m_block_multiset.end(), *c_info);
      }
   }

   //!Allocates a block of nodes. Can throw std::bad_alloc
   void priv_alloc_block(std::size_t n)
   {  return priv_alloc_block(n, IsAlignOnly());   }

   private:
   typedef typename boost::pointer_to_other
      <void_pointer, segment_manager_base_type>::type   segment_mngr_base_ptr_t;
   const std::size_t m_max_free_blocks;
   const std::size_t m_real_node_size;
   //Round the size to a power of two value.
   //This is the total memory size (including payload) that we want to
   //allocate from the general-purpose allocator
   const std::size_t m_real_block_alignment;
   std::size_t m_num_subblocks;
   //This is the real number of nodes per block
   //const
   std::size_t m_real_num_node;
   segment_mngr_base_ptr_t                mp_segment_mngr_base;   //Segment manager
   block_multiset_t                       m_block_multiset;       //Intrusive block list
   std::size_t                            m_totally_free_blocks;  //Free blocks
};

}  //namespace containers_detail {
}  //namespace container {
}  //namespace boost {

#include INCLUDE_BOOST_CONTAINER_DETAIL_CONFIG_END_HPP

#endif   //#ifndef BOOST_CONTAINER_DETAIL_ADAPTIVE_NODE_POOL_IMPL_HPP


#include "min-cut-graph.hpp"

//#include "perceive/foundation.hpp"
#define INFO(m) printf("%s:%d  %s\n", __FILE__, int(__LINE__), m);

#include <stdint.h>

// #include <unordered_set>
#include <algorithm> // for std::for_each
#include <exception>
#include <unordered_map>
#include <unordered_set>
#include <utility> // for std::pair

#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/stoer_wagner_min_cut.hpp>
#include <boost/graph/topological_sort.hpp>
// #include <boost/graph/read_dimacs.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>

#define This MinCutGraph

using std::cout;
using std::endl;
using std::string;
using std::swap;

// -------------------------------------------------------------------- typedefs
struct VertexProperties
{
   bool alpha_expansion_vertex{false};
   // boost::vertex_color_t z{boost::default_color_type};
   // double boost::vertex_distance_t;
};

typedef boost::adjacency_list_traits<boost::vecS, boost::vecS, boost::directedS>
    FTraits;

typedef boost::adjacency_list<
    boost::vecS,
    boost::vecS,
    boost::directedS,

    boost::property<
        boost::vertex_index_t,
        unsigned,
        boost::property<
            boost::vertex_color_t,
            boost::default_color_type,
            boost::property<boost::vertex_distance_t,
                            double,
                            boost::property<boost::vertex_predecessor_t,
                                            FTraits::edge_descriptor>>>>,

    boost::property<
        boost::edge_weight_t,
        float,
        boost::property<
            boost::edge_capacity_t,
            double,
            boost::property<boost::edge_residual_capacity_t,
                            double,
                            boost::property<boost::edge_reverse_t,
                                            FTraits::edge_descriptor>>>>>

    G;

using vertex_descriptor = boost::graph_traits<G>::vertex_descriptor;
using edge_descriptor   = boost::graph_traits<G>::edge_descriptor;
// using weight_map_t      = boost::property_map<G, boost::edge_weight_t>::type;
// using weight_t          = boost::property_traits<weight_map_t>::value_type;
using vertex_iterator = boost::graph_traits<G>::vertex_iterator;
using edge_iterator   = boost::graph_traits<G>::edge_iterator;

namespace perceive
{
// ----------------------------------------------------------------------- Pimpl

static uint64_t key(uint32_t a, uint32_t b) noexcept
{
   return (uint64_t(a) << 32) | (uint64_t(b) << 0);
}

class This::Pimpl
{
 public:
   G g;

   Pimpl(unsigned n_verts)
       : g(n_verts)
   {}

   Pimpl(const Pimpl& rhs) = default;
   Pimpl(Pimpl&& rhs)      = default;
   Pimpl& operator=(const Pimpl& rhs) = default;
   Pimpl& operator=(Pimpl&& rhs) = default;
};

// ---------------------------------------------------------------- Construction

This::MinCutGraph(unsigned n_verts)
    : pimpl_(std::make_unique<Pimpl>(n_verts))
{}

This::MinCutGraph(MinCutGraph&& o) noexcept = default;

This::MinCutGraph(const MinCutGraph& rhs)
    : pimpl_(std::make_unique<Pimpl>(*rhs.pimpl_))
{}

This::~MinCutGraph() = default;

MinCutGraph& This::operator=(MinCutGraph&& o) noexcept = default;

MinCutGraph& This::operator=(const MinCutGraph& rhs)
{
   *pimpl_ = *rhs.pimpl_;
   return *this;
}

// ----------------------------------------------------------- removing vertices

template<typename T>
static void
rm_vertex_helper(T& g, vertex_descriptor v1, vector<uint64_t>& to_remove_buffer)
{
   boost::graph_traits<G>::out_edge_iterator ei, e_end;
   std::tie(ei, e_end) = boost::out_edges(v1, g);
   auto& to_remove     = to_remove_buffer;
   to_remove.clear();
   to_remove.reserve(size_t(std::distance(ei, e_end)));
   for(; ei != e_end; ++ei) {
      uint32_t uu = uint32_t(boost::source(*ei, g));
      uint32_t vv = uint32_t(boost::target(*ei, g));
      to_remove.push_back(key(uu, vv));
      to_remove.push_back(key(vv, uu));
   }

   for(auto uv : to_remove) {
      uint32_t uu = (uv & 0xffffffff00000000ull >> 32);
      uint32_t vv = (uv & 0x00000000ffffffffull >> 0);
      boost::remove_edge(uu, vv, g);
   }

   boost::remove_vertex(v1, g);
}

// ------------------------------------------------------------------ n-vertices

void This::reserve_vertices(unsigned n_verts)
{
   pimpl_->g.m_vertices.reserve(n_verts);
}

void This::reserve_edges(unsigned vert_ind, unsigned n_edges)
{
   auto& g = pimpl_->g;
   if(vert_ind < g.m_vertices.size())
      g.m_vertices[vert_ind].m_out_edges.reserve(n_edges);
}

void This::set_n_vertices(unsigned n_verts)
{
   if(n_verts == n_vertices()) return;

   auto& g = pimpl_->g;
   vector<uint64_t> to_remove_buffer;

   while(n_verts < n_vertices()) {
      rm_vertex_helper(g, boost::vertex(n_vertices() - 1, g), to_remove_buffer);
   }

   g.m_vertices.reserve(n_verts);

   auto counter = n_vertices();
   while(n_vertices() < n_verts) boost::add_vertex(g);

   assert(n_verts == n_vertices());
}

unsigned This::n_vertices() const noexcept { return size(); }
unsigned This::n_edges() const noexcept
{
   return unsigned(boost::num_edges(pimpl_->g));
}
unsigned This::size() const noexcept
{
   return unsigned(boost::num_vertices(pimpl_->g));
}

// ----------------------------------------------------------------------- edges

bool This::has_edge(unsigned v1, unsigned v2) const noexcept
{
   assert(v1 < n_vertices());
   const auto& o = pimpl_->g.m_vertices[v1].m_out_edges;
   const auto ii = std::find(cbegin(o), cend(o), v2);
   return ii != cend(o);
}

bool This::add_edge(unsigned v1, unsigned v2, double weight)
{
   auto& g = pimpl_->g;

   const auto& eweight           = get(boost::edge_weight, g);
   const auto& capacity          = get(boost::edge_capacity, g);
   const auto& residual_capacity = get(boost::edge_residual_capacity, g);
   const auto& reverse_edge      = get(boost::edge_reverse, g);
   const auto& vindex            = get(boost::vertex_index, g);

   edge_descriptor e1, e2;
   bool success = false;

   const auto& vert1       = vindex[v1];
   const auto& vert2       = vindex[v2];
   boost::tie(e1, success) = boost::add_edge(vert1, vert2, g);
   if(!success) return false;
   boost::tie(e2, success) = boost::add_edge(vert2, vert1, g);
   if(!success) {
      boost::remove_edge(vert1, vert2, g);
      return false;
   }

   eweight[e1] = eweight[e2] = float(weight);
   capacity[e1] = capacity[e2] = weight;
   residual_capacity[e1] = residual_capacity[e2] = 0.0;
   reverse_edge[e1]                              = e2;
   reverse_edge[e2]                              = e1;

   return success;
}

void This::remove_edge(unsigned v1, unsigned v2)
{
   boost::remove_edge(v1, v2, pimpl_->g);
   boost::remove_edge(v2, v1, pimpl_->g);
}

void This::remove_all_edges()
{
   auto& g      = pimpl_->g;
   const auto N = n_vertices();

   vector<uint64_t> to_remove;
   for(auto i = 0u; i < N; ++i) {
      boost::graph_traits<G>::out_edge_iterator ei, e_end;
      std::tie(ei, e_end) = boost::out_edges(i, g);
      to_remove.reserve(size_t(std::distance(ei, e_end)));
      for(; ei != e_end; ++ei) {
         uint32_t uu = uint32_t(boost::source(*ei, g));
         uint32_t vv = uint32_t(boost::target(*ei, g));
         to_remove.push_back(key(uu, vv));
         to_remove.push_back(key(vv, uu));
      }
      for(auto uv : to_remove) {
         uint32_t uu = (uv & 0xffffffff00000000ull >> 32);
         uint32_t vv = (uv & 0x00000000ffffffffull >> 0);
         boost::remove_edge(uu, vv, g);
      }
   }
}

// ---------------------------------------------------------------- edge weights

// float This::weight(unsigned v1, unsigned v2) const noexcept
// {
//    auto& g             = pimpl_->g;
//    const auto& eweight = get(boost::edge_weight, g);

//    boost::graph_traits<G>::out_edge_iterator ei, e_end;
//    std::tie(ei, e_end) = out_edges(v1, g);
//    auto ii             = std::find_if(ei, e_end, [&](const auto& o) -> bool {
//       return boost::source(o, g) == v2;
//    });
//    if(ii == e_end) return std::numeric_limits<double>::quiet_NaN();

//    return eweight[*ii];
// }

// bool This::set_weight(unsigned v1, unsigned v2, double weight) noexcept
// {
//    auto& g             = pimpl_->g;
//    auto& e             = pimpl_->edge_idx;
//    const auto& weights = get(boost::edge_weight, g);
//    boost::graph_traits<G>::out_edge_iterator ei, e_end;
//    tie(ei, e_end) = out_edges(v1, g);
//    if(std::distance(ei, e_end) > 20) {
//       auto ee = e.find(key(v1, v2));
//       if(ee == e.end()) return false;
//       weights[ee->second] = weight;
//       return true;
//    }

//    auto ee = boost::edge(v1, v2, g);
//    if(ee.second) weights[ee.first] = weight;
//    return ee.second;
// }

// ----------------------------------------------------- Prepare Alpha Expansion
// using label_of_vertex_func = std::function<unsigned (unsigned)>;
// using vertex_label_cost_func = std::function<double (unsigned, unsigned)>;
// using label_label_cost_func = std::function<double (unsigned, unsigned)>;
// struct AlphaExpansionData {
//     unsigned source_vertex{0};
//     unsigned sink_vertex{0};
//     unsigned n_additional_vertices{0}; // number of verts added
// };
// This::AlphaExpansionData
// This::modify_for_alpha_expansion(unsigned alpha_label,
//                                  label_of_vertex_func   l,
//                                  vertex_label_cost_func f,
//                                  label_label_cost_func  e,
//                                  double smoothing_lambda)
// {
//     return modify_for_alpha_expansion(alpha_label, l, f,
//                                       [&] (unsigned v1, unsigned v2,
//                                            unsigned l1, unsigned l2) {
//                                           return e(l1, l2);
//                                       },
//                                       smoothing_lambda);
// }

// This::AlphaExpansionData
// This::modify_for_alpha_expansion(unsigned alpha_label,
//                                  label_of_vertex_func l,
//                                  vertex_label_cost_func f,
//                                  label_label_cost_func e,
//                                  double smoothing_lambda)
// {
//    AlphaExpansionData dat;
// dat.alpha_label = alpha_label;
// auto& g         = pimpl_->g;
// auto& eidx      = pimpl_->edge_idx;

// const auto N        = size();
// const auto max_cost = 1e9;
// const auto lambda   = smoothing_lambda;

// const auto& weights  = get(boost::edge_weight, g);
// const auto& weights2 = get(boost::edge_weight2, g);

// // dat.source_vertex = n_vertices();
// // dat.sink_vertex = dat.source_vertex + 1;

// // Add the source and sink vertices
// boost::add_vertex(g);
// dat.source_vertex                           = N;
// g[dat.source_vertex].alpha_expansion_vertex = true;
// boost::add_vertex(g);
// dat.sink_vertex                           = N + 1;
// g[dat.sink_vertex].alpha_expansion_vertex = true;

// // Add t-links
// for(auto n = 0u; n < N; ++n) {
//    auto label = l(n);
//    add_edge(n, dat.source_vertex, f(n, alpha_label));
//    add_edge(
//        n, dat.sink_vertex, (label == alpha_label) ? max_cost : f(n,
//        label));
// }

// // Add e-links
// vector<std::pair<unsigned, unsigned>> to_remove;

// for(auto n = 0u; n < N; ++n) {
//    boost::graph_traits<G>::out_edge_iterator ei, e_end;
//    for(tie(ei, e_end) = out_edges(n, g); ei != e_end; ++ei) {
//       auto v1 = boost::source(*ei, g);
//       auto v2 = boost::target(*ei, g);
//       if(v1 >= N || v2 >= N) continue; // don't worry about t-links
//       if(v1 < v2) { // undirected graph... avoid double-counting
//          auto label1 = l(v1);
//          auto label2 = l(v2);
//          if(label1 == label2) {
//             auto ee = boost::edge(v1, v2, g);
//             assert(ee.second);
//             weights2[ee.first] = weights[ee.first];
//             weights[ee.first]  = lambda * e(label1, alpha_label);
//          } else {
//             // labels are different... insert node
//             to_remove.emplace_back(v1, v2);
//          }
//       }
//    }
// }

// // Add the additional nodes
// unsigned new_vertex = N + 2;
// for(const auto& x : to_remove) {
//    auto v1 = x.first;
//    auto v2 = x.second;
//    auto ee = boost::edge(v1, v2, g);
//    assert(ee.second);
//    auto label1 = l(v1);
//    auto label2 = l(v2);
//    assert(new_vertex == n_vertices());
//    boost::add_vertex(g);
//    auto a                      = new_vertex++;
//    g[a].alpha_expansion_vertex = true;
//    add_edge(v1, a, lambda * e(label1, alpha_label));
//    add_edge(v2, a, lambda * e(alpha_label, label2));
//    add_edge(a, dat.sink_vertex, lambda * e(label1, label2));
//    weights2[boost::edge(v1, a, g).first]              = weights[ee.first];
//    weights2[boost::edge(v2, a, g).first]              = weights[ee.first];
//    weights2[boost::edge(a, dat.sink_vertex, g).first] = weights[ee.first];
// }

// // Break edges
// for(const auto& x : to_remove) this->remove_edge(x.first, x.second);

//    return dat;
// }

// void This::undo_alpha_expansion_modifications() noexcept(false)
// {
// vector<vertex_descriptor> to_remove;
// const auto N         = n_vertices();
// auto& g              = pimpl_->g;
// auto& e_idx          = pimpl_->edge_idx;
// const auto& weights  = get(boost::edge_weight, g);
// const auto& weights2 = get(boost::edge_weight2, g);

// vector<uint64_t> buffer;

// to_remove.reserve(N);
// for(auto n = 0u; n < N; ++n)
//    if(g[n].alpha_expansion_vertex) to_remove.push_back(n);

// if(to_remove.size() == 0) return; // Nothing to undo

// if(to_remove.size() == 1) {
//    rm_vertex_helper(g, boost::vertex(to_remove[0], g), e_idx, buffer);
//    throw std::logic_error("alpha-expansion graph in invalid state");
// }

// const auto original_N = N - to_remove.size();

// // Remove "additional" nodes other than source and sink (requires
// relinking) while(to_remove.size() > 2) {
//    auto v1 = boost::vertex(to_remove.back(), g);
//    if(v1 != boost::vertex(n_vertices() - 1, g))
//       throw std::logic_error("alpha-expansion graph in invalid state");
//    boost::graph_traits<G>::out_edge_iterator ei, e_end;
//    tie(ei, e_end) = out_edges(v1, g);
//    auto arity     = std::distance(ei, e_end);
//    if(int(arity) != 3)
//       throw std::logic_error("alpha-expansion graph in invalid state");

//    auto u0 = boost::source(*(ei + 0), g);
//    auto u1 = boost::target(*(ei + 0), g);
//    auto s0 = boost::source(*(ei + 1), g);
//    auto s1 = boost::target(*(ei + 1), g);
//    auto n0 = boost::source(*(ei + 2), g);
//    auto n1 = boost::target(*(ei + 2), g);

//    auto restore_weight = weights2[*ei];

//    // two should be v1. The other two are the edge we need to recreate
//    if(n0 >= original_N || n1 >= original_N) {
//       // using u0, u1, s0, s1
//    } else if(s0 >= original_N || s1 >= original_N) {
//       swap(s0, n0);
//       swap(s1, n1);
//    } else {
//       swap(u0, n0);
//       swap(u1, n1);
//    }

//    auto uu = (u0 == v1) ? u1 : u0;
//    auto ss = (s0 == v1) ? s1 : s0;

//    if(g[uu].alpha_expansion_vertex | g[ss].alpha_expansion_vertex)
//       throw std::logic_error("alpha-expansion graph in invalid state");

//    // Build an edge between (uu and ss)
//    this->remove_edge(u0, u1);
//    this->remove_edge(s0, s1);
//    this->remove_edge(boost::source(*(ei + 2), g),
//                      boost::target(*(ei + 2), g));
//    this->add_edge(uu, ss);
//    weights2[boost::edge(uu, ss, g).first] = restore_weight;

//    rm_vertex_helper(g, boost::vertex(to_remove.back(), g), e_idx, buffer);
//    to_remove.pop_back();
// }

// // Remove source and sink -- no additional edges to relink
// while(to_remove.size() > 0) {
//    rm_vertex_helper(g, boost::vertex(to_remove.back(), g), e_idx, buffer);
//    to_remove.pop_back();
// }

// { // Zero out all weight2s
//    boost::graph_traits<G>::edge_iterator ei, ei_end;
//    for(tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) {
//       auto w2       = weights2[*ei];
//       weights[*ei]  = w2;
//       weights2[*ei] = 0.0;
//    }
// }
//}

// --------------------------------------------------------------------- min-cut

// Extract the min-cut from graph-flow

// static This::CutResult
// directed_cut(const G& g, unsigned source_id, unsigned sink_id)
// {
//    This::CutResult cut;
//    const unsigned N = boost::num_vertices(g);

//    typedef boost::
//        adjacency_list_traits<boost::vecS, boost::vecS, boost::directedS>
//            FTraits;
//    typedef boost::adjacency_list<
//        boost::vecS,
//        boost::vecS,
//        boost::directedS,
//        boost::property<
//            boost::vertex_index_t,
//            unsigned,
//            boost::property<
//                boost::vertex_color_t,
//                boost::default_color_type,
//                boost::property<boost::vertex_distance_t,
//                                double,
//                                boost::property<boost::vertex_predecessor_t,
//                                                FTraits::edge_descriptor>>>>,

//        boost::property<
//            boost::edge_capacity_t,
//            double,
//            boost::property<boost::edge_residual_capacity_t,
//                            double,
//                            boost::property<boost::edge_reverse_t,
//                                            FTraits::edge_descriptor>>>>
//        F;

//    using namespace boost;

//    typedef typename graph_traits<F>::vertex_descriptor f_vertex_descriptor;
//    typedef typename graph_traits<F>::edge_descriptor f_edge_descriptor;

//    F f(N);

//    // std::unordered_set<uint64_t> keys;
//    auto add_edge = [&](unsigned v1, unsigned v2, double weight) {
//       // if(keys.count(key(v1, v2)) > 0) return;
//       // keys.insert(key(v1, v2));
//       f_edge_descriptor e1, e2;
//       bool success1, success2;
//       boost::tie(e1, success1) = boost::add_edge(v1, v2, f);
//       boost::tie(e2, success2) = boost::add_edge(v2, v1, f);

//       if(!success1 || !success2) {
//          fprintf(stderr, "kBAM!!!\n");
//          exit(1);
//       }

//       const auto& capacity          = get(boost::edge_capacity, f);
//       const auto& residual_capacity = get(edge_residual_capacity, f);
//       const auto& reverse_edge      = get(boost::edge_reverse, f);
//       capacity[e1] = capacity[e2] = weight;
//       residual_capacity[e1] = residual_capacity[e2] = 0.0;
//       reverse_edge[e1]                              = e2;
//       reverse_edge[e2]                              = e1;
//    };

//    { // reserve edge space
//       for(auto n = 0u; n < N; ++n) {
//          const auto sz = g.m_vertices[n].m_out_edges.size();
//          f.m_vertices[n].m_out_edges.reserve(2 * sz);
//          // f.m_vertices[n].m_in_edges.reserve(sz);
//       }
//    }

//    {
//       const auto& weights = get(boost::edge_weight, g);
//       boost::graph_traits<G>::edge_iterator ei, e_end;
//       for(boost::tie(ei, e_end) = boost::edges(g); ei != e_end; ++ei)
//          add_edge(boost::source(*ei, g), boost::target(*ei, g),
//          weights[*ei]);
//    }

//    std::vector<default_color_type> color(num_vertices(f));
//    std::vector<long> distance(num_vertices(f));

//    // Find the flow
//    cut.flow = boykov_kolmogorov_max_flow(
//        f, boost::vertex(source_id, f), boost::vertex(sink_id, f));

//    const auto& colours = get(boost::vertex_color, f);
//    cut.source_set.resize(N);
//    std::fill(cut.source_set.begin(), cut.source_set.end(), false);
//    const auto black = boost::color_traits<F>::black();
//    for(auto n = 0u; n < N; ++n) cut.source_set[n] = (colours[n] == black);

//    return cut;
// }

This::CutResult This::min_cut(unsigned source_id, unsigned sink_id)
{
   auto& g = pimpl_->g;

   CutResult cut;
   cut.flow = boykov_kolmogorov_max_flow(
       g, boost::vertex(source_id, g), boost::vertex(sink_id, g));

   const unsigned N = n_vertices();
   cut.source_set.resize(N);
   std::fill(cut.source_set.begin(), cut.source_set.end(), false);
   const auto& colours = get(boost::vertex_color, g);
   const auto black    = boost::color_traits<G>::black();
   for(auto n = 0u; n < N; ++n) cut.source_set[n] = (colours[n] == black);

   return cut;
}

// ------------------------------------------------------------------- graph-viz

void This::write_graphviz(std::ostream& out) const
{
   using namespace boost;
   const auto& g = pimpl_->g;
   ::boost::write_graphviz(out, g, default_writer(), default_writer());
}

namespace detail
{
   template<typename Function> class functional_label_writer
   {
    private:
      Function f;

    public:
      functional_label_writer(Function func)
          : f(func)
      {}

      template<class VertexOrEdge>
      void operator()(std::ostream& out, const VertexOrEdge& v_or_e) const
      {
         out << f(v_or_e);
      }
   };
} // namespace detail

void This::write_graphviz(std::ostream& out,
                          vertex_label_function vert_labels,
                          edge_label_function edge_labels,
                          const CutResult* ret) const
{
   const auto& g = pimpl_->g;

   auto write_vertices = [&](std::ostream& out) {
      const auto N = n_vertices();
      for(auto n = 0u; n < N; ++n) {
         const bool in_source_set
             = (ret == nullptr ? false : ret->source_set[n]);
         out << "   " << n << " [label=\"" << vert_labels(n) << "\""
             << (in_source_set ? ", color=red" : "") << "];\n";
      }
   };

   auto write_edges = [&](std::ostream& out) {
      boost::graph_traits<G>::edge_iterator ei, ei_end;
      const auto& vindex  = get(boost::vertex_index, g);
      const auto& eweight = get(boost::edge_weight, g);

      std::array<char, 80> buffer;

      for(std::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei) {
         int ind0 = int(boost::source(*ei, g));
         int ind1 = int(boost::target(*ei, g));

         if(ind0 < ind1) continue;

         const bool in0
             = (ret == nullptr ? false : ret->source_set[size_t(ind0)]);
         const bool in1
             = (ret == nullptr ? false : ret->source_set[size_t(ind1)]);

         const auto wgt = eweight(*ei);
         auto label     = edge_labels(unsigned(ind0), unsigned(ind1));
         if(label.empty()) {
            snprintf(&buffer[0], buffer.size(), "%g", double(wgt));
            label = &buffer[0];
         }

         out << "   " << ind0 << " -- " << ind1 << " [label=\"" << label
             << "\", weight=\"" << eweight(*ei) << "\"";
         if(in0 && in1) out << ", color=red";
         if(in0 != in1) out << ", color=gray";

         out << "];\n";
      }
   };

   out << "graph G {\n";
   write_vertices(out);
   out << "\n";
   write_edges(out);
   out << "}" << std::flush;
}

// Write out vertices

void This::save_graphviz(std::string filename,
                         vertex_label_function vlabels,
                         edge_label_function elabels,
                         const CutResult* ret) const
{
   std::ofstream ofs;
   ofs.open(filename);
   this->write_graphviz(ofs, vlabels, elabels, ret);
   ofs.close();
   std::stringstream ss("");
   ss << "dot -Tpng -O \"" << filename << "\"";
   auto cmd     = ss.str();
   auto success = system(cmd.c_str()) == EXIT_SUCCESS;
}

} // namespace perceive


// __BEGIN_LICENSE__
//  Copyright (c) 2006-2013, United States Government as represented by the
//  Administrator of the National Aeronautics and Space Administration. All
//  rights reserved.
//
//  The NASA Vision Workbench is licensed under the Apache License,
//  Version 2.0 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// __END_LICENSE__

#include <vw/Math/EulerAngles.h>
#include <vw/Camera/CameraModel.h>
#include <vw/Camera/CameraSolve.h>
#include <vw/Camera/OpticalBarModel.h>

#include <iomanip>
#include <boost/filesystem/convenience.hpp>

namespace vw {
namespace camera {

using namespace vw;
using namespace vw::camera;

Vector2 OpticalBarModel::pixel_to_sensor_plane(Vector2 const& pixel) const {
  Vector2 result = (pixel - m_center_loc_pixels) * m_pixel_size;
  return result;
}

double OpticalBarModel::sensor_to_alpha(vw::Vector2 const& sensor_loc) const {
  // This calculation comes from the focal point projected on to a circular surface.
  return sensor_loc[0] / m_focal_length;
}

void OpticalBarModel::compute_scan_rate() {

  // Compute the scan angle using pixel information.
  Vector2 p1 = pixel_to_sensor_plane(Vector2(0,0));
  Vector2 p2 = pixel_to_sensor_plane(Vector2(m_image_size - Vector2i(1,1)));

  double alpha1     = sensor_to_alpha(p1);
  double alpha2     = sensor_to_alpha(p2);
  double scan_angle = alpha2 - alpha1;

  m_scan_rate_radians = scan_angle / m_scan_time;
}

double OpticalBarModel::pixel_to_time_delta(Vector2 const& pix) const {

  // Since the camera sweeps a scan through columns, use that to
  //  determine the fraction of the way it is through the image.
  const int max_col = m_image_size[0]-1;
  double scan_fraction = 0;
  if (m_scan_left_to_right)
    scan_fraction = pix[0] / max_col; // TODO: Add 0.5 pixels to calculation?
  else // Right to left scan direction
    scan_fraction = (max_col - pix[0]) / max_col;
  double time_delta = scan_fraction * m_scan_time;
  return time_delta;
}

Vector3 OpticalBarModel::get_velocity(vw::Vector2 const& pixel) const {

  // TODO: For speed, store the pose*velocity vector.
  // Convert the velocity from sensor coords to GCC coords
  Matrix3x3 pose = camera_pose(pixel).rotation_matrix();

  // Recover the satellite attitude relative to the tilted camera position
  //Matrix3x3 m = vw::math::rotation_x_axis(-m_forward_tilt_radians)*pose;
  Matrix3x3 m = pose*vw::math::rotation_x_axis(m_forward_tilt_radians);

  return m*Vector3(0,m_speed,0);
}

Vector3 OpticalBarModel::camera_center(Vector2 const& pix) const {
  // We model with a constant velocity.
  double dt = pixel_to_time_delta(pix);

  return m_initial_position + dt*get_velocity(pix);
}

Quat OpticalBarModel::camera_pose(Vector2 const& pix) const {
  // Camera pose is treated as constant for the duration of a scan.
  return axis_angle_to_quaternion(m_initial_orientation);
}

Vector3 OpticalBarModel::pixel_to_vector_uncorrected(Vector2 const& pixel) const {

  Vector2 sensor_plane_pos = pixel_to_sensor_plane(pixel);
  Vector3 cam_center       = camera_center(pixel);
  Quat    cam_pose         = camera_pose  (pixel);

  // This is the horizontal angle away from the center point (from straight out of the camera)
  double alpha = sensor_to_alpha(sensor_plane_pos);

  // Distance from the camera center to the ground.
  double H = norm_2(cam_center) - (m_mean_surface_elevation + m_mean_earth_radius);

  // Distortion caused by compensation for the satellite's forward motion during the image.
  // - The film was actually translated underneath the lens to compensate for the motion.
  double image_motion_compensation = ((m_focal_length * m_speed) / (H*m_scan_rate_radians))
                                      * sin(alpha) * m_motion_compensation;
  if (!m_scan_left_to_right) // Sync alpha with motion compensation.
    image_motion_compensation *= -1.0;

  // This vector is ESD format, consistent with the linescan model.
  Vector3 r(m_focal_length * sin(alpha),
            sensor_plane_pos[1] + image_motion_compensation,
            m_focal_length * cos(alpha));
  r = normalize(r);

  // r is the ray vector in the local camera system

  // Convert the ray vector into GCC coordinates.
  Vector3 result = cam_pose.rotate(r);

  return result;
}

Vector3 OpticalBarModel::pixel_to_vector(Vector2 const& pixel) const {

  Vector3 output_vector;

  try {
    output_vector = pixel_to_vector_uncorrected(pixel);

    Vector3 cam_ctr = camera_center(pixel);
    if (m_correct_atmospheric_refraction)
      output_vector
        = apply_atmospheric_refraction_correction(cam_ctr, m_mean_earth_radius,
                                                  m_mean_surface_elevation, output_vector);

    if (m_correct_velocity_aberration)
      output_vector
        = apply_velocity_aberration_correction(cam_ctr, get_velocity(pixel),
                                               m_mean_earth_radius, output_vector);

  } catch(const vw::Exception &e) {
    // Repackage any of our exceptions thrown below this point as a
    //  pixel to ray exception that other code will be able to handle.
    vw_throw(vw::camera::PixelToRayErr() << e.what());
  }

  return output_vector;
}

Vector2 OpticalBarModel::point_to_pixel(Vector3 const& point) const {

  // Use the generic solver to find the pixel
  // - This method will be slower but works for more complicated geometries
  CameraGenericLMA model( this, point );
  int status;
  Vector2 start = m_image_size / 2.0; // Use the center as the initial guess

  // Solver constants
  const double ABS_TOL = 1e-16;
  const double REL_TOL = 1e-16;
  const int    MAX_ITERATIONS = 1e+5;

  Vector3 objective(0, 0, 0);
  Vector2 solution = math::levenberg_marquardtFixed<CameraGenericLMA, 2,3>(model, start, objective, status,
                                               ABS_TOL, REL_TOL, MAX_ITERATIONS);
  VW_ASSERT( status > 0,
             camera::PointToPixelErr() << "Unable to project point into Linescan model" );

  return solution;
}

void OpticalBarModel::apply_transform(vw::Matrix3x3 const & rotation,
                                      vw::Vector3   const & translation,
                                      double                scale) {
  // Extract current parameters
  vw::Vector3 position = this->camera_center();
  vw::Quat    pose     = this->camera_pose();

  vw::Quat rotation_quaternion(rotation);

  // New position and rotation
  position = scale*rotation*position + translation;
  pose     = rotation_quaternion*pose;

  this->set_camera_center(position);
  this->set_camera_pose  (pose.axis_angle());
}

void OpticalBarModel::read(std::string const& filename) {

  // Open the input file
  std::ifstream cam_file;
  cam_file.open(filename.c_str());
  if (cam_file.fail())
    vw_throw( IOErr() << "OpticalBarModel::read_file: Could not open file: " << filename );

  // Check for version number on the first line
  std::string line;
  std::getline(cam_file, line);
  if (line.find("VERSION") == std::string::npos) {
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Version missing!\n" );
  }

  int file_version = 1;
  sscanf(line.c_str(),"VERSION_%d", &file_version); // Parse the version of the input file
  if (file_version < 4)
    vw_throw( ArgumentErr() << "OpticalBarModel::read_file(): Versions prior to 4 are not supported!\n" );

  // Read the camera type
  std::getline(cam_file, line);
  if (line.find("OPTICAL_BAR") == std::string::npos)
        vw_throw( ArgumentErr() << "OpticalBarModel::read_file: Expected OPTICAL_BAR type, but got type "
                                << line );

  // Start parsing all the parameters from the lines.
  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"image_size = %d %d",
      &m_image_size[0], &m_image_size[1]) != 2) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the image size\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"image_center = %lf %lf",
      &m_center_loc_pixels[0], &m_center_loc_pixels[1]) != 2) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the image center\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"pitch = %lf", &m_pixel_size) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the pixel pitch\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"f = %lf", &m_focal_length) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the focal_length\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"scan_time = %lf", &m_scan_time) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the scan time\n" );
  }
  /*
  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"scan_rate = %lf", &m_scan_rate_radians) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the scan rate\n" );
  }
  */

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"forward_tilt = %lf", &m_forward_tilt_radians) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the forward tilt angle\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"iC = %lf %lf %lf",
        &m_initial_position(0), &m_initial_position(1), &m_initial_position(2)) != 3) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the initial position\n" );
  }

  // Read and convert the rotation matrix.
  Matrix3x3 rot_mat;
  std::getline(cam_file, line);
  if ( !cam_file.good() ||
       sscanf(line.c_str(), "iR = %lf %lf %lf %lf %lf %lf %lf %lf %lf",
              &rot_mat(0,0), &rot_mat(0,1), &rot_mat(0,2),
              &rot_mat(1,0), &rot_mat(1,1), &rot_mat(1,2),
              &rot_mat(2,0), &rot_mat(2,1), &rot_mat(2,2)) != 9 ) {
    cam_file.close();
    vw_throw( IOErr() << "PinholeModel::read_file(): Could not read the rotation matrix\n" );
  }
  Quat q(rot_mat);
  m_initial_orientation = q.axis_angle();

  /*
  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"iR = %lf %lf %lf",
        &m_initial_orientation(0), &m_initial_orientation(1), &m_initial_orientation(2)) != 3) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the initial orientation\n" );
  }
  //// !!! DEBUG !!! -> Ignore the orientation, point it locally NED down.
  //vw::cartography::Datum d("WGS84");
  //Vector3 llh = d.cartesian_to_geodetic(m_initial_position);
  //Matrix3x3 m = d.lonlat_to_ned_matrix(Vector2(llh[0],llh[1]));
  //Quat q(m);
  //m_initial_orientation = q.axis_angle();
  */

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"speed = %lf", &m_speed) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the speed\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"mean_earth_radius = %lf", &m_mean_earth_radius) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the mean earth radius\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"mean_surface_elevation = %lf", &m_mean_surface_elevation) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read the mean surface elevation\n" );
  }

  std::getline(cam_file, line);
  if (!cam_file.good() || sscanf(line.c_str(),"motion_compensation_factor = %lf",
                                 &m_motion_compensation) != 1) {
    cam_file.close();
    vw_throw( IOErr() << "OpticalBarModel::read_file(): Could not read motion compensation factor\n" );
  }

  std::getline(cam_file, line);
  m_scan_left_to_right = line.find("scan_dir = left") == std::string::npos;

  cam_file.close();

  compute_scan_rate(); // This needs to be updated!
}

void OpticalBarModel::write(std::string const& filename) const {

  // TODO: Make compatible with .tsai files!

  // Open the output file for writing
  std::ofstream cam_file(filename.c_str());
  if( !cam_file.is_open() )
    vw_throw( IOErr() << "OpticalBarModel::write: Could not open file: " << filename );

  // Write the pinhole camera model parts
  //   # digits to survive double->text->double conversion
  const size_t ACCURATE_DIGITS = 17; // = std::numeric_limits<double>::max_digits10
  cam_file << std::setprecision(ACCURATE_DIGITS);
  cam_file << "VERSION_4\n";
  cam_file << "OPTICAL_BAR\n";
  cam_file << "image_size = "   << m_image_size[0] << " "
                                << m_image_size[1]<< "\n";
  cam_file << "image_center = " << m_center_loc_pixels[0] << " "
                                << m_center_loc_pixels[1] << "\n";
  cam_file << "pitch = "        << m_pixel_size             << "\n";
  cam_file << "f = "            << m_focal_length           << "\n";
  cam_file << "scan_time = "   << m_scan_time     << "\n";
  //cam_file << "scan_rate = "    << m_scan_rate_radians      << "\n";
  cam_file << "forward_tilt = " << m_forward_tilt_radians   << "\n";
  cam_file << "iC = " << m_initial_position[0] << " "
                      << m_initial_position[1] << " "
                      << m_initial_position[2] << "\n";
  // Store in the same format as the pinhole camera model.
  Matrix3x3 rot_mat = camera_pose(Vector2(0,0)).rotation_matrix();
  //cam_file << "iR = " << rot_mat[0] << " " rot_mat[1] << " " rot_mat[2] << " "
  //                    << m_initial_orientation[1] << " "
  //                    << m_initial_orientation[2] << "\n";
  cam_file << "iR = " << rot_mat(0,0) << " " << rot_mat(0,1) << " " << rot_mat(0,2) << " "
                      << rot_mat(1,0) << " " << rot_mat(1,1) << " " << rot_mat(1,2) << " "
                      << rot_mat(2,0) << " " << rot_mat(2,1) << " " << rot_mat(2,2) << "\n";
  cam_file << "speed = " << m_speed << "\n";
  cam_file << "mean_earth_radius = "          << m_mean_earth_radius      << "\n";
  cam_file << "mean_surface_elevation = "     << m_mean_surface_elevation << "\n";
  cam_file << "motion_compensation_factor = " << m_motion_compensation << "\n";
  if (m_scan_left_to_right)
    cam_file << "scan_dir = right\n";
  else
    cam_file << "scan_dir = left\n";
  cam_file.close();
}

std::ostream& operator<<( std::ostream& os, OpticalBarModel const& camera_model) {
  os << "\n------------------------ Optical Bar Model -----------------------\n\n";
  os << " Image size:             " << camera_model.m_image_size             << "\n";
  os << " Center loc (pixels):    " << camera_model.m_center_loc_pixels      << "\n";
  os << " Pixel size (m):         " << camera_model.m_pixel_size             << "\n";
  os << " Focal length (m):       " << camera_model.m_focal_length           << "\n";
  os << " Scan time (s):          " << camera_model.m_scan_time              << "\n";
  os << " Scan rate (rad/s):      " << camera_model.m_scan_rate_radians      << "\n";
  os << " Forward tilt (rad):     " << camera_model.m_forward_tilt_radians   << "\n";
  os << " Initial position:       " << camera_model.m_initial_position       << "\n";
  os << " Initial pose:           " << camera_model.m_initial_orientation    << "\n";
  os << " Speed:                  " << camera_model.m_speed                  << "\n";
  os << " Mean earth radius:      " << camera_model.m_mean_earth_radius      << "\n";
  os << " Mean surface elevation: " << camera_model.m_mean_surface_elevation << "\n";
  os << " Motion comp factor:     " << camera_model.m_motion_compensation<< "\n";
  os << " Left to right scan:     " << camera_model.m_scan_left_to_right     << "\n";
  os << "\n------------------------------------------------------------------------\n\n";
  return os;
}

}} // namespace asp::camera


/*
 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/make_shared.hpp>
#include <boost/test/unit_test.hpp>
#include <ored/marketdata/marketimpl.hpp>
#include <ored/portfolio/builders/equityforward.hpp>
#include <ored/portfolio/builders/equityoption.hpp>
#include <ored/portfolio/builders/equityfuturesoption.hpp>
#include <ored/portfolio/enginedata.hpp>
#include <ored/portfolio/equityforward.hpp>
#include <ored/portfolio/equityoption.hpp>
#include <ored/portfolio/equityfuturesoption.hpp>
#include <oret/toplevelfixture.hpp>
#include <ql/math/distributions/normaldistribution.hpp>
#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/time/daycounters/actualactual.hpp>
#include <qle/indexes/equityindex.hpp>

using namespace QuantLib;
using namespace QuantExt;
using namespace boost::unit_test_framework;
using namespace std;
using namespace ore::data;

namespace {

class TestMarket : public MarketImpl {
public:
    TestMarket() {
        asof_ = Date(3, Feb, 2016);

        // build discount
        yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")] = flatRateYts(0.1);
        yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "USD")] = flatRateYts(0.075);

        // add fx rates
        fxSpots_[Market::defaultConfiguration].addQuote("EURUSD", Handle<Quote>(boost::make_shared<SimpleQuote>(1.2)));

        // build fx vols
        fxVols_[make_pair(Market::defaultConfiguration, "EURUSD")] = flatRateFxv(0.10);

        // add equity spots
        equitySpots_[make_pair(Market::defaultConfiguration, "zzzCorp")] =
            Handle<Quote>(boost::make_shared<SimpleQuote>(100));

        // add dividend yield
        yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::EquityDividend, "zzzCorp")] =
            flatRateYts(0.05);

        // add equity curve
        equityCurves_[make_pair(Market::defaultConfiguration, "zzzCorp")] =
            Handle<EquityIndex>(boost::make_shared<EquityIndex>(
                "zzzCorp", TARGET(), parseCurrency("EUR"), equitySpot("zzzCorp"),
                yieldCurve(YieldCurveType::Discount, "EUR"), yieldCurve(YieldCurveType::EquityDividend, "zzzCorp")));

        // build equity vols
        equityVols_[make_pair(Market::defaultConfiguration, "zzzCorp")] = flatRateFxv(0.20);
    }

private:
    Handle<YieldTermStructure> flatRateYts(Real forward) {
        boost::shared_ptr<YieldTermStructure> yts(new FlatForward(0, NullCalendar(), forward, ActualActual()));
        return Handle<YieldTermStructure>(yts);
    }
    Handle<BlackVolTermStructure> flatRateFxv(Volatility forward) {
        boost::shared_ptr<BlackVolTermStructure> fxv(new BlackConstantVol(0, NullCalendar(), forward, ActualActual()));
        return Handle<BlackVolTermStructure>(fxv);
    }
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(EquityTradesTests)

// Equity option/forward test, example from Haug, Chapter 1
BOOST_AUTO_TEST_CASE(testEquityTradePrices) {

    BOOST_TEST_MESSAGE("Testing EquityOption Price...");

    Date today = Settings::instance().evaluationDate();

    // build market
    boost::shared_ptr<Market> market = boost::make_shared<TestMarket>();
    Settings::instance().evaluationDate() = market->asofDate();
    Date expiry = market->asofDate() + 6 * Months + 1 * Days;
    ostringstream o;
    o << QuantLib::io::iso_date(expiry);
    string exp_str = o.str();

    // build EquityOption - expiry in 1 Year
    OptionData callData("Long", "Call", "European", true, vector<string>(1, exp_str));
    OptionData callDataPremium("Long", "Call", "European", true, vector<string>(1, exp_str), "Cash", "",
                               PremiumData{1.0, "EUR", expiry});
    OptionData putData("Short", "Put", "European", true, vector<string>(1, exp_str));
    OptionData putDataPremium("Short", "Put", "European", true, vector<string>(1, exp_str), "Cash", "",
                              PremiumData{1.0, "EUR", expiry});
    Envelope env("CP1");
    EquityOption eqCall(env, callData, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    EquityOption eqCallPremium(env, callDataPremium, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    EquityOption eqPut(env, putData, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    EquityOption eqPutPremium(env, putDataPremium, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    ore::data::EquityForward eqFwd(env, "Long", EquityUnderlying("zzzCorp"), "EUR", 1.0, exp_str, 95.0);

    Real expectedNPV_Put = -2.4648;           // negative for sold option
    Real expectedNPV_Put_Premium = -1.513558; // less negative due to received premium of 1 EUR at expiry

    // Build and price
    boost::shared_ptr<EngineData> engineData = boost::make_shared<EngineData>();
    engineData->model("EquityOption") = "BlackScholesMerton";
    engineData->engine("EquityOption") = "AnalyticEuropeanEngine";
    engineData->model("EquityForward") = "DiscountedCashflows";
    engineData->engine("EquityForward") = "DiscountingEquityForwardEngine";
    boost::shared_ptr<EngineFactory> engineFactory = boost::make_shared<EngineFactory>(engineData, market);
    engineFactory->registerBuilder(boost::make_shared<EquityEuropeanOptionEngineBuilder>());
    engineFactory->registerBuilder(boost::make_shared<EquityForwardEngineBuilder>());

    eqCall.build(engineFactory);
    eqCallPremium.build(engineFactory);
    eqPut.build(engineFactory);
    eqPutPremium.build(engineFactory);
    eqFwd.build(engineFactory);

    Real npv_call = eqCall.instrument()->NPV();
    Real npv_call_premium = eqCallPremium.instrument()->NPV();
    Real npv_put = eqPut.instrument()->NPV();
    Real npv_put_premium = eqPutPremium.instrument()->NPV();
    Real npv_fwd = eqFwd.instrument()->NPV();

    Real put_call_sum = npv_call + npv_put;
    Real put_call_premium_sum = npv_call_premium + npv_put_premium;

    BOOST_CHECK_CLOSE(expectedNPV_Put, npv_put, 0.001);
    BOOST_CHECK_CLOSE(expectedNPV_Put_Premium, npv_put_premium, 0.001);
    BOOST_CHECK_CLOSE(npv_fwd, put_call_sum, 0.001);         // put-call parity check
    BOOST_CHECK_CLOSE(npv_fwd, put_call_premium_sum, 0.001); // put-call parity check

    Settings::instance().evaluationDate() = today; // reset
}

// when futureExpiryDate == optionExpiryDate then the trade should behave like an EquityOption
BOOST_AUTO_TEST_CASE(testEquityFutureOptionPrices) {

    BOOST_TEST_MESSAGE("Testing EquityFutureOption Price...");

    Date today = Settings::instance().evaluationDate();

    // build market
    boost::shared_ptr<Market> market = boost::make_shared<TestMarket>();
    Settings::instance().evaluationDate() = market->asofDate();
    Date expiry = market->asofDate() + 6 * Months + 1 * Days;
    ostringstream o;
    o << QuantLib::io::iso_date(expiry);
    string exp_str = o.str();

    boost::shared_ptr<ore::data::Underlying> underlying = boost::make_shared<ore::data::EquityUnderlying>("zzzCorp");
    // build EquityOption - expiry in 1 Year
    OptionData callData("Long", "Call", "European", true, vector<string>(1, exp_str));
    OptionData callDataPremium("Long", "Call", "European", true, vector<string>(1, exp_str), "Physical", "",
                               PremiumData(1.0, "EUR", expiry));
    OptionData putData("Short", "Put", "European", true, vector<string>(1, exp_str));
    OptionData putDataPremium("Short", "Put", "European", true, vector<string>(1, exp_str), "Physical", "",
                              PremiumData(1.0, "EUR", expiry));
    Envelope env("CP1");
    EquityFutureOption eqFwdCall(env, callData, "EUR", 1.0, underlying, 95.0, expiry);
    EquityFutureOption eqFwdCallPremium(env, callDataPremium, "EUR", 1.0, underlying, 95.0, expiry);
    EquityFutureOption eqFwdPut(env, putData, "EUR", 1.0, underlying, 95.0, expiry);
    EquityFutureOption eqFwdPutPremium(env, putDataPremium, "EUR", 1.0, underlying, 95.0, expiry);

    EquityOption eqCall(env, callData, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    EquityOption eqCallPremium(env, callDataPremium, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    EquityOption eqPut(env, putData, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);
    EquityOption eqPutPremium(env, putDataPremium, EquityUnderlying("zzzCorp"), "EUR", 95.0, 1.0);

    // Build and price
    boost::shared_ptr<EngineData> engineData = boost::make_shared<EngineData>();
    engineData->model("EquityOption") = "BlackScholesMerton";
    engineData->engine("EquityOption") = "AnalyticEuropeanEngine";
    engineData->model("EquityFutureOption") = "BlackScholes";
    engineData->engine("EquityFutureOption") = "AnalyticEuropeanForwardEngine";
    boost::shared_ptr<EngineFactory> engineFactory = boost::make_shared<EngineFactory>(engineData, market);
    engineFactory->registerBuilder(boost::make_shared<EquityEuropeanOptionEngineBuilder>());
    engineFactory->registerBuilder(boost::make_shared<EquityFutureEuropeanOptionEngineBuilder>());
    engineFactory->registerBuilder(boost::make_shared<EquityForwardEngineBuilder>());

    eqFwdCall.build(engineFactory);
    eqFwdCallPremium.build(engineFactory);
    eqFwdPut.build(engineFactory);
    eqFwdPutPremium.build(engineFactory);

    eqCall.build(engineFactory);
    eqCallPremium.build(engineFactory);
    eqPut.build(engineFactory);
    eqPutPremium.build(engineFactory);

    BOOST_CHECK_CLOSE(eqCall.instrument()->NPV(), eqFwdCall.instrument()->NPV(), 0.001);
    BOOST_CHECK_CLOSE(eqCallPremium.instrument()->NPV(), eqFwdCallPremium.instrument()->NPV(), 0.001);
    BOOST_CHECK_CLOSE(eqPut.instrument()->NPV(), eqFwdPut.instrument()->NPV(), 0.001);
    BOOST_CHECK_CLOSE(eqPutPremium.instrument()->NPV(), eqFwdPutPremium.instrument()->NPV(), 0.001);

    Settings::instance().evaluationDate() = today; // reset
}

BOOST_AUTO_TEST_CASE(testEquityFutureParity) {

    BOOST_TEST_MESSAGE("Testing EquityFutureOption Put-Call parity...");

    Date today = Settings::instance().evaluationDate();

    // build market
    boost::shared_ptr<Market> market = boost::make_shared<TestMarket>();
    Settings::instance().evaluationDate() = market->asofDate();
    Date expiry = market->asofDate() + 6 * Months + 1 * Days;
    ostringstream o;
    o << QuantLib::io::iso_date(expiry);
    string exp_str = o.str();

    Date futureExpiry = market->asofDate() + 12 * Months + 1 * Days;
    ostringstream o_2;
    o_2 << QuantLib::io::iso_date(futureExpiry);
    string f_exp_str = o_2.str();

    boost::shared_ptr<ore::data::Underlying> underlying = boost::make_shared<ore::data::EquityUnderlying>("zzzCorp");
    double strike = 95.0;
    double spot = 100;
    // build EquityOption - expiry in 1 Year
    OptionData callData("Long", "Call", "European", true, vector<string>(1, exp_str));
    OptionData callDataPremium("Long", "Call", "European", true, vector<string>(1, exp_str), "Physical", "",
                               PremiumData(1.0, "EUR", expiry));
    OptionData putData("Long", "Put", "European", true, vector<string>(1, exp_str));
    OptionData putDataPremium("Long", "Put", "European", true, vector<string>(1, exp_str), "Physical", "",
                              PremiumData(1.0, "EUR", expiry));
    Envelope env("CP1");
    EquityFutureOption eqCall(env, callData, "EUR", 1.0, underlying, strike, futureExpiry);
    EquityFutureOption eqCallPremium(env, callDataPremium, "EUR", 1.0, underlying, strike, futureExpiry);
    EquityFutureOption eqPut(env, putData, "EUR", 1.0, underlying, strike, futureExpiry);
    EquityFutureOption eqPutPremium(env, putDataPremium, "EUR", 1.0, underlying, strike, futureExpiry);
    ore::data::EquityForward eqFwd(env, "Long", EquityUnderlying("zzzCorp"), "EUR", 1.0, f_exp_str, 0);

    // Build and price
    boost::shared_ptr<EngineData> engineData = boost::make_shared<EngineData>();
    engineData->model("EquityFutureOption") = "BlackScholes";
    engineData->engine("EquityFutureOption") = "AnalyticEuropeanForwardEngine";
    engineData->model("EquityOption") = "BlackScholesMerton";
    engineData->engine("EquityOption") = "AnalyticEuropeanEngine";
    engineData->model("EquityForward") = "DiscountedCashflows";
    engineData->engine("EquityForward") = "DiscountingEquityForwardEngine";
    boost::shared_ptr<EngineFactory> engineFactory = boost::make_shared<EngineFactory>(engineData, market);
    engineFactory->registerBuilder(boost::make_shared<EquityEuropeanOptionEngineBuilder>());
    engineFactory->registerBuilder(boost::make_shared<EquityFutureEuropeanOptionEngineBuilder>());
    engineFactory->registerBuilder(boost::make_shared<EquityForwardEngineBuilder>());

    eqCall.build(engineFactory);
    eqCallPremium.build(engineFactory);
    eqPut.build(engineFactory);
    eqPutPremium.build(engineFactory);
    eqFwd.build(engineFactory);

    Handle<YieldTermStructure> discountCurve =
            market->discountCurve("EUR", Market::defaultConfiguration);

    Handle<YieldTermStructure> dividend = market->equityDividendCurve("zzzCorp", Market::defaultConfiguration);
    Handle<YieldTermStructure> forecast = market->equityForecastCurve("zzzCorp", Market::defaultConfiguration);

    Real npv_call = eqCall.instrument()->NPV();
    Real npv_call_premium = eqCallPremium.instrument()->NPV();
    Real npv_put = eqPut.instrument()->NPV();
    Real npv_put_premium = eqPutPremium.instrument()->NPV();

    Real npv_fwd = spot * dividend->discount(futureExpiry) / forecast->discount(futureExpiry);;

    Real put_sum = npv_put + ( npv_fwd - strike ) * discountCurve->discount(expiry);
    Real put_premium_sum = npv_put_premium + ( npv_fwd - strike ) * discountCurve->discount(expiry);

    BOOST_CHECK_CLOSE(npv_call, put_sum, 0.001);         // put-call parity check
    BOOST_CHECK_CLOSE(npv_call_premium, put_premium_sum, 0.001); // put-call parity check

    Settings::instance().evaluationDate() = today; // reset
}
BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


//
//  config.cpp
//  atlas
//
//  Created by Janis on 22.05.15.
//  Copyright (c) 2015 Janis. All rights reserved.
//

#include "config.h"

#include <stdio.h>
#include <iostream>

#include <armadillo>
#include <yaml-cpp/yaml.h>

Config::Config (const std::string file)
{
    YAML::Node config = YAML::LoadFile(file);

    // Anchor Configuration
    for(int i = 0; i < config["anchors"].size(); ++i)
    {
        uint64_t eui = config["anchors"][i]["euid"].as<uint64_t>();
        std::string port = config["anchors"][i]["port"].as<std::string>();
        m_anchors.ports.insert(std::pair<uint64_t, std::string>(eui, port));
        arma::vec pos;
        pos << config["anchors"][i]["position"][0].as<double>()
            << config["anchors"][i]["position"][1].as<double>()
            << config["anchors"][i]["position"][2].as<double>()
            << arma::endr;
        m_anchors.positions.insert(std::pair<uint64_t, arma::vec>(eui, pos));
    }

    // Sync Configuration
    if (config["sync"]["port"] && config["sync"]["baudrate"])
    {
        m_sync.port = config["sync"]["port"].as<std::string>();
        m_sync.baudrate = config["sync"]["baudrate"].as<int>();
        m_sync.eui = config["sync"]["euid"].as<uint64_t>();
        m_sync.interval = config["sync"]["interval"].as<uint64_t>();

        arma::vec pos;
        pos << config["sync"]["position"][0].as<double>()
            << config["sync"]["position"][1].as<double>()
            << config["sync"]["position"][2].as<double>()
            << arma::endr;
        m_sync.position = pos;

        m_parser.tagWhitelist.insert(std::pair<uint64_t, uint64_t>(m_sync.eui, m_sync.eui));
    }
    else
    {
        std::cout << "Error - Config Port for UWB Sync Node not defined!" << std::endl;
    }

    // Calibration Configuration
    if (config["calibration"]["euid"] && config["calibration"]["position"])
    {
        m_cal.eui = config["calibration"]["euid"].as<uint64_t>();
        m_cal.window = config["calibration"]["window"].as<uint64_t>();

        arma::vec pos;
        pos << config["calibration"]["position"][0].as<double>()
            << config["calibration"]["position"][1].as<double>()
            << config["calibration"]["position"][2].as<double>()
            << arma::endr;
        m_cal.position = pos;

        m_parser.tagWhitelist.insert(std::pair<uint64_t, uint64_t>(m_cal.eui, m_cal.eui));
    }
    else
    {
        std::cout << "Error - Config Port for UWB Calibration Node not defined!" << std::endl;
    }

    // Output Configuration
    if (config["output"]["port"] && config["output"]["baudrate"])
    {
        m_output.port = config["output"]["port"].as<std::string>();
        m_output.baudrate = config["output"]["baudrate"].as<int>();
    }
    else
    {
        std::cout << "Error - Config Port for Output not defined!" << std::endl;
    }

    // Parser Configuration
    for(int i = 0; i < config["whitelist"].size(); ++i)
    {
        uint64_t eui = config["whitelist"][i].as<uint64_t>();
        m_parser.tagWhitelist.insert(std::pair<uint64_t, uint64_t>(eui, eui));
    }

    // LRF Configuration
    if(config["lrf"]["wgs84ref"])
    {
        m_lrf.wgs84ref << config["lrf"]["wgs84ref"][0].as<double>()
                       << config["lrf"]["wgs84ref"][1].as<double>()
                       << config["lrf"]["wgs84ref"][2].as<double>()
                       << arma::endr;
    }
    else
    {
        std::cout << "Error - Config LRF Reference not defined!" << std::endl;
    }

    if(config["lrf"]["wgs84axis"])
    {
        m_lrf.wgs84axis << config["lrf"]["wgs84axis"][0].as<double>()
                        << config["lrf"]["wgs84axis"][1].as<double>()
                        << config["lrf"]["wgs84axis"][2].as<double>()
                        << arma::endr;
    }
    else
    {
        std::cout << "Error - Config LRF Axis not defined!" << std::endl;
    }

    // EKF Configuration
    if(config["ekf"]["processNoise"]) m_ekf.processNoise = config["ekf"]["processNoise"].as<double>();
    if(config["ekf"]["measurementVariance"]) m_ekf.measurementVariance = config["ekf"]["measurementVariance"].as<double>();
    if(config["ekf"]["initialInterval"]) m_ekf.initialInterval = config["ekf"]["initialInterval"].as<double>();
    if(config["ekf"]["initialVariance"]) m_ekf.initialVariance = config["ekf"]["initialVariance"].as<double>();
    if(config["ekf"]["initialVarianceDelta"]) m_ekf.initialVarianceDelta = config["ekf"]["initialVarianceDelta"].as<double>();

    if(config["ekf"]["initialState"])
    {
        m_ekf.initialState << config["ekf"]["initialState"][0].as<double>()
                           << config["ekf"]["initialState"][1].as<double>()
                           << config["ekf"]["initialState"][2].as<double>()
                           << config["ekf"]["initialState"][3].as<double>()
                           << config["ekf"]["initialState"][4].as<double>()
                           << config["ekf"]["initialState"][5].as<double>()
                           << arma::endr;
    }
}

void Config::list()
{
    std::cout << "Config file listing:" << std::endl;

    for (auto it = m_anchors.ports.begin(); it != m_anchors.ports.end(); ++it)
    {
        std::cout << " " << it->first << ": " << it->second << std::endl;
    }

    for (auto it = m_anchors.positions.begin(); it != m_anchors.positions.end(); ++it)
    {
        std::cout << " " << it->first << ": " << it->second(0) << ", " << it->second(1) << ", " << it->second(2) << std::endl;
    }
}

Config::~Config()
{

}


/*
 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 <qle/pricingengines/crossccyswapengine.hpp>
#ifdef QL_USE_INDEXED_COUPON
#include <ql/cashflows/floatingratecoupon.hpp>
#endif

#include <qle/termstructures/crossccybasisswaphelper.hpp>

#include <boost/make_shared.hpp>

namespace QuantExt {

namespace {
void no_deletion(YieldTermStructure*) {}
} // namespace

CrossCcyBasisSwapHelper::CrossCcyBasisSwapHelper(
    const Handle<Quote>& spreadQuote, const Handle<Quote>& spotFX, Natural settlementDays,
    const Calendar& settlementCalendar, const Period& swapTenor, BusinessDayConvention rollConvention,
    const boost::shared_ptr<QuantLib::IborIndex>& flatIndex, const boost::shared_ptr<QuantLib::IborIndex>& spreadIndex,
    const Handle<YieldTermStructure>& flatDiscountCurve, const Handle<YieldTermStructure>& spreadDiscountCurve,
    bool eom, bool flatIsDomestic, boost::optional<Period> flatTenor, boost::optional<Period> spreadTenor,
    Real spreadOnFlatLeg, Real flatGearing, Real spreadGearing, const Calendar& flatCalendar,
    const Calendar& spreadCalendar, const std::vector<Natural>& spotFXSettleDaysVec, const std::vector<Calendar>& spotFXSettleCalendarVec,
    DateGeneration::Rule rule )
    : RelativeDateRateHelper(spreadQuote), spotFX_(spotFX), settlementDays_(settlementDays),
      settlementCalendar_(settlementCalendar), swapTenor_(swapTenor), rollConvention_(rollConvention),
      flatIndex_(flatIndex), spreadIndex_(spreadIndex), flatDiscountCurve_(flatDiscountCurve),
      spreadDiscountCurve_(spreadDiscountCurve), eom_(eom), flatIsDomestic_(flatIsDomestic),
      flatTenor_(flatTenor ? *flatTenor : flatIndex_->tenor()),
      spreadTenor_(spreadTenor ? *spreadTenor : spreadIndex_->tenor()), spreadOnFlatLeg_(spreadOnFlatLeg),
      flatGearing_(flatGearing), spreadGearing_(spreadGearing), flatCalendar_(flatCalendar),
      spreadCalendar_(spreadCalendar), spotFXSettleDaysVec_(spotFXSettleDaysVec), spotFXSettleCalendarVec_(spotFXSettleCalendarVec),
      rule_(rule) {

    flatLegCurrency_ = flatIndex_->currency();
    spreadLegCurrency_ = spreadIndex_->currency();

    bool flatIndexHasCurve = !flatIndex_->forwardingTermStructure().empty();
    bool spreadIndexHasCurve = !spreadIndex_->forwardingTermStructure().empty();
    bool haveFlatDiscountCurve = !flatDiscountCurve_.empty();
    bool haveSpreadDiscountCurve = !spreadDiscountCurve_.empty();

    QL_REQUIRE(!(flatIndexHasCurve && spreadIndexHasCurve && haveFlatDiscountCurve && haveSpreadDiscountCurve),
               "Have all curves, "
               "nothing to solve for.");

    if (flatCalendar_.empty())
        flatCalendar_ = settlementCalendar;
    if (spreadCalendar_.empty())
        spreadCalendar_ = settlementCalendar;
	//check spotFXSettleDaysVec_ and spotFXSettleCalendarVec_
    Size numSpotFXSettleDays = spotFXSettleDaysVec_.size();
    QL_REQUIRE(numSpotFXSettleDays == spotFXSettleCalendarVec_.size(),
               "Array size of spot fx settlement days must equal that of spot fx settlement calendars");
	if(numSpotFXSettleDays == 0) {
		spotFXSettleDaysVec_.resize(1,0);
		spotFXSettleCalendarVec_.resize(1,settlementCalendar);
	}

    /* Link the curve being bootstrapped to the index if the index has
       no projection curve */
    if (flatIndexHasCurve && haveFlatDiscountCurve) {
        if (!spreadIndexHasCurve) {
            spreadIndex_ = spreadIndex_->clone(termStructureHandle_);
            spreadIndex_->unregisterWith(termStructureHandle_);
        }
    } else if (spreadIndexHasCurve && haveSpreadDiscountCurve) {
        if (!flatIndexHasCurve) {
            flatIndex_ = flatIndex_->clone(termStructureHandle_);
            flatIndex_->unregisterWith(termStructureHandle_);
        }
    } else {
        QL_FAIL("Need one leg of the cross currency basis swap to "
                "have all of its curves.");
    }

    registerWith(spotFX_);
    registerWith(flatIndex_);
    registerWith(spreadIndex_);
    registerWith(flatDiscountCurve_);
    registerWith(spreadDiscountCurve_);

    initializeDates();
}

void CrossCcyBasisSwapHelper::initializeDates() {

    Date refDate = evaluationDate_;
    // if the evaluation date is not a business day
    // then move to the next business day
    refDate = settlementCalendar_.adjust(refDate);

    Date settlementDate = settlementCalendar_.advance(refDate, settlementDays_, Days);
    Date maturityDate = settlementDate + swapTenor_;
	//calc spotFXSettleDate
	Date spotFXSettleDate = refDate;
    Size numSpotFXSettleDays = spotFXSettleDaysVec_.size();//guarranteed to be at least 1
    for (Size i = 0; i < numSpotFXSettleDays; i++) {
		//Guarranteed here that spotFXSettleDaysVec_ and spotFXSettleCalendarVec_ have the same size
		spotFXSettleDate = spotFXSettleCalendarVec_[i].advance(spotFXSettleDate, spotFXSettleDaysVec_[i], Days);
	}

    Schedule flatLegSchedule = MakeSchedule()
                                   .from(settlementDate)
                                   .to(maturityDate)
                                   .withTenor(flatTenor_)
                                   .withCalendar(flatCalendar_)
                                   .withConvention(rollConvention_)
                                   .endOfMonth(eom_)
                                   .withRule(rule_);

    Schedule spreadLegSchedule = MakeSchedule()
                                     .from(settlementDate)
                                     .to(maturityDate)
                                     .withTenor(spreadTenor_)
                                     .withCalendar(spreadCalendar_)
                                     .withConvention(rollConvention_)
                                     .endOfMonth(eom_)
                                     .withRule(rule_);

    Real flatLegNominal = 1.0;
    Real spreadLegNominal = 1.0;
    if (flatIsDomestic_) {
        flatLegNominal = spotFX_->value();
    } else {
        spreadLegNominal = spotFX_->value();
    }

    /* Arbitrarily set the spread leg as the pay leg */
    swap_ = boost::shared_ptr<CrossCcyBasisSwap>(new CrossCcyBasisSwap(
        spreadLegNominal, spreadLegCurrency_, spreadLegSchedule, spreadIndex_, 0.0, spreadGearing_, flatLegNominal,
        flatLegCurrency_, flatLegSchedule, flatIndex_, spreadOnFlatLeg_, flatGearing_));

    boost::shared_ptr<PricingEngine> engine;
    if (flatIsDomestic_) {
        engine.reset(new CrossCcySwapEngine(flatLegCurrency_, flatDiscountRLH_, spreadLegCurrency_, spreadDiscountRLH_,
                                            spotFX_, boost::none, Date(), Date(), spotFXSettleDate));
    } else {
        engine.reset(new CrossCcySwapEngine(spreadLegCurrency_, spreadDiscountRLH_, flatLegCurrency_, flatDiscountRLH_,
                                            spotFX_, boost::none, Date(), Date(), spotFXSettleDate));
    }
    swap_->setPricingEngine(engine);

    earliestDate_ = swap_->startDate();
    latestDate_ = swap_->maturityDate();

/* May need to adjust latestDate_ if you are projecting libor based
   on tenor length rather than from accrual date to accrual date. */
#ifdef QL_USE_INDEXED_COUPON
    if (termStructureHandle_ == spreadIndex_->forwardingTermStructure()) {
        Size numCashflows = swap_->leg(0).size();
        if (numCashflows > 2) {
            boost::shared_ptr<FloatingRateCoupon> lastFloating =
                boost::dynamic_pointer_cast<FloatingRateCoupon>(swap_->leg(0)[numCashflows - 2]);
            Date fixingValueDate = spreadIndex_->valueDate(lastFloating->fixingDate());
            Date endValueDate = spreadIndex_->maturityDate(fixingValueDate);
            latestDate_ = std::max(latestDate_, endValueDate);
        }
    }
    if (termStructureHandle_ == flatIndex_->forwardingTermStructure()) {
        Size numCashflows = swap_->leg(1).size();
        if (numCashflows > 2) {
            boost::shared_ptr<FloatingRateCoupon> lastFloating =
                boost::dynamic_pointer_cast<FloatingRateCoupon>(swap_->leg(1)[numCashflows - 2]);
            Date fixingValueDate = flatIndex_->valueDate(lastFloating->fixingDate());
            Date endValueDate = flatIndex_->maturityDate(fixingValueDate);
            latestDate_ = std::max(latestDate_, endValueDate);
        }
    }
#endif
}

void CrossCcyBasisSwapHelper::setTermStructure(YieldTermStructure* t) {

    bool observer = false;
    boost::shared_ptr<YieldTermStructure> temp(t, no_deletion);

    termStructureHandle_.linkTo(temp, observer);

    if (flatDiscountCurve_.empty())
        flatDiscountRLH_.linkTo(temp, observer);
    else
        flatDiscountRLH_.linkTo(*flatDiscountCurve_, observer);

    if (spreadDiscountCurve_.empty())
        spreadDiscountRLH_.linkTo(temp, observer);
    else
        spreadDiscountRLH_.linkTo(*spreadDiscountCurve_, observer);

    RelativeDateRateHelper::setTermStructure(t);
}

Real CrossCcyBasisSwapHelper::impliedQuote() const {
    QL_REQUIRE(termStructure_, "Term structure needs to be set");
    swap_->recalculate();
    return swap_->fairPaySpread();
}

void CrossCcyBasisSwapHelper::accept(AcyclicVisitor& v) {
    Visitor<CrossCcyBasisSwapHelper>* v1 = dynamic_cast<Visitor<CrossCcyBasisSwapHelper>*>(&v);
    if (v1)
        v1->visit(*this);
    else
        RateHelper::accept(v);
}
} // namespace QuantExt


#include <boost/geometry/formulas/geographic.hpp>