//          Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <string>
#include <Eigen/Core>
#include "test_case.hpp"
#include "metro/likelihood/Mixture.hpp"
#include "metro/IndependentObservationLogLikelihood.hpp"

typedef Eigen::MatrixXd Matrix ;
typedef Eigen::VectorXd Vector ;

// #define DEBUG_MULTIVARIATE_T 1

BOOST_AUTO_TEST_SUITE( test_mixture )

namespace {
	struct ConstantLogLikelihood: public metro::IndependentObservationLogLikelihood< double, Eigen::VectorXd, Eigen::MatrixXd > {
		typedef std::auto_ptr< ConstantLogLikelihood > UniquePtr ;

		ConstantLogLikelihood( Eigen::MatrixXd const& data ):
			m_n( data.rows() ),
			m_parameters( Eigen::VectorXd::Constant( 1, 0 ) )
		{}

		void set_data( Eigen::MatrixXd const& data ) {
			m_n = data.rows() ;
		}

		void evaluate_at( Vector const& parameters, int numberOfDerivatives = 2 ) {
			evaluate_at( parameters, metro::DataRange( 0, m_n ), numberOfDerivatives ) ;
		}

		void evaluate_at( Vector const& parameters, metro::DataSubset const& subset, int numberOfDerivatives = 2 ) {
			assert( parameters.size() == 1 ) ;
			m_parameters = parameters ;
			evaluate( numberOfDerivatives ) ;
		}

		void evaluate( int numberOfDerivatives = 2 ) {
			// nothing to do
		}

		double get_value_of_function() const {
			return m_n * m_parameters(0) ;
		}

		Eigen::VectorXd get_value_of_first_derivative() const {
			assert(0) ;
		}
		Eigen::MatrixXd get_value_of_second_derivative() const {
			assert(0) ;
		}

		int number_of_parameters() const { return m_parameters.size() ; }

		Eigen::VectorXd parameters() const {
			return m_parameters ;
		}

		std::string get_summary() const {
			return "ConstantLogLikelihood" ;
		}

		virtual void get_terms_of_function( MatrixRef result ) const {
			assert( result.rows() == m_n ) ;
			result.setConstant( m_parameters(0) ) ;
		} ;

	private:
		int m_n ;
		Eigen::VectorXd m_parameters ;
	} ;
}

AUTO_TEST_CASE( test_parameters1 ) {
	// A single data point with probability 1 under one component and 100 under the other
	Eigen::MatrixXd data = Eigen::MatrixXd::Zero( 1, 0 ) ;

	metro::likelihood::Mixture< double, Eigen::VectorXd, Eigen::MatrixXd > mixture( data ) ;
	// ConstantLogLikelihood has a single parameter, by default equal to 0.
	BOOST_CHECK_EQUAL( mixture.parameters().size(), 0 ) ;
	mixture.add_component( "one", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	BOOST_CHECK_EQUAL( mixture.parameters().size(), 1 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(0), 0 ) ;

	mixture.add_component( "two", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	BOOST_CHECK_EQUAL( mixture.parameters().size(), 3 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(0), 0 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(1), 0.5 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(2), 0 ) ;

	mixture.add_component( "three", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	BOOST_CHECK_EQUAL( mixture.parameters().size(), 5 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(0), 0 ) ;
	BOOST_CHECK_CLOSE( mixture.parameters()(1), 1/3.0, 1E-12 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(2), 0 ) ;
	BOOST_CHECK_CLOSE( mixture.parameters()(3), 1/3.0, 1E-12 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(4), 0 ) ;

	mixture.add_component( "three", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	BOOST_CHECK_EQUAL( mixture.parameters().size(), 7 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(0), 0 ) ;
	BOOST_CHECK_CLOSE( mixture.parameters()(1), 1/4.0, 1E-12 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(2), 0 ) ;
	BOOST_CHECK_CLOSE( mixture.parameters()(3), 1/4.0, 1E-12 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(4), 0 ) ;
	BOOST_CHECK_CLOSE( mixture.parameters()(5), 1/4.0, 1E-12 ) ;
	BOOST_CHECK_EQUAL( mixture.parameters()(6), 0 ) ;
}

AUTO_TEST_CASE( test_parameters2 ) {
	// A single data point with probability 1 under one component and 100 under the other
	Eigen::MatrixXd data = Eigen::MatrixXd::Zero( 1, 0 ) ;

	metro::likelihood::Mixture< double, Eigen::VectorXd, Eigen::MatrixXd > mixture( data ) ;
	mixture.add_component( "one", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	{
		Eigen::VectorXd params( 1 ) ;
		params << 5 ;
		mixture.evaluate_at( params ) ;
		BOOST_CHECK_EQUAL( mixture.parameters()(0), 5 ) ;
	}
	mixture.add_component( "two", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	{
		Eigen::VectorXd params( 3 ) ;
		params << 5, 0.5, 3 ;
		mixture.evaluate_at( params ) ;
		BOOST_CHECK_EQUAL( mixture.parameters()(0), 5 ) ;
		BOOST_CHECK_EQUAL( mixture.parameters()(1), 0.5 ) ;
		BOOST_CHECK_EQUAL( mixture.parameters()(2), 3 ) ;
	}
}

AUTO_TEST_CASE( test_one_component_mixture ) {
	// A single data point with probability 1 under one component and 100 under the other
	Eigen::MatrixXd data = Eigen::MatrixXd::Zero( 1, 0 ) ;

	metro::likelihood::Mixture< double, Eigen::VectorXd, Eigen::MatrixXd > mixture( data ) ;
	mixture.add_component( "one", 1, ConstantLogLikelihood::UniquePtr( new ConstantLogLikelihood( data ) ) ) ;
	ConstantLogLikelihood ll( data ) ;
	ll.set_data( data ) ;
	mixture.set_data( data ) ;

	Eigen::VectorXd parameters = Eigen::VectorXd::Constant( 1, 0.5 ) ;
	mixture.evaluate_at( parameters ) ;
	ll.evaluate_at( parameters ) ;
	// should be 0.5 * 1 + 0.5 * 100 = 50.5
	BOOST_CHECK_CLOSE( mixture.get_value_of_function(), ll.get_value_of_function(), 1E-6 ) ;
	BOOST_CHECK_CLOSE( mixture.get_value_of_function(), 0.5, 1E-6 ) ;
}

AUTO_TEST_CASE( test_two_component_mixture ) {
	Eigen::MatrixXd data = Eigen::MatrixXd::Zero( 1, 0 ) ;

	// Equally-weighted mixture...
	metro::likelihood::Mixture< double, Eigen::VectorXd, Eigen::MatrixXd > mixture( data ) ;
	mixture.add_component( "one", 1, new ConstantLogLikelihood( data ) ) ;
	mixture.add_component( "two", 1, new ConstantLogLikelihood( data ) ) ;
	mixture.set_data( data ) ;

	Eigen::VectorXd parameters( 3 ) ;
	// 50:50 mixture of 1 and 100.
	parameters << std::log( 1 ), 0.5, std::log( 100 ) ;
	mixture.evaluate_at( parameters ) ;
	BOOST_CHECK_CLOSE( mixture.get_value_of_function(), std::log( 50.5 ), 1E-7 ) ;

	// 50:50 Mixture of 8 and 16.
	parameters << std::log( 8 ), 0.5, std::log( 16 ) ;
	mixture.evaluate_at( parameters ) ;
	BOOST_CHECK_CLOSE( mixture.get_value_of_function(), std::log( 12 ), 1E-7 ) ;

	// 0:100 Mixture of 8 and 16.
	parameters << std::log( 8 ), 0, std::log( 16 ) ;
	mixture.evaluate_at( parameters ) ;
	BOOST_CHECK_CLOSE( mixture.get_value_of_function(), std::log( 16 ), 1E-7 ) ;

	// 1:0 Mixture of 8 and 16.
	parameters << std::log( 8 ), 1, std::log( 16 ) ;
	mixture.evaluate_at( parameters ) ;
	BOOST_CHECK_CLOSE( mixture.get_value_of_function(), std::log( 8 ), 1E-7 ) ;
}

BOOST_AUTO_TEST_SUITE_END()


/*
 Copyright (C) 2018 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 <ored/portfolio/commodityoption.hpp>

#include <boost/make_shared.hpp>

#include <ql/errors.hpp>
#include <ql/exercise.hpp>
#include <ql/instruments/compositeinstrument.hpp>
#include <ql/instruments/vanillaoption.hpp>

#include <ored/portfolio/builders/commodityoption.hpp>
#include <ored/portfolio/enginefactory.hpp>
#include <ored/utilities/log.hpp>

using namespace std;
using namespace QuantLib;

namespace ore {
namespace data {

CommodityOption::CommodityOption(const Envelope& env, const OptionData& optionData, const string& commodityName,
                                 const string& currency, Real strike, Real quantity)
    : Trade("CommodityOption", env), optionData_(optionData), commodityName_(commodityName), currency_(currency),
      strike_(strike), quantity_(quantity) {}

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

    // Checks
    QL_REQUIRE(quantity_ > 0, "Commodity option requires a positive quatity");
    QL_REQUIRE(strike_ > 0, "Commodity option requires a positive strike");
    QL_REQUIRE(tradeActions().empty(), "Trade actions not supported for commodity option");
    QL_REQUIRE(optionData_.style() == "European",
               "Option style is '" << optionData_.style() << "'. Only European style is supported");
    QL_REQUIRE(optionData_.exerciseDates().size() == 1, "Expect a single exercise date for European option");

    // Build up option trade data
    Currency ccy = parseCurrency(currency_);
    Option::Type type = parseOptionType(optionData_.callPut());
    Date expiryDate = parseDate(optionData_.exerciseDates().front());
    Position::Type positionType = parsePositionType(optionData_.longShort());

    // Create the QuantLib instrument (option for unit of commodity)
    boost::shared_ptr<StrikedTypePayoff> payoff = boost::make_shared<PlainVanillaPayoff>(type, strike_);
    boost::shared_ptr<Exercise> exercise = boost::make_shared<EuropeanExercise>(expiryDate);
    boost::shared_ptr<Instrument> vanilla = boost::make_shared<VanillaOption>(payoff, exercise);

    // Attach our engine
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder(tradeType_);
    QL_REQUIRE(builder, "No builder found for " << tradeType_);
    boost::shared_ptr<CommodityOptionEngineBuilder> engineBuilder =
        boost::dynamic_pointer_cast<CommodityOptionEngineBuilder>(builder);
    vanilla->setPricingEngine(engineBuilder->engine(commodityName_, ccy));

    // Take care of fees
    vector<boost::shared_ptr<Instrument>> additionalInstruments;
    vector<Real> additionalMultipliers;
    if (optionData_.premiumPayDate() != "" && optionData_.premiumCcy() != "") {
        // Pay premium if long, receive if short
        Real premiumAmount = optionData_.premium();
        if (positionType == Position::Long)
            premiumAmount *= -1.0;

        Currency premiumCurrency = parseCurrency(optionData_.premiumCcy());
        Date premiumDate = parseDate(optionData_.premiumPayDate());

        addPayment(additionalInstruments, additionalMultipliers, premiumDate, premiumAmount, premiumCurrency, ccy,
                   engineFactory, engineBuilder->configuration(MarketContext::pricing));

        DLOG("Option premium added for commodity option " << id());
    }

    // Create the composite instrument wrapper (option trade + fee "trade")
    Real factor = positionType == Position::Long ? quantity_ : -quantity_;
    instrument_ = boost::make_shared<VanillaInstrument>(vanilla, factor, additionalInstruments, additionalMultipliers);

    // We just use strike to get notional rather than spot price
    notional_ = strike_ * quantity_;

    npvCurrency_ = currency_;
    maturity_ = expiryDate;
}

void CommodityOption::fromXML(XMLNode* node) {

    Trade::fromXML(node);

    XMLNode* commodityNode = XMLUtils::getChildNode(node, "CommodityOptionData");
    QL_REQUIRE(commodityNode, "A commodity option needs a 'CommodityOptionData' node");

    optionData_.fromXML(XMLUtils::getChildNode(commodityNode, "OptionData"));

    commodityName_ = XMLUtils::getChildValue(commodityNode, "Name", true);
    currency_ = XMLUtils::getChildValue(commodityNode, "Currency", true);
    strike_ = XMLUtils::getChildValueAsDouble(commodityNode, "Strike", true);
    quantity_ = XMLUtils::getChildValueAsDouble(commodityNode, "Quantity", true);
}

XMLNode* CommodityOption::toXML(XMLDocument& doc) {

    XMLNode* node = Trade::toXML(doc);

    XMLNode* eqNode = doc.allocNode("CommodityOptionData");
    XMLUtils::appendNode(node, eqNode);

    XMLUtils::appendNode(eqNode, optionData_.toXML(doc));

    XMLUtils::addChild(doc, eqNode, "Name", commodityName_);
    XMLUtils::addChild(doc, eqNode, "Currency", currency_);
    XMLUtils::addChild(doc, eqNode, "Strike", strike_);
    XMLUtils::addChild(doc, eqNode, "Quantity", quantity_);

    return node;
}

} // namespace data
} // namespace ore


#pragma once

#include <boost/hana/for_each.hpp>
#include <boost/hana/size.hpp>
#include <boost/hana/tuple.hpp>

namespace codys {

namespace detail {

template <class T, class Tuple>
struct TagIndex;

template <class T, typename... Ts>
struct TagIndex<T, boost::hana::tuple<Ts...>> {
    static constexpr int index = []() {
        constexpr std::array<bool, sizeof...(Ts)> a{
            {std::is_same<T, typename Ts::Operand>::value...}};

        const auto it = std::find(a.begin(), a.end(), true);
        if (it == a.end()) {
            return static_cast<decltype(std::distance(a.begin(), it))>(
                sizeof...(Ts));
        }

        return std::distance(a.begin(), it);
    }();
};

template <typename SystemType, typename StateSpaceType>
constexpr bool all_states_have_derivatives() {
    constexpr auto states = typename SystemType::UnderlyingType{};
    constexpr auto stateSize = decltype(boost::hana::size(states))::value;
    constexpr auto stateIndices = std::make_index_sequence<stateSize>{};

    bool ret = true;
    boost::hana::for_each(stateIndices, [&ret](auto idx) {
        constexpr auto ind = TagIndex<
            typename std::remove_cvref_t<decltype(boost::hana::at(states,
                                                                  idx))>,
            std::remove_cvref_t<decltype(StateSpaceType::make_dot())>>::index;

        ret &= (ind >= 0) && (ind < stateSize);
    });

    return ret;
}

template <class T, class Tuple>
struct Contains;

template <class T, typename... Ts>
struct Contains<T, boost::hana::tuple<Ts...>> {
    static constexpr bool value = []() {
        constexpr std::array<bool, sizeof...(Ts)> a{
            {std::is_same<T, Ts>::value...}};

        return std::find(a.begin(), a.end(), true) != a.end();
    }();
};

template<class Unit>
using derivative_in_time_t = decltype(std::declval<Unit>() / (units::isq::si::time<units::isq::si::second>{}));

} // namespace detail

template <typename T>
concept PhysicalType = requires {
    typename T::Unit;
};

template <typename T>
concept PhysicalValue = requires (T t) {
    t.number();
};

template <typename SystemType>
concept TypeIndexedList = requires(SystemType sys, typename SystemType::UnderlyingType states) {
    typename SystemType::UnderlyingType;

   {sys.template idx_of<std::remove_cvref_t<decltype(boost::hana::at(states,boost::hana::int_c<0>))>>()} -> std::same_as<std::size_t>;
};

template <typename T, typename SystemType>
concept SystemStateFor = PhysicalType<T> && detail::Contains<T, typename SystemType::UnderlyingType>::value;

template<typename Expression, typename Operand>
concept TimeDerivativeOf = std::is_same_v<detail::derivative_in_time_t<typename Operand::Unit>, typename Expression::Unit>;

template <typename T, typename SystemType>
concept DerivativeSystemOf = TypeIndexedList<SystemType> && detail::all_states_have_derivatives<SystemType, T>();

} // namespace codys


/*
 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 <ored/portfolio/builders/capfloor.hpp>
#include <ored/utilities/log.hpp>

#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>

#include <boost/make_shared.hpp>

namespace ore {
namespace data {

boost::shared_ptr<PricingEngine> CapFloorEngineBuilder::engineImpl(const Currency& ccy) {
    const string& ccyCode = ccy.code();
    Handle<YieldTermStructure> yts = market_->discountCurve(ccyCode, configuration(MarketContext::pricing));
    QL_REQUIRE(!yts.empty(), "engineFactory error: yield term structure not found for currency " << ccyCode);
    Handle<OptionletVolatilityStructure> ovs = market_->capFloorVol(ccyCode, configuration(MarketContext::pricing));
    QL_REQUIRE(!ovs.empty(), "engineFactory error: caplet volatility structure not found for currency " << ccyCode);

    switch (ovs->volatilityType()) {
    case ShiftedLognormal:
        LOG("Build BlackCapFloorEngine for currency " << ccyCode);
        return boost::make_shared<BlackCapFloorEngine>(yts, ovs, ovs->displacement());
        break;
    case Normal:
        LOG("Build BachelierSwaptionEngine for currency " << ccyCode);
        return boost::make_shared<BachelierCapFloorEngine>(yts, ovs);
        break;
    default:
        QL_FAIL("Caplet volatility type, " << ovs->volatilityType() << ", not covered in EngineFactory");
        break;
    }
}
} // namespace data
} // namespace ore


#include "channel_encryption.hpp"

#include <boost/algorithm/hex.hpp>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <sodium.h>

#include <exception>
#include <string>

std::vector<uint8_t> hexToBytes(const std::string& hex) {
    std::vector<uint8_t> temp;
    boost::algorithm::unhex(hex, std::back_inserter(temp));
    return temp;
}

template <typename T>
ChannelEncryption<T>::ChannelEncryption(const std::vector<uint8_t>& private_key)
    : private_key_(private_key) {}

template <typename T>
std::vector<uint8_t> ChannelEncryption<T>::calculateSharedSecret(
    const std::vector<uint8_t>& pubKey) const {
    std::vector<uint8_t> sharedSecret(crypto_scalarmult_BYTES);
    if (pubKey.size() != crypto_scalarmult_curve25519_BYTES) {
        throw std::runtime_error("Bad pubKey size");
    }
    if (crypto_scalarmult(sharedSecret.data(), this->private_key_.data(),
                          pubKey.data()) != 0) {
        throw std::runtime_error(
            "Shared key derivation failed (crypto_scalarmult)");
    }
    return sharedSecret;
}

template <typename T>
T ChannelEncryption<T>::encrypt(const T& plaintext,
                                const std::string& pubKey) const {
    const std::vector<uint8_t> pubKeyBytes = hexToBytes(pubKey);
    const std::vector<uint8_t> sharedKey = calculateSharedSecret(pubKeyBytes);

    // Initialise cipher
    const EVP_CIPHER* cipher = EVP_aes_256_cbc();
    const int ivLength = EVP_CIPHER_iv_length(cipher);

    // Generate IV
    unsigned char iv[ivLength];
    if (RAND_bytes(iv, ivLength) != 1) {
        throw std::runtime_error("Could not generate IV");
    }

    // Initialise cipher context
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (EVP_EncryptInit_ex(ctx, cipher, NULL, sharedKey.data(), iv) <= 0) {
        throw std::runtime_error("Could not initialise encryption context");
    }

    int len;
    size_t ciphertext_len = 0;
    auto p = reinterpret_cast<const unsigned char*>(plaintext.data());
    const size_t plaintext_len = plaintext.size();

    // Add some padding of 'blockSize' as upper limit
    const int blockSize = EVP_CIPHER_CTX_block_size(ctx);
    T output;
    output.resize(plaintext_len + blockSize);
    auto o = reinterpret_cast<unsigned char*>(&output[0]);

    // Encrypt every full blocks
    if (EVP_EncryptUpdate(ctx, o, &len, p, plaintext_len) <= 0) {
        throw std::runtime_error("Could not encrypt plaintext");
    }
    ciphertext_len += len;

    // Encrypt any remaining partial blocks
    if (EVP_EncryptFinal_ex(ctx, o + len, &len) <= 0) {
        throw std::runtime_error("Could not finalise encryption");
    }
    ciphertext_len += len;

    // Remove excess padding
    output.resize(ciphertext_len);

    // Insert iv at the start
    output.insert(output.begin(), iv, iv + ivLength);

    EVP_CIPHER_CTX_free(ctx);

    return output;
}

template <typename T>
T ChannelEncryption<T>::decrypt(const T& ciphertextAndIV,
                                const std::string& pubKey) const {
    const std::vector<uint8_t> pubKeyBytes = hexToBytes(pubKey);
    const std::vector<uint8_t> sharedKey = calculateSharedSecret(pubKeyBytes);

    // Initialise cipher
    const EVP_CIPHER* cipher = EVP_aes_256_cbc();
    const int ivLength = EVP_CIPHER_iv_length(cipher);

    auto inPtr = reinterpret_cast<const unsigned char*>(&ciphertextAndIV[0]);

    // Initialise cipher context
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (EVP_DecryptInit_ex(ctx, cipher, NULL, sharedKey.data(), inPtr) <= 0) {
        throw std::runtime_error("Could not initialise decryption context");
    }

    int len;
    size_t plaintextLength = 0;
    const size_t ciphertextLength = ciphertextAndIV.size() - ivLength;

    // Add some padding of 'blockSize' as upper limit
    const int blockSize = EVP_CIPHER_CTX_block_size(ctx);
    T output;
    output.resize(ciphertextLength + blockSize);

    auto outPtr = reinterpret_cast<unsigned char*>(&output[0]);

    // Decrypt every full blocks
    if (EVP_DecryptUpdate(ctx, outPtr, &len, inPtr + ivLength,
                          ciphertextLength) <= 0) {
        throw std::runtime_error("Could not initialise decryption context");
    }
    plaintextLength += len;

    // Decrypt any remaining partial blocks
    if (EVP_DecryptFinal_ex(ctx, outPtr + len, &len) <= 0) {
        throw std::runtime_error("Could not finalise decryption");
    }
    plaintextLength += len;

    // Remove excess bytes
    output.resize(plaintextLength);

    EVP_CIPHER_CTX_free(ctx);
    return output;
}

// explicit template specialization
template class ChannelEncryption<std::string>;

template class ChannelEncryption<std::vector<uint8_t>>;


/***************************************************************************
 *   Copyright (C) by gempa GmbH                                           *
 *                                                                         *
 *   You can redistribute and/or modify this program under the             *
 *   terms of the SeisComP Public License.                                 *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   SeisComP Public License for more details.                             *
 ***************************************************************************/

#define SEISCOMP_COMPONENT MN

#include <seiscomp3/logging/log.h>
#include <seiscomp3/system/environment.h>
#include <seiscomp3/math/geo.h>

#include <boost/thread/mutex.hpp>

#include "./regions.h"

//#define TEST_WITHOUT_REGIONS

namespace Seiscomp {
namespace Magnitudes {
namespace MN {

namespace {

#ifndef TEST_WITHOUT_REGIONS
bool validRegionInitialized = false;
Seiscomp::Geo::GeoFeatureSet validRegion;
boost::mutex regionMutex;
#endif

}

bool initialize(const Config::Config *config) {
#ifndef TEST_WITHOUT_REGIONS
	boost::mutex::scoped_lock l(regionMutex);

	if ( !validRegionInitialized ) {
		// Don't try again
		validRegionInitialized = true;

		std::string filename;

		try {
			filename = config->getString("magnitudes.MN.region");
		}
		catch ( ... ) {
			filename = "@DATADIR@/magnitudes/MN/MN.bna";
		}

		filename = Seiscomp::Environment::Instance()->absolutePath(filename);

		if ( !validRegion.readBNAFile(filename, NULL) ) {
			SEISCOMP_ERROR("Failed to read/parse MN region file: %s",
			               filename.c_str());
			return false;
		}
	}
	else if ( validRegion.features().empty() ) {
		// No region defined, nothing to do
		SEISCOMP_ERROR("No regions defined in amplitudes.MN.region file");
		return false;
	}

#endif
	return true;
}

bool isInsideRegion(double lat0, double lon0,
                    double lat1, double lon1) {
#ifndef TEST_WITHOUT_REGIONS
	double dist, az, baz;

	Math::Geo::delazi_wgs84(lat0, lon0, lat1, lon1, &dist, &az, &baz);

	// Convert to km
	dist = Math::Geo::deg2km(dist);

	// Check path each 10 km
	int steps = dist / 10;

	for ( int i = 1; i < steps; ++i ) {
		Math::Geo::delandaz2coord(Math::Geo::km2deg(dist*i/steps), az, lat0, lon0,
		                          &lat1, &lon1);
		if ( !isInsideRegion(lat1, lon1) )
			return false;
	}
#endif
	return true;
}

bool isInsideRegion(double lat, double lon) {
#ifndef TEST_WITHOUT_REGIONS
	boost::mutex::scoped_lock l(regionMutex);
	size_t numFeatures = validRegion.features().size();
	for ( size_t i = 0; i < numFeatures; ++i ) {
		Seiscomp::Geo::GeoFeature *feature = validRegion.features()[i];
		if ( feature->contains(Seiscomp::Geo::Vertex(lat, lon)) )
			return true;
	}

	return false;
#else
	return true;
#endif
}

}
}
}


#include <string>
#include <sstream>
#include <iostream>
#include <map>
#include <regex>
#include <fstream>
#include <boost/algorithm/string.hpp>

#include "../common.hpp"

using namespace std;

std::ostream nullout(nullptr);

class IntcodeComputer {
    public:
        deque<long> inputs;
        long output = 0; // TODO should this be a list?
        vector<long> backup;
        vector<long> memory;
        bool halted = true;

        void load(istream& is);
        void run();    // Run the computer (until it halts or blocks on input)
        void resume(); // Resumes after being blocked
        bool hasHalted() const { return halted; }; // has the computer halted.

        void dump_memory();

    private:
        std::ostream & debug = nullout; // cout;

        int pc = 0;
        long relative_base = 0;

        long * param(int parameter, bool must_be_address = false);
        int ensure_bounds(int address);

        // Some debug prints
        string param_string(int parameter) const;
        string address_string(int address) const;
};

// Ensure there is enough memory (in the computer) to write to this address.
// This may cause pointers to change! Make sure any existing pointers into memory are refreshed.
int IntcodeComputer::ensure_bounds(int address) {
    assert(address >= 0);
    assert(address < 1024 * 1024 * 1024); // Arbitraray limit
    if (memory.size() < address) {
        memory.resize(address);
    }

    return address;
}

// Returns the the address of a value to read/write.
// This is always a valid pointer.
long * IntcodeComputer::param(int parameter, bool for_writing) {
    int offset = pow(10, parameter + 1);
    int mode = memory.at(pc) / offset % 10;

    if (mode == 0) { // position (read or writing)
        int address = ensure_bounds(memory.at(pc + parameter));
        return &memory[address];
    }
    if (mode == 2) { // relative base (read or writing)
        int address = ensure_bounds(memory.at(pc + parameter) + relative_base);
        return &memory[address];
    }

    assert(!for_writing);

    if (mode == 1) { // immediate (only for reading)
        return &memory[pc + parameter];
    }

    assert(false);
}

string IntcodeComputer::address_string(int address) const {
    if (address < 0 || address >= memory.size()) {
        return " (\?\?\?)";
    }
    return " (" + to_string(memory.at(address)) + ")";
}

// Debug string
string IntcodeComputer::param_string(int parameter) const {
    int offset = pow(10, parameter + 1);
    int mode = memory.at(pc) / offset % 10;
    if (mode == 0) { // position
        int address = memory.at(pc + parameter);
        return "&" + to_string(address) + address_string(address);
    }
    if (mode == 1) { // immediate
        int value = memory.at(pc + parameter);
        return to_string(value);
    }
    if (mode == 2) { // relative base
        int address = memory.at(pc + parameter) + relative_base;
        return "rel &" + to_string(relative_base) + " + " + to_string(memory.at(pc + parameter)) + address_string(address);
    }
    assert(false);
}

void IntcodeComputer::run() {
    this->memory = this->backup; // Reset the software
    ensure_bounds(1024 * 1024);

    this->pc = 0;
    this->relative_base = 0;
    this->halted = false;

    resume();
}

void IntcodeComputer::resume() {
    int last_pc = -1;
    while (pc >= 0 && pc < memory.size()) {
        assert(pc != last_pc); // Stuck in loop
        last_pc = pc;

        int op = memory.at(pc) % 100;
        switch (op) {
            case 1: { // add
                debug << "ADD " << param_string(3) << " = " << param_string(1) << " + " << param_string(2) << endl;

                long * dest = param(3, true);
                long * src1 = param(1);
                long * src2 = param(2);
                *dest = *src1 + *src2;
                pc += 4;
                break;
            }
            case 2: {// mul
                debug << "MUL " << param_string(3) << " = " << param_string(1) << " * " << param_string(2) << endl;

                long * dest = param(3, true);
                long * src1 = param(1);
                long * src2 = param(2);
                *dest = *src1 * *src2;
                pc += 4;
                break;
            }
            case 3: { // input
                debug << "INPUT " << param_string(1) << endl;

                if (inputs.size() <= 0 ) {
                    // Block (a resume() call has to be made)
                    return;
                }

                long * dest = param(1, true);
                *dest = inputs.front();
                inputs.pop_front();

                pc += 2;
                break;
            }
            case 4: { // output
                debug << "OUTPUT " << param_string(1) << endl;
                long * src = param(1);
                output = *src;
                pc += 2;
                break;
            }

            case 5: { // jump-if-true
                debug << "IF " << param_string(1) << " != TRUE then pc = " << param_string(2) << endl;
                long * cond = param(1);
                if (*cond != 0) {
                    pc = *param(2);
                } else {
                    pc += 3;
                }
                break;
            }

            case 6: { // jump-if-false
                debug << "IF " << param_string(1) << " = FALSE then pc = " << param_string(2) << endl;
                long * cond = param(1);
                if (*cond == 0) {
                    pc = *param(2);
                } else {
                    pc += 3;
                }
                break;
            }

            case 7: { // less than
                debug << param_string(3) << " = " << param_string(1) << " < " << param_string(2) << endl;
                long * dest = param(3, true);
                long * src1 = param(1);
                long * src2 = param(2);

                *dest = (*src1 < *src2) ? 1 : 0;

                pc += 4;
                break;
            }

            case 8: { // equals
                debug << param_string(3) << " = " << param_string(1) << " == " << param_string(2) << endl;
                long * dest = param(3, true);
                long * src1 = param(1);
                long * src2 = param(2);

                *dest = (*src1 == *src2) ? 1 : 0;

                pc += 4;
                break;
            }

            case 9: { // adjusts the relative base
                debug << "REL_BASE(" << this->relative_base << ") += " << param_string(1) << endl;

                long * base = param(1);
                this->relative_base += *base;

                pc += 2;
                break;
            }

            case 99: // break
                halted = true;
                return;
            default:
                halted = true;
                cout << "Unhandled op:" << op << " modes:" << (memory.at(pc) / 100) << endl;
                return;
        };
    }

    cout << "No more instructions without halting pc:" << pc << endl;
    dump_memory();
}

void IntcodeComputer::dump_memory() {
    for (int i = 0; i < memory.size(); i++) {
        cout << i << "\t" << memory[i] << endl;
    }
}

void IntcodeComputer::load(istream& in) {
    // Read the program
    this->backup.clear();

    string line;
    while (getline(in, line, ',')) {
        boost::trim(line);

        if (line == "") {
            continue;
        }

        backup.push_back(stoi(line));
    }
}

int main() {
    ifstream file ("2019/9.txt");
    if (!file.is_open()) {
        cout << "Failed to open file: " << strerror(errno) << endl;
        return -1;
    }

    IntcodeComputer computer;
    computer.load(file);
    computer.inputs.push_back(1);
    computer.run();

    long answer1 = computer.output;
    cout << "Answer 9.1: " << answer1 << endl;

    computer.inputs.push_back(2);
    computer.run();

    long answer2 = computer.output;
    cout << "Answer 9.2: " << answer2 << endl;
}


#ifndef BOOST_PREPROCESSOR_ARITHMETIC_DIV_HPP
#define BOOST_PREPROCESSOR_ARITHMETIC_DIV_HPP

/* Copyright (C) 2001
 * Housemarque Oy
 * http://www.housemarque.com
 *
 * Permission to copy, use, modify, sell and distribute this software is
 * granted provided this copyright notice appears in all copies. This
 * software is provided "as is" without express or implied warranty, and
 * with no claim as to its suitability for any purpose.
 *
 * See http://www.boost.org for most recent version.
 */

#include <boost/preprocessor/comparison/less_equal.hpp>
#include <boost/preprocessor/inc.hpp>

/**
Expands to the quotient of X and Y.

Both X and Y must expand to integer literals
in the range [0, BOOST_PP_LIMIT_MAG].

For example, BOOST_PP_DIV(4,3) expands to 1 (a
single token).

Uses

BOOST_PP_WHILE()

Test

arithmetic_test.cpp

*/
#define BOOST_PP_DIV(X,Y) BOOST_PP_DIV_D(0,X,Y)

/**
Can be used inside BOOST_PP_WHILE().
 */
#define BOOST_PP_DIV_D(D,X,Y) BOOST_PP_TUPLE_ELEM(3,0,BOOST_PP_WHILE##D(BOOST_PP_DIV_C,BOOST_PP_DIV_F,(0,X,Y)))
#if !defined(BOOST_NO_COMPILER_CONFIG) && defined(__MWERKS__)
#  define BOOST_PP_DIV_C(D,P) BOOST_PP_LESS_EQUAL_D(D,BOOST_PP_TUPLE_ELEM(3,2,P),BOOST_PP_TUPLE_ELEM(3,1,P))
#  define BOOST_PP_DIV_F(D,P) (BOOST_PP_INC(BOOST_PP_TUPLE_ELEM(3,0,P)),BOOST_PP_SUB_D(D,BOOST_PP_TUPLE_ELEM(3,1,P),BOOST_PP_TUPLE_ELEM(3,2,P)),BOOST_PP_TUPLE_ELEM(3,2,P))
#else
#  define BOOST_PP_DIV_C(D,P) BOOST_PP_LESS_EQUAL_D(D,BOOST_PP_TUPLE3_ELEM2 P,BOOST_PP_TUPLE3_ELEM1 P)
#  define BOOST_PP_DIV_F(D,P) (BOOST_PP_INC(BOOST_PP_TUPLE3_ELEM0 P),BOOST_PP_SUB_D(D,BOOST_PP_TUPLE3_ELEM1 P,BOOST_PP_TUPLE3_ELEM2 P),BOOST_PP_TUPLE3_ELEM2 P)
#endif

/*
Obsolete. Use BOOST_PP_DIV().
 */
#define BOOST_PREPROCESSOR_DIV(X,Y) BOOST_PP_DIV(X,Y)
#endif


#include <boost/geometry/strategies/geographic/distance_thomas.hpp>


#ifndef HYPERTRIE_ORDEREDJOIN_IMPL_HPP
#define HYPERTRIE_ORDEREDJOIN_IMPL_HPP
#include "hypertrie/internal/util/CONSTANTS.hpp"

#include "hypertrie/internal/Join_impl.hpp"
#include "hypertrie/internal/BoolHypertrie.hpp"
#include "hypertrie/internal/util/PermutationSort.hpp"
#include "hypertrie/internal/util/CONSTANTS.hpp"
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>

namespace {
	using namespace iter;
}
namespace hypertrie::internal {
	template<typename key_part_type>
	class Join<key_part_type, hypertrie::internal::container::boost_flat_map, hypertrie::internal::container::boost_flat_set> {

		template<typename key, typename value>
		using map_type = container::tsl_sparse_map<key, value>;
		template<typename key>
		using set_type = container::boost_flat_set<key>;

		using Boolhypertrie = BoolHypertrieImpl<key_part_type, map_type, set_type>;
		using poss_type = std::vector<pos_type>;
		using Diagonal = typename Boolhypertrie::Diagonal;
		const std::vector<Boolhypertrie> &hypertries;
		const std::vector<std::vector<pos_type>> &positions;

	public:
		Join(const std::vector<Boolhypertrie> &hypertries,
		     const std::vector<std::vector<pos_type>> &positions)
				: hypertries(hypertries), positions(positions) {}

		class iterator {

		public:
			using iterator_category = std::forward_iterator_tag;
			using value_type = std::pair<const std::vector<Boolhypertrie>, key_part_type>;
			using difference_type = ptrdiff_t;
			using pointer = value_type *;
			using reference = value_type &;
		private:
			Join &join;
			std::vector<Boolhypertrie> outs;
			std::vector<std::vector<pos_type>> out_poss;
			std::vector<Diagonal> ops;
			std::vector<std::vector<pos_type>> op_poss;
			std::vector<pos_type> ops_in_out;
			std::vector<pos_type> pos_of_ops_in_out;

			key_part_type current_key_part;
			key_part_type end_key_part;
			bool ended = false;

			value_type value{outs, current_key_part};
		public:
			explicit iterator(Join &join, bool ended = false) : join{join} {
				for (const auto[pos, join_poss, hypertrie] : zip(range(std::size(join.hypertries)), join.positions,
				                                                 join.hypertries)) {
					if (size(join_poss) > 0) {
						ops.emplace_back(hypertrie.getDiagonal(join_poss));
						op_poss.emplace_back(pos);
					}

					if (size(join_poss) < hypertrie.depth()) {
						outs.emplace_back(hypertrie);
						out_poss.emplace_back(pos);
					}

				}
				auto min = std::numeric_limits<key_part_type>::min();
				if (not narrowRange(min)) {
					this->ended = true;
					return;
				}
				optimizeOperandOrder();
				ops[0].setLower(min); // TODO: go on here
				next();
			}

			inline void update() {
				for (const auto[out_pos, op_pos] : zip(pos_of_ops_in_out, ops_in_out)) {
					outs[out_pos] = ops[op_pos].getValue();
				}
			}

			inline void next() {
				// check if the end was reached
				static bool found;
				// _current_key_part is increased if containsAndUpdateLower returns false
				Diagonal &current_operand = ops.front();
				if (current_operand.empty()) {
					ended = true;
					return;
				}
				current_key_part = current_operand.getKeyPart();
				while (current_key_part < end_key_part) {
					found = true;
					// iterate all but the first Diagonal
					for (auto &operand: util::skip<1>(ops)) {

						if (not operand.containsAndIncrement(current_key_part)) {
							if (operand.empty()) {
								ended = true;
								return;
							}

							current_key_part = current_operand.setLower(operand.getKeyPart());
							found = false;
							break;
						}
					}

					if (found) {
						++current_operand;
						update();
						return;
					}

				}
				ended = true;
			}

			iterator &operator++() {
				if (not ended)
					next();
				return *this;
			}

			const iterator operator++(int) &{
				iterator next_it = *this;
				++(*this);
				return next_it;
			}

			inline bool operator==(const iterator &other) const {
				return this == &other or
				       (this->ended and other.ended) or
				       (not(this->ended or other.ended) and
				        (this->current_key_part == other.current_key_part));
			}

			inline bool operator!=(const iterator &other) const {
				return not operator==(other);
			}

			inline bool operator==(bool other) const { return other == *this; }

			inline bool operator!=(bool other) const { return other != *this; }

			reference operator*() const {
				return value;
			}

			explicit operator bool() const {
				return ended;
			}

		private:
			void optimizeOperandOrder() {
				// TODO: test it
				static const auto compare_func = [&](const Diagonal &x, const Diagonal &y) {
					return x.size() < y.size();
				};
				const auto permutation = util::sort_permutation::get<
						compare_func>(ops);
				util::sort_permutation::apply(ops, permutation);
				util::sort_permutation::apply(op_poss, permutation);

				// finds the stored positions from op_poss that are also in out_poss
				// it stores where they are found in out_poss to ops_in_out

				ops_in_out.clear();
				pos_of_ops_in_out.clear();

				auto out_poss_begin = out_poss.cbegin();
				auto out_poss_end = out_poss.cend();

				for (const auto[pos, op_pos] : enumerate(op_poss)) {
					auto found = std::find(out_poss_begin, out_poss_end, op_pos);
					if (found != out_poss_end) {
						pos_of_ops_in_out.emplace_back(std::distance(out_poss_begin, found));
						ops_in_out.emplace_back(pos);
					}
				}
			}

			bool narrowRange(key_part_type &min) {
				auto max = std::numeric_limits<key_part_type>::max();
				// get min and max
				for (auto i : range(2)) // do it twice
					for (const auto &diag :ops) {
						diag.narrowRange(min, max); // min and max are passed by reference and updated
						if (diag.empty())
							return false;
					}
				return true;
			}

			iterator begin() { return iterator(*this); }

			bool end() const { return false; }
		};

		iterator begin() { return iterator(*this); }

		bool end() const { return false; }

	};
}

#endif //HYPERTRIE_ORDEREDJOIN_IMPL_HPP


#include "rawInput.hpp"
#include "CbcLagrangeSolver.hpp"
#include "ClpRecourseSolver.hpp"
#include "combineScenarios.hpp"
#include "lagrangeRootNode.hpp"
#include <boost/scoped_ptr.hpp>

using namespace std;
using boost::scoped_ptr;

class fakeIntegerWrapper : public rawInput {
public:
	fakeIntegerWrapper(const std::string &datarootname, int overrideScenarioNumber = 0, MPI_Comm comm = MPI_COMM_WORLD) :
		rawInput(datarootname, overrideScenarioNumber, comm) {}

	virtual bool isFirstStageColInteger(int col) { return true; }

};

int main(int argc, char **argv) {

	MPI_Init(&argc, &argv);

	int mype;
	MPI_Comm_rank(MPI_COMM_WORLD,&mype);

	if (argc != 4) {
		if (mype == 0) printf("Usage: %s [rawdump root name] [num scenarios] [number per subproblem]\n",argv[0]);
		return 1;
	}

	string datarootname(argv[1]);
	int nscen = atoi(argv[2]);
	int nper = atoi(argv[3]);

	if (mype == 0) printf("Initializing data interface\n");
	scoped_ptr<fakeIntegerWrapper> s(new fakeIntegerWrapper(datarootname,nscen));
	combinedInput in = combineScenarios(*s,nper,true);
	//combinedInput in = combineScenarios(*s,nper,false);

	lagrangeRootNode<CbcLagrangeSolver,ClpRecourseSolver>(in);

	MPI_Finalize();

	return 0;

}