//
// udpvpn.cpp
// ~~~~~~~~~~
//

#include <string>
#include <iostream>
#include <fstream>
#include <memory>
#include <functional>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <linux/if.h>
#include <linux/if_tun.h>

// 256 bit keys and 128 bit initial vector
static constexpr int symmetric_key_len = 256 / 8;
static constexpr int symmetric_iv_len = 128 / 8;
static constexpr int signing_key_len = 256 / 8;
static constexpr int signature_len = 256 / 8;

// When used with encryption the payload consists of
// 256-bit SHA-256 HMAC signature followed by a random
// 128-bit initial vector (used by the AES-CBC encryption)
// followed by an encrypted payload.
#pragma pack(push, 1)
struct UdpVpnEncryptedData {
  unsigned char signature[signature_len];
  unsigned char initial_vector[symmetric_iv_len];
  unsigned char data[];
};
#pragma pack(pop)

class UdpVpnServer {
 public:
  UdpVpnServer(boost::asio::io_service& io_service,
               const boost::asio::ip::udp::endpoint& remote,
               const boost::asio::ip::udp::endpoint& local,
               const std::string& tunnel,
               const unsigned char* encryption_key,
               const unsigned char* signing_key)
      : send_socket_(io_service, boost::asio::ip::udp::v4()),
        remote_(remote),
        receive_socket_(io_service, local),
        tunfd_(io_service, tun_alloc(tunnel, IFF_TUN)),
        encryption_key_(encryption_key), signing_key_(signing_key) {
    crypt_init();
  }

  ~UdpVpnServer() {
    if (rx_crypt_ctx_) {
      EVP_CIPHER_CTX_free(rx_crypt_ctx_);
    }
    if (tx_crypt_ctx_) {
      EVP_CIPHER_CTX_free(tx_crypt_ctx_);
    }
    if (hmac_ctx_) {
      HMAC_CTX_free(hmac_ctx_);
    }
  }

  void Start() {
    if (tunfd_.is_open()) {
      TunReceive();
      UdpReceive();
    }
  }

  void TunReceive() {
    tunfd_.async_read_some(
        boost::asio::buffer(tunnel_buffer_, buf_max_len),
        [this](const boost::system::error_code& error,
               std::size_t bytes_transferred) {
          if (bytes_transferred) {
            //std::cout << "Received Tunnel " << bytes_transferred << " bytes\n";
            std::array<unsigned char, buf_max_len>* buffer_data;
            if (tx_crypt_ctx_) {
              UdpVpnEncryptedData* enc_data = (UdpVpnEncryptedData*)
                  encrypted_tunnel_buffer_.data();

              // Generate a random initial vector
              if (RAND_bytes(enc_data->initial_vector, symmetric_iv_len) != 1) {
                ERR_print_errors_fp(stderr);
                throw CryptoException();
              }
              // Encrypt using the random IV
              bytes_transferred = encrypt(
                  tx_crypt_ctx_, enc_data->initial_vector,
                  tunnel_buffer_.data(), bytes_transferred,
                  enc_data->data);
              // Generate a signature over the encrypted data
              sign_data(enc_data->signature, enc_data->data, bytes_transferred);
              // Replace with the complete buffer
              buffer_data = &encrypted_tunnel_buffer_;
              bytes_transferred += signature_len + symmetric_iv_len;
            } else {
              // Use the plaintext buffer
              buffer_data = &tunnel_buffer_;
            }
            send_socket_.async_send_to(
                boost::asio::buffer(*buffer_data, bytes_transferred),
                remote_,
                [this](const boost::system::error_code& error,
                       std::size_t bytes_transferred) {
                  //std::cout << "Sent UDP " << bytes_transferred << " bytes: " << error << "\n";
                  TunReceive();
                });
          }
        });
  }

  void UdpReceive() {
    receive_socket_.async_receive_from(
        boost::asio::buffer(udp_buffer_, buf_max_len),
        from_endpoint_,
        [this](const boost::system::error_code& error,
               std::size_t bytes_transferred) {
          if (bytes_transferred) {
            std::array<unsigned char, buf_max_len>* buffer_data;
            //std::cout << "Received UDP " << bytes_transferred << " bytes\n";
            if (rx_crypt_ctx_) {
              const UdpVpnEncryptedData* enc_data = (UdpVpnEncryptedData*)
                  udp_buffer_.data();
              // Adjust to remove the signature and initial_vector
              bytes_transferred = bytes_transferred - signature_len - symmetric_iv_len;
              // Check that the signature is good
              if (!check_signature(
                      enc_data->signature, enc_data->data, bytes_transferred)) {
                std::cout << "Failed the signature check - drop the data\n";
                UdpReceive();
                return;
              }
              // Decrypt the data using the included initial vector
              bytes_transferred = decrypt(
                  rx_crypt_ctx_, enc_data->initial_vector,
                  enc_data->data, bytes_transferred, plaintext_udp_buffer_.data());
              buffer_data = &plaintext_udp_buffer_;
            } else {
              // Just use the plaintext buffer
              buffer_data = &udp_buffer_;
            }
            tunfd_.async_write_some(
                boost::asio::buffer(*buffer_data, bytes_transferred),
                [this](const boost::system::error_code& error,
                       std::size_t bytes_transferred) {
                  //std::cout << "Sent Tunnel " << bytes_transferred << " bytes: " << error << "\n";
                  UdpReceive();
                });
          }
        });
  }

 private:

  int tun_alloc(std::string dev, int flags) {
    struct ifreq ifr;
    int fd, err;
    char name[IFNAMSIZ];

    strncpy(name, dev.c_str(), IFNAMSIZ);

    if ((fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK)) < 0 ) {
      perror("Opening /dev/net/tun");
      return fd;
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = flags;
    strncpy(ifr.ifr_name, name, IFNAMSIZ);
    if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) {
      if (errno == EPERM) {
        std::cerr << "Insufficient priviledge to create or bind to a Tunnel Device.\n" <<
            "Try elevating the user to root access\n";
        close(fd);
        return err;
      }
      perror("ioctl(TUNSETIFF)");
      close(fd);
      return err;
    }

    strcpy(name, ifr.ifr_name);
    return fd;
  }

  struct CryptoException : std::exception {
    char const* what() const throw() {
      return "CryptoException";
    }
  };

  void crypt_init() {
    if (encryption_key_ == nullptr || signing_key_ == nullptr) {
      return;
    }

    /* Create and initialise the contexts */
    if (!(rx_crypt_ctx_ = EVP_CIPHER_CTX_new()))
      throw CryptoException();

    if (!(tx_crypt_ctx_ = EVP_CIPHER_CTX_new()))
      throw CryptoException();

    if (!(hmac_ctx_ = HMAC_CTX_new()))
      throw CryptoException();
  }

  void sign_data(unsigned char* destination,
                 const unsigned char* source,
                 int length) {
    HMAC_Init_ex(hmac_ctx_, signing_key_, signing_key_len, EVP_sha256(), NULL);
    HMAC_Update(hmac_ctx_, source, length);
    unsigned int len = signature_len;
    HMAC_Final(hmac_ctx_, destination, &len);
  }

  bool check_signature(const unsigned char* data_signature,
                       const unsigned char* source,
                       int length) {
    unsigned char signature[signature_len];

    sign_data(signature, source, length);

    return CRYPTO_memcmp(data_signature, signature, signature_len) == 0;
  }

  int encrypt(EVP_CIPHER_CTX *ctx, unsigned char *iv,
              const unsigned char *plaintext,
              int plaintext_len, unsigned char *ciphertext)
  {
    int len;
    int ciphertext_len;

    /*
     * Initialise the encryption operation. IMPORTANT - ensure you use a key
     * and IV size appropriate for your cipher
     * In this example we are using 256 bit AES (i.e. a 256 bit key). The
     * IV size for *most* modes is the same as the block size. For AES this
     * is 128 bits
     */
    if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, encryption_key_, iv)) {
      ERR_print_errors_fp(stderr);
      throw CryptoException();
    }

    /*
     * Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can be called multiple times if necessary
     */
    if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) {
      ERR_print_errors_fp(stderr);
      throw CryptoException();
    }
    ciphertext_len = len;

    /*
     * Finalise the encryption. Further ciphertext bytes may be written at
     * this stage.
     */
    if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
      ERR_print_errors_fp(stderr);
      throw CryptoException();
    }
    ciphertext_len += len;

    return ciphertext_len;
  }

  int decrypt(EVP_CIPHER_CTX *ctx, const unsigned char *iv,
              const unsigned char *ciphertext,
              int ciphertext_len, unsigned char *plaintext)
  {
    int len;
    int plaintext_len;

    /*
     * Initialise the decryption operation. IMPORTANT - ensure you use a key
     * and IV size appropriate for your cipher
     * In this example we are using 256 bit AES (i.e. a 256 bit key). The
     * IV size for *most* modes is the same as the block size. For AES this
     * is 128 bits
     */
    if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, encryption_key_, iv)) {
      ERR_print_errors_fp(stderr);
      throw CryptoException();
    }

    /*
     * Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary.
     */
    if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
      ERR_print_errors_fp(stderr);
      throw CryptoException();
    }
    plaintext_len = len;

    /*
     * Finalise the decryption. Further plaintext bytes may be written at
     * this stage.
     */
    if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
      ERR_print_errors_fp(stderr);
      throw CryptoException();
    }
    plaintext_len += len;

    return plaintext_len;
  }

  static constexpr int buf_max_len = 1600;
  std::array<unsigned char, buf_max_len> tunnel_buffer_;
  std::array<unsigned char, buf_max_len> encrypted_tunnel_buffer_;
  std::array<unsigned char, buf_max_len> udp_buffer_;
  std::array<unsigned char, buf_max_len> plaintext_udp_buffer_;

  boost::asio::ip::udp::socket send_socket_;
  boost::asio::ip::udp::endpoint remote_;
  boost::asio::ip::udp::socket receive_socket_;
  boost::asio::ip::udp::endpoint from_endpoint_;

  boost::asio::posix::stream_descriptor tunfd_;

  const unsigned char *encryption_key_;
  const unsigned char *signing_key_;
  HMAC_CTX *hmac_ctx_ = nullptr;
  EVP_CIPHER_CTX *rx_crypt_ctx_ = nullptr;
  EVP_CIPHER_CTX *tx_crypt_ctx_ = nullptr;
};

bool decompose_ip_port(const std::string endpoint, std::string& ip, int& port) {
  int cpos = endpoint.find_first_of(':');
  if (cpos == std::string::npos) {
    return false;
  }
  ip = endpoint.substr(0, cpos);
  try {
    boost::asio::ip::address::from_string(ip);
    port = stoi(endpoint.substr(cpos + 1));
  } catch (...) { return false; }
  return true;
}

bool load_encryption_data(const std::string& key_file_path,
                          unsigned char* symmetric_key,
                          unsigned char* signing_key) {
  std::ifstream key_file(key_file_path, std::ifstream::binary);

  key_file.read((char*)symmetric_key, symmetric_key_len);
  if (key_file.gcount() != symmetric_key_len) {
    return false;
  }
  key_file.read((char*)signing_key, signing_key_len);
  if (key_file.gcount() != signing_key_len) {
    return false;
  }

  return true;
}

namespace po = boost::program_options;

int main(int argc, char **argv) {
  std::string endpoint_local;
  std::string endpoint_remote;
  std::string tunnel_device;
  std::string key_file_path;

  po::options_description description(
      "A program to implement a simple VPN tunnel over UDP");
  description.add_options()
      ("help", "Display this help message")
      ("local", po::value<std::string>(&endpoint_local)->required(),
       "(required) Local IP Address and port number for UDP packets (IP:UDPPort)")
      ("remote", po::value<std::string>(&endpoint_remote)->required(),
       "(required) Remote IP Address and port number for UDP packets (IP:UDPPort)")
      ("tunnel", po::value<std::string>(&tunnel_device)->default_value("tun1"),
       "(optional) Tunnel device name to use (default: tun1)")
      ("key", po::value<std::string>(&key_file_path),
       "(optional) Key file to use for symmetric encryption (key file must contain a 256 bit binary key, followed by a 128 bit binary initial vector)");
  po::variables_map vm;

  po::store(
      po::parse_command_line(argc, argv, description), vm);

  try {
    po::notify(vm);
  } catch (...) {
    std::cout << description << "\n";
    exit(1);
  }

  if (vm.count("help")) {
    std::cout << description << "\n";
    exit(0);
  }

  unsigned char symmetric_key[symmetric_key_len];
  unsigned char signing_key[signing_key_len];

  if (!key_file_path.empty()) {
    if (!load_encryption_data(key_file_path, symmetric_key, signing_key)) {
      std::cout << "Unable to load key file from " << key_file_path << "\n";
      exit(1);
    }
  }

  std::string remote_ip;
  int remote_port;
  std::string local_ip;
  int local_port;

  if (!decompose_ip_port(endpoint_remote, remote_ip, remote_port) ||
      !decompose_ip_port(endpoint_local, local_ip, local_port)) {
    std::cout << "IP Address and port number must be in the form IP:UDPPort\n";
    exit(1);
  }

  std::cout << "Remote: " << remote_ip << ":" << remote_port << "\n";
  std::cout << "Local: " << local_ip << ":" << local_port << "\n";
  std::cout << "Tunnel: " << tunnel_device << "\n";

  boost::asio::io_service io_service;

  boost::asio::ip::udp::endpoint remote(
      boost::asio::ip::address::from_string(remote_ip), remote_port);
  boost::asio::ip::udp::endpoint local(
      boost::asio::ip::address::from_string(local_ip), local_port);

  try {
    UdpVpnServer udp_server(io_service, remote, local, tunnel_device,
                            !key_file_path.empty() ? symmetric_key : nullptr,
                            !key_file_path.empty() ? signing_key : nullptr);
    udp_server.Start();
    io_service.run();
  } catch (const boost::exception& ex) {
    std::cout << "exception: " << diagnostic_information(ex) << "\n";
    exit(1);
  }

  exit(0);
}


#include <string>
#include <iostream>
#include <sstream>
#include <filesystem>

#include <dlib\dnn.h>
#include <dlib\clustering.h>
#include <dlib\string.h>
#include <dlib\image_io.h>
#include <dlib\image_processing\frontal_face_detector.h>

template <template <int, template<typename>class, int, typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual = dlib::add_prev1<block<N, BN, 1, dlib::tag1<SUBNET>>>;

template <template <int, template<typename>class, int, typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual_down = dlib::add_prev2<dlib::avg_pool<2, 2, 2, 2, dlib::skip1<dlib::tag2<block<N, BN, 2, dlib::tag1<SUBNET>>>>>>;

template <int N, template <typename> class BN, int stride, typename SUBNET>
using block = BN<dlib::con<N, 3, 3, 1, 1, dlib::relu<BN<dlib::con<N, 3, 3, stride, stride, SUBNET>>>>>;

template <int N, typename SUBNET> using ares = dlib::relu<residual<block, N, dlib::affine, SUBNET>>;
template <int N, typename SUBNET> using ares_down = dlib::relu<residual_down<block, N, dlib::affine, SUBNET>>;

template <typename SUBNET> using alevel0 = ares_down<256, SUBNET>;
template <typename SUBNET> using alevel1 = ares<256, ares<256, ares_down<256, SUBNET>>>;
template <typename SUBNET> using alevel2 = ares<128, ares<128, ares_down<128, SUBNET>>>;
template <typename SUBNET> using alevel3 = ares<64, ares<64, ares<64, ares_down<64, SUBNET>>>>;
template <typename SUBNET> using alevel4 = ares<32, ares<32, ares<32, SUBNET>>>;

using anet_type = dlib::loss_metric< dlib::fc_no_bias<128, dlib::avg_pool_everything<
	alevel0<
	alevel1<
	alevel2<
	alevel3<
	alevel4<
	dlib::max_pool<3, 3, 2, 2, dlib::relu<dlib::affine<dlib::con<32, 7, 7, 2, 2,
	dlib::input_rgb_image_sized<150>
	>>>>>>>>>>>>;

void findAndReplaceAll(std::string & data, std::string toSearch, std::string replaceStr)
{
	// Get the first occurrence
	size_t pos = data.find(toSearch);

	// Repeat till end is reached
	while (pos != std::string::npos)
	{
		// Replace this occurrence of Sub String
		data.replace(pos, toSearch.size(), replaceStr);
		// Get the next occurrence from the current position
		pos = data.find(toSearch, pos + replaceStr.size());
	}
}

int main(int argc, char **argv)
{
	std::string path = argv[1];

	dlib::shape_predictor						m_sp;
	anet_type									m_net;
	dlib::frontal_face_detector					m_detector;

	std::vector<dlib::matrix<dlib::rgb_pixel>>	m_refFaces;
	std::vector<dlib::matrix<float, 0, 1>>		m_refFacesDescriptors;
	std::vector<std::string>					m_refImagesNames;

	dlib::deserialize("./refFaces_data.dat") >> m_refFacesDescriptors;

	std::cout << "Got " << m_refFacesDescriptors.size() << " descriptors" << std::endl;

	return 0;

	std::cout << "Init..." << std::endl;

	dlib::deserialize("./shape_predictor_5_face_landmarks.dat") >> m_sp;
	dlib::deserialize("./dlib_face_recognition_resnet_model_v1.dat") >> m_net;

	m_detector = dlib::get_frontal_face_detector();

	std::cout << "Success" << std::endl;

	for (const auto & entry : std::filesystem::directory_iterator(path)) {
		if (!entry.is_regular_file())
			continue;

		if (entry.path().extension() != ".jpg")
			continue;

		auto imageName = entry.path().filename().string();

		dlib::matrix<dlib::rgb_pixel> img;
		dlib::load_image(img, entry.path().string());

		auto faces = m_detector(img);

		std::cout << "Found " << faces.size() << " in " << imageName << std::endl;

		for (auto face : faces)
		{
			auto shape = m_sp(img, face);
			std::string newName = std::string(imageName.begin(), imageName.end());
			findAndReplaceAll(newName, "_", "-");
			findAndReplaceAll(newName, ".jpg", "@epitech.eu");
			std::cout << "New name went from " << imageName << " to " << newName << std::endl;

			dlib::matrix<dlib::rgb_pixel> face_chip;
			extract_image_chip(img, get_face_chip_details(shape, 150, 0.25), face_chip);
			m_refFaces.push_back(std::move(face_chip));
			m_refImagesNames.push_back(newName);
		}

	}
	std::cout << "Loaded " << m_refFaces.size() << " face(s) from " << m_refImagesNames.size() << " file(s)" << std::endl;

	std::cout << "Serialize labels" << std::endl;
	std::ofstream outputLabels;
	outputLabels.open("./refFaces_labels.dat");
	dlib::serialize(m_refImagesNames, outputLabels);
	outputLabels.flush();
	outputLabels.close();

	std::cout << "Calculating descriptors" << std::endl;
	m_refFacesDescriptors = m_net(m_refFaces);

	std::cout << "Serialize descriptors" << std::endl;
	std::ofstream outputData;
	outputData.open("./refFaces_data.dat");
	outputData.close();

	dlib::serialize("./refFaces_data.dat") << m_refFacesDescriptors;
	/*outputData.flush();
	outputData.close();*/

	std::cout << "Success !" << std::endl;

	/*std::ifstream inputData;
	inputData.open("./refFaces_data.dat");*/
	dlib::deserialize("./refFaces_data.dat") >> m_refFacesDescriptors;

	std::cout << "Got " << m_refFacesDescriptors.size() << " descriptors" << std::endl;

	return 0;
}


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

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

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

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

#include <boost/algorithm/string.hpp>
#include <boost/make_shared.hpp>
#include <ored/configuration/conventions.hpp>
#include <ored/marketdata/marketimpl.hpp>
#include <ored/utilities/indexparser.hpp>
#include <ored/utilities/parsers.hpp>
#include <qle/termstructures/blackinvertedvoltermstructure.hpp>

using namespace std;
using std::make_pair;
using std::map;
using std::string;

using namespace QuantLib;

using QuantExt::PriceTermStructure;
using QuantExt::CommodityIndex;

namespace ore {
namespace data {

namespace {

template <class A, class B, class C>
A lookup(const B& map, const C& key, const string& configuration, const string& type) {
    auto it = map.find(make_pair(configuration, key));
    if (it == map.end()) {
        // fall back to default configuration
        it = map.find(make_pair(Market::defaultConfiguration, key));
        QL_REQUIRE(it != map.end(), "did not find object " << key << " of type " << type << " under configuration '"
                                                           << configuration << "' or 'default'");
    }
    return it->second;
}

template <class A, class B, class C>
A lookup(const B& map, const C& key, const YieldCurveType y, const string& configuration, const string& type) {
    auto it = map.find(make_tuple(configuration, y, key));
    if (it == map.end()) {
        // fall back to default configuration
        it = map.find(make_tuple(Market::defaultConfiguration, y, key));
        QL_REQUIRE(it != map.end(), "did not find object " << key << " of type " << type << " under configuration '"
                                                           << configuration << "' or 'default' in YieldCurves");
    }
    return it->second;
}

} // anonymous namespace

Handle<YieldTermStructure> MarketImpl::yieldCurve(const YieldCurveType& type, const string& key,
                                                  const string& configuration) const {
    // we allow for standard (i.e. not convention based) ibor index names as keys and return the index forward curve in
    // case of a match
    boost::shared_ptr<IborIndex> notUsed;
    if (tryParseIborIndex(key, notUsed, conventions_.has(key, Convention::Type::IborIndex)
        ? conventions_.get(key) : nullptr)) {
        return iborIndex(key, configuration)->forwardingTermStructure();
    }
    // no ibor index found under key => look for a genuine yield curve
    DLOG("no ibor index found under '" << key << "' - look for a genuine yield curve");
    if (type == YieldCurveType::Discount)
        require(MarketObject::DiscountCurve, key, configuration);
    else if (type == YieldCurveType::Yield)
        require(MarketObject::YieldCurve, key, configuration);
    else if (type == YieldCurveType::EquityDividend)
        require(MarketObject::EquityCurve, key, configuration);
    else {
        QL_FAIL("yield curve type not handled");
    }
    return lookup<Handle<YieldTermStructure>>(yieldCurves_, key, type, configuration, "yield curve / ibor index");
}

Handle<YieldTermStructure> MarketImpl::discountCurve(const string& key, const string& configuration) const {
    require(MarketObject::DiscountCurve, key, configuration);
    return lookup<Handle<YieldTermStructure>>(yieldCurves_, key, YieldCurveType::Discount, configuration,
                                              "discount curve");
}

Handle<YieldTermStructure> MarketImpl::yieldCurve(const string& key, const string& configuration) const {
    require(MarketObject::YieldCurve, key, configuration);
    return yieldCurve(YieldCurveType::Yield, key, configuration);
}

Handle<IborIndex> MarketImpl::iborIndex(const string& key, const string& configuration) const {
    require(MarketObject::IndexCurve, key, configuration);
    return lookup<Handle<IborIndex>>(iborIndices_, key, configuration, "ibor index");
}

Handle<SwapIndex> MarketImpl::swapIndex(const string& key, const string& configuration) const {
    require(MarketObject::SwapIndexCurve, key, configuration);
    return lookup<Handle<SwapIndex>>(swapIndices_, key, configuration, "swap index");
}

Handle<QuantLib::SwaptionVolatilityStructure> MarketImpl::swaptionVol(const string& key,
                                                                      const string& configuration) const {
    require(MarketObject::SwaptionVol, key, configuration);
    return lookup<Handle<QuantLib::SwaptionVolatilityStructure>>(swaptionCurves_, key, configuration, "swaption curve");
}

const string MarketImpl::shortSwapIndexBase(const string& key, const string& configuration) const {
    require(MarketObject::SwaptionVol, key, configuration);
    return lookup<pair<string, string>>(swaptionIndexBases_, key, configuration, "short swap index base").first;
}

const string MarketImpl::swapIndexBase(const string& key, const string& configuration) const {
    require(MarketObject::SwaptionVol, key, configuration);
    return lookup<pair<string, string>>(swaptionIndexBases_, key, configuration, "swap index base").second;
}

Handle<QuantLib::SwaptionVolatilityStructure> MarketImpl::yieldVol(const string& key,
                                                                   const string& configuration) const {
    require(MarketObject::YieldVol, key, configuration);
    return lookup<Handle<QuantLib::SwaptionVolatilityStructure>>(yieldVolCurves_, key, configuration,
                                                                 "yield volatility curve");
}

Handle<Quote> MarketImpl::fxSpot(const string& ccypair, const string& configuration) const {
    require(MarketObject::FXSpot, ccypair, configuration);
    auto it = fxSpots_.find(configuration);
    if (it == fxSpots_.end())
        it = fxSpots_.find(Market::defaultConfiguration);
    QL_REQUIRE(it != fxSpots_.end(), "did not find object " << ccypair << " of type fx spot under configuration '"
                                                            << configuration << "' or 'default'");
    return it->second.getQuote(ccypair); // will throw if not found
}

Handle<BlackVolTermStructure> MarketImpl::fxVol(const string& ccypair, const string& configuration) const {
    require(MarketObject::FXVol, ccypair, configuration);
    auto it = fxVols_.find(make_pair(configuration, ccypair));
    if (it != fxVols_.end())
        return it->second;
    else {
        // check for reverse EURUSD or USDEUR and add to the map
        QL_REQUIRE(ccypair.length() == 6, "invalid ccy pair length");
        std::string ccypairInverted = ccypair.substr(3, 3) + ccypair.substr(0, 3);
        require(MarketObject::FXVol, ccypairInverted, configuration);
        it = fxVols_.find(make_pair(configuration, ccypairInverted));
        if (it != fxVols_.end()) {
            Handle<BlackVolTermStructure> h(boost::make_shared<QuantExt::BlackInvertedVolTermStructure>(it->second));
            h->enableExtrapolation();
            // we have found a surface for the inverted pair.
            // so we can invert the surface and store that under the original pair.
            fxVols_[make_pair(configuration, ccypair)] = h;
            return h;
        } else {
            if (configuration == Market::defaultConfiguration)
                QL_FAIL("did not find fx vol object " << ccypair);
            else
                return fxVol(ccypair, Market::defaultConfiguration);
        }
    }
}

Handle<DefaultProbabilityTermStructure> MarketImpl::defaultCurve(const string& key, const string& configuration) const {
    require(MarketObject::DefaultCurve, key, configuration);
    return lookup<Handle<DefaultProbabilityTermStructure>>(defaultCurves_, key, configuration, "default curve");
}

Handle<Quote> MarketImpl::recoveryRate(const string& key, const string& configuration) const {
    // recovery rates can be built together with default curve or securities
    require(MarketObject::DefaultCurve, key, configuration);
    require(MarketObject::Security, key, configuration);
    return lookup<Handle<Quote>>(recoveryRates_, key, configuration, "recovery rate");
}

Handle<BlackVolTermStructure> MarketImpl::cdsVol(const string& key, const string& configuration) const {
    require(MarketObject::CDSVol, key, configuration);
    return lookup<Handle<BlackVolTermStructure>>(cdsVols_, key, configuration, "cds vol curve");
}

Handle<BaseCorrelationTermStructure<BilinearInterpolation>>
MarketImpl::baseCorrelation(const string& key, const string& configuration) const {
    require(MarketObject::BaseCorrelation, key, configuration);
    return lookup<Handle<BaseCorrelationTermStructure<BilinearInterpolation>>>(baseCorrelations_, key, configuration,
                                                                               "base correlation curve");
}

Handle<OptionletVolatilityStructure> MarketImpl::capFloorVol(const string& key, const string& configuration) const {
    require(MarketObject::CapFloorVol, key, configuration);
    return lookup<Handle<OptionletVolatilityStructure>>(capFloorCurves_, key, configuration, "capfloor curve");
}

Handle<YoYOptionletVolatilitySurface> MarketImpl::yoyCapFloorVol(const string& key,
                                                                           const string& configuration) const {
    require(MarketObject::YoYInflationCapFloorVol, key, configuration);
    return lookup<Handle<YoYOptionletVolatilitySurface>>(yoyCapFloorVolSurfaces_, key, configuration,
                                                                   "yoy inflation capfloor curve");
}

Handle<ZeroInflationIndex> MarketImpl::zeroInflationIndex(const string& indexName, const string& configuration) const {
    require(MarketObject::ZeroInflationCurve, indexName, configuration);
    return lookup<Handle<ZeroInflationIndex>>(zeroInflationIndices_, indexName, configuration, "zero inflation index");
}

Handle<YoYInflationIndex> MarketImpl::yoyInflationIndex(const string& indexName, const string& configuration) const {
    require(MarketObject::YoYInflationCurve, indexName, configuration);
    return lookup<Handle<YoYInflationIndex>>(yoyInflationIndices_, indexName, configuration, "yoy inflation index");
}

Handle<CPIVolatilitySurface> MarketImpl::cpiInflationCapFloorVolatilitySurface(const string& indexName,
                                                                               const string& configuration) const {
    require(MarketObject::ZeroInflationCapFloorVol, indexName, configuration);
    return lookup<Handle<CPIVolatilitySurface>>(cpiInflationCapFloorVolatilitySurfaces_, indexName, configuration,
                                                "cpi cap floor volatility surface");
}

Handle<Quote> MarketImpl::equitySpot(const string& key, const string& configuration) const {
    require(MarketObject::EquityCurve, key, configuration);
    return lookup<Handle<Quote>>(equitySpots_, key, configuration, "equity spot");
}

Handle<QuantExt::EquityIndex> MarketImpl::equityCurve(const string& key, const string& configuration) const {
    require(MarketObject::EquityCurve, key, configuration);
    return lookup<Handle<QuantExt::EquityIndex>>(equityCurves_, key, configuration, "equity curve");
};

Handle<YieldTermStructure> MarketImpl::equityDividendCurve(const string& key, const string& configuration) const {
    require(MarketObject::EquityCurve, key, configuration);
    return lookup<Handle<YieldTermStructure>>(yieldCurves_, key, YieldCurveType::EquityDividend, configuration,
                                              "dividend yield curve");
}

Handle<BlackVolTermStructure> MarketImpl::equityVol(const string& key, const string& configuration) const {
    require(MarketObject::EquityVol, key, configuration);
    return lookup<Handle<BlackVolTermStructure>>(equityVols_, key, configuration, "equity vol curve");
}

Handle<YieldTermStructure> MarketImpl::equityForecastCurve(const string& eqName, const string& configuration) const {
    require(MarketObject::EquityCurve, eqName, configuration);
    return equityCurve(eqName, configuration)->equityForecastCurve();
}

Handle<Quote> MarketImpl::securitySpread(const string& key, const string& configuration) const {
    require(MarketObject::Security, key, configuration);
    return lookup<Handle<Quote>>(securitySpreads_, key, configuration, "security spread");
}

Handle<QuantExt::InflationIndexObserver> MarketImpl::baseCpis(const string& key, const string& configuration) const {
    require(MarketObject::ZeroInflationCurve, key, configuration);
    return lookup<Handle<QuantExt::InflationIndexObserver>>(baseCpis_, key, configuration, "base CPI");
}

Handle<PriceTermStructure> MarketImpl::commodityPriceCurve(const string& commodityName,
                                                           const string& configuration) const {
    return commodityIndex(commodityName, configuration)->priceCurve();
}

Handle<CommodityIndex> MarketImpl::commodityIndex(const string& commodityName, const string& configuration) const {
    require(MarketObject::CommodityCurve, commodityName, configuration);
    return lookup<Handle<CommodityIndex>>(commodityIndices_, commodityName, configuration, "commodity indices");
}

Handle<BlackVolTermStructure> MarketImpl::commodityVolatility(const string& commodityName,
                                                              const string& configuration) const {
    require(MarketObject::CommodityVolatility, commodityName, configuration);
    return lookup<Handle<BlackVolTermStructure>>(commodityVols_, commodityName, configuration, "commodity volatility");
}

Handle<QuantExt::CorrelationTermStructure> MarketImpl::correlationCurve(const string& index1, const string& index2,
                                                                        const string& configuration) const {
    // straight pair
    require(MarketObject::Correlation, index1 + "&" + index2, configuration);
    auto it = correlationCurves_.find(make_tuple(configuration, index1, index2));
    if (it != correlationCurves_.end())
        return it->second;
    // inverse pair
    require(MarketObject::Correlation, index2 + "&" + index1, configuration);
    it = correlationCurves_.find(make_tuple(configuration, index2, index1));
    if (it != correlationCurves_.end())
        return it->second;
    // inverse fx index1
    if (isFxIndex(index1)) {
        require(MarketObject::Correlation, inverseFxIndex(index1) + "&" + index2, configuration);
        it = correlationCurves_.find(make_tuple(configuration, inverseFxIndex(index1), index2));
        if (it != correlationCurves_.end())
            return Handle<QuantExt::CorrelationTermStructure>(
                boost::make_shared<QuantExt::NegativeCorrelationTermStructure>(it->second));
        require(MarketObject::Correlation, index2 + "&" + inverseFxIndex(index1), configuration);
        it = correlationCurves_.find(make_tuple(configuration, index2, inverseFxIndex(index1)));
        if (it != correlationCurves_.end())
            return Handle<QuantExt::CorrelationTermStructure>(
                boost::make_shared<QuantExt::NegativeCorrelationTermStructure>(it->second));
    }
    // inverse fx index2
    if (isFxIndex(index2)) {
        require(MarketObject::Correlation, index1 + "&" + inverseFxIndex(index2), configuration);
        it = correlationCurves_.find(make_tuple(configuration, index1, inverseFxIndex(index2)));
        if (it != correlationCurves_.end())
            return Handle<QuantExt::CorrelationTermStructure>(
                boost::make_shared<QuantExt::NegativeCorrelationTermStructure>(it->second));
        require(MarketObject::Correlation, inverseFxIndex(index2) + "&" + index1, configuration);
        it = correlationCurves_.find(make_tuple(configuration, inverseFxIndex(index2), index1));
        if (it != correlationCurves_.end())
            return Handle<QuantExt::CorrelationTermStructure>(
                boost::make_shared<QuantExt::NegativeCorrelationTermStructure>(it->second));
    }
    // both fx indices inverted
    if (isFxIndex(index1) && isFxIndex(index2)) {
        require(MarketObject::Correlation, inverseFxIndex(index1) + "&" + inverseFxIndex(index2), configuration);
        it = correlationCurves_.find(make_tuple(configuration, inverseFxIndex(index1), inverseFxIndex(index2)));
        if (it != correlationCurves_.end())
            return it->second;
        require(MarketObject::Correlation, inverseFxIndex(index2) + "&" + inverseFxIndex(index1), configuration);
        it = correlationCurves_.find(make_tuple(configuration, inverseFxIndex(index2), inverseFxIndex(index1)));
        if (it != correlationCurves_.end())
            return it->second;
    }
    // if not found, fall back to default configuration
    if (configuration == Market::defaultConfiguration) {
        QL_FAIL("did not find object " << index1 << "/" << index2 << " in CorrelationCurves");
    } else {
        return correlationCurve(index1, index2, Market::defaultConfiguration);
    }
}

Handle<Quote> MarketImpl::cpr(const string& securityID, const string& configuration) const {
    require(MarketObject::Security, securityID, configuration);
    return lookup<Handle<Quote>>(cprs_, securityID, configuration, "cpr");
}

void MarketImpl::addSwapIndex(const string& swapIndex, const string& discountIndex, const string& configuration) const {
    try {
        std::vector<string> tokens;
        split(tokens, swapIndex, boost::is_any_of("-"));
        QL_REQUIRE(tokens.size() == 3 || tokens.size() == 4,
                   "three or four tokens required in " << swapIndex << ": CCY-CMS-TENOR or CCY-CMS-TAG-TENOR");
        QL_REQUIRE(tokens[0].size() == 3, "invalid currency code in " << swapIndex);
        QL_REQUIRE(tokens[1] == "CMS", "expected CMS as middle token in " << swapIndex);

        Handle<YieldTermStructure> discounting, forwarding;
        boost::shared_ptr<IborIndex> dummeyIndex;
        if (tryParseIborIndex(discountIndex, dummeyIndex))
            discounting = iborIndex(discountIndex, configuration)->forwardingTermStructure();
        else
            discounting = yieldCurve(discountIndex, configuration);

        auto swapCon = boost::dynamic_pointer_cast<data::SwapIndexConvention>(conventions_.get(swapIndex));
        QL_REQUIRE(swapCon, "expected SwapIndexConvention for " << swapIndex);
        auto con = boost::dynamic_pointer_cast<data::IRSwapConvention>(conventions_.get(swapCon->conventions()));
        QL_REQUIRE(con, "expected IRSwapConvention for " << swapCon->conventions());

        string fi = con->indexName();
        if (isGenericIborIndex(fi))
            forwarding = discounting;
        else
            forwarding = iborIndex(con->indexName(), configuration)->forwardingTermStructure();

        boost::shared_ptr<SwapIndex> si = data::parseSwapIndex(swapIndex, forwarding, discounting, con, swapCon);
        swapIndices_[make_pair(configuration, swapIndex)] = Handle<SwapIndex>(si);
    } catch (std::exception& e) {
        QL_FAIL("Failure in MarketImpl::addSwapIndex() with index " << swapIndex << " : " << e.what());
    }
}

void MarketImpl::refresh(const string& configuration) {

    auto it = refreshTs_.find(configuration);
    if (it == refreshTs_.end()) {
        it = refreshTs_.insert(make_pair(configuration, std::set<boost::shared_ptr<TermStructure>>())).first;
    }

    if (it->second.empty()) {
        for (auto& x : yieldCurves_) {
            if (get<0>(x.first) == configuration || get<0>(x.first) == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : iborIndices_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration) {
                Handle<YieldTermStructure> y = x.second->forwardingTermStructure();
                if (!y.empty())
                    it->second.insert(*y);
            }
        }
        for (auto& x : swapIndices_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration) {
                Handle<YieldTermStructure> y = x.second->forwardingTermStructure();
                if (!y.empty())
                    it->second.insert(*y);
                y = x.second->discountingTermStructure();
                if (!y.empty())
                    it->second.insert(*y);
            }
        }
        for (auto& x : swaptionCurves_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : capFloorCurves_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : yoyCapFloorVolSurfaces_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : fxVols_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : defaultCurves_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : cdsVols_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : baseCorrelations_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : zeroInflationIndices_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration) {
                it->second.insert(*x.second->zeroInflationTermStructure());
            }
        }
        for (auto& x : yoyInflationIndices_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration) {
                it->second.insert(*x.second->yoyInflationTermStructure());
            }
        }
        for (auto& x : cpiInflationCapFloorVolatilitySurfaces_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : yoyCapFloorVolSurfaces_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : equityVols_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
        for (auto& x : equityCurves_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration) {
                Handle<YieldTermStructure> y = x.second->equityForecastCurve();
                if (!y.empty())
                    it->second.insert(*y);
                y = x.second->equityDividendCurve();
                if (!y.empty())
                    it->second.insert(*y);
            }
        }
        for (auto& x : commodityIndices_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration) {
                const auto& pts = x.second->priceCurve();
                if (!pts.empty())
                    it->second.insert(*pts);
            }
        }
        for (auto& x : commodityVols_) {
            if (x.first.first == configuration || x.first.first == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }

        for (auto& x : correlationCurves_) {
            if (get<0>(x.first) == configuration || get<0>(x.first) == Market::defaultConfiguration)
                it->second.insert(*x.second);
        }
    }

    // term structures might be wrappers around nested termstructures that need to be updated as well,
    // therefore we need to call deepUpdate() (=update() if no such nesting is present)
    for (auto& x : it->second)
        x->deepUpdate();

    // update fx spot quotes
    auto fxSpots = fxSpots_.find(configuration);
    if (fxSpots != fxSpots_.end()) {
        for (auto& x : fxSpots->second.quotes()) {
            auto dq = boost::dynamic_pointer_cast<Observer>(*x.second);
            if (dq != nullptr)
                dq->update();
        }
    }

} // refresh

} // namespace data
} // namespace ore


// Boost.Geometry

// Copyright (c) 2020-2021, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_HPP
#define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_HPP

#include <boost/geometry/strategies/area/spherical.hpp>
#include <boost/geometry/strategies/azimuth/spherical.hpp>
#include <boost/geometry/strategies/convex_hull/spherical.hpp>
#include <boost/geometry/strategies/envelope/spherical.hpp>
#include <boost/geometry/strategies/expand/spherical.hpp>
#include <boost/geometry/strategies/io/spherical.hpp>
#include <boost/geometry/strategies/index/spherical.hpp>
#include <boost/geometry/strategies/relate/spherical.hpp>

namespace boost { namespace geometry
{

namespace strategies
{

template
<
    typename RadiusTypeOrSphere = double,
    typename CalculationType = void
>
class spherical
    // derived from the umbrella strategy defining the most strategies
    : public strategies::index::detail::spherical<RadiusTypeOrSphere, CalculationType>
{
    using base_t = strategies::index::detail::spherical<RadiusTypeOrSphere, CalculationType>;

public:
    spherical() = default;

    template <typename RadiusOrSphere>
    explicit spherical(RadiusOrSphere const& radius_or_sphere)
        //: base_t(radius_or_sphere)
    {
        // TODO: pass into the constructor
        base_t::m_radius = strategy_detail::get_radius
            <
                RadiusOrSphere
            >::apply(radius_or_sphere);
    }

    static auto azimuth()
    {
        return strategy::azimuth::spherical<CalculationType>();
    }

    static auto point_order()
    {
        return strategy::point_order::spherical<CalculationType>();
    }
};

} // namespace strategies

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_HPP


// -*- C++ -*-
//
// Copyright Sylvain Bougerel 2009 - 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file COPYING or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#define BOOST_TEST_DYN_LINK
#define SPATIAL_ENABLE_ASSERT // detect interal issues that should not occur

#include <boost/test/unit_test.hpp>
#include "../../src/idle_box_multiset.hpp"
#include "spatial_test_types.hpp"

using namespace spatial;

BOOST_AUTO_TEST_CASE( test_idle_box_constructors )
{
  idle_box_multiset<2, int2> idle_box;
  idle_box_multiset<0, int2> runtime_idle_box;
}

BOOST_AUTO_TEST_CASE( test_idle_box_copy_assignment )
{
  idle_box_multiset<2, int2> idle_box;
  idle_box.insert(zeros);
  idle_box.insert(ones);
  idle_box.insert(twos);
  idle_box_multiset<2, int2> copy(idle_box);
  BOOST_CHECK_EQUAL(idle_box.size(), copy.size());
  BOOST_CHECK(*idle_box.begin() == *copy.begin());
  idle_box = copy;
  BOOST_CHECK_EQUAL(idle_box.size(), copy.size());
  BOOST_CHECK(*idle_box.begin() == *copy.begin());
}

BOOST_AUTO_TEST_CASE( test_zero_idle_box_copy_assignment )
{
  idle_box_multiset<0, int2> idle_box;
  idle_box.insert(zeros);
  idle_box.insert(ones);
  idle_box.insert(twos);
  idle_box_multiset<0, int2> copy(idle_box);
  BOOST_CHECK_EQUAL(idle_box.size(), copy.size());
  BOOST_CHECK(*idle_box.begin() == *copy.begin());
  idle_box = copy;
  BOOST_CHECK_EQUAL(idle_box.size(), copy.size());
  BOOST_CHECK(*idle_box.begin() == *copy.begin());
}


// Boost.Units - A C++ library for zero-overhead dimensional analysis and
// unit/quantity manipulation and conversion
//
// Copyright (C) 2003-2008 Matthias Christian Schabel
// Copyright (C) 2007-2008 Steven Watanabe
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_UNITS_DETAIL_STATIC_RATIONAL_POWER_HPP
#define BOOST_UNITS_DETAIL_STATIC_RATIONAL_POWER_HPP

#include <boost/config/no_tr1/cmath.hpp>

#include <boost/units/detail/one.hpp>
#include <boost/units/operators.hpp>

namespace boost {

namespace units {

template<long N,long D>
class static_rational;

namespace detail {

namespace typeof_pow_adl_barrier {

using std::pow;

template<class Y>
struct typeof_pow
{
#if defined(BOOST_UNITS_HAS_BOOST_TYPEOF)
    BOOST_TYPEOF_NESTED_TYPEDEF_TPL(nested, pow(typeof_::make<Y>(), 0.0))
    typedef typename nested::type type;
#elif defined(BOOST_UNITS_HAS_MWERKS_TYPEOF)
    typedef __typeof__(pow(typeof_::make<Y>(), 0.0)) type;
#elif defined(BOOST_UNITS_HAS_GNU_TYPEOF)
    typedef typeof(pow(typeof_::make<Y>(), 0.0)) type;
#else
    typedef Y type;
#endif
};

}

template<class R, class Y>
struct static_rational_power_impl
{
    typedef typename typeof_pow_adl_barrier::typeof_pow<Y>::type type;
    static type call(const Y& y)
    {
        using std::pow;
        return(pow(y, static_cast<double>(R::Numerator) / static_cast<double>(R::Denominator)));
    }
};

template<class R>
struct static_rational_power_impl<R, one>
{
    typedef one type;
    static one call(const one&)
    {
        one result;
        return(result);
    }
};

template<long N>
struct static_rational_power_impl<static_rational<N, 1>, one>
{
    typedef one type;
    static one call(const one&)
    {
        one result;
        return(result);
    }
};

template<long N, bool = (N % 2 == 0)>
struct static_int_power_impl;

template<long N>
struct static_int_power_impl<N, true>
{
    template<class Y, class R>
    struct apply
    {
        typedef typename multiply_typeof_helper<Y, Y>::type square_type;
        typedef typename static_int_power_impl<(N >> 1)>::template apply<square_type, R> next;
        typedef typename next::type type;
        static type call(const Y& y, const R& r)
        {
            const square_type square = y * y;
            return(next::call(square, r));
        }
    };
};

template<long N>
struct static_int_power_impl<N, false>
{
    template<class Y, class R>
    struct apply
    {
        typedef typename multiply_typeof_helper<Y, Y>::type square_type;
        typedef typename multiply_typeof_helper<Y, R>::type new_r;
        typedef typename static_int_power_impl<(N >> 1)>::template apply<square_type, new_r> next;
        typedef typename next::type type;
        static type call(const Y& y, const R& r)
        {
            const Y square = y * y;
            return(next::call(square, y * r));
        }
    };
};

template<>
struct static_int_power_impl<1, false>
{
    template<class Y, class R>
    struct apply
    {
        typedef typename multiply_typeof_helper<Y, R>::type type;
        static type call(const Y& y, const R& r)
        {
            return(y * r);
        }
    };
};

template<>
struct static_int_power_impl<0, true>
{
    template<class Y, class R>
    struct apply
    {
        typedef R type;
        static R call(const Y&, const R& r)
        {
            return(r);
        }
    };
};

template<int N, bool = (N < 0)>
struct static_int_power_sign_impl;

template<int N>
struct static_int_power_sign_impl<N, false>
{
    template<class Y>
    struct apply
    {
        typedef typename static_int_power_impl<N>::template apply<Y, one> impl;
        typedef typename impl::type type;
        static type call(const Y& y)
        {
            one result;
            return(impl::call(y, result));
        }
    };
};

template<int N>
struct static_int_power_sign_impl<N, true>
{
    template<class Y>
    struct apply
    {
        typedef typename static_int_power_impl<-N>::template apply<Y, one> impl;
        typedef typename divide_typeof_helper<one, typename impl::type>::type type;
        static type call(const Y& y)
        {
            one result;
            return(result/impl::call(y, result));
        }
    };
};

template<long N, class Y>
struct static_rational_power_impl<static_rational<N, 1>, Y>
{
    typedef typename static_int_power_sign_impl<N>::template apply<Y> impl;
    typedef typename impl::type type;
    static type call(const Y& y)
    {
        return(impl::call(y));
    }
};

template<class R, class Y>
typename detail::static_rational_power_impl<R, Y>::type static_rational_power(const Y& y)
{
    return(detail::static_rational_power_impl<R, Y>::call(y));
}

} // namespace detail

} // namespace units

} // namespace boost

#endif