/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

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

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

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

#include <Dataflow/Network/ModuleStateInterface.h>
#include <Graphics/Widgets/BoundingBoxWidget.h>
#include <Core/Datatypes/Color.h>
#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Datatypes/Feedback.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/VMesh.h>
#include <Core/GeometryPrimitives/BBox.h>
#include <Core/GeometryPrimitives/Point.h>
#include <Graphics/Widgets/Widget.h>
#include <Graphics/Widgets/WidgetFactory.h>
#include <Modules/Fields/EditMeshBoundingBox.h>
#include <boost/format.hpp>

using namespace SCIRun;
using namespace Modules::Fields;
using namespace Core::Datatypes;
using namespace Dataflow::Networks;
using namespace Core::Algorithms;
using namespace Core::Geometry;
using namespace Graphics;
using namespace Datatypes;

MODULE_INFO_DEF(EditMeshBoundingBox, ChangeMesh, SCIRun)

EditMeshBoundingBox::EditMeshBoundingBox()
: GeometryGeneratingModule(staticInfo_)
{
  INITIALIZE_PORT(InputField);
  INITIALIZE_PORT(OutputField);
  INITIALIZE_PORT(Transformation_Widget);
  INITIALIZE_PORT(Transformation_Matrix);
  firstRun_ = true;
  widgetMoved_ = true;
}

void EditMeshBoundingBox::processWidgetFeedback(const ModuleFeedback& var)
{
  try
  {
    auto vsf = dynamic_cast<const ViewSceneFeedback&>(var);
    if (vsf.matchesWithModuleId(id()))
    {
      widgetMoved_ = true;
      adjustGeometryFromTransform(vsf.transform, vsf.movementType);
      enqueueExecuteAgain(true);
    }
  }
  catch (std::bad_cast&)
  {
    //ignore
  }
}

void EditMeshBoundingBox::adjustGeometryFromTransform(const Transform& feedbackTrans, const WidgetMovement& movementType)
{
  switch(movementType)
  {
    case WidgetMovement::SCALE:
    {
      // Need to strip translation from scaling
      auto temp = feedbackTrans;
      temp.set_translation(0);

      widgetScale_ = temp * widgetScale_;
      widgetScale_.orthogonalize();
      break;
    }
    case WidgetMovement::ROTATE:
    {
      // Need to strip translation from rotation
      auto temp = feedbackTrans;
      temp.set_translation(0);

      widgetRotation_ = temp * widgetRotation_;
      widgetRotation_.orthogonalize();
      break;
    }
    case WidgetMovement::TRANSLATE:
      widgetTranslation_ += feedbackTrans.get_translation();
      break;
    default:
      break;
  }

  setOutputParameters();
}

void EditMeshBoundingBox::setOutputParameters()
{
  auto state = get_state();
  state->setValue(OutputCenterX, widgetTranslation_.x());
  state->setValue(OutputCenterY, widgetTranslation_.y());
  state->setValue(OutputCenterZ, widgetTranslation_.z());
  auto vecs = widgetScale_.get_transformation_vectors();
  state->setValue(OutputSizeX, vecs[0].norm());
  state->setValue(OutputSizeY, vecs[1].norm());
  state->setValue(OutputSizeZ, vecs[2].norm());
}

void EditMeshBoundingBox::clearVals()
{
  auto state = get_state();
  const std::string cleared("---");
  state->setValue(InputCenterX, cleared);
  state->setValue(InputCenterY, cleared);
  state->setValue(InputCenterZ, cleared);
  state->setValue(InputSizeX, cleared);
  state->setValue(InputSizeY, cleared);
  state->setValue(InputSizeZ, cleared);
}

void EditMeshBoundingBox::setStateDefaults()
{
  clearVals();
  auto state = get_state();
  state->setValue(ScaleTransformMatrix, VariableList());
  state->setValue(RotationTransformMatrix, VariableList());
  state->setValue(TranslationPoint, VariableList());
  state->setValue(InverseFieldTransformMatrix, VariableList());
  state->setValue(DataSaved, false);
  state->setValue(Scale, 1.0);
  state->setValue(OldScale, 1.0);
  state->setValue(ScaleChanged, false);
  state->setValue(ResetToInput, true);
  state->setValue(SetOutputCenter, false);
  state->setValue(RestrictX, false);
  state->setValue(RestrictY, false);
  state->setValue(RestrictZ, false);
  state->setValue(RestrictR, false);
  state->setValue(RestrictD, false);
  state->setValue(RestrictI, false);
  state->setValue(OutputCenterX, 0.0);
  state->setValue(OutputCenterY, 0.0);
  state->setValue(OutputCenterZ, 0.0);
  state->setValue(OutputSizeX, 1.0);
  state->setValue(OutputSizeY, 1.0);
  state->setValue(OutputSizeZ, 1.0);
  state->setValue(Scale, 0.1);
  state->setValue(NoTranslation, true);
  state->setValue(XYZTranslation, false);
  state->setValue(RDITranslation, false);
  state->setValue(BoxRealScale, 0.0);
  state->setValue(BoxMode, 0);

  getOutputPort(Transformation_Widget)->connectConnectionFeedbackListener([this]
    (const ModuleFeedback& var) { processWidgetFeedback(var); });
}

void EditMeshBoundingBox::execute()
{
  auto field = getRequiredInput(InputField);
  if (needToExecute() || widgetMoved_)
  {
    updateState(field);
    sendOutputPorts();
    saveToParameters();
  }
}

void EditMeshBoundingBox::updateState(FieldHandle field)
{
  outputField_ = FieldHandle(field->deep_clone());
  auto state = get_state();
  widgetMoved_ = false;
  bool inputResetRequested = transient_value_cast<bool>(state->getTransientValue(ResetToInput));

  if(inputsChanged() || inputResetRequested)
    resetToInputField();
  if (firstRun_ && state->getValue(DataSaved).toBool())
  {
    loadFromParameters();
    updateInputFieldAttributes();
  }
  firstRun_ = false;

  if(transient_value_cast<bool>(state->getTransientValue(SetOutputCenter)))
    setOutputCenter();
  else if (transient_value_cast<bool>(state->getTransientValue(ResetCenter)))
    resetOutputCenter();
  if(transient_value_cast<bool>(state->getTransientValue(SetOutputSize)))
    setOutputSize();
  else if (transient_value_cast<bool>(state->getTransientValue(ResetSize)))
    resetOutputSize();
}

void EditMeshBoundingBox::sendOutputPorts()
{
  auto widgetTrans = (widgetRotation_ * widgetScale_);
  widgetTrans.set_translation(widgetTranslation_);
  auto fieldTrans = widgetTrans * inputFieldInverse_;

  outputField_->vmesh()->transform(fieldTrans);
  sendOutput(Transformation_Matrix, MatrixHandle(new DenseMatrix(widgetTrans)));
  sendOutput(OutputField, outputField_);

  generateGeomsList();
  auto geo = createGeomComposite(*this, "bbox", geoms_.begin(), geoms_.end());
  sendOutput(Transformation_Widget, geo);
}

void EditMeshBoundingBox::resetToInputField()
{
  auto state = get_state();
  widgetRotation_ = Transform();
  widgetScale_ = Transform();
  eigvecs_.resize(mDIMENSIONS);
  eigvecs_[0] = Vector(1,0,0);
  eigvecs_[1] = Vector(0,1,0);
  eigvecs_[2] = Vector(0,0,1);

  state->setTransientValue(ResetToInput, false);
  computeWidgetBox(outputField_->vmesh()->get_bounding_box());

  ogPos_ = pos_;
  widgetTranslation_ = pos_;
  auto trans = Transform(pos_, eigvecs_[0]*eigvals_[0], eigvecs_[1]*eigvals_[1], eigvecs_[2]*eigvals_[2]);
  ogScale_ = Vector(eigvals_[0], eigvals_[1], eigvals_[2]);
  widgetScale_.pre_scale(Vector(eigvals_[0], eigvals_[1], eigvals_[2]));

  inputFieldInverse_ = Transform(trans);
  inputFieldInverse_.invert();

  updateInputFieldAttributes();

  state->setValue(OutputCenterX, ogPos_.x());
  state->setValue(OutputCenterY, ogPos_.y());
  state->setValue(OutputCenterZ, ogPos_.z());
  state->setValue(OutputSizeX, ogScale_.x());
  state->setValue(OutputSizeY, ogScale_.y());
  state->setValue(OutputSizeZ, ogScale_.z());
}

// Sets the translation vector in the homogeneous matrices
void EditMeshBoundingBox::setOutputCenter()
{
  auto state = get_state();
  state->setTransientValue(SetOutputCenter, false);

  widgetTranslation_ = Point(state->getValue(OutputCenterX).toDouble(),
                              state->getValue(OutputCenterY).toDouble(),
                              state->getValue(OutputCenterZ).toDouble());

}

void EditMeshBoundingBox::resetOutputCenter()
{
  auto state = get_state();
  state->setTransientValue(ResetCenter, false);

  widgetTranslation_ = ogPos_;
  state->setValue(OutputCenterX, ogPos_.x());
  state->setValue(OutputCenterY, ogPos_.y());
  state->setValue(OutputCenterZ, ogPos_.z());
}

void EditMeshBoundingBox::setOutputSize()
{
  auto state = get_state();
  state->setTransientValue(SetOutputSize, false);

  widgetScale_ = Transform();
  widgetScale_.post_scale(Vector(state->getValue(OutputSizeX).toDouble(),
                                 state->getValue(OutputSizeY).toDouble(),
                                 state->getValue(OutputSizeZ).toDouble()));
}

void EditMeshBoundingBox::resetOutputSize()
{
  auto state = get_state();
  state->setTransientValue(ResetSize, false);

  widgetScale_ = Transform();
  widgetScale_.pre_scale(ogScale_);
  state->setValue(OutputSizeX, ogScale_.x());
  state->setValue(OutputSizeY, ogScale_.y());
  state->setValue(OutputSizeZ, ogScale_.z());
}

std::string EditMeshBoundingBox::convertForLabel(double coord)
{
  return str(boost::format("%8.4f") % coord);
}

void EditMeshBoundingBox::computeWidgetBox(const BBox& box)
{
  bbox_ = box;
  if (!bbox_.valid())
  {
    warning("Input field is empty -- using unit cube.");
    bbox_.extend(Point(0, 0, 0));
    bbox_.extend(Point(1, 1, 1));
  }

  // build a widget identical to the BBox
  auto diag = bbox_.diagonal();
  const auto SMALL = 1e-4;
  if (fabs(diag.x())<SMALL)
  {
    diag.x(2 * SMALL);
    bbox_.extend(bbox_.get_min() - Vector(SMALL, 0.0, 0.0));
    bbox_.extend(bbox_.get_max() + Vector(SMALL, 0.0, 0.0));
  }
  if (fabs(diag.y())<SMALL)
  {
    diag.y(2 * SMALL);
    bbox_.extend(bbox_.get_min() - Vector(0.0, SMALL, 0.0));
    bbox_.extend(bbox_.get_max() + Vector(0.0, SMALL, 0.0));
  }
  if (fabs(diag.z())<SMALL)
  {
    diag.z(2 * SMALL);
    bbox_.extend(bbox_.get_min() - Vector(0.0, 0.0, SMALL));
    bbox_.extend(bbox_.get_max() + Vector(0.0, 0.0, SMALL));
  }

  eigvals_.resize(mDIMENSIONS);
  pos_ = bbox_.center();
  eigvals_[0] = diag.x() * 0.5;
  eigvals_[1] = diag.y() * 0.5;
  eigvals_[2] = diag.z() * 0.5;
}

void EditMeshBoundingBox::generateGeomsList()
{
  geoms_.clear();
  auto bbox = WidgetFactory::createBoundingBox({*this, "EMBB"},
    {{get_state()->getValue(Scale).toDouble(), "no-color", widgetTranslation_, bbox_, resolution_},
    {widgetTranslation_, (widgetRotation_ * widgetScale_).get_transformation_vectors()}});
  auto bboxWidget = boost::dynamic_pointer_cast<CompositeWidget>(bbox);
  geoms_.insert(geoms_.end(), bboxWidget->subwidgetBegin(), bboxWidget->subwidgetEnd());
}

void EditMeshBoundingBox::loadFromParameters()
{
  auto state = get_state();
  widgetScale_ = transformFromString(state->getValue(ScaleTransformMatrix).toString());
  widgetRotation_ = transformFromString(state->getValue(RotationTransformMatrix).toString());
  widgetTranslation_ =
pointFromString(state->getValue(TranslationPoint).toString());
  inputFieldInverse_ = transformFromString(state->getValue(InverseFieldTransformMatrix).toString());
  setOutputParameters();
}

void EditMeshBoundingBox::updateInputFieldAttributes()
{
  auto state = get_state();
  state->setValue(InputCenterX, convertForLabel(ogPos_[0]));
  state->setValue(InputCenterY, convertForLabel(ogPos_[1]));
  state->setValue(InputCenterZ, convertForLabel(ogPos_[2]));
  state->setValue(InputSizeX, convertForLabel(ogScale_[0]));
  state->setValue(InputSizeY, convertForLabel(ogScale_[1]));
  state->setValue(InputSizeZ, convertForLabel(ogScale_[2]));
}

void EditMeshBoundingBox::saveToParameters()
{
  auto state = get_state();
  state->setValue(ScaleTransformMatrix, widgetScale_.get_string());
  state->setValue(RotationTransformMatrix, widgetRotation_.get_string());
  state->setValue(TranslationPoint, widgetTranslation_.get_string());
  state->setValue(InverseFieldTransformMatrix, inputFieldInverse_.get_string());
  state->setValue(DataSaved, true);
}

const AlgorithmParameterName EditMeshBoundingBox::ScaleTransformMatrix("ScaleTransformMatrix");
const AlgorithmParameterName
EditMeshBoundingBox::RotationTransformMatrix("RotationTransformMatrix");
const AlgorithmParameterName
EditMeshBoundingBox::TranslationPoint("TranslationPoint");
const AlgorithmParameterName
EditMeshBoundingBox::InverseFieldTransformMatrix("InverseFieldTransformMatrix");
const AlgorithmParameterName EditMeshBoundingBox::ResetToInput("ResetToInput");
const AlgorithmParameterName EditMeshBoundingBox::ResetCenter("ResetCenter");
const AlgorithmParameterName EditMeshBoundingBox::ResetSize("ResetSize");
const AlgorithmParameterName EditMeshBoundingBox::DataSaved("DataSaved");

const AlgorithmParameterName EditMeshBoundingBox::InputCenterX("InputCenterX");
const AlgorithmParameterName EditMeshBoundingBox::InputCenterY("InputCenterY");
const AlgorithmParameterName EditMeshBoundingBox::InputCenterZ("InputCenterZ");
const AlgorithmParameterName EditMeshBoundingBox::InputSizeX("InputSizeX");
const AlgorithmParameterName EditMeshBoundingBox::InputSizeY("InputSizeY");
const AlgorithmParameterName EditMeshBoundingBox::InputSizeZ("InputSizeZ");
 //Output Field Atributes
const AlgorithmParameterName EditMeshBoundingBox::SetOutputCenter("SetOutputCenter");
const AlgorithmParameterName EditMeshBoundingBox::SetOutputSize("SetOutputSize");
const AlgorithmParameterName EditMeshBoundingBox::OutputCenterX("OutputCenterX");
const AlgorithmParameterName EditMeshBoundingBox::OutputCenterY("OutputCenterY");
const AlgorithmParameterName EditMeshBoundingBox::OutputCenterZ("OutputCenterZ");
const AlgorithmParameterName EditMeshBoundingBox::OutputSizeX("OutputSizeX");
const AlgorithmParameterName EditMeshBoundingBox::OutputSizeY("OutputSizeY");
const AlgorithmParameterName EditMeshBoundingBox::OutputSizeZ("OutputSizeZ");
//Widget Scale/Mode
const AlgorithmParameterName EditMeshBoundingBox::Scale("Scale");
const AlgorithmParameterName EditMeshBoundingBox::OldScale("OldScale");
const AlgorithmParameterName EditMeshBoundingBox::ScaleChanged("ScaleChanged");
const AlgorithmParameterName EditMeshBoundingBox::NoTranslation("NoTranslation");
const AlgorithmParameterName EditMeshBoundingBox::XYZTranslation("XYZTranslation");
const AlgorithmParameterName EditMeshBoundingBox::RDITranslation("RDITranslation");
const AlgorithmParameterName EditMeshBoundingBox::RestrictX("RestrictX");
const AlgorithmParameterName EditMeshBoundingBox::RestrictY("RestrictY");
const AlgorithmParameterName EditMeshBoundingBox::RestrictZ("RestrictZ");
const AlgorithmParameterName EditMeshBoundingBox::RestrictR("RestrictR");
const AlgorithmParameterName EditMeshBoundingBox::RestrictD("RestrictD");
const AlgorithmParameterName EditMeshBoundingBox::RestrictI("RestrictI");

const AlgorithmParameterName EditMeshBoundingBox::BoxMode("BoxMode");
const AlgorithmParameterName EditMeshBoundingBox::BoxRealScale("BoxRealScale");


/*
Copyright 2020 Dennis Rohde

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

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

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

#include <vector>
#include <limits>

#include <boost/chrono/include.hpp>

#include "frechet.hpp"

namespace Frechet {

namespace Continuous {

distance_t epsilon = 0.001;
bool round = true;

std::string Distance::repr() const {
    std::stringstream ss;
    ss << value;
    return ss.str();
}

Distance distance(const Curve &curve1, const Curve &curve2) {
    if ((curve1.complexity() < 2) or (curve2.complexity() < 2)) {
        std::cerr << "WARNING: comparison possible only for curves of at least two points" << std::endl;
        Distance result;
        result.value = std::numeric_limits<distance_t>::signaling_NaN();
        return result;
    }
    if (curve1.dimensions() != curve2.dimensions()) {
        std::cerr << "WARNING: comparison possible only for curves of equal number of dimensions" << std::endl;
        Distance result;
        result.value = std::numeric_limits<distance_t>::signaling_NaN();
        return result;
    }

    auto start = boost::chrono::process_real_cpu_clock::now();
    const auto lb = std::sqrt(std::max(curve1[0].dist_sqr(curve2[0]), curve1[curve1.complexity()-1].dist_sqr(curve2[curve2.complexity()-1])));
    const auto ub = _greedy_upper_bound(curve1, curve2);
    auto end = boost::chrono::process_real_cpu_clock::now();

    #if DEBUG
    std::cout << "narrowed to [" << lb << ", " << ub.value << "]" << std::endl;
    #endif

    auto dist = _distance(curve1, curve2, ub, lb);
    dist.time_bounds = (end-start).count() / 1000000000.0;
    if (round) dist.value =  std::round(dist.value * 1e3) / 1e3;

    return dist;
}

Distance _distance(const Curve &curve1, const Curve &curve2, distance_t ub, distance_t lb) {
    Distance result;
    auto start = boost::chrono::process_real_cpu_clock::now();

    distance_t split = (ub + lb)/2;
    std::size_t number_searches = 0;

    if (ub - lb > epsilon) {
        auto infty = std::numeric_limits<distance_t>::infinity();
        std::vector<std::vector<distance_t>> reachable1(curve1.complexity()-1, std::vector<distance_t>(curve2.complexity(), infty));
        std::vector<std::vector<distance_t>> reachable2(curve1.complexity(), std::vector<distance_t>(curve2.complexity()-1, infty));

        std::vector<std::vector<Interval>> free_intervals1(curve2.complexity(), std::vector<Interval>(curve1.complexity(), Interval()));
        std::vector<std::vector<Interval>> free_intervals2(curve1.complexity(), std::vector<Interval>(curve2.complexity(), Interval()));

        if (std::isnan(lb) or std::isnan(ub)) {
            result.value = std::numeric_limits<distance_t>::signaling_NaN();
            return result;
        }

        //Binary search over the feasible distances
        while (ub - lb > epsilon) {
            ++number_searches;
            split = (ub + lb)/2;
            auto isLessThan = _less_than_or_equal(split, curve1, curve2, reachable1, reachable2, free_intervals1, free_intervals2);
            if (isLessThan) {
                ub = split;
            }
            else {
                lb = split;
            }
            #if DEBUG
            std::cout << "narrowed to [" << lb << ", " << ub << "]" << std::endl;
            #endif
        }
    }

    distance_t value = (ub + lb)/2.;
    auto end = boost::chrono::process_real_cpu_clock::now();
    result.value = value;
    result.time_searches = (end-start).count() / 1000000000.0;
    result.number_searches = number_searches;
    return result;
}

bool _less_than_or_equal(const distance_t distance, Curve const& curve1, Curve const& curve2,
        std::vector<std::vector<distance_t>> &reachable1, std::vector<std::vector<distance_t>> &reachable2,
        std::vector<std::vector<Interval>> &free_intervals1, std::vector<std::vector<Interval>> &free_intervals2) {
    assert(curve1.complexity() >= 2);
    assert(curve2.complexity() >= 2);

    distance_t dist_sqr = distance * distance;
    auto infty = std::numeric_limits<distance_t>::infinity();

    if (curve1[0].dist_sqr(curve2[0]) > dist_sqr or curve1.back().dist_sqr(curve2.back()) > dist_sqr) return false;

    for (auto &elem: reachable1) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = infty;
        }
    }

    for (auto &elem: reachable2) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = infty;
        }
    }

    for (auto &elem: free_intervals1) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = Interval();
        }
    }

    for (auto &elem: free_intervals2) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = Interval();
        }
    }

    for (curve_size_t i = 0; i < curve1.complexity() - 1; ++i) {
        reachable1[i][0] = 0.;
        if (curve2[0].dist_sqr(curve1[i+1]) > dist_sqr) break;
    }

    for (curve_size_t j = 0; j < curve2.complexity() - 1; ++j) {
        reachable2[0][j] = 0.;
        if (curve1[0].dist_sqr(curve2[j+1]) > dist_sqr) break;
    }

    #pragma omp parallel for collapse(2)
    for (curve_size_t i = 0; i < curve1.complexity(); ++i) {
        for (curve_size_t j = 0; j < curve2.complexity(); ++j) {
            if ((i < curve1.complexity() - 1) and (j > 0)) {
                free_intervals1[j][i] = curve2[j].intersection_interval(dist_sqr, curve1[i], curve1[i+1]);
            }
            if ((j < curve2.complexity() - 1) and (i > 0)) {
                free_intervals2[i][j] = curve1[i].intersection_interval(dist_sqr, curve2[j], curve2[j+1]);
            }
        }
    }

    for (curve_size_t i = 0; i < curve1.complexity(); ++i) {
        for (curve_size_t j = 0; j < curve2.complexity(); ++j) {
            if ((i < curve1.complexity() - 1) and (j > 0)) {
                if (not free_intervals1[j][i].is_empty()) {
                    if (reachable2[i][j-1] != infty) {
                        reachable1[i][j] = free_intervals1[j][i].begin();
                    }
                    else if (reachable1[i][j-1] <= free_intervals1[j][i].end()) {
                        reachable1[i][j] = std::max(free_intervals1[j][i].begin(), reachable1[i][j-1]);
                    }
                }
            }
            if ((j < curve2.complexity() - 1) and (i > 0)) {
                if (not free_intervals2[i][j].is_empty()) {
                    if (reachable1[i-1][j] != infty) {
                        reachable2[i][j] = free_intervals2[i][j].begin();
                    }
                    else if (reachable2[i-1][j] <= free_intervals2[i][j].end()) {
                        reachable2[i][j] = std::max(free_intervals2[i][j].begin(), reachable2[i-1][j]);
                    }
                }
            }
        }
    }

    assert((reachable1.back().back() < infty) == (reachable2.back().back() < infty));

    return reachable1.back().back() < infty;
}

distance_t _greedy_upper_bound(const Curve &curve1, const Curve &curve2) {
    distance_t result = 0;

    const curve_size_t len1 = curve1.complexity(), len2 = curve2.complexity();
    curve_size_t i = 0, j = 0;

    while ((i < len1 - 1) and (j < len2 - 1)) {
        result = std::max(result, curve1[i].dist_sqr(curve2[j]));

        distance_t dist1 = curve1[i+1].dist_sqr(curve2[j]),
            dist2 = curve1[i].dist_sqr(curve2[j+1]),
            dist3 = curve1[i+1].dist_sqr(curve2[j+1]);

        if ((dist1 <= dist2) and (dist1 <= dist3)) ++i;
        else if ((dist2 <= dist1) and (dist2 <= dist3)) ++j;
        else {
            ++i;
            ++j;
        }
    }

    while (i < len1) result = std::max(result, curve1[i++].dist_sqr(curve2[j]));

    --i;

    while (j < len2) result = std::max(result, curve1[i].dist_sqr(curve2[j++]));

    return std::sqrt(result);
}

} // end namespace Continuous

namespace Discrete {

std::string Distance::repr() const {
    std::stringstream ss;
    ss << value;
    return ss.str();
}

Distance distance(const Curve &curve1, const Curve &curve2) {
    Distance result;
    auto start = boost::chrono::process_real_cpu_clock::now();
    std::vector<std::vector<distance_t>> a(curve1.complexity(), std::vector<distance_t>(curve2.complexity(), -1));
    auto value = std::sqrt(_dp(a, curve1.complexity() - 1, curve2.complexity() - 1, curve1, curve2));
    auto end = boost::chrono::process_real_cpu_clock::now();
    result.time = (end-start).count() / 1000000000.0;
    result.value = value;
    return result;

}

distance_t _dp(std::vector<std::vector<distance_t>> &a, const curve_size_t i, const curve_size_t j, const Curve &curve1, const Curve &curve2) {
    if (a[i][j] > -1) return a[i][j];
    else if (i == 0 and j == 0) return curve1[i].dist_sqr(curve2[j]);
    else if (i > 0 and j == 0) return std::max(_dp(a, i-1, 0, curve1, curve2), curve1[i].dist_sqr(curve2[j]));
    else if (i == 0 and j > 0) return std::max(_dp(a, 0, j-1, curve1, curve2), curve1[i].dist_sqr(curve2[j]));
    else {
        a[i][j] = std::max(
                    std::min(
                        std::min(_dp(a, i-1, j, curve1, curve2),
                            _dp(a, i-1, j-1, curve1, curve2)),
                        _dp(a, i, j-1, curve1, curve2)),
                    curve1[i].dist_sqr(curve2[j]));
    }
    return a[i][j];
}

} // end namespace Discrete

} // end namespace Frechet


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_SWAR_FUNCTIONS_SIMD_COMMON_ENUMERATE_HPP_INCLUDED
#define BOOST_SIMD_SWAR_FUNCTIONS_SIMD_COMMON_ENUMERATE_HPP_INCLUDED

#include <boost/simd/swar/functions/enumerate.hpp>
#include <boost/simd/include/functions/simd/splat.hpp>
#include <boost/simd/include/functions/simd/plus.hpp>
#include <boost/simd/include/functions/simd/make.hpp>
#include <boost/simd/include/functions/simd/fma.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( enumerate_, tag::cpu_
                                    , (X)(T)
                                    , ((target_< simd_< arithmetic_<T>,X> >))
                                    )
  {
    typedef typename T::type result_type;

    BOOST_FORCEINLINE result_type operator()(T const& ) const
    {
      return eval(typename simd::meta::cardinal_of<result_type>::type());
    }

    BOOST_FORCEINLINE result_type eval(boost::mpl::size_t<2> const&) const
    {
      return make<result_type>(0,1);
    }

    BOOST_FORCEINLINE result_type eval(boost::mpl::size_t<4> const&) const
    {
      return make<result_type>(0, 1, 2, 3);
    }

    BOOST_FORCEINLINE result_type eval(boost::mpl::size_t<8> const&) const
    {
      return make<result_type>(0, 1, 2, 3, 4, 5, 6, 7);
    }

    BOOST_FORCEINLINE result_type eval(boost::mpl::size_t<16> const&) const
    {
      return make<result_type>( 0, 1,  2,  3,  4,  5,  6,  7
                              , 8, 9, 10, 11, 12, 13, 14, 15
                              );
    }

    BOOST_FORCEINLINE result_type eval(boost::mpl::size_t<32> const&) const
    {
      return make<result_type>(  0,  1,  2,  3,  4,  5,  6,  7
                              ,  8,  9, 10, 11, 12, 13, 14, 15
                              , 16, 17, 18, 19, 20, 21, 22, 23
                              , 24, 25, 26, 27, 28, 29, 30, 31
                              );
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( enumerate_, tag::cpu_
                                    , (A0)(X)(T)
                                    , (scalar_< arithmetic_<A0> >)
                                      ((target_< simd_< arithmetic_<T>,X> >))
                                    )
  {
    typedef typename T::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, T const&) const
    {
      return enumerate<result_type>() + splat<result_type>(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( enumerate_, tag::cpu_
                                    , (A0)(A1)(X)(Y)
                                    , ((simd_< arithmetic_<A0>, X >))
                                      ((target_< simd_< arithmetic_<A1>, Y> >))
                                    )
  {
    typedef typename A1::type result_type;
    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const& ) const
    {
      return splat<result_type>(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( enumerate_, tag::cpu_
                                    , (A0)(A1)(X)(T)
                                    , (scalar_< arithmetic_<A0> >)
                                      (scalar_< arithmetic_<A1> >)
                                      ((target_< simd_< arithmetic_<T>,X> >))
                                    )
  {
    typedef typename T::type result_type;

    BOOST_FORCEINLINE
    result_type operator()(A0 const& a0, A1 const& a1, T const& ) const
    {
      return fma( enumerate<result_type>()
                , splat<result_type>(a1)
                , splat<result_type>(a0)
                );
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( enumerate_, tag::cpu_
                                    , (A0)(A1)(X)(T)
                                    , ((simd_< arithmetic_<A0>, X >))
                                      (generic_< arithmetic_<A1> >)
                                      ((target_< simd_< arithmetic_<T>,X> >))
                                    )
  {
    typedef typename T::type result_type;

    BOOST_FORCEINLINE
    result_type operator()(A0 const& a0, A1 const&, T const& ) const
    {
      return splat<result_type>(a0);
    }
  };
} } }

#endif


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_INTERPOL_FUNCTIONS_GENERIC_NEAREST_HPP_INCLUDED
#define NT2_INTERPOL_FUNCTIONS_GENERIC_NEAREST_HPP_INCLUDED

#include <nt2/interpol/functions/nearest.hpp>
#include <nt2/include/functions/is_nge.hpp>
#include <nt2/include/functions/is_nle.hpp>
#include <nt2/include/functions/is_equal.hpp>
#include <nt2/include/functions/issorted.hpp>
#include <nt2/include/functions/bsearch.hpp>
#include <nt2/include/functions/if_else.hpp>
#include <nt2/include/functions/fma.hpp>
#include <nt2/include/functions/logical_or.hpp>
#include <nt2/include/functions/oneminus.hpp>
#include <nt2/include/functions/oneplus.hpp>
#include <nt2/include/functions/repnum.hpp>
#include <nt2/include/functions/width.hpp>
#include <nt2/include/functions/average.hpp>
#include <nt2/include/functions/numel.hpp>
#include <nt2/include/constants/nan.hpp>
#include <nt2/core/container/table/table.hpp>
#include <nt2/sdk/meta/as_integer.hpp>
#include <nt2/sdk/meta/as_logical.hpp>
#include <boost/assert.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( nearest_, tag::cpu_
                            , (A0)(A1)(A2)
                            , (scalar_<arithmetic_<A0> > )
                              (unspecified_<A1> )
                              (unspecified_<A2> )
                            )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE
    result_type operator()(const A0 &, const A1 &, const A2 &) const

    {
      BOOST_ASSERT_MSG(false, "Interpolation requires at least two sample points in each dimension.");
      return Nan<A0>();
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( nearest_, tag::cpu_
                            , (A0)(A1)(A2)(A3)
                            , (scalar_<arithmetic_<A0> > )
                              (unspecified_<A1> )
                              (unspecified_<A2> )
                              (unspecified_<A3> )
                            )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE
    result_type operator()(const A0 &, const A0 & , const A0 & , const A0 & ) const
    {
      BOOST_ASSERT_MSG(false, "Interpolation requires at least two sample points in each dimension.");
      return  Nan<A0>();
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( run_assign_, tag::cpu_
                            , (A0)(A1)(N1)
                            , ((ast_<A0, nt2::container::domain>))
                              ((node_<A1,nt2::tag::nearest_,N1,nt2::container::domain>))
                            )
  {
    typedef typename boost::proto::result_of::child_c<A1&,0>::value_type  child0;
    typedef typename boost::proto::result_of::child_c<A1&,1>::value_type  child1;
    typedef typename boost::proto::result_of::child_c<A1&,2>::value_type  child2;
    typedef typename child0::value_type                               value_type;
    typedef typename meta::as_integer<value_type>::type               index_type;
    typedef typename A0::value_type                                    sale_type;
    typedef A0&                                                      result_type;

    result_type operator()(A0& yi, A1& inputs) const
    {
      yi.resize(inputs.extent());
      const child0 & x   =  boost::proto::child_c<0>(inputs);
      if (numel(x) <=  1)
        BOOST_ASSERT_MSG(numel(x) >  1, "Interpolation requires at least two sample points in each dimension.");
      else
      {
        BOOST_ASSERT_MSG(issorted(x, 'a'), "for 'nearest' interpolation x values must be sorted in ascending order");
        const child1 & y   =  boost::proto::child_c<1>(inputs);
        BOOST_ASSERT_MSG(numel(x) == numel(y), "The grid vectors do not define a grid of points that match the given values.");
        const child2 & xi  =  boost::proto::child_c<2>(inputs);
        bool extrap = false;
        value_type extrapval = Nan<value_type>();
        choices(inputs, extrap, extrapval, N1());
        table<index_type>   index = bsearch (x, xi);
        table<value_type>  dx    =  xi-x(index);
        table<index_type> indexp1 =  oneplus(index);
        yi = y(nt2::if_else(lt(nt2::abs(xi-x(index)), nt2::abs(xi-x(indexp1))), index,  indexp1));
        value_type  b =  value_type(x(begin_));
        value_type  e =  value_type(x(end_));
        if (!extrap) yi = nt2::if_else(nt2::logical_or(boost::simd::is_nge(xi, b),
                                                       boost::simd::is_nle(xi, e)), extrapval, yi);
      }

      return yi;
    }
  private :
    static void choices(const A1&, bool &,  value_type&, boost::mpl::long_<3> const &)
      { }
    static void choices(const A1& inputs, bool & extrap,  value_type& extrapval, boost::mpl::long_<4> const &)
      {
        typedef typename boost::proto::result_of::child_c<A1&,3>::type             child3;
        typedef typename meta::scalar_of<child3>::type                    cref_param_type;
        typedef typename meta::strip<cref_param_type>::type                    param_type;
        get(inputs, extrap, extrapval, param_type());
      }
    static void get(const A1& inputs, bool & extrap,  value_type&,  const bool &)
      {
        extrap =  boost::proto::child_c<3>(inputs);
      }
    static void get(const A1& inputs, bool &,  value_type& extrapval,  const value_type &)
      {
        extrapval =  boost::proto::child_c<3>(inputs);
      }
  };
} }

// template < class XPR1, class XPR2,  class XPRi>
// nc::array < typename ttt::float_promotion <typename XPRi::type_t >::type_t >
// itp_nearest(const XPR1 & x, const XPR2 & y, const XPRi & xi, bool extrap = false) {
//   typedef ne::Matricial_Checks < check_on_t > mc_t;
//   mc_t::DimensionsTest(__FILE__, __LINE__, "itp_nearest", x, y);
//   mc_t::RowTest  (__FILE__, __LINE__, x);
//   mc_t::SortedTest(__FILE__, __LINE__, x);
//   typedef typename  ttt::float_promotion <typename XPRi::type_t >::type_t  float_t;
//   nc::array < ptrdiff_t > index = bsearch (x, xi);
//   nc::array < float_t > yi =  where(abs(xi-x(index)) <  abs(xi-x(index+1)), y(index),  y(index+1));
//   if(!extrap) yi(xi < float_t(x(Begin())) || xi >  float_t(x(End()))) = NaN < float_t > ();
//   return yi;
// }

#endif


// Copyright (c) 2014-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <validation.h>
#include <net.h>
#include <prime/prime.h>

#include <test/test_bitcoin.h>

#include <boost/signals2/signal.hpp>
#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(main_tests, TestingSetup)

static void TestBlockSubsidyHalvings(const Consensus::Params& consensusParams)
{
    int maxLength = 24;
    CAmount nInitialSubsidy = 999 * COIN;

    //DATACOIN CHANGED
    CAmount nPreviousSubsidy = nInitialSubsidy * 2; // for height == 0
    BOOST_CHECK_EQUAL(nPreviousSubsidy, nInitialSubsidy * 2);
    for (int nLength = consensusParams.nTargetMinLength; nLength < maxLength; nLength++) {
        //int nHeight = nHalvings * consensusParams.nSubsidyHalvingInterval;
        CAmount nSubsidy = GetBlockSubsidy(TargetFromInt(nLength), consensusParams);
        //std::cerr << nLength << std::endl;
        BOOST_CHECK(nSubsidy < nPreviousSubsidy);
        //DATACOIN OPTIMIZE?  inapplicable
        //BOOST_CHECK_EQUAL(nSubsidy, nPreviousSubsidy / 2);
        nPreviousSubsidy = nSubsidy;
    }
    BOOST_CHECK_EQUAL(GetBlockSubsidy(TargetFromInt(maxLength), consensusParams), 173000000);
}

static void TestBlockSubsidyHalvings(int nSubsidyHalvingInterval)
{
    Consensus::Params consensusParams;
    consensusParams.nSubsidyHalvingInterval = nSubsidyHalvingInterval;
    consensusParams.nTargetMinLength = 7;
    TestBlockSubsidyHalvings(consensusParams);
}

BOOST_AUTO_TEST_CASE(block_subsidy_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    TestBlockSubsidyHalvings(chainParams->GetConsensus()); // As in main
    TestBlockSubsidyHalvings(150); // As in regtest
    TestBlockSubsidyHalvings(1000); // Just another interval
}

BOOST_AUTO_TEST_CASE(subsidy_limit_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    CAmount nSum = 0;
    for (int nHeight = 0; nHeight < 14000000; nHeight += 1000) {
        CAmount nSubsidy = GetBlockSubsidy(nHeight, chainParams->GetConsensus());
        BOOST_CHECK(nSubsidy <= 50 * COIN);
        nSum += nSubsidy * 1000;
        BOOST_CHECK(MoneyRange(nSum));
    }
    //BOOST_CHECK_EQUAL(nSum, 2099999997690000ULL); //DATACOIN OPTIMIZE? unapplicable
}

bool ReturnFalse() { return false; }
bool ReturnTrue() { return true; }

BOOST_AUTO_TEST_CASE(test_combiner_all)
{
    boost::signals2::signal<bool (), CombinerAll> Test;
    BOOST_CHECK(Test());
    Test.connect(&ReturnFalse);
    BOOST_CHECK(!Test());
    Test.connect(&ReturnTrue);
    BOOST_CHECK(!Test());
    Test.disconnect(&ReturnFalse);
    BOOST_CHECK(Test());
    Test.disconnect(&ReturnTrue);
    BOOST_CHECK(Test());
}
BOOST_AUTO_TEST_SUITE_END()


/*
 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 <ql/cashflows/coupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/exercise.hpp>
#include <ql/instruments/compositeinstrument.hpp>
#include <ql/instruments/nonstandardswaption.hpp>
#include <ql/instruments/swaption.hpp>
#include <ql/time/daycounters/actualactual.hpp>

#include <qle/instruments/rebatedexercise.hpp>

#include <ored/portfolio/builders/swap.hpp>
#include <ored/portfolio/builders/swaption.hpp>
#include <ored/portfolio/optionwrapper.hpp>
#include <ored/portfolio/swaption.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/to_string.hpp>

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/timer/timer.hpp>

#include <algorithm>

using boost::timer::cpu_timer;
using boost::timer::default_places;
using namespace QuantLib;
using std::lower_bound;
using std::sort;

namespace ore {
namespace data {

void Swaption::build(const boost::shared_ptr<EngineFactory>& engineFactory) {

    QL_REQUIRE(swap_.size() == 2, "underlying swap must have 2 legs");

    // we can assume 2 legs now
    bool isCrossCcy = swap_[0].currency() != swap_[1].currency();

    bool isFixedFloating = (swap_[0].legType() == "Fixed" && swap_[1].legType() == "Floating") ||
                           (swap_[1].legType() == "Fixed" && swap_[0].legType() == "Floating");

    // First elimate these
    QL_REQUIRE(!isCrossCcy, "Cross Currency Swaptions not supported");
    QL_REQUIRE(isFixedFloating, "Basis Swaptions not supported");

    // check at least one exercise date is given
    QL_REQUIRE(!option_.exerciseDates().empty(), "No exercise dates given");

    // check if all exercise dates (adjusted by the notice period) are in the past and if so, build an expired
    // instrument
    Period noticePeriod = option_.noticePeriod().empty() ? 0 * Days : parsePeriod(option_.noticePeriod());
    Calendar noticeCal = option_.noticeCalendar().empty() ? NullCalendar() : parseCalendar(option_.noticeCalendar());
    BusinessDayConvention noticeBdc =
        option_.noticeConvention().empty() ? Unadjusted : parseBusinessDayConvention(option_.noticeConvention());
    std::vector<Date> exerciseDates(option_.exerciseDates().size());
    for (Size i = 0; i < option_.exerciseDates().size(); ++i) {
        exerciseDates.push_back(noticeCal.advance(parseDate(option_.exerciseDates()[i]), -noticePeriod, noticeBdc));
    }

    Date latestExerciseDate = *std::max_element(exerciseDates.begin(), exerciseDates.end());
    if (QuantLib::detail::simple_event(latestExerciseDate).hasOccurred()) {
        exercise_ = boost::make_shared<BermudanExercise>(exerciseDates);
        legs_ = {{boost::make_shared<QuantLib::SimpleCashFlow>(0.0, latestExerciseDate)}};
        instrument_ = boost::shared_ptr<InstrumentWrapper>(
            new VanillaInstrument(boost::make_shared<QuantLib::Swap>(legs_, std::vector<bool>(1, false)), 1.0, {}, {}));
        legCurrencies_ = {swap_[0].currency()};
        legPayers_ = {false};
        npvCurrency_ = swap_[0].currency();
        notional_ = 0.0;
        notionalCurrency_ = npvCurrency_;
        maturity_ = latestExerciseDate;
        return;
    }

    // build swaption with at least one alive exercise date
    bool fixedFirst = swap_[0].legType() == "Fixed";
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedFirst ? 0 : 1].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[fixedFirst ? 1 : 0].concreteLegData());
    underlyingIndex_ = floatingLegData->index();

    bool isNonStandard =
        (swap_[0].notionals().size() > 1 || swap_[1].notionals().size() > 1 || floatingLegData->spreads().size() > 1 ||
         floatingLegData->gearings().size() > 1 || fixedLegData->rates().size() > 1);

    Exercise::Type exerciseType = parseExerciseType(option_.style());

    // treat non standard europeans as bermudans, also if exercise fees are present
    QL_REQUIRE(exerciseType == Exercise::Bermudan || exerciseType == Exercise::European,
               "Exercise type " << option_.style() << " not implemented for Swaptions");
    if (exerciseType == Exercise::Bermudan || (isNonStandard && exerciseType == Exercise::European) ||
        !option_.exerciseFees().empty())
        buildBermudan(engineFactory);
    else
        buildEuropean(engineFactory);

    // add required fixings, we add the required fixing for the underlying swap, which might be more
    // than actually required, i.e. we are conservative here
    addToRequiredFixings(underlyingLeg_,
                         boost::make_shared<FixingDateGetter>(
                             requiredFixings_, std::map<string, string>{{underlyingIndexQlName_, underlyingIndex_}}));
}

namespace {
QuantLib::Settlement::Method defaultMethod(const QuantLib::Settlement::Type t) {
    if (t == QuantLib::Settlement::Physical)
        return QuantLib::Settlement::PhysicalOTC;
    else
        return QuantLib::Settlement::ParYieldCurve; // ql < 1.14 behaviour
}
} // namespace

void Swaption::buildEuropean(const boost::shared_ptr<EngineFactory>& engineFactory) {

    LOG("Building European Swaption " << id());

    // Swaption details
    Settlement::Type settleType = parseSettlementType(option_.settlement());
    Settlement::Method settleMethod = option_.settlementMethod() == ""
                                          ? defaultMethod(settleType)
                                          : parseSettlementMethod(option_.settlementMethod());
    QL_REQUIRE(option_.exerciseDates().size() == 1, "Only one exercise date expected for European Option");

    Period noticePeriod = option_.noticePeriod().empty() ? 0 * Days : parsePeriod(option_.noticePeriod());
    Calendar noticeCal = option_.noticeCalendar().empty() ? NullCalendar() : parseCalendar(option_.noticeCalendar());
    BusinessDayConvention noticeBdc =
        option_.noticeConvention().empty() ? Unadjusted : parseBusinessDayConvention(option_.noticeConvention());
    Date exDate = noticeCal.advance(parseDate(option_.exerciseDates().front()), -noticePeriod, noticeBdc);
    QL_REQUIRE(exDate >= Settings::instance().evaluationDate(), "Exercise date expected in the future.");

    boost::shared_ptr<VanillaSwap> swap = buildVanillaSwap(engineFactory, exDate);
    underlyingLeg_ = swap->floatingLeg();

    string ccy = swap_[0].currency();
    Currency currency = parseCurrency(ccy);

    exercise_ = boost::make_shared<EuropeanExercise>(exDate);

    // Build Swaption
    boost::shared_ptr<QuantLib::Swaption> swaption(new QuantLib::Swaption(swap, exercise_, settleType, settleMethod));

    // Add Engine
    string tt("EuropeanSwaption");
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder(tt);
    QL_REQUIRE(builder, "No builder found for " << tt);
    boost::shared_ptr<EuropeanSwaptionEngineBuilder> swaptionBuilder =
        boost::dynamic_pointer_cast<EuropeanSwaptionEngineBuilder>(builder);
    swaption->setPricingEngine(swaptionBuilder->engine(currency));

    Position::Type positionType = parsePositionType(option_.longShort());
    Real multiplier = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);

    // If premium data is provided
    // 1) build the fee trade and pass it to the instrument wrapper for pricing
    // 2) add fee payment as additional trade leg for cash flow reporting
    std::vector<boost::shared_ptr<Instrument>> additionalInstruments;
    std::vector<Real> additionalMultipliers;
    if (option_.premiumPayDate() != "" && option_.premiumCcy() != "") {
        Real premiumAmount = -multiplier * option_.premium(); // pay if long, receive if short
        Currency premiumCurrency = parseCurrency(option_.premiumCcy());
        Date premiumDate = parseDate(option_.premiumPayDate());
        addPayment(additionalInstruments, additionalMultipliers, premiumDate, premiumAmount, premiumCurrency, currency,
                   engineFactory, swaptionBuilder->configuration(MarketContext::pricing));
        DLOG("option premium added for european swaption " << id());
    }

    // Now set the instrument wrapper, depending on delivery
    if (settleType == Settlement::Physical) {
        // tracks state for any option flavour, including physical delivery
        instrument_ = boost::shared_ptr<InstrumentWrapper>(
            new EuropeanOptionWrapper(swaption, positionType == Position::Long ? true : false, exDate,
                                      settleType == Settlement::Physical ? true : false, swap, 1.0, 1.0,
                                      additionalInstruments, additionalMultipliers));
        // maturity of underlying
        maturity_ = std::max(swap->fixedSchedule().dates().back(), swap->floatingSchedule().dates().back());
    } else {
        instrument_ = boost::shared_ptr<InstrumentWrapper>(
            new VanillaInstrument(swaption, multiplier, additionalInstruments, additionalMultipliers));
        maturity_ = exDate;
    }

    DLOG("Building European Swaption done");
}

void Swaption::buildBermudan(const boost::shared_ptr<EngineFactory>& engineFactory) {
    DLOG("Building Bermudan Swaption " << id());

    QL_REQUIRE(swap_.size() >= 1, "underlying swap must have at least 1 leg");

    string ccy_str = swap_[0].currency();
    Currency currency = parseCurrency(ccy_str);

    bool fixedFirst = swap_[0].legType() == "Fixed";
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedFirst ? 0 : 1].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[fixedFirst ? 1 : 0].concreteLegData());
    underlyingIndex_ = floatingLegData->index();

    bool isNonStandard =
        (swap_[0].notionals().size() > 1 || swap_[1].notionals().size() > 1 || floatingLegData->spreads().size() > 1 ||
         floatingLegData->gearings().size() > 1 || fixedLegData->rates().size() > 1);

    boost::shared_ptr<VanillaSwap> vanillaSwap;
    boost::shared_ptr<NonstandardSwap> nonstandardSwap;
    boost::shared_ptr<Swap> swap;
    if (!isNonStandard) {
        vanillaSwap = buildVanillaSwap(engineFactory);
        underlyingLeg_ = vanillaSwap->floatingLeg();
        swap = vanillaSwap;
    } else {
        nonstandardSwap = buildNonStandardSwap(engineFactory);
        underlyingLeg_ = nonstandardSwap->floatingLeg();
        swap = nonstandardSwap;
    }

    DLOG("Swaption::Build(): Underlying Start = " << QuantLib::io::iso_date(swap->startDate()));

    // build exercise: only keep a) future exercise dates and b) exercise dates that exercise into a whole
    // accrual period of the underlying; TODO handle exercises into broken periods?
    Date lastAccrualStartDate = Date::minDate();
    for (Size i = 0; i < 2; ++i) {
        for (auto const& c : swap->leg(i)) {
            if (auto cpn = boost::dynamic_pointer_cast<Coupon>(c))
                lastAccrualStartDate = std::max(lastAccrualStartDate, cpn->accrualStartDate());
        }
    }
    Period noticePeriod = option_.noticePeriod().empty() ? 0 * Days : parsePeriod(option_.noticePeriod());
    Calendar noticeCal = option_.noticeCalendar().empty() ? NullCalendar() : parseCalendar(option_.noticeCalendar());
    BusinessDayConvention noticeBdc =
        option_.noticeConvention().empty() ? Unadjusted : parseBusinessDayConvention(option_.noticeConvention());
    std::vector<QuantLib::Date> sortedExerciseDates;
    for (auto const& d : option_.exerciseDates())
        sortedExerciseDates.push_back(parseDate(d));
    std::sort(sortedExerciseDates.begin(), sortedExerciseDates.end());
    std::vector<QuantLib::Date> noticeDates, exerciseDates;
    std::vector<bool> isExerciseDateAlive(sortedExerciseDates.size(), false);
    for (Size i = 0; i < sortedExerciseDates.size(); i++) {
        Date noticeDate = noticeCal.advance(sortedExerciseDates[i], -noticePeriod, noticeBdc);
        if (noticeDate > Settings::instance().evaluationDate() && noticeDate <= lastAccrualStartDate) {
            isExerciseDateAlive[i] = true;
            noticeDates.push_back(noticeDate);
            exerciseDates.push_back(sortedExerciseDates[i]);
            DLOG("Got notice date " << QuantLib::io::iso_date(noticeDate) << " using notice period " << noticePeriod
                                    << ", convention " << noticeBdc << ", calendar " << noticeCal.name()
                                    << " from exercise date " << exerciseDates.back());
        }
        if (noticeDate > lastAccrualStartDate)
            WLOG("Remove notice date " << ore::data::to_string(noticeDate) << " (exercise date "
                                       << sortedExerciseDates[i] << ") after last accrual start date "
                                       << ore ::data::to_string(lastAccrualStartDate));
    }
    QL_REQUIRE(noticeDates.size() > 0, "Bermudan Swaption does not have any alive exercise dates");
    maturity_ = noticeDates.back();
    exercise_ = boost::make_shared<BermudanExercise>(noticeDates);

    // check for exercise fees, if present build a rebated exercise with rebates = -fee
    if (!option_.exerciseFees().empty()) {
        // build an exercise date "schedule" by adding the maximum possible date at the end
        std::vector<Date> exDatesPlusInf(sortedExerciseDates);
        exDatesPlusInf.push_back(Date::maxDate());
        vector<double> allRebates =
            buildScheduledVectorNormalised(option_.exerciseFees(), option_.exerciseFeeDates(), exDatesPlusInf, 0.0);
        // filter on alive rebates, so that we can a vector of rebates corresponding to the exerciseDates vector
        vector<double> rebates;
        for (Size i = 0; i < sortedExerciseDates.size(); ++i) {
            if (isExerciseDateAlive[i])
                rebates.push_back(allRebates[i]);
        }
        // flip the sign of the fee to get a rebate
        for (auto& r : rebates)
            r = -r;
        vector<string> feeType = buildScheduledVectorNormalised<string>(option_.exerciseFeeTypes(),
                                                                        option_.exerciseFeeDates(), exDatesPlusInf, "");
        // convert relative to absolute fees if required
        for (Size i = 0; i < rebates.size(); ++i) {
            // default to Absolute
            if (feeType[i].empty())
                feeType[i] = "Absolute";
            if (feeType[i] == "Percentage") {
                // get next float coupon after exercise to determine relevant notional
                Real feeNotional = Null<Real>();
                for (auto const& c : swap->leg(1)) {
                    if (auto cpn = boost::dynamic_pointer_cast<Coupon>(c)) {
                        if (feeNotional == Null<Real>() && cpn->accrualStartDate() >= exerciseDates[i])
                            feeNotional = cpn->nominal();
                    }
                }
                if (feeNotional == Null<Real>())
                    rebates[i] = 0.0; // notional is zero
                else {
                    DLOG("Convert percentage rebate "
                         << rebates[i] << " to absolute reabte " << rebates[i] * feeNotional << " using nominal "
                         << feeNotional << " for exercise date " << QuantLib::io::iso_date(exerciseDates[i]));
                    rebates[i] *= feeNotional; // multiply percentage fee by relevant notional
                }
            } else {
                QL_REQUIRE(feeType[i] == "Absolute", "fee type must be Absolute or Relative");
            }
        }
        Period feeSettlPeriod = option_.exerciseFeeSettlementPeriod().empty()
                                    ? 0 * Days
                                    : parsePeriod(option_.exerciseFeeSettlementPeriod());
        Calendar feeSettlCal = option_.exerciseFeeSettlementCalendar().empty()
                                   ? NullCalendar()
                                   : parseCalendar(option_.exerciseFeeSettlementCalendar());
        BusinessDayConvention feeSettlBdc = option_.exerciseFeeSettlementConvention().empty()
                                                ? Unadjusted
                                                : parseBusinessDayConvention(option_.exerciseFeeSettlementConvention());
        exercise_ = boost::make_shared<QuantExt::RebatedExercise>(*exercise_, exerciseDates, rebates, feeSettlPeriod,
                                                                  feeSettlCal, feeSettlBdc);
        // log rebates
        auto dbgEx = boost::static_pointer_cast<QuantExt::RebatedExercise>(exercise_);
        for (Size i = 0; i < exerciseDates.size(); ++i) {
            DLOG("Got rebate " << dbgEx->rebate(i) << " with payment date "
                               << QuantLib::io::iso_date(dbgEx->rebatePaymentDate(i)) << " (exercise date="
                               << QuantLib::io::iso_date(exerciseDates[i]) << ") using rebate settl period "
                               << feeSettlPeriod << ", calendar " << feeSettlCal << ", convention " << feeSettlBdc);
        }
    }

    Settlement::Type delivery = parseSettlementType(option_.settlement());
    Settlement::Method deliveryMethod =
        option_.settlementMethod() == "" ? defaultMethod(delivery) : parseSettlementMethod(option_.settlementMethod());

    if (delivery == Settlement::Cash && deliveryMethod == Settlement::ParYieldCurve)
        WLOG("Cash-settled Bermudan Swaption (id = "
             << id() << ") with ParYieldCurve settlement method not supported by Lgm engine. "
             << "Approximate pricing using CollateralizedCashPrice pricing methodology");

    // Build swaption
    DLOG("Build Swaption instrument");
    boost::shared_ptr<QuantLib::Instrument> swaption;
    if (isNonStandard) {
        swaption = boost::shared_ptr<QuantLib::Instrument>(
            new QuantLib::NonstandardSwaption(nonstandardSwap, exercise_, delivery, deliveryMethod));
        // workaround for missing registration in QL versions < 1.13
        swaption->registerWithObservables(nonstandardSwap);
    } else
        swaption = boost::shared_ptr<QuantLib::Instrument>(
            new QuantLib::Swaption(vanillaSwap, exercise_, delivery, deliveryMethod));

    QuantLib::Position::Type positionType = parsePositionType(option_.longShort());
    if (delivery == Settlement::Physical)
        maturity_ = swap->maturityDate();

    DLOG("Get/Build Bermudan Swaption engine");
    cpu_timer timer;

    string tt("BermudanSwaption");
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder(tt);
    QL_REQUIRE(builder, "No builder found for " << tt);
    boost::shared_ptr<BermudanSwaptionEngineBuilder> swaptionBuilder =
        boost::dynamic_pointer_cast<BermudanSwaptionEngineBuilder>(builder);

    // determine strikes for calibration basket (simple approach, a la summit)
    std::vector<Real> strikes(noticeDates.size(), Null<Real>());
    if (!isNonStandard) {
        Real tmp = vanillaSwap->fixedRate() - vanillaSwap->spread();
        std::fill(strikes.begin(), strikes.end(), tmp);
        DLOG("calibration strike is " << tmp << "(fixed rate " << vanillaSwap->fixedRate() << ", spread "
                                      << vanillaSwap->spread() << ")");
    } else {
        for (Size i = 0; i < noticeDates.size(); ++i) {
            const Schedule& fix = nonstandardSwap->fixedSchedule();
            const Schedule& flt = nonstandardSwap->floatingSchedule();
            Size fixIdx =
                std::lower_bound(fix.dates().begin(), fix.dates().end(), noticeDates[i]) - fix.dates().begin();
            Size fltIdx =
                std::lower_bound(flt.dates().begin(), flt.dates().end(), noticeDates[i]) - flt.dates().begin();
            // play safe
            fixIdx = std::min(fixIdx, nonstandardSwap->fixedRate().size() - 1);
            fltIdx = std::min(fltIdx, nonstandardSwap->spreads().size() - 1);
            strikes[i] = nonstandardSwap->fixedRate()[fixIdx] - nonstandardSwap->spreads()[fltIdx];
            DLOG("calibration strike for ex date " << QuantLib::io::iso_date(noticeDates[i]) << " is " << strikes[i]
                                                   << " (fixed rate " << nonstandardSwap->fixedRate()[fixIdx]
                                                   << ", spread " << nonstandardSwap->spreads()[fltIdx] << ")");
        }
    }

    boost::shared_ptr<PricingEngine> engine =
        swaptionBuilder->engine(id(), isNonStandard, ccy_str, noticeDates, swap->maturityDate(), strikes);

    timer.stop();
    DLOG("Swaption model calibration time: " << timer.format(default_places, "%w") << " s");

    swaption->setPricingEngine(engine);

    // timer.restart();
    // Real npv = swaption->NPV();
    // Real pt = timer.elapsed();
    // DLOG("Swaption pricing time: " << pt*1000 << " ms");

    boost::shared_ptr<EngineBuilder> tmp = engineFactory->builder("Swap");
    boost::shared_ptr<SwapEngineBuilderBase> swapBuilder = boost::dynamic_pointer_cast<SwapEngineBuilderBase>(tmp);
    QL_REQUIRE(swapBuilder, "No Swap Builder found for Swaption " << id());
    boost::shared_ptr<PricingEngine> swapEngine = swapBuilder->engine(currency);

    std::vector<boost::shared_ptr<Instrument>> underlyingSwaps = buildUnderlyingSwaps(swapEngine, swap, noticeDates);

    // If premium data is provided
    // 1) build the fee trade and pass it to the instrument wrapper for pricing
    // 2) add fee payment as additional trade leg for cash flow reporting
    std::vector<boost::shared_ptr<Instrument>> additionalInstruments;
    std::vector<Real> additionalMultipliers;
    if (option_.premiumPayDate() != "" && option_.premiumCcy() != "") {
        Real multiplier = positionType == Position::Long ? 1.0 : -1.0;
        Real premiumAmount = -multiplier * option_.premium(); // pay if long, receive if short
        Currency premiumCurrency = parseCurrency(option_.premiumCcy());
        Date premiumDate = parseDate(option_.premiumPayDate());
        addPayment(additionalInstruments, additionalMultipliers, premiumDate, premiumAmount, premiumCurrency, currency,
                   engineFactory, swaptionBuilder->configuration(MarketContext::pricing));
        DLOG("option premium added for bermudan swaption " << id());
    }

    // instrument_ = boost::shared_ptr<InstrumentWrapper> (new VanillaInstrument (swaption, multiplier));
    instrument_ = boost::shared_ptr<InstrumentWrapper>(
        new BermudanOptionWrapper(swaption, positionType == Position::Long ? true : false, noticeDates,
                                  delivery == Settlement::Physical ? true : false, underlyingSwaps, 1.0, 1.0,
                                  additionalInstruments, additionalMultipliers));

    DLOG("Building Bermudan Swaption done");
}

boost::shared_ptr<VanillaSwap> Swaption::buildVanillaSwap(const boost::shared_ptr<EngineFactory>& engineFactory,
                                                          const Date& firstExerciseDate) {
    // Limitation to vanilla for now
    QL_REQUIRE(swap_.size() == 2 && swap_[0].currency() == swap_[1].currency() &&
                   swap_[0].isPayer() != swap_[1].isPayer() && swap_[0].notionals() == swap_[1].notionals() &&
                   swap_[0].notionals().size() == 1,
               "Not a Vanilla Swap");

    Size fixedLegIndex, floatingLegIndex;
    if (swap_[0].legType() == "Floating" && swap_[1].legType() == "Fixed") {
        floatingLegIndex = 0;
        fixedLegIndex = 1;
    } else if (swap_[1].legType() == "Floating" && swap_[0].legType() == "Fixed") {
        floatingLegIndex = 1;
        fixedLegIndex = 0;
    } else {
        QL_FAIL("Invalid leg types " << swap_[0].legType() << " + " << swap_[1].legType());
    }
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedLegIndex].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[floatingLegIndex].concreteLegData());

    QL_REQUIRE(fixedLegData->rates().size() == 1, "Vanilla Swaption: constant rate required");
    QL_REQUIRE(floatingLegData->spreads().size() <= 1, "Vanilla Swaption: constant spread required");

    boost::shared_ptr<EngineBuilder> tmp = engineFactory->builder("Swap");
    boost::shared_ptr<SwapEngineBuilderBase> swapBuilder = boost::dynamic_pointer_cast<SwapEngineBuilderBase>(tmp);
    QL_REQUIRE(swapBuilder, "No Swap Builder found for Swaption " << id());

    // Get Trade details
    string ccy = swap_[0].currency();
    Currency currency = parseCurrency(ccy);
    Real nominal = swap_[0].notionals().back();

    Real rate = fixedLegData->rates().front();
    Real spread = floatingLegData->spreads().empty() ? 0.0 : floatingLegData->spreads().front();
    string indexName = floatingLegData->index();

    Schedule fixedSchedule = makeSchedule(swap_[fixedLegIndex].schedule());
    DayCounter fixedDayCounter = parseDayCounter(swap_[fixedLegIndex].dayCounter());

    Schedule floatingSchedule = makeSchedule(swap_[floatingLegIndex].schedule());
    Handle<IborIndex> index =
        engineFactory->market()->iborIndex(indexName, swapBuilder->configuration(MarketContext::pricing));
    underlyingIndexQlName_ = index->name();
    DayCounter floatingDayCounter = parseDayCounter(swap_[floatingLegIndex].dayCounter());

    BusinessDayConvention paymentConvention = parseBusinessDayConvention(swap_[floatingLegIndex].paymentConvention());

    VanillaSwap::Type type = swap_[fixedLegIndex].isPayer() ? VanillaSwap::Payer : VanillaSwap::Receiver;

    // We treat overnight and bma indices approximately as ibor indices and warn about this in the log
    if (boost::dynamic_pointer_cast<OvernightIndex>(*index) ||
        boost ::dynamic_pointer_cast<QuantExt::BMAIndexWrapper>(*index))
        ALOG("Swaption trade " << id() << " on ON or BMA index '" << underlyingIndex_
                               << "' built, will treat the index approximately as an ibor index");

    // only take into account accrual periods with start date on or after first exercise date (if given)
    if (firstExerciseDate != Null<Date>()) {
        std::vector<Date> fixDates = fixedSchedule.dates();
        auto it1 = std::lower_bound(fixDates.begin(), fixDates.end(), firstExerciseDate);
        fixDates.erase(fixDates.begin(), it1);
        // check we have at least 1 to stop set fault on vector(fixDates.size() - 1, true) but maybe check should be 2
        QL_REQUIRE(fixDates.size() >= 2,
                   "Not enough schedule dates are left in Swaption fixed leg (check exercise dates)");
        fixedSchedule = Schedule(fixDates, fixedSchedule.calendar(), Unadjusted, boost::none, boost::none, boost::none,
                                 boost::none, std::vector<bool>(fixDates.size() - 1, true));
        std::vector<Date> floatingDates = floatingSchedule.dates();
        auto it2 = std::lower_bound(floatingDates.begin(), floatingDates.end(), firstExerciseDate);
        floatingDates.erase(floatingDates.begin(), it2);
        QL_REQUIRE(floatingDates.size() >= 2,
                   "Not enough schedule dates are left in Swaption floating leg (check exercise dates)");
        floatingSchedule = Schedule(floatingDates, floatingSchedule.calendar(), Unadjusted, boost::none, boost::none,
                                    boost::none, boost::none, std::vector<bool>(floatingDates.size() - 1, true));
    }

    // Build a vanilla (bullet) swap underlying
    boost::shared_ptr<VanillaSwap> swap(new VanillaSwap(type, nominal, fixedSchedule, rate, fixedDayCounter,
                                                        floatingSchedule, *index, spread, floatingDayCounter,
                                                        paymentConvention));

    swap->setPricingEngine(swapBuilder->engine(currency));

    // Set other ore::data::Trade details
    npvCurrency_ = ccy;
    notional_ = nominal;
    notionalCurrency_ = ccy;
    legCurrencies_ = vector<string>(2, ccy);
    legs_.push_back(swap->fixedLeg());
    legs_.push_back(swap->floatingLeg());
    legPayers_.push_back(swap_[fixedLegIndex].isPayer());
    legPayers_.push_back(swap_[floatingLegIndex].isPayer());

    return swap;
}

boost::shared_ptr<NonstandardSwap>
Swaption::buildNonStandardSwap(const boost::shared_ptr<EngineFactory>& engineFactory) {
    QL_REQUIRE(swap_.size() == 2, "Two swap legs expected");
    QL_REQUIRE(swap_[0].currency() == swap_[1].currency(), "single currency swap expected");
    QL_REQUIRE(swap_[0].isPayer() != swap_[1].isPayer(), "pay and receive leg expected");

    Size fixedLegIndex, floatingLegIndex;
    if (swap_[0].legType() == "Floating" && swap_[1].legType() == "Fixed") {
        floatingLegIndex = 0;
        fixedLegIndex = 1;
    } else if (swap_[1].legType() == "Floating" && swap_[0].legType() == "Fixed") {
        floatingLegIndex = 1;
        fixedLegIndex = 0;
    } else {
        QL_FAIL("Invalid leg types " << swap_[0].legType() << " + " << swap_[1].legType());
    }
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedLegIndex].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[floatingLegIndex].concreteLegData());

    boost::shared_ptr<EngineBuilder> tmp = engineFactory->builder("Swap");
    boost::shared_ptr<SwapEngineBuilderBase> swapBuilder = boost::dynamic_pointer_cast<SwapEngineBuilderBase>(tmp);
    QL_REQUIRE(swapBuilder, "No Swap Builder found for Swaption " << id());

    // Get Trade details
    string ccy = swap_[0].currency();
    Currency currency = parseCurrency(ccy);
    Schedule fixedSchedule = makeSchedule(swap_[fixedLegIndex].schedule());
    Schedule floatingSchedule = makeSchedule(swap_[floatingLegIndex].schedule());
    vector<Real> fixedNominal = buildScheduledVectorNormalised(
        swap_[fixedLegIndex].notionals(), swap_[fixedLegIndex].notionalDates(), fixedSchedule, (Real)Null<Real>());
    vector<Real> floatNominal =
        buildScheduledVectorNormalised(swap_[floatingLegIndex].notionals(), swap_[floatingLegIndex].notionalDates(),
                                       floatingSchedule, (Real)Null<Real>());
    vector<Real> fixedRate = buildScheduledVectorNormalised(fixedLegData->rates(), fixedLegData->rateDates(),
                                                            fixedSchedule, (Real)Null<Real>());
    vector<Real> spreads = buildScheduledVectorNormalised(floatingLegData->spreads(), floatingLegData->spreadDates(),
                                                          floatingSchedule, (Real)Null<Real>());
    // gearings are optional, i.e. may be empty
    vector<Real> gearings = buildScheduledVectorNormalised(floatingLegData->gearings(), floatingLegData->gearingDates(),
                                                           floatingSchedule, 1.0);
    string indexName = floatingLegData->index();
    DayCounter fixedDayCounter = parseDayCounter(swap_[fixedLegIndex].dayCounter());
    Handle<IborIndex> index =
        engineFactory->market()->iborIndex(indexName, swapBuilder->configuration(MarketContext::pricing));
    underlyingIndexQlName_ = index->name();
    DayCounter floatingDayCounter = parseDayCounter(swap_[floatingLegIndex].dayCounter());
    BusinessDayConvention paymentConvention = parseBusinessDayConvention(swap_[floatingLegIndex].paymentConvention());

    // We treat overnight and bma indices approximately as ibor indices and warn about this in the log
    if (boost::dynamic_pointer_cast<OvernightIndex>(*index) ||
        boost ::dynamic_pointer_cast<QuantExt::BMAIndexWrapper>(*index))
        ALOG("Swaption trade " << id() << " on ON or BMA index '" << underlyingIndex_
                               << "' built, will treat the index approximately as an ibor index");

    VanillaSwap::Type type = swap_[fixedLegIndex].isPayer() ? VanillaSwap::Payer : VanillaSwap::Receiver;

    // Build a vanilla (bullet) swap underlying
    boost::shared_ptr<NonstandardSwap> swap(new NonstandardSwap(type, fixedNominal, floatNominal, fixedSchedule,
                                                                fixedRate, fixedDayCounter, floatingSchedule, *index,
                                                                gearings, spreads, floatingDayCounter,
                                                                false, // no intermediate notional exchanges
                                                                false, // no final notional exchanges
                                                                paymentConvention));

    swap->setPricingEngine(swapBuilder->engine(currency));

    // Set other ore::data::Trade details
    npvCurrency_ = ccy;
    notional_ = std::max(currentNotional(swap->fixedLeg()), currentNotional(swap->floatingLeg()));
    notionalCurrency_ = npvCurrency_;
    legCurrencies_ = vector<string>(2, ccy);
    legs_.push_back(swap->fixedLeg());
    legs_.push_back(swap->floatingLeg());
    legPayers_.push_back(swap_[fixedLegIndex].isPayer());
    legPayers_.push_back(swap_[floatingLegIndex].isPayer());

    return swap;
}

std::vector<boost::shared_ptr<Instrument>>
Swaption::buildUnderlyingSwaps(const boost::shared_ptr<PricingEngine>& swapEngine,
                               const boost::shared_ptr<Swap>& underlyingSwap, const std::vector<Date>& exerciseDates) {
    std::vector<boost::shared_ptr<Instrument>> swaps;
    for (Size i = 0; i < exerciseDates.size(); ++i) {
        std::vector<Leg> legs(2);
        std::vector<bool> payer(2);
        for (Size j = 0; j < legs.size(); ++j) {
            legs[j] = underlyingSwap->leg(j);
            // if in the swap data the fixed leg is the first component, the legs in the underlying
            // swap match the order of the legs in the swap data, otherwise they are swapped
            payer[j] = swap_[0].legType() == "Fixed" ? swap_[j].isPayer() : swap_[1 - j].isPayer();
            boost::shared_ptr<Coupon> coupon = boost::dynamic_pointer_cast<Coupon>(legs[j].front());
            while (legs[j].size() > 0 && coupon->accrualStartDate() < exerciseDates[i]) {
                legs[j].erase(legs[j].begin());
                coupon = boost::dynamic_pointer_cast<Coupon>(legs[j].front());
            }
        }
        boost::shared_ptr<Swap> newSwap(new Swap(legs, payer));
        newSwap->setPricingEngine(swapEngine);
        swaps.push_back(newSwap);
        if (legs[0].size() > 0 && legs[1].size() > 0) {
            boost::shared_ptr<Coupon> coupon1 = boost::dynamic_pointer_cast<Coupon>(legs[0].front());
            boost::shared_ptr<Coupon> coupon2 = boost::dynamic_pointer_cast<Coupon>(legs[1].front());
            DLOG("Added underlying Swap start " << QuantLib::io::iso_date(coupon1->accrualStartDate()) << " "
                                                << QuantLib::io::iso_date(coupon2->accrualStartDate()) << " "
                                                << "exercise " << QuantLib::io::iso_date(exerciseDates[i]));
        } else {
            WLOG("Added underlying Swap with at least one empty leg for exercise "
                 << QuantLib::io::iso_date(exerciseDates[i]) << "!");
        }
    }
    return swaps;
}

void Swaption::fromXML(XMLNode* node) {
    Trade::fromXML(node);
    XMLNode* swapNode = XMLUtils::getChildNode(node, "SwaptionData");
    option_.fromXML(XMLUtils::getChildNode(swapNode, "OptionData"));
    swap_.clear();
    vector<XMLNode*> nodes = XMLUtils::getChildrenNodes(swapNode, "LegData");
    for (Size i = 0; i < nodes.size(); i++) {
        LegData ld;
        ld.fromXML(nodes[i]);
        swap_.push_back(ld);
    }
}

XMLNode* Swaption::toXML(XMLDocument& doc) {
    XMLNode* node = Trade::toXML(doc);
    XMLNode* swaptionNode = doc.allocNode("SwaptionData");
    XMLUtils::appendNode(node, swaptionNode);

    XMLUtils::appendNode(swaptionNode, option_.toXML(doc));
    for (Size i = 0; i < swap_.size(); i++)
        XMLUtils::appendNode(swaptionNode, swap_[i].toXML(doc));

    return node;
}
} // namespace data
} // namespace ore


#ifndef FSTDISTANCE_H
#define FSTDISTANCE_H

/*!======================================================================
   Feature Selection Toolbox 3 source code
   ---------------------------------------

   \file    distance.hpp
   \brief   Defines interface for distance evaluators (to be used primarily in Classifier_kNN)
   \author  Petr Somol (somol@utia.cas.cz) with collaborators, see Contacts at http://fst.utia.cz
   \date    October 2010
   \version 3.0.0.beta
   \note    FST3 was developed using gcc 4.3 and requires
   \note    \li Boost library (http://www.boost.org/, tested with versions 1.33.1 and 1.44),
   \note    \li (\e optionally) LibSVM (http://www.csie.ntu.edu.tw/~cjlin/libsvm/,
                tested with version 3.00)
   \note    Note that LibSVM is required for SVM related tools only,
            as demonstrated in demo12t.cpp, demo23.cpp, demo25t.cpp, demo32t.cpp, etc.

*/ /*
=========================================================================
Copyright:
  * FST3 software (with exception of any externally linked libraries)
    is copyrighted by Institute of Information Theory and Automation (UTIA),
    Academy of Sciences of the Czech Republic.
  * FST3 source codes as presented here do not contain code of third parties.
    FST3 may need linkage to external libraries to exploit its functionality
    in full. For details on obtaining and possible usage restrictions
    of external libraries follow their original sources (referenced from
    FST3 documentation wherever applicable).
  * FST3 software is available free of charge for non-commercial use.
    Please address all inquires concerning possible commercial use
    of FST3, or if in doubt, to FST3 maintainer (see http://fst.utia.cz).
  * Derivative works based on FST3 are permitted as long as they remain
    non-commercial only.
  * Re-distribution of FST3 software is not allowed without explicit
    consent of the copyright holder.
Disclaimer of Warranty:
  * FST3 software is presented "as is", without warranty of any kind,
    either expressed or implied, including, but not limited to, the implied
    warranties of merchantability and fitness for a particular purpose.
    The entire risk as to the quality and performance of the program
    is with you. Should the program prove defective, you assume the cost
    of all necessary servicing, repair or correction.
Limitation of Liability:
  * The copyright holder will in no event be liable to you for damages,
    including any general, special, incidental or consequential damages
    arising out of the use or inability to use the code (including but not
    limited to loss of data or data being rendered inaccurate or losses
    sustained by you or third parties or a failure of the program to operate
    with any other programs).
========================================================================== */

#include <boost/smart_ptr.hpp>
#include <iostream>
#include <cmath>
#include <list>
#include <cstring> // memcpy
#include "error.hpp"
#include "global.hpp"
#include "clonable.hpp"

/*============== Template parameter type naming conventions ==============
--------- Numeric types: -------------------------------------------------
DATATYPE - data sample values - usually real numbers (but may be integers
          in text processing etc.)
REALTYPE - must be real numbers - for representing intermediate results of
          calculations like mean, covariance etc.
IDXTYPE - index values for enumeration of data samples - (nonnegative) integers,
          extent depends on numbers of samples in data
DIMTYPE - index values for enumeration of features (dimensions), or classes (not
          class sizes) - (nonnegative) integers, usually lower extent than IDXTYPE,
          but be aware of expressions like _classes*_features*_features !
          in linearized representations of feature matrices for all classes
BINTYPE - feature selection marker type - represents ca. <10 different feature
          states (selected, deselected, sel./desel. temporarily 1st nested loop, 2nd...)
RETURNTYPE - criterion value: real value, but may be extended in future to support
          multiple values
--------- Class types: ---------------------------------------------------
SUBSET       - class of class type Subset
CLASSIFIER   - class implementing interface defined in abstract class Classifier
EVALUATOR    - class implementing interface defined in abstract class Sequential_Step
DISTANCE     - class implementing interface defined in abstract class Distance
DATAACCESSOR - class implementing interface defined in abstract class Data_Accessor
INTERVALCONTAINER - class of class type TIntervaller
CONTAINER    - STL container of class type TInterval
========================================================================== */

namespace FST {

//! Abstract class, defines interface for distance evaluators to be used in Classifier_kNN
template<typename DATATYPE, typename DIMTYPE, class SUBSET>
class Distance : public Clonable {
public:
	typedef boost::shared_ptr<SUBSET> PSubset;
	Distance() : _n(0), _d(0) {}
	Distance(const Distance &d);
	virtual ~Distance() {}
	void reset(const DIMTYPE n);

	void narrow_to(const PSubset sub); // fill _index[] -> access to data is then mapped to sub-matrix
	void denarrow(); // reset _index[] -> access is reset to the full matrix

	DIMTYPE get_n() const {return _n;}
	DIMTYPE get_d() const {return _d;}

	virtual DATATYPE distance(const DATATYPE *pattern1, const DATATYPE *pattern2) =0;

	Distance* sharing_clone() const {throw fst_error("Distance::sharing_clone() not supported, use Distance::clone() instead.");}
	Distance* stateless_clone() const {throw fst_error("Distance::stateless_clone() not supported, use Distance::clone() instead.");}
protected:
	DIMTYPE _n;
	DIMTYPE _d;
	boost::scoped_array<DIMTYPE> _index;
};

template<typename DATATYPE, typename DIMTYPE, class SUBSET>
Distance<DATATYPE,DIMTYPE,SUBSET>::Distance(const Distance& d) :
	_n(d._n),
	_d(d._d)
{
	notify("Distance copy-constructor.");
	if(_n>0) {_index.reset(new DIMTYPE[_n]); memcpy((void *)_index.get(),(void *)(d._index).get(),sizeof(DIMTYPE)*_n);}
}

template<typename DATATYPE, typename DIMTYPE, class SUBSET>
void Distance<DATATYPE,DIMTYPE,SUBSET>::reset(const DIMTYPE n)
{
	assert(n>0);
	_n=n;
	_index.reset(new DIMTYPE[_n]);
	for(DIMTYPE i=0;i<_n;i++) _index[i]=i;
}

template<typename DATATYPE, typename DIMTYPE, class SUBSET>
void Distance<DATATYPE,DIMTYPE,SUBSET>::narrow_to(const PSubset sub)
{
	assert(_n>=sub->get_d_raw()); //enough space to store subset ?
	assert(_index);
	DIMTYPE f;
	_d=0;
	for(bool b=sub->getFirstFeature(f);b!=false;b=sub->getNextFeature(f)) _index[_d++]=f;
	assert(_d>0);
}

template<typename DATATYPE, typename DIMTYPE, class SUBSET>
void Distance<DATATYPE,DIMTYPE,SUBSET>::denarrow()
{
	assert(_index);
	for(DIMTYPE i=0;i<_n;i++) _index[i]=i;
	_d=_n;
}

} // namespace
#endif // FSTDISTANCE_H ///:~


// Copyright (c) 2014-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "chainparams.h"
#include "validation.h"
#include "net.h"

#include "test/test_bitcoin.h"

#include <boost/signals2/signal.hpp>
#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(main_tests, TestingSetup)

static void TestBlockSubsidyHalvings(const Consensus::Params& consensusParams)
{
    int maxHalvings = 64;
    CAmount nInitialSubsidy = 50 * COIN;

    BOOST_CHECK_EQUAL(GetBlockSubsidy(1, consensusParams, consensusParams.nMTPSwitchTime-1000), nInitialSubsidy);
    nInitialSubsidy /= consensusParams.nMTPRewardReduction;
    BOOST_CHECK_EQUAL(GetBlockSubsidy(2, consensusParams, consensusParams.nMTPSwitchTime), nInitialSubsidy);

    CAmount nPreviousSubsidy = nInitialSubsidy;
    for (int nHalvings = 1; nHalvings < maxHalvings; nHalvings++) {
        int nHeight = consensusParams.nSubsidyHalvingFirst + (nHalvings-1) * consensusParams.nSubsidyHalvingInterval;
        if (nHeight >= consensusParams.nSubsidyHalvingStopBlock)
            break;
        CAmount nSubsidy = GetBlockSubsidy(nHeight, consensusParams, consensusParams.nMTPSwitchTime);
        BOOST_CHECK(nSubsidy <= nInitialSubsidy);
        if(nHeight > 0)
            BOOST_CHECK_EQUAL(nSubsidy, nPreviousSubsidy / 2);
        nPreviousSubsidy = nPreviousSubsidy / 2;
    }
    BOOST_CHECK_EQUAL(GetBlockSubsidy(consensusParams.nSubsidyHalvingStopBlock, consensusParams), 0);
}

BOOST_AUTO_TEST_CASE(block_subsidy_test)
{
    TestBlockSubsidyHalvings(Params(CBaseChainParams::MAIN).GetConsensus()); // As in main
    //TestBlockSubsidyHalvings(1000); // Just another interval
}

BOOST_AUTO_TEST_CASE(subsidy_limit_test)
{
    Consensus::Params consensusParams = Params(CBaseChainParams::MAIN).GetConsensus();
    CAmount nSum = 0;
    const int nMTPFirstBlock = 117564;
    int lastHalving = (consensusParams.nSubsidyHalvingStopBlock - consensusParams.nSubsidyHalvingFirst)/consensusParams.nSubsidyHalvingInterval;
    int lastHalvingBlock = consensusParams.nSubsidyHalvingFirst + lastHalving*consensusParams.nSubsidyHalvingInterval;

    int step = 1;

    for(int nHeight = 0; nHeight < 14000000; nHeight += step)
    {
        if (nHeight == consensusParams.nSubsidyHalvingFirst)
            step = 1000;
        else if (nHeight == lastHalvingBlock)
            step = 1;
        else if (nHeight == consensusParams.nSubsidyHalvingStopBlock)
            step = 10000;

        CAmount nSubsidy = GetBlockSubsidy(nHeight, consensusParams, nHeight<nMTPFirstBlock ? consensusParams.nMTPSwitchTime-1000 : consensusParams.nMTPSwitchTime);
        if (nHeight == 0)
            nSubsidy = 50*COIN;
        BOOST_CHECK(nSubsidy <= 50 * COIN);
        nSum += nSubsidy * step;
        BOOST_CHECK(MoneyRange(nSum));
    }
    BOOST_CHECK_EQUAL(nSum, 2095751201171875ULL);
}

bool ReturnFalse() { return false; }
bool ReturnTrue() { return true; }

BOOST_AUTO_TEST_CASE(test_combiner_all)
{
    boost::signals2::signal<bool (), CombinerAll> Test;
    BOOST_CHECK(Test());
    Test.connect(&ReturnFalse);
    BOOST_CHECK(!Test());
    Test.connect(&ReturnTrue);
    BOOST_CHECK(!Test());
    Test.disconnect(&ReturnFalse);
    BOOST_CHECK(Test());
    Test.disconnect(&ReturnTrue);
    BOOST_CHECK(Test());
}
BOOST_AUTO_TEST_SUITE_END()


// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

#include "conv.h"
#include "common.h"
#include "gemm.h"
#include "utils/shape_utils.h"
#include <Eigen/Core>
#include <Eigen/Dense>
#include <vector>

// Wasm interop method
void conv_f32(void *data) {
  uint32_t *dataIndex = static_cast<uint32_t *>(data);
  uint32_t const argc = dataIndex[0];
  conv2D_f32_imp(
      PARAM_FLOAT_PTR(data, dataIndex[1]), PARAM_INT32_PTR(data, dataIndex[2]),
      PARAM_FLOAT_PTR(data, dataIndex[3]), PARAM_INT32_PTR(data, dataIndex[4]),
      PARAM_FLOAT_PTR(data, dataIndex[5]), PARAM_INT32_PTR(data, dataIndex[6]),
      PARAM_FLOAT_PTR(data, dataIndex[7]), PARAM_INT32_PTR(data, dataIndex[8]),
      PARAM_INT32(data, dataIndex[9]), PARAM_INT32_PTR(data, dataIndex[10]),
      PARAM_INT32_PTR(data, dataIndex[11]), PARAM_INT32(data, dataIndex[12]));
}

// TODO: There's lots of room for optimization here
// Core operator implementation
void conv2D_f32_imp(float *X, int *X_shape, float *W, int *W_shape, float *Y,
                    int *Y_shape, float *bias, int *dilations, int group,
                    int *pads, int *strides, int dimension) {
  const int N = X_shape[0]; // Batch size
  const int C = X_shape[1]; // Number of input channels

  const int M = W_shape[0]; // Output channels

  const int CG = C / group;

  std::vector<int32_t> inputSizes(X_shape,
                                  X_shape + dimension +
                                      2); // Input dimension sizes
  std::vector<int32_t> kernelSizes(W_shape,
                                   W_shape + dimension +
                                       2); // Kernel dimension sizes
  std::vector<int32_t> outputSizes(Y_shape,
                                   Y_shape + dimension +
                                       2); // Output dimension sizes

  const size_t kernelSize = ShapeUtils::size_from_dims(kernelSizes);
  const size_t inputSize = ShapeUtils::size_from_dims(inputSizes);
  const size_t outputSize = ShapeUtils::size_from_dims(outputSizes);

  const size_t outputDataSize = ShapeUtils::size_from_dims(outputSizes, 2);
  const size_t kernelDataSize = ShapeUtils::size_from_dims(kernelSizes, 2);

  std::vector<int32_t> outputStrides = ShapeUtils::compute_strides(outputSizes);
  std::vector<int32_t> inputStrides = ShapeUtils::compute_strides(inputSizes);
  std::vector<int32_t> kernelStrides = ShapeUtils::compute_strides(kernelSizes);

  std::vector<int32_t> outputIndices(dimension + 2, 0);
  size_t outputIndex = 0;

  std::vector<int32_t> inputIndices(dimension + 2, 0);
  size_t inputIndex = 0;

  std::vector<int32_t> kernelIndices(dimension + 2, 0);
  size_t kernelIndex = 0;

  // Iterate over all batches
  for (int32_t n = 0; n < N; n++) {
    outputIndices[0] = n;
    inputIndices[0] = n;
    // Iterate over all output channels
    for (int32_t m = 0; m < M; m++) {
      outputIndices[1] = m;
      kernelIndices[0] = m;
      if (bias != nullptr) {
        const float b = bias[m];

        size_t outIx = n * outputStrides[0] + m * outputStrides[1];
        const size_t end = outIx + outputDataSize;

        while (outIx < end) {
          Y[outIx] = b;
          outIx++;
        }
      }

      for (int32_t cg = 0; cg < CG; cg++) {
        const int32_t c = m * CG + cg;
        kernelIndices[1] = cg;
        inputIndices[1] = c;

        std::fill(outputIndices.begin() + 2, outputIndices.end(), 0);

        size_t outputBase = outputStrides[0] * n + outputStrides[1] * m;
        outputIndex = 0;
        while (outputIndex < outputDataSize) {

          std::fill(kernelIndices.begin() + 2, kernelIndices.end(), 0);
          kernelIndex = 0;
          size_t kernelBase = kernelStrides[0] * m + kernelStrides[1] * cg;
          while (kernelIndex < kernelDataSize) {
            bool skip = false;

            inputIndex = n * inputStrides[0] + c * inputStrides[1];
            for (size_t axis = 0; axis < dimension; axis++) {
              const int32_t ix = outputIndices[axis + 2] * strides[axis] -
                                 pads[axis] +
                                 kernelIndices[axis + 2] * dilations[axis];

              if (ix < 0 || ix >= inputSizes[axis + 2]) {
                skip = true;
                break;
              }

              inputIndices[axis + 2] = ix;
              inputIndex += inputStrides[axis + 2] * ix;
            }

            if (!skip) {
              Y[outputBase + outputIndex] +=
                  W[kernelBase + kernelIndex] * X[inputIndex];
            }

            ShapeUtils::increment_index(kernelIndices, kernelSizes,
                                        dimension + 2);
            kernelIndex++;
          }

          outputIndex++;
          ShapeUtils::increment_index(outputIndices, outputSizes,
                                      dimension + 2);
        }
      }
    }
  }
}


#pragma once

#include <utility>

#include <boost/operators.hpp>

#include <elle/cryptography/fwd.hh>
#include <elle/cryptography/types.hh>
#include <elle/cryptography/SecretKey.hh>
#include <elle/cryptography/Oneway.hh>
#include <elle/cryptography/Cipher.hh>
#include <elle/cryptography/dh/fwd.hh>

#include <elle/attribute.hh>
#include <elle/operator.hh>

//
// ---------- Class -----------------------------------------------------------
//

namespace elle
{
  namespace cryptography
  {
    namespace dh
    {
      /// Represent a private key in the DH asymmetric cryptosystem.
      class PrivateKey
        : public elle::Printable
        , private boost::totally_ordered<PrivateKey>
      {
        /*-------------.
        | Construction |
        `-------------*/
      public:
        /// Construct a private key based on the given EVP_PKEY key whose
        /// ownership is transferred.
        explicit
        PrivateKey(::EVP_PKEY* key);
        /// Construct a private key based on the given DH key whose
        /// ownership is transferred to the private key.
        explicit
        PrivateKey(::DH* dh);
        PrivateKey(PrivateKey const& other);
        PrivateKey(PrivateKey&& other);
        virtual
        ~PrivateKey() = default;

        /*--------.
        | Methods |
        `--------*/
      private:
        /// Construct the object based on the given DH structure whose
        /// ownership is transferred to the callee.
        void
        _construct(::DH* dh);
        /// Check that the key is valid.
        void
        _check() const;
      public:
        /// Compute a shared session secret key based on the peer's public
        /// key.
        SecretKey
        agree(PublicKey const& peer_K) const;
        /// Return the private key's size in bytes.
        uint32_t
        size() const;
        /// Return the private key's length in bits.
        uint32_t
        length() const;

        /*----------.
        | Operators |
        `----------*/
      public:
        bool
        operator ==(PrivateKey const& other) const;
        ELLE_OPERATOR_NO_ASSIGNMENT(PrivateKey);

        /*----------.
        | Printable |
        `----------*/
      public:
        void
        print(std::ostream& stream) const override;

        /*-----------.
        | Attributes |
        `-----------*/
      private:
        ELLE_ATTRIBUTE_R(types::EVP_PKEY, key);
        ELLE_ATTRIBUTE_R(Oneway, digest_algorithm);
      };
    }
  }
}

namespace std
{
  template <>
  struct hash<elle::cryptography::dh::PrivateKey>
  {
    size_t
    operator ()(elle::cryptography::dh::PrivateKey const& value) const;
  };
}