#if !defined(ARCADIA_BUILD)
#    include <Common/config.h>
#endif
#include <Common/Exception.h>
#include <base/types.h>
#include <IO/VarInt.h>
#include <Compression/CompressionFactory.h>
#include <Compression/CompressionCodecEncrypted.h>
#include <Poco/Logger.h>
#include <base/logger_useful.h>
#include <Common/ErrorCodes.h>

// This depends on BoringSSL-specific API, notably <openssl/aead.h>.
#if USE_SSL && USE_INTERNAL_SSL_LIBRARY
#include <Parsers/ASTLiteral.h>
#include <openssl/digest.h> // Y_IGNORE
#include <openssl/err.h>
#include <boost/algorithm/hex.hpp>
#include <openssl/aead.h> // Y_IGNORE
#endif

// Common part for both parts (with SSL and without)
namespace DB
{

namespace ErrorCodes
{
    extern const int OPENSSL_ERROR;
}

namespace
{

/// Get string name for method. Return empty string for undefined Method
String getMethodName(EncryptionMethod Method)
{
    if (Method == AES_128_GCM_SIV)
    {
        return "AES_128_GCM_SIV";
    }
    else if (Method == AES_256_GCM_SIV)
    {
        return "AES_256_GCM_SIV";
    }
    else
    {
        return "";
    }
}

/// Get method code (used for codec, to understand which one we are using)
uint8_t getMethodCode(EncryptionMethod Method)
{
    if (Method == AES_128_GCM_SIV)
    {
        return uint8_t(CompressionMethodByte::AES_128_GCM_SIV);
    }
    else if (Method == AES_256_GCM_SIV)
    {
        return uint8_t(CompressionMethodByte::AES_256_GCM_SIV);
    }
    else
    {
        throw Exception("Wrong encryption Method. Got " + getMethodName(Method), ErrorCodes::BAD_ARGUMENTS);
    }
}

} // end of namespace

} // end of namespace DB

#if USE_SSL && USE_INTERNAL_SSL_LIBRARY
namespace DB
{

namespace ErrorCodes
{
    extern const int ILLEGAL_SYNTAX_FOR_CODEC_TYPE;
    extern const int LOGICAL_ERROR;
    extern const int BAD_ARGUMENTS;
    extern const int INCORRECT_DATA;
}

namespace
{
constexpr size_t tag_size        = 16;   /// AES-GCM-SIV always uses a tag of 16 bytes length
constexpr size_t key_id_max_size = 8;    /// Max size of varint.
constexpr size_t nonce_max_size  = 13;   /// Nonce size and one byte to show if nonce in in text

/// Get encryption/decryption algorithms.
auto getMethod(EncryptionMethod Method)
{
    if (Method == AES_128_GCM_SIV)
    {
        return EVP_aead_aes_128_gcm_siv;
    }
    else if (Method == AES_256_GCM_SIV)
    {
        return EVP_aead_aes_256_gcm_siv;
    }
    else
    {
        throw Exception("Wrong encryption Method. Got " + getMethodName(Method), ErrorCodes::BAD_ARGUMENTS);
    }
}

/// Find out key size for each algorithm
UInt64 methodKeySize(EncryptionMethod Method)
{
    if (Method == AES_128_GCM_SIV)
    {
        return 16;
    }
    else if (Method == AES_256_GCM_SIV)
    {
        return 32;
    }
    else
    {
        throw Exception("Wrong encryption Method. Got " + getMethodName(Method), ErrorCodes::BAD_ARGUMENTS);
    }
}

std::string lastErrorString()
{
    std::array<char, 1024> buffer = {};
    ERR_error_string_n(ERR_get_error(), buffer.data(), buffer.size());
    return std::string(buffer.data());
}

/// Encrypt plaintext with particular algorithm and put result into ciphertext_and_tag.
/// This function get key and nonce and encrypt text with their help.
/// If something went wrong (can't init context or can't encrypt data) it throws exception.
/// It returns length of encrypted text.
size_t encrypt(const std::string_view & plaintext, char * ciphertext_and_tag, EncryptionMethod method, const String & key, const String & nonce)
{
    /// Init context for encryption, using key.
    EVP_AEAD_CTX encrypt_ctx;
    EVP_AEAD_CTX_zero(&encrypt_ctx);
    const int ok_init = EVP_AEAD_CTX_init(&encrypt_ctx, getMethod(method)(),
                                            reinterpret_cast<const uint8_t*>(key.data()), key.size(),
                                            16 /* tag size */, nullptr);
    if (!ok_init)
        throw Exception(lastErrorString(), ErrorCodes::OPENSSL_ERROR);

    /// encrypt data using context and given nonce.
    size_t out_len;
    const int ok_open = EVP_AEAD_CTX_seal(&encrypt_ctx,
                                            reinterpret_cast<uint8_t *>(ciphertext_and_tag),
                                            &out_len, plaintext.size() + 16,
                                            reinterpret_cast<const uint8_t *>(nonce.data()), nonce.size(),
                                            reinterpret_cast<const uint8_t *>(plaintext.data()), plaintext.size(),
                                            nullptr, 0);
    if (!ok_open)
        throw Exception(lastErrorString(), ErrorCodes::OPENSSL_ERROR);

    return out_len;
}

/// Encrypt plaintext with particular algorithm and put result into ciphertext_and_tag.
/// This function get key and nonce and encrypt text with their help.
/// If something went wrong (can't init context or can't encrypt data) it throws exception.
/// It returns length of encrypted text.
size_t decrypt(const std::string_view & ciphertext, char * plaintext, EncryptionMethod method, const String & key, const String & nonce)
{
    /// Init context for decryption with given key.
    EVP_AEAD_CTX decrypt_ctx;
    EVP_AEAD_CTX_zero(&decrypt_ctx);

    const int ok_init = EVP_AEAD_CTX_init(&decrypt_ctx, getMethod(method)(),
                                          reinterpret_cast<const uint8_t*>(key.data()), key.size(),
                                          16 /* tag size */, nullptr);
    if (!ok_init)
        throw Exception(lastErrorString(), ErrorCodes::OPENSSL_ERROR);

    /// decrypt data using given nonce
    size_t out_len;
    const int ok_open = EVP_AEAD_CTX_open(&decrypt_ctx,
                                          reinterpret_cast<uint8_t *>(plaintext),
                                          &out_len, ciphertext.size(),
                                          reinterpret_cast<const uint8_t *>(nonce.data()), nonce.size(),
                                          reinterpret_cast<const uint8_t *>(ciphertext.data()), ciphertext.size(),
                                          nullptr, 0);
    if (!ok_open)
        throw Exception(lastErrorString(), ErrorCodes::OPENSSL_ERROR);

    return out_len;
}

/// Register codec in factory
void registerEncryptionCodec(CompressionCodecFactory & factory, EncryptionMethod Method)
{
    const auto method_code = getMethodCode(Method); /// Codec need to know its code
    factory.registerCompressionCodec(getMethodName(Method), method_code, [&, Method](const ASTPtr & arguments) -> CompressionCodecPtr
    {
        if (arguments)
        {
            if (!arguments->children.empty())
                throw Exception("Codec " + getMethodName(Method) + " must not have parameters, given " +
                                std::to_string(arguments->children.size()),
                                ErrorCodes::ILLEGAL_SYNTAX_FOR_CODEC_TYPE);
        }
        return std::make_shared<CompressionCodecEncrypted>(Method);
    });
}

String unhexKey(const String & hex)
{
    try
    {
        return boost::algorithm::unhex(hex);
    }
    catch (const std::exception &)
    {
        throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot read key_hex, check for valid characters [0-9a-fA-F] and length");
    }
}

/// Firstly, write a byte, which shows if the nonce will be put in text (if it was defined in config)
/// Secondly, write nonce in text (this step depends from first step)
/// return new position to write
inline char* writeNonce(const String& nonce, char* dest)
{
    /// If nonce consists of nul bytes, it shouldn't be in dest. Zero byte is the only byte that should be written.
    /// Otherwise, 1 is written and data from nonce is copied
    if (nonce != String("\0\0\0\0\0\0\0\0\0\0\0\0", 12))
    {
        *dest = 1;
        ++dest;
        size_t copied_symbols = nonce.copy(dest, nonce.size());
        if (copied_symbols != nonce.size())
            throw Exception(ErrorCodes::INCORRECT_DATA, "Can't copy nonce into destination. Count of copied symbols {}, need to copy {}", copied_symbols, nonce.size());
        dest += copied_symbols;
        return dest;
    }
    else
    {
        *dest = 0;
        return ++dest;
    }
}

/// Firstly, read a byte, which shows if the nonce will be put in text (if it was defined in config)
/// Secondly, read nonce in text (this step depends from first step)
/// return new position to read
inline const char* readNonce(String& nonce, const char* source)
{
    /// If first is zero byte: move source and set zero-bytes nonce
    if (!*source)
    {
        nonce = {"\0\0\0\0\0\0\0\0\0\0\0\0", 12};
        return ++source;
    }
    /// Move to next byte. Nonce will begin from there
    ++source;

    /// Otherwise, use data from source in nonce
    nonce = {source, 12};
    source += 12;
    return source;
}

}

CompressionCodecEncrypted::Configuration & CompressionCodecEncrypted::Configuration::instance()
{
    static CompressionCodecEncrypted::Configuration ret;
    return ret;
}

void CompressionCodecEncrypted::Configuration::loadImpl(
    const Poco::Util::AbstractConfiguration & config, const String & config_prefix, EncryptionMethod method, std::unique_ptr<Params> & new_params)
{
    // if method is not smaller than MAX_ENCRYPTION_METHOD it is incorrect
    if (method >= MAX_ENCRYPTION_METHOD)
        throw Exception("Wrong argument for loading configurations.", ErrorCodes::BAD_ARGUMENTS);

    /// Scan all keys in config and add them into storage. If key is in hex, transform it.
    /// Remember key ID for each key, because it will be used in encryption/decryption
    Strings config_keys;
    config.keys(config_prefix, config_keys);
    for (const std::string & config_key : config_keys)
    {
        String key;
        UInt64 key_id;

        if ((config_key == "key") || config_key.starts_with("key["))
        {
            key = config.getString(config_prefix + "." + config_key, "");
            key_id = config.getUInt64(config_prefix + "." + config_key + "[@id]", 0);
        }
        else if ((config_key == "key_hex") || config_key.starts_with("key_hex["))
        {
            key = unhexKey(config.getString(config_prefix + "." + config_key, ""));
            key_id = config.getUInt64(config_prefix + "." + config_key + "[@id]", 0);
        }
        else
            continue;

        /// For each key its id should be unique.
        if (new_params->keys_storage[method].contains(key_id))
            throw Exception(ErrorCodes::BAD_ARGUMENTS, "Multiple keys have the same ID {}", key_id);

        /// Check size of key. Its length depends on encryption algorithm.
        if (key.size() != methodKeySize(method))
            throw Exception(
                ErrorCodes::BAD_ARGUMENTS,
                "Got an encryption key with unexpected size {}, the size should be {}",
                key.size(), methodKeySize(method));

        new_params->keys_storage[method][key_id] = key;
    }

    /// Check that we have at least one key for this method (otherwise it is incorrect to use it).
    if (new_params->keys_storage[method].empty())
        throw Exception(ErrorCodes::BAD_ARGUMENTS, "No keys, an encryption needs keys to work");

    /// Try to find which key will be used for encryption. If there is no current_key,
    /// first key will be used for encryption (its index equals to zero).
    new_params->current_key_id[method] = config.getUInt64(config_prefix + ".current_key_id", 0);

    /// Check that we have current key. Otherwise config is incorrect.
    if (!new_params->keys_storage[method].contains(new_params->current_key_id[method]))
        throw Exception(ErrorCodes::BAD_ARGUMENTS, "Not found a key with the current ID {}", new_params->current_key_id[method]);

    /// Read nonce (in hex or in string). Its length should be 12 bytes.
    if (config.has(config_prefix + ".nonce_hex"))
        new_params->nonce[method] = unhexKey(config.getString(config_prefix + ".nonce_hex"));
    else
        new_params->nonce[method] = config.getString(config_prefix + ".nonce", "");

    if (new_params->nonce[method].size() != 12 && !new_params->nonce[method].empty())
        throw Exception(ErrorCodes::BAD_ARGUMENTS, "Got nonce with unexpected size {}, the size should be 12", new_params->nonce[method].size());
}

bool CompressionCodecEncrypted::Configuration::tryLoad(const Poco::Util::AbstractConfiguration & config, const String & config_prefix)
{
    /// Try to create new parameters and fill them from config.
    /// If there will be some errors, print their message to notify user that
    /// something went wrong and new parameters are not available
    try
    {
        load(config, config_prefix);
    }
    catch (...)
    {
        tryLogCurrentException(__PRETTY_FUNCTION__);
        return false;
    }
    return true;
}

void CompressionCodecEncrypted::Configuration::load(const Poco::Util::AbstractConfiguration & config, const String & config_prefix)
{
    /// Try to create new parameters and fill them from config.
    /// If there will be some errors, throw error
    std::unique_ptr<Params> new_params(new Params);
    if (config.has(config_prefix + ".aes_128_gcm_siv"))
    {
        loadImpl(config, config_prefix + ".aes_128_gcm_siv", AES_128_GCM_SIV, new_params);
    }
    if (config.has(config_prefix + ".aes_256_gcm_siv"))
    {
        loadImpl(config, config_prefix + ".aes_256_gcm_siv", AES_256_GCM_SIV, new_params);
    }

    params.set(std::move(new_params));
}

void CompressionCodecEncrypted::Configuration::getCurrentKeyAndNonce(EncryptionMethod method, UInt64 & current_key_id, String &current_key, String & nonce) const
{
    /// It parameters were not set, throw exception
    if (!params.get())
        throw Exception("Empty params in CompressionCodecEncrypted configuration", ErrorCodes::BAD_ARGUMENTS);

    /// Save parameters in variable, because they can always change.
    /// As this function not atomic, we should be certain that we get information from one particular version for correct work.
    const auto current_params = params.get();
    current_key_id = current_params->current_key_id[method];

    /// As parameters can be created empty, we need to check that this key is available.
    if (current_params->keys_storage[method].contains(current_key_id))
        current_key = current_params->keys_storage[method].at(current_key_id);
    else
        throw Exception(ErrorCodes::BAD_ARGUMENTS, "There is no current_key {} in config. Please, put it in config and reload.", current_key_id);

    /// If there is no nonce in config, we need to generate particular one,
    /// because all encryptions should have nonce and random nonce generation will lead to cases
    /// when nonce after config reload (nonce is not defined in config) will differ from previously generated one.
    /// This will lead to data loss.
    nonce = current_params->nonce[method];
    if (nonce.empty())
        nonce = {"\0\0\0\0\0\0\0\0\0\0\0\0", 12};
}

String CompressionCodecEncrypted::Configuration::getKey(EncryptionMethod method, const UInt64 & key_id) const
{
    String key;
    /// See description of previous finction, logic is the same.
    if (!params.get())
        throw Exception("Empty params in CompressionCodecEncrypted configuration", ErrorCodes::BAD_ARGUMENTS);

    const auto current_params = params.get();

    /// check if there is current key in storage
    if (current_params->keys_storage[method].contains(key_id))
        key = current_params->keys_storage[method].at(key_id);
    else
        throw Exception(ErrorCodes::BAD_ARGUMENTS, "There is no key {} in config", key_id);

    return key;
}

CompressionCodecEncrypted::CompressionCodecEncrypted(EncryptionMethod Method): encryption_method(Method)
{
    setCodecDescription(getMethodName(encryption_method));
}

uint8_t CompressionCodecEncrypted::getMethodByte() const
{
    return getMethodCode(encryption_method);
}

void CompressionCodecEncrypted::updateHash(SipHash & hash) const
{
    getCodecDesc()->updateTreeHash(hash);
}

UInt32 CompressionCodecEncrypted::getMaxCompressedDataSize(UInt32 uncompressed_size) const
{
    // The GCM mode is a stream cipher. No paddings are
    // involved. There will be a tag at the end of ciphertext (16
    // octets). Also it has not more than 8 bytes for key_id in the beginning
    // KeyID is followed by byte, that shows if nonce was set in config (and also will be put into data)
    // and 12 bytes nonce or this byte will be equal to zero and no nonce will follow it.
    return uncompressed_size + tag_size + key_id_max_size + nonce_max_size;
}

UInt32 CompressionCodecEncrypted::doCompressData(const char * source, UInt32 source_size, char * dest) const
{
    // Generate an IV out of the data block and the key-generation
    // key. It is completely deterministic, but does not leak any
    // information about the data block except for equivalence of
    // identical blocks (under the same key).

    const std::string_view plaintext = std::string_view(source, source_size);

    /// Get key and nonce for encryption
    UInt64 current_key_id;
    String current_key, nonce;
    Configuration::instance().getCurrentKeyAndNonce(encryption_method, current_key_id, current_key, nonce);

    /// Write current key id to support multiple keys.
    /// (key id in the beginning will help to decrypt data after changing current key)
    char* ciphertext_with_nonce = writeVarUInt(current_key_id, dest);
    size_t keyid_size = ciphertext_with_nonce - dest;

    /// write nonce in data. This will help to read data even after changing nonce in config
    /// If there were no nonce in data, one zero byte will be written
    char* ciphertext = writeNonce(nonce, ciphertext_with_nonce);
    UInt64 nonce_size = ciphertext - ciphertext_with_nonce;

    // The IV will be used as an authentication tag. The ciphertext and the
    // tag will be written directly in the dest buffer.
    size_t out_len = encrypt(plaintext, ciphertext, encryption_method, current_key, nonce);

    /// Length of encrypted text should be equal to text length plus tag_size (which was added by algorithm).
    if (out_len != source_size + tag_size)
        throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't encrypt data, length after encryption {} is wrong, expected {}", out_len, source_size + tag_size);

    return out_len + keyid_size + nonce_size;
}

void CompressionCodecEncrypted::doDecompressData(const char * source, UInt32 source_size, char * dest, UInt32 uncompressed_size) const
{
    /// The key is needed for decrypting. That's why it is read at the beginning of process.
    UInt64 key_id;
    const char * ciphertext_with_nonce = readVarUInt(key_id, source, source_size);

    /// Size of text should be decreased by key_size, because key_size bytes were not participating in encryption process.
    size_t keyid_size = ciphertext_with_nonce - source;
    String nonce;
    String key = Configuration::instance().getKey(encryption_method, key_id);

    /// try to read nonce from file (if it was set while encrypting)
    const char * ciphertext = readNonce(nonce, ciphertext_with_nonce);

    /// Size of text should be decreased by nonce_size, because nonce_size bytes were not participating in encryption process.
    UInt64 nonce_size = ciphertext - ciphertext_with_nonce;

    /// Count text size (nonce and key_id was read from source)
    size_t ciphertext_size = source_size - keyid_size - nonce_size;
    if (ciphertext_size != uncompressed_size + tag_size)
        throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't decrypt data, uncompressed_size {} is wrong, expected {}", uncompressed_size, ciphertext_size - tag_size);

    size_t out_len = decrypt({ciphertext, ciphertext_size}, dest, encryption_method, key, nonce);
    if (out_len != ciphertext_size - tag_size)
        throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't decrypt data, out length after decryption {} is wrong, expected {}", out_len, ciphertext_size - tag_size);
}

}

#else /* USE_SSL && USE_INTERNAL_SSL_LIBRARY */

namespace DB
{

namespace
{

/// Register codec in factory
void registerEncryptionCodec(CompressionCodecFactory & factory, EncryptionMethod Method)
{
    auto throw_no_ssl = [](const ASTPtr &) -> CompressionCodecPtr { throw Exception(ErrorCodes::OPENSSL_ERROR, "Server was built without SSL support. Encryption is disabled."); };
    const auto method_code = getMethodCode(Method); /// Codec need to know its code
    factory.registerCompressionCodec(getMethodName(Method), method_code, throw_no_ssl);
}

}

CompressionCodecEncrypted::Configuration & CompressionCodecEncrypted::Configuration::instance()
{
    static CompressionCodecEncrypted::Configuration ret;
    return ret;
}

/// if encryption is disabled.
bool CompressionCodecEncrypted::Configuration::tryLoad(const Poco::Util::AbstractConfiguration & config [[maybe_unused]], const String & config_prefix [[maybe_unused]])
{
    return false;
}

/// if encryption is disabled, print warning about this.
void CompressionCodecEncrypted::Configuration::load(const Poco::Util::AbstractConfiguration & config [[maybe_unused]], const String & config_prefix [[maybe_unused]])
{
    LOG_WARNING(&Poco::Logger::get("CompressionCodecEncrypted"), "Server was built without SSL support. Encryption is disabled.");
}

}

#endif /* USE_SSL && USE_INTERNAL_SSL_LIBRARY */

namespace DB
{
/// Register codecs for all algorithms
void registerCodecEncrypted(CompressionCodecFactory & factory)
{
    registerEncryptionCodec(factory, AES_128_GCM_SIV);
    registerEncryptionCodec(factory, AES_256_GCM_SIV);
}
}


// Copyright (c) 2015-2019 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 <chain.h>
#include <chainparams.h>
#include <pow.h>
#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup)

/* Test calculation of next difficulty target with no constraints applying */
BOOST_AUTO_TEST_CASE(get_next_work)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1261130161; // Block #30240
    CBlockIndex pindexLast;
    pindexLast.nHeight = 32255;
    pindexLast.nTime = 1262152739;  // Block #32255
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00d86aU);
}

/* Test the constraint on the upper bound for next work */
BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1231006505; // Block #0
    CBlockIndex pindexLast;
    pindexLast.nHeight = 2015;
    pindexLast.nTime = 1233061996;  // Block #2015
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00ffffU);
}

/* Test the constraint on the lower bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1279008237; // Block #66528
    CBlockIndex pindexLast;
    pindexLast.nHeight = 68543;
    pindexLast.nTime = 1279297671;  // Block #68543
    pindexLast.nBits = 0x1c05a3f4;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1c0168fdU);
}

/* Test the constraint on the upper bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1263163443; // NOTE: Not an actual block time
    CBlockIndex pindexLast;
    pindexLast.nHeight = 46367;
    pindexLast.nTime = 1269211443;  // Block #46367
    pindexLast.nBits = 0x1c387f6f;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00e1fdU);
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    nBits = UintToArith256(consensus.powLimit).GetCompact(true);
    hash.SetHex("0x1");
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_overflow_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits = ~0x00800000;
    hash.SetHex("0x1");
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_too_easy_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    arith_uint256 nBits_arith = UintToArith256(consensus.powLimit);
    nBits_arith *= 2;
    nBits = nBits_arith.GetCompact();
    hash.SetHex("0x1");
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_biger_hash_than_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    arith_uint256 hash_arith = UintToArith256(consensus.powLimit);
    nBits = hash_arith.GetCompact();
    hash_arith *= 2; // hash > nBits
    hash = ArithToUint256(hash_arith);
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_zero_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    arith_uint256 hash_arith{0};
    nBits = hash_arith.GetCompact();
    hash = ArithToUint256(hash_arith);
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    std::vector<CBlockIndex> blocks(10000);
    for (int i = 0; i < 10000; i++) {
        blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
        blocks[i].nHeight = i;
        blocks[i].nTime = 1269211443 + i * chainParams->GetConsensus().nPowTargetSpacing;
        blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */
        blocks[i].nChainTrust = i ? blocks[i - 1].nChainTrust + GetBlockProof(blocks[i - 1]) : arith_uint256(0);
    }

    for (int j = 0; j < 1000; j++) {
        CBlockIndex *p1 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p2 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p3 = &blocks[InsecureRandRange(10000)];

        int64_t tdiff = GetBlockProofEquivalentTime(*p1, *p2, *p3, chainParams->GetConsensus());
        BOOST_CHECK_EQUAL(tdiff, p1->GetBlockTime() - p2->GetBlockTime());
    }
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright (c) 2015-2019 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 <chain.h>
#include <chainparams.h>
#include <pow.h>
#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup)

/* Test calculation of next difficulty target with no constraints applying */
BOOST_AUTO_TEST_CASE(get_next_work)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1261130161; // Block #30240
    CBlockIndex pindexLast;
    pindexLast.nHeight = 32255;
    pindexLast.nTime = 1262152739;  // Block #32255
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00d86aU);
}

/* Test the constraint on the upper bound for next work */
BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1231006505; // Block #0
    CBlockIndex pindexLast;
    pindexLast.nHeight = 2015;
    pindexLast.nTime = 1233061996;  // Block #2015
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00ffffU);
}

/* Test the constraint on the lower bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1279008237; // Block #66528
    CBlockIndex pindexLast;
    pindexLast.nHeight = 68543;
    pindexLast.nTime = 1279297671;  // Block #68543
    pindexLast.nBits = 0x1c05a3f4;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1c0168fdU);
}

/* Test the constraint on the upper bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1263163443; // NOTE: Not an actual block time
    CBlockIndex pindexLast;
    pindexLast.nHeight = 46367;
    pindexLast.nTime = 1269211443;  // Block #46367
    pindexLast.nBits = 0x1c387f6f;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00e1fdU);
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    nBits = UintToArith256(consensus.powLimit).GetCompact(true);
    hash.SetHex("0x1");
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_overflow_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits = ~0x00800000;
    hash.SetHex("0x1");
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_too_easy_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    arith_uint256 nBits_arith = UintToArith256(consensus.powLimit);
    nBits_arith *= 2;
    nBits = nBits_arith.GetCompact();
    hash.SetHex("0x1");
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_biger_hash_than_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    arith_uint256 hash_arith = UintToArith256(consensus.powLimit);
    nBits = hash_arith.GetCompact();
    hash_arith *= 2; // hash > nBits
    hash = ArithToUint256(hash_arith);
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_zero_target)
{
    const auto consensus = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
    uint256 hash;
    unsigned int nBits;
    arith_uint256 hash_arith{0};
    nBits = hash_arith.GetCompact();
    hash = ArithToUint256(hash_arith);
    BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
}

BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    std::vector<CBlockIndex> blocks(10000);
    for (int i = 0; i < 10000; i++) {
        blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
        blocks[i].nHeight = i;
        blocks[i].nTime = 1269211443 + i * chainParams->GetConsensus().nPowTargetSpacing;
        blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */
        blocks[i].nChainWork = i ? blocks[i - 1].nChainWork + GetBlockProof(blocks[i - 1]) : arith_uint256(0);
    }

    for (int j = 0; j < 1000; j++) {
        CBlockIndex *p1 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p2 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p3 = &blocks[InsecureRandRange(10000)];

        int64_t tdiff = GetBlockProofEquivalentTime(*p1, *p2, *p3, chainParams->GetConsensus());
        BOOST_CHECK_EQUAL(tdiff, p1->GetBlockTime() - p2->GetBlockTime());
    }
}

// ASERT DAA related tests
// Tests of the CalculateASERT function.

std::string StrPrintCalcArgs(const arith_uint256 refTarget,
                             const int64_t targetSpacing,
                             const int64_t timeDiff,
                             const int64_t heightDiff,
                             const arith_uint256 expectedTarget,
                             const uint32_t expectednBits) {
    return strprintf("\n"
                     "ref=         %s\n"
                     "spacing=     %d\n"
                     "timeDiff=    %d\n"
                     "heightDiff=  %d\n"
                     "expTarget=   %s\n"
                     "exp nBits=   0x%08x\n",
                     refTarget.ToString(),
                     targetSpacing,
                     timeDiff,
                     heightDiff,
                     expectedTarget.ToString(),
                     expectednBits);
}

BOOST_AUTO_TEST_CASE(calculate_asert_test) {
  const Consensus::Params params = CreateChainParams(CBaseChainParams::MAIN)->GetConsensus();
  const int64_t nHalfLife = params.nASERTHalfLife;

  const arith_uint256 powLimit = UintToArith256(params.powLimit);
  arith_uint256 initialTarget = powLimit >> 4; // anchor block target
  int64_t height = 0; // height will always be preincrementing

  // The CalculateASERT function uses the absolute ASERT formulation
  // and adds +1 to the height difference that it receives.
  // The time difference passed to it must factor in the difference
  // to the *parent* of the reference block.
  // We assume the parent is ideally spaced in time before the reference block.
  static const int64_t parent_time_diff = params.nPowTargetSpacing; // diff of nTime of anchor and parent of anchor

  // Steady
  arith_uint256 nextTarget = CalculateASERT(initialTarget, params.nPowTargetSpacing, parent_time_diff + 600 /* nTimeDiff - diff between current tip and anchor's parent */, ++height, powLimit, nHalfLife);
  BOOST_CHECK(nextTarget == initialTarget);

  // A block that arrives in half the expected time
  nextTarget = CalculateASERT(initialTarget, params.nPowTargetSpacing, parent_time_diff + 600 + 300, ++height, powLimit, nHalfLife);
  BOOST_CHECK(nextTarget < initialTarget); // aka higher difficulty

  // A block that makes up for the shortfall of the previous one, restores the target to initial
  arith_uint256 prevTarget = nextTarget;
  nextTarget = CalculateASERT(initialTarget, params.nPowTargetSpacing, parent_time_diff + 600 + 300 + 900, ++height, powLimit, nHalfLife);
  BOOST_CHECK(nextTarget > prevTarget);
  BOOST_CHECK(nextTarget == initialTarget);

  // Two days ahead of schedule should double the target (halve the difficulty) (for 288s block the ideal would be 288*600)
  prevTarget = nextTarget;
  nextTarget = CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288*1200, 288, powLimit, nHalfLife);
  BOOST_CHECK(nextTarget == prevTarget * 2);

  // Two days behind of schedule should halve the target (double the difficulty) (for 288s block the ideal would be 288*600)
  prevTarget = nextTarget;
  nextTarget = CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288*0, 288, powLimit, nHalfLife);
  BOOST_CHECK(nextTarget == prevTarget / 2);
  BOOST_CHECK(nextTarget == initialTarget);

  // Ramp up from initialTarget to PowLimit - should only take 4 doublings...
  uint32_t powLimit_nBits = powLimit.GetCompact();
  uint32_t next_nBits;
  for (size_t k = 0; k < 3; k++) {
      prevTarget = nextTarget;
      nextTarget = CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288*1200, 288, powLimit, nHalfLife);
      BOOST_CHECK(nextTarget == prevTarget * 2);
      BOOST_CHECK(nextTarget < powLimit);
      next_nBits = nextTarget.GetCompact();
      BOOST_CHECK(next_nBits != powLimit_nBits);
  }

  prevTarget = nextTarget;
  nextTarget = CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288*1200, 288, powLimit, nHalfLife);
  next_nBits = nextTarget.GetCompact();
  BOOST_CHECK(nextTarget == prevTarget * 2);
  BOOST_CHECK(next_nBits == powLimit_nBits);

  // Fast periods now cannot increase target beyond POW limit, even if we try to overflow nextTarget.
  // prevTarget is a uint256, so 256*2 = 512 days would overflow nextTarget unless CalculateASERT
  // correctly detects this error
  nextTarget = CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 512*144*600, 0, powLimit, nHalfLife);
  next_nBits = nextTarget.GetCompact();
  BOOST_CHECK(next_nBits == powLimit_nBits);

  // We also need to watch for underflows on nextTarget. We need to withstand an extra ~446 days worth of blocks.
  // This should bring down a powLimit target to the a minimum target of 1.
  nextTarget = CalculateASERT(powLimit, params.nPowTargetSpacing, 0, 2*(256-33)*144, powLimit, nHalfLife);
  next_nBits = nextTarget.GetCompact();
  BOOST_CHECK_EQUAL(next_nBits, arith_uint256(1).GetCompact());

  // Define a structure holding parameters to pass to CalculateASERT.
  // We are going to check some expected results  against a vector of
  // possible arguments.
  struct calc_params {
      arith_uint256 refTarget;
      int64_t targetSpacing;
      int64_t timeDiff;
      int64_t heightDiff;
      arith_uint256 expectedTarget;
      uint32_t expectednBits;
  };

  // Define some named input argument values
  const arith_uint256 SINGLE_300_TARGET { "0000000000ffb1004e0000000000000000000000000000000000000000000000" };
  const arith_uint256 FUNNY_REF_TARGET { "000000000080000000000000000fffffffffffffffffffffffffffffffffffff" };

  // Define our expected input and output values.
  // The timeDiff entries exclude the `parent_time_diff` - this is
  // added in the call to CalculateASERT in the test loop.
  const std::vector<calc_params> calculate_args = {

      /* refTarget, targetSpacing, timeDiff, heightDiff, expectedTarget, expectednBits */

      { powLimit, 600, 0, 2*144, powLimit >> 1, 0x1b7fff80 }, // block comes too early (exactly nHalfLife) the target should be dropped twice
      { powLimit, 600, 0, 4*144, powLimit >> 2, 0x1b3fffc0 },
      { powLimit >> 1, 600, 0, 2*144, powLimit >> 2, 0x1b3fffc0 },
      { powLimit >> 2, 600, 0, 2*144, powLimit >> 3, 0x1b1fffe0 },
      { powLimit >> 3, 600, 0, 2*144, powLimit >> 4, 0x1b0ffff0 },
      { powLimit, 600, 0, 2*(256-42)*144, 3, 0x01030000 },
      { powLimit, 600, 0, 2*(256-32)*144 + 119, 1, 0x01010000 },
      { powLimit, 600, 0, 2*(256-40)*144 + 120, 1, 0x01010000 },
      { powLimit, 600, 0, 2*(256-39)*144-1, 1, 0x01010000 },
      { powLimit, 600, 0, 2*(256-33)*144, 1, 0x01010000 },  // 1 bit less since we do not need to shift to 0
      { powLimit, 600, 0, 2*(256-32)*144, 1, 0x01010000 },  // more will not decrease below 1
      { 1, 600, 0, 2*(256-32)*144, 1, 0x01010000 },
      { powLimit, 600, 2*(512-32)*144, 0, powLimit, powLimit_nBits },
      { 1, 600, (512-64)*144*600, 0, powLimit, powLimit_nBits },
      { powLimit, 600, 300, 1, SINGLE_300_TARGET, 0x1c00ffb1 },  // clamps to powLimit
      { FUNNY_REF_TARGET, 600, 600*2*33*144, 0, powLimit, powLimit_nBits }, // confuses any attempt to detect overflow by inspecting result
      { 1, 600, 600*2*256*144, 0, powLimit, powLimit_nBits }, // overflow to exactly 2^256
      { 1, 600, 600*2*224*144 - 1, 0, arith_uint256(0xffff) << 200, powLimit_nBits }, // just under powlimit (not clamped) yet over powlimit_nbits
  };

  for (auto& v : calculate_args) {
      nextTarget = CalculateASERT(v.refTarget, v.targetSpacing, parent_time_diff + v.timeDiff, v.heightDiff, powLimit, nHalfLife);
      next_nBits = nextTarget.GetCompact();
      const auto failMsg =
          StrPrintCalcArgs(v.refTarget, v.targetSpacing, parent_time_diff + v.timeDiff, v.heightDiff, v.expectedTarget, v.expectednBits)
          + strprintf("nextTarget=  %s\nnext nBits=  0x%08x\n", nextTarget.ToString(), next_nBits);
      BOOST_CHECK_MESSAGE(nextTarget == v.expectedTarget && next_nBits == v.expectednBits, failMsg);
  }

}

BOOST_AUTO_TEST_SUITE_END()


#ifndef PYTHONIC_INCLUDE_SCIPY_SPECIAL_GAMMA_HPP
#define PYTHONIC_INCLUDE_SCIPY_SPECIAL_GAMMA_HPP

#include "pythonic/include/types/ndarray.hpp"
#include "pythonic/include/utils/functor.hpp"
#include "pythonic/include/utils/numpy_traits.hpp"
#include <boost/simd/function/gamma.hpp>

PYTHONIC_NS_BEGIN

namespace scipy
{
  namespace special
  {

#define NUMPY_NARY_FUNC_NAME gamma
#define NUMPY_NARY_FUNC_SYM boost::simd::gamma
#include "pythonic/include/types/numpy_nary_expr.hpp"
  }
}
PYTHONIC_NS_END

#endif


//
// Created by tao on 19-3-25.
//

#include "AES_func_adaptor.h"
#include "log_init.h"

#include <cstring>
#include <string>
#include "adaptor_base.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <sstream>
#include <iosfwd>
#define BUFF_SIZE_4K 4096

std::string printBinToStr(const unsigned char *uc_pMd, int len) {
    std::stringbuf resBuff;
    char tmp[5];
    int i = 0;
    for (i = 0; i < len; i++) {
        snprintf(tmp, sizeof(tmp), "%02x", uc_pMd[i]);
        resBuff.sputn(tmp, strlen(tmp));
    }
    return resBuff.str();
}

std::string printBinToStr(const char *uc_pMd, int len) {
    std::stringbuf resBuff;
    char tmp[5];
    int i = 0;
    for (i = 0; i < len; i++) {
        snprintf(tmp, sizeof(tmp), "%02x", uc_pMd[i]);
        resBuff.sputn(tmp, strlen(tmp));
    }
    return resBuff.str();
}

int checkFile(std::string fileStr) {
    namespace bf=boost::filesystem;
    bf::path file(fileStr);
    if ((!bf::exists(file)) || (!bf::is_regular(file))) {
        ERROR_LOG("file not exists :" << fileStr)
        return -1;
    }
    return 0;
}

int trunkFile(std::string fileStr) {
    namespace bf=boost::filesystem;
    bf::path file(fileStr);
    if ((!bf::exists(file)) || (!bf::is_regular(file))) {
        return 0;
    }
    bf::remove(file);
    return 0;
}

int aesDataToData(std::string keyStr, const char *inData, size_t inLen, char *outData, size_t &outlen, int type) {
    const unsigned char *key = (unsigned char *) keyStr.c_str();
    BIO *enc = nullptr, *out = nullptr;
    unsigned char iv[16] = {0};
    const unsigned char *inStr = (const unsigned char *) inData;

    EVP_CIPHER_CTX *ctx = nullptr;
    BUF_MEM *bptr = nullptr;

    OpenSSL_add_all_algorithms();
    out = BIO_new(BIO_s_mem());
    if (out == NULL) {
        ERROR_LOG("out null\n");
        return -1;
    }
    if ((enc = BIO_new(BIO_f_cipher())) == nullptr) {
        ERROR_LOG("Cipher New BIO Error\n");
        return -1;
    }
    BIO_get_cipher_ctx(enc, &ctx);
    if (ctx == nullptr) {
        ERROR_LOG("ctx null\n");
        return -1;
    }

    if (!EVP_CipherInit_ex(ctx, EVP_des_cbc(), NULL, key, iv, type)) {
        ERROR_LOG("EVP_CipherInit_ex Error\n");
        return -1;
    }

    enc = BIO_push(enc, out);
    BIO_write(enc, inStr, inLen);
    BIO_flush(enc);
    BIO_flush(out);
    BIO_get_mem_ptr(out, &bptr);
    if (bptr->length > outlen) {
        ERROR_LOG("out put mem is too small")
        return -1;
    }
    memcpy(outData, bptr->data, bptr->length);
    outlen = bptr->length;
    BIO_free_all(out);
    return 0;
}

int aesFileToFile(std::string keyStr, std::string inFile, std::string outFile, int type, size_t &curr_size) {
    const unsigned char *key = (unsigned char *) keyStr.c_str();
    BIO *enc = nullptr, *out_bio_file = nullptr;
    unsigned char iv[16] = {0};
    curr_size = 0;
    namespace bf=boost::filesystem;

    char buff[BUFF_SIZE_4K];
    EVP_CIPHER_CTX *ctx = nullptr;

    OpenSSL_add_all_algorithms();

    out_bio_file = BIO_new(BIO_s_file());
    if (checkFile(inFile)) {
        ERROR_LOG("in file not exists\n");
        return -1;
    }
    trunkFile(outFile);

    BIO_write_filename(out_bio_file, (void *) outFile.c_str());

    if (out_bio_file == NULL) {
        ERROR_LOG("out_bio_file null\n");
        return -1;
    }
    if ((enc = BIO_new(BIO_f_cipher())) == nullptr) {
        ERROR_LOG("Cipher New BIO Error\n");
        return -1;
    }
    BIO_get_cipher_ctx(enc, &ctx);
    if (ctx == nullptr) {
        ERROR_LOG("ctx null\n");
        return -1;
    }

    if (!EVP_CipherInit_ex(ctx, EVP_des_cbc(), NULL, key, iv, type)) {
        ERROR_LOG("EVP_CipherInit_ex Error\n");
        return -1;
    }

    enc = BIO_push(enc, out_bio_file);
    bf::ifstream file(inFile);

    while (!file.eof()) {
        file.read(buff, sizeof(buff));
        BIO_write(enc, buff, file.gcount());
        curr_size += file.gcount();
    }

    BIO_flush(enc);
    BIO_flush(out_bio_file);
    BIO_free_all(out_bio_file);
    return 0;
}

int aesFdToFd(std::string keyStr, int inFd, int outFd, int type, size_t &curr_size) {
    const unsigned char *key = (unsigned char *) keyStr.c_str();
    BIO *enc = nullptr, *out_bio_file = nullptr;
    unsigned char iv[16] = {0};
    curr_size = 0;
    namespace bf=boost::filesystem;

    char buff[BUFF_SIZE_4K];
    EVP_CIPHER_CTX *ctx = nullptr;

    OpenSSL_add_all_algorithms();

    out_bio_file = BIO_new(BIO_s_fd());

    out_bio_file = BIO_new_fd(outFd, BIO_NOCLOSE);

    if (out_bio_file == NULL) {
        ERROR_LOG("out_bio_file null\n");
        return -1;
    }
    if ((enc = BIO_new(BIO_f_cipher())) == nullptr) {
        ERROR_LOG("Cipher New BIO Error\n");
        return -1;
    }
    BIO_get_cipher_ctx(enc, &ctx);
    if (ctx == nullptr) {
        ERROR_LOG("ctx null\n");
        return -1;
    }

    if (!EVP_CipherInit_ex(ctx, EVP_des_cbc(), NULL, key, iv, type)) {
        ERROR_LOG("EVP_CipherInit_ex Error\n");
        return -1;
    }

    enc = BIO_push(enc, out_bio_file);
    int readLen = 0;
    while ((readLen = read(inFd, buff, sizeof(buff))) > 0) {
        BIO_write(enc, buff, readLen);
        curr_size += readLen;
    }

    BIO_flush(enc);
    BIO_flush(out_bio_file);
    BIO_free_all(out_bio_file);
    return 0;
}


//
// Created by yale on 7/26/18.
//

#ifndef POW_ETHASH_H
#define POW_ETHASH_H

#include <cstdint>
#include <vector>
#include <string>
#include <boost/multiprecision/cpp_int.hpp>

namespace celesos {
    namespace ethash {
        static const uint32_t FNV_PRIME = 0x01000193;
        static const uint32_t WORD_BYTES = 4;
        static const uint32_t NODE_BYTES = 64;
        static const uint32_t NODE_WORDS = NODE_BYTES / WORD_BYTES;
        static const uint32_t MIX_BYTES = NODE_BYTES * 2;
        static const uint32_t MIX_WORDS = MIX_BYTES / WORD_BYTES;
        static const uint32_t MIX_NODES = MIX_WORDS / NODE_WORDS;
        static const uint32_t DATASET_PARENTS = 256;
        static const uint32_t CACHE_ROUNDS = 3;
        static const uint32_t ACCESSES = 3;

        union node {
            uint8_t bytes[NODE_BYTES];
            uint32_t words[NODE_WORDS];
        };

        bool calc_cache(std::vector<node> &cache, uint32_t cache_count, const std::string &seed);

        node calc_dataset_item(const std::vector<node> &cache, uint32_t item_idx);

        bool
        calc_dataset(std::vector<node> &dataset, uint32_t dataset_count, const std::vector<node> &cache);

        boost::multiprecision::uint256_t
        hash_light(const std::string &forest,
                   const boost::multiprecision::uint256_t &wood,
                   uint32_t dataset_count,
                   const std::vector<node> &cache);

        boost::multiprecision::uint256_t
        hash_full(const std::string &forest,
                  const boost::multiprecision::uint256_t &wood,
                  uint32_t dataset_count,
                  const std::vector<node> &dataset);

        bool uint256_to_hex(std::string &dst, const boost::multiprecision::uint256_t &src);

        bool hex_to_uint256(boost::multiprecision::uint256_t &dst, const std::string &src);

        boost::multiprecision::uint256_t
        hash_light_hex(const std::string &forest,
                       const std::string &wood_hex,
                       uint32_t dataset_count,
                       const std::vector<node> &cache);

        boost::multiprecision::uint256_t
        hash_full_hex(const std::string &forest,
                      const std::string &wood_hex,
                      uint32_t dataset_count,
                      const std::vector<node> &dataset);
    }
}

#endif //POW_ETHASH_H


/**
 ** Isaac Genome Alignment Software
 ** Copyright (c) 2010-2017 Illumina, Inc.
 ** All rights reserved.
 **
 ** This software is provided under the terms and conditions of the
 ** GNU GENERAL PUBLIC LICENSE Version 3
 **
 ** You should have received a copy of the GNU GENERAL PUBLIC LICENSE Version 3
 ** along with this program. If not, see
 ** <https://github.com/illumina/licenses/>.
 **
 ** \file ReferenceKmer.hh
 **
 ** Representation of a k-mer at a given position in a reference genome.
 **
 ** \author Come Raczy
 **/

#ifndef iSAAC_REFERENCE_REFERENCE_KMER_HH
#define iSAAC_REFERENCE_REFERENCE_KMER_HH

#include <utility>

#include <boost/mpl/back.hpp>
#include <boost/mpl/begin_end.hpp>
#include <boost/mpl/copy_if.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/modulus.hpp>

#include "oligo/Kmer.hh"
#include "oligo/Permutate.hh"
#include "reference/ReferencePosition.hh"

namespace isaac
{
namespace reference
{

typedef boost::mpl::copy_if<
    oligo::SUPPORTED_KMERS
    , boost::mpl::equal_to<boost::mpl::modulus<boost::mpl::_1, boost::mpl::int_<4> >, boost::mpl::int_<0> >
    , boost::mpl::back_inserter< boost::mpl::vector<> >
    >::type SUPPORTED_KMERS;

static const unsigned FIRST_SUPPORTED_KMER = boost::mpl::front<SUPPORTED_KMERS>::type::value;
static const unsigned LAST_SUPPORTED_KMER = boost::mpl::back<SUPPORTED_KMERS>::type::value;

} // namespace reference
} // namespace isaac

#endif // #ifndef iSAAC_REFERENCE_REFERENCE_KMER_HH


#include <vector>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;

template <typename Container, typename Iter>
struct srec_parser: qi::grammar<Iter, Container()> {
	srec_parser(): srec_parser::base_type(start) {
		start = qi::no_skip[lines];
		// _a: address length of this line
		// _b: data length of this line
		// _c: start address of this line
		// _d: data of this line
		// _e: line doesn't contain data
		lines = *('S'
			> qi::digit[
				phx::switch_(qi::_1) [
					phx::case_<'3'>(qi::_a = 4),
					phx::case_<'7'>(qi::_a = 4),
					phx::case_<'2'>(qi::_a = 3),
					phx::case_<'8'>(qi::_a = 3),
					phx::default_(qi::_a = 2)
				],
				phx::switch_(qi::_1) [
					phx::case_<'1'>(qi::_e = false),
					phx::case_<'2'>(qi::_e = false),
					phx::case_<'3'>(qi::_e = false),
					phx::default_(qi::_e = true)
				]
			  ]
			> hex_byte[qi::_b = qi::_1 - qi::_a - 1]
			> qi::eps[qi::_c = 0] > qi::repeat(qi::_a)[hex_byte[qi::_c = (qi::_c << 8) | qi::_1]]
			> qi::eps[phx::clear(qi::_d)] > qi::repeat(qi::_b)[hex_byte[phx::push_back(qi::_d, qi::_1)]]
			> hex_byte[
				qi::_pass = qi::_1 == (0xFF & ~phx::accumulate(qi::_d, (qi::_c>>24) + (qi::_c>>16) + (qi::_c>>8) + qi::_c + qi::_b + qi::_a + 1)),
				if_(!qi::_e) [
					phx::resize(qi::_val, qi::_b+qi::_c),
					phx::for_(qi::_b = 0, qi::_b < phx::size(qi::_d), ++qi::_b) [
						qi::_val[qi::_b+qi::_c] = qi::_d[qi::_b]
					]
				]
			  ]
			> +qi::eol);
		hex_byte = qi::uint_parser<unsigned, 16, 2, 2>();
		qi::on_error<qi::fail>
		(
            lines
          , std::cout
			<< phx::val("Error! Expecting ")
			<< qi::_4                               // what failed?
			<< phx::val(" here: \"")
			<< phx::construct<std::string>(qi::_3, qi::_2)   // iterators to error-pos, end
			<< phx::val("\"")
                << std::endl
        );	}
	qi::rule<Iter, Container()> start;
	qi::rule<Iter, Container(), qi::locals<unsigned, unsigned, std::size_t, std::vector<unsigned char>, bool> > lines;
	qi::rule<Iter, unsigned char()> hex_byte;
};

template <typename Container, typename Iter>
Container parse_srec(Iter &first, Iter last) {
	Container c;
	qi::parse(first, last, srec_parser<Container, Iter>(), c);
	return c;
}

template <typename Container, typename Input>
Container parse_srec(const Input &in) {
	typename Input::const_iterator ite = in.begin();
	typename Input::const_iterator end = in.end();
	return parse_srec<Container>(ite, end);
}

#include <boost/asio.hpp>

namespace asio = boost::asio;

#include <string>
#include <fstream>
#include <iterator>
#include <vector>
#include <deque>
#include <boost/exception/all.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>

using namespace std;
using boost::system::error_code;
using boost::format;

inline string get_option_arg(int &argc, char **&argv) {
	if (strlen(*argv)==2) {
		if (--argc) return string(*++argv);
		++argv;
		return string();
	}
	return string((*argv)+2);
}

inline void require(unsigned char val, unsigned char ex) {
	if (val == ex) return;
	cerr << (format("invalid response '0x%02X'(should be 0x%02X\n") % (unsigned)val % (unsigned)ex);
	throw runtime_error("unexpected respoonse");
}

void help() {
	cerr << "usage: yaki-h8 [opts...] {file}\n";
	cerr << "\n";
	cerr << "  file:        S-record file to transfer\n";
	cerr << " options:\n";
	cerr << "  -p PORT      specify serial port\n";
	cerr << "  -s BAUDRATE  specify initial hand-shake speed\n";
	cerr << "  -c CLOCK     specify target system clock freq\n";
	cerr << "  -t BAUDRATE  specify data transfer speed\n";
	cerr << "  -h           print this message\n";
}

#include "stub-h8tiny.h"

inline std::vector<unsigned char> read_file(const string &file) {
	std::ifstream f(file);
	f.seekg(0, std::ios_base::end);
	std::vector<unsigned char> r(f.tellg());
	f.seekg(0, std::ios_base::beg);
	f.read((char *)&r[0], r.size());
	return r;
}

int main(int argc, char **argv) {
	string mot_name;
	string port_name("COM1");
	int init_speed = 9600;
	int alt_speed = 0;
	double target_clock = 20.0;

	while (--argc) {
		string arg(*++argv);
		if (arg.empty()) continue;
		if (arg[0]=='-') {
			if (arg.length()==1) {
				if (--argc) mot_name = *++argv;
				else ++argc;
				continue;
			} else {
				switch (arg[1]) {
				case 'h': help(); continue;
				case 'p': port_name = get_option_arg(argc, argv); continue;
				case 's': init_speed = boost::lexical_cast<int>(get_option_arg(argc, argv)); continue;
				case 'c': target_clock = boost::lexical_cast<double>(get_option_arg(argc, argv)); continue;
				case 't': alt_speed = boost::lexical_cast<int>(get_option_arg(argc, argv)); continue;
				}
			}
			cerr << "unknown option: '" << arg << "'\n";
			continue;
		}
		if (mot_name.empty()) mot_name = std::move(arg);
		else cerr << "multiple target files. ignored.\n";
	}
	if (mot_name.empty()) {
		cerr << "target file must be set\n";
		return 1;
	}

	asio::io_service serv;
	asio::serial_port ser(serv, port_name);
	ser.set_option(asio::serial_port::baud_rate(init_speed));
	ser.set_option(asio::serial_port::flow_control());
	ser.set_option(asio::serial_port::parity());
	ser.set_option(asio::serial_port::stop_bits());
	ser.set_option(asio::serial_port::character_size());

	const auto sendb = [&ser](unsigned char b) {
		asio::write(ser, asio::buffer(&b, 1));
	};
	const auto sendw = [&ser](uint16_t w) {
		const unsigned char a[] = { (unsigned char)(w>>8), (unsigned char)w };
		asio::write(ser, asio::buffer(a));
	};
	const auto sendl = [&ser](uint32_t l) {
		const unsigned char a[] = { (unsigned char)(l>>24), (unsigned char)(l>>16), (unsigned char)(l>>8), (unsigned char)(l) };
		asio::write(ser, asio::buffer(a));
	};
	const auto recvb = [&ser]() -> unsigned char {
		unsigned char c;
		asio::read(ser, asio::buffer(&c, 1));
		return c;
	};

	auto romdata(parse_srec<deque<unsigned char>>(read_file(mot_name)));
	romdata.resize(((romdata.size()-1)|0x000000FF)+1, 0xFF);

	{
		cout << "synchronizing" << flush;
		unsigned char c;
		const array<unsigned char, 256> zeroes = {{}};
		bool synced = false;
		asio::async_read(ser, asio::buffer(&c, 1), [&synced](error_code ec, size_t) { synced = true; });
		for (unsigned n = 0; n < static_cast<unsigned>(-1) && !synced; ++n) {
			cout << '.' << flush;
			asio::async_write(ser, asio::buffer(zeroes), [](error_code ec, size_t) { });
			serv.run_one();
		}
		ser.cancel();
		if (!synced) {
			cerr << "no responce from target.\n";
			return 2;
		}
		require(c, 0x00);
		cout << " done." << endl;
	} {
		cout << "erasing..." << flush;
		sendb(0x55);
		unsigned char c;
		while ((c=recvb()) == 0x00);
		require(c, 0xAA);
		cout << " done." << endl;
	} {
		const vector<unsigned char> stub(stub_h8tiny, stub_h8tiny+sizeof(stub_h8tiny));
		cout << "sending stub (" << stub.size() << "B) " << flush;
		sendw(stub.size()&0xFFFF);
		cout << '.' << flush;
		recvb(); recvb();
		for (unsigned n=0; n<stub.size(); ++n) {
			if (n%16==0) cout << '.' << flush;
			sendb(stub[n]);
			recvb();
		}
		require(recvb(), 0xAA);
		cout << "done." << endl;
	} {
		cout << "writing " << mot_name << " (" << (romdata.size()/1024) << "KiB) "<< flush;
		require(recvb(), 'B');
		cout << '.' << flush;
		const int baud = alt_speed ? alt_speed : init_speed;
		const unsigned char spdchr = static_cast<unsigned char>(round(target_clock / 32 / baud * 1000000 - 1));
		sendb(spdchr);
		require(recvb(), spdchr);
		ser.set_option(asio::serial_port::baud_rate(baud));
		while (recvb() != 'S');
		do {
			sendb('s');
		} while (recvb() != 'T') ;
		sendb('t');
		cout << '.' << flush;
		const uint16_t m = static_cast<uint16_t>(romdata.size()>>8);
		sendw(m);
		for (uint32_t n = 0; n < m; ++n) {
			require(recvb(), 'A');
			cout << '.' << flush;
			asio::write(ser, asio::buffer(&romdata[n*256], 128));
			require(recvb(), 'A');
			asio::write(ser, asio::buffer(&romdata[n*256+128], 128));
		}
		require(recvb(), 'F');
		cout << "done." << endl;
	}
	cout << "finish." << endl;
	return 0;
}


#pragma once
#include <fc/static_variant.hpp>
#include <scorum/protocol/betting/wincase.hpp>
#include <scorum/protocol/betting/market_kind.hpp>
#include <scorum/protocol/config.hpp>

#include <boost/range/algorithm/transform.hpp>

namespace scorum {
namespace protocol {

template <market_kind kind, typename tag = void> struct over_under_market
{
    int16_t threshold;

    template <bool site> using wincase = over_under_wincase<site, kind, tag>;
    using over = wincase<true>;
    using under = wincase<false>;
    static constexpr market_kind kind_v = kind;

    bool has_trd_state() const
    {
        return threshold % SCORUM_BETTING_THRESHOLD_FACTOR == 0;
    }
};

template <market_kind kind, typename tag = void> struct score_yes_no_market
{
    uint16_t home;
    uint16_t away;

    template <bool site> using wincase = score_yes_no_wincase<site, kind, tag>;
    using yes = wincase<true>;
    using no = wincase<false>;
    static constexpr market_kind kind_v = kind;

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

template <market_kind kind, typename tag = void> struct yes_no_market
{
    template <bool site> using wincase = yes_no_wincase<site, kind, tag>;
    using yes = wincase<true>;
    using no = wincase<false>;
    static constexpr market_kind kind_v = kind;

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

struct home_tag;
struct away_tag;
struct draw_tag;
struct both_tag;

using result_home = yes_no_market<market_kind::result, home_tag>;
using result_draw = yes_no_market<market_kind::result, draw_tag>;
using result_away = yes_no_market<market_kind::result, away_tag>;

using round_home = yes_no_market<market_kind::round>;

using handicap = over_under_market<market_kind::handicap>;

using correct_score_home = yes_no_market<market_kind::correct_score, home_tag>;
using correct_score_draw = yes_no_market<market_kind::correct_score, draw_tag>;
using correct_score_away = yes_no_market<market_kind::correct_score, away_tag>;
using correct_score = score_yes_no_market<market_kind::correct_score>;

using goal_home = yes_no_market<market_kind::goal, home_tag>;
using goal_both = yes_no_market<market_kind::goal, both_tag>;
using goal_away = yes_no_market<market_kind::goal, away_tag>;

using total = over_under_market<market_kind::total>;

using total_goals_home = over_under_market<market_kind::total_goals, home_tag>;
using total_goals_away = over_under_market<market_kind::total_goals, away_tag>;

using market_type = fc::static_variant<result_home,
                                       result_draw,
                                       result_away,
                                       round_home,
                                       handicap,
                                       correct_score_home,
                                       correct_score_draw,
                                       correct_score_away,
                                       correct_score,
                                       goal_home,
                                       goal_both,
                                       goal_away,
                                       total,
                                       total_goals_home,
                                       total_goals_away>;

using wincase_type = fc::static_variant<result_home::yes,
                                        result_home::no,
                                        result_draw::yes,
                                        result_draw::no,
                                        result_away::yes,
                                        result_away::no,
                                        round_home::yes,
                                        round_home::no,
                                        handicap::over,
                                        handicap::under,
                                        correct_score_home::yes,
                                        correct_score_home::no,
                                        correct_score_draw::yes,
                                        correct_score_draw::no,
                                        correct_score_away::yes,
                                        correct_score_away::no,
                                        correct_score::yes,
                                        correct_score::no,
                                        goal_home::yes,
                                        goal_home::no,
                                        goal_both::yes,
                                        goal_both::no,
                                        goal_away::yes,
                                        goal_away::no,
                                        total::over,
                                        total::under,
                                        total_goals_home::over,
                                        total_goals_home::under,
                                        total_goals_away::over,
                                        total_goals_away::under>;

std::pair<wincase_type, wincase_type> create_wincases(const market_type& market);
wincase_type create_opposite(const wincase_type& wincase);
market_type create_market(const wincase_type& wincase);
bool has_trd_state(const market_type& market);
bool match_wincases(const wincase_type& lhs, const wincase_type& rhs);

market_kind get_market_kind(const wincase_type& wincase);

template <typename T> std::set<market_kind> get_markets_kind(const T& markets)
{
    std::set<market_kind> actual_markets;
    boost::transform(markets, std::inserter(actual_markets, actual_markets.begin()), [](const market_type& m) {
        return m.visit([&](const auto& market_impl) { return market_impl.kind_v; });
    });

    return actual_markets;
}

template <typename T> bool is_belong_markets(const wincase_type& wincase, const T& markets)
{
    const auto wincase_market = create_market(wincase);

    for (const auto& market : markets)
    {
        if (wincase_market == market)
            return true;
    }

    return false;
}

} // namespace protocol
} // namespace scorum

#include <scorum/protocol/betting/wincase_comparison.hpp>
#include <scorum/protocol/betting/market_comparison.hpp>

namespace fc {
using scorum::protocol::market_type;
using scorum::protocol::wincase_type;

template <> void to_variant(const wincase_type& wincase, fc::variant& variant);
template <> void from_variant(const fc::variant& variant, wincase_type& wincase);

template <> void to_variant(const market_type& market, fc::variant& var);
template <> void from_variant(const fc::variant& var, market_type& market);
}

FC_REFLECT_EMPTY(scorum::protocol::result_home)
FC_REFLECT_EMPTY(scorum::protocol::result_draw)
FC_REFLECT_EMPTY(scorum::protocol::result_away)
FC_REFLECT_EMPTY(scorum::protocol::round_home)
FC_REFLECT(scorum::protocol::handicap, (threshold))
FC_REFLECT_EMPTY(scorum::protocol::correct_score_home)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_draw)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_away)
FC_REFLECT(scorum::protocol::correct_score, (home)(away))
FC_REFLECT_EMPTY(scorum::protocol::goal_home)
FC_REFLECT_EMPTY(scorum::protocol::goal_both)
FC_REFLECT_EMPTY(scorum::protocol::goal_away)
FC_REFLECT(scorum::protocol::total, (threshold))
FC_REFLECT(scorum::protocol::total_goals_home, (threshold))
FC_REFLECT(scorum::protocol::total_goals_away, (threshold))

FC_REFLECT_EMPTY(scorum::protocol::result_home::yes)
FC_REFLECT_EMPTY(scorum::protocol::result_home::no)
FC_REFLECT_EMPTY(scorum::protocol::result_draw::yes)
FC_REFLECT_EMPTY(scorum::protocol::result_draw::no)
FC_REFLECT_EMPTY(scorum::protocol::result_away::yes)
FC_REFLECT_EMPTY(scorum::protocol::result_away::no)
FC_REFLECT_EMPTY(scorum::protocol::round_home::yes)
FC_REFLECT_EMPTY(scorum::protocol::round_home::no)
FC_REFLECT(scorum::protocol::handicap::over, (threshold))
FC_REFLECT(scorum::protocol::handicap::under, (threshold))
FC_REFLECT_EMPTY(scorum::protocol::correct_score_home::yes)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_home::no)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_draw::yes)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_draw::no)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_away::yes)
FC_REFLECT_EMPTY(scorum::protocol::correct_score_away::no)
FC_REFLECT(scorum::protocol::correct_score::yes, (home)(away))
FC_REFLECT(scorum::protocol::correct_score::no, (home)(away))
FC_REFLECT_EMPTY(scorum::protocol::goal_home::yes)
FC_REFLECT_EMPTY(scorum::protocol::goal_home::no)
FC_REFLECT_EMPTY(scorum::protocol::goal_both::yes)
FC_REFLECT_EMPTY(scorum::protocol::goal_both::no)
FC_REFLECT_EMPTY(scorum::protocol::goal_away::yes)
FC_REFLECT_EMPTY(scorum::protocol::goal_away::no)
FC_REFLECT(scorum::protocol::total::over, (threshold))
FC_REFLECT(scorum::protocol::total::under, (threshold))
FC_REFLECT(scorum::protocol::total_goals_home::over, (threshold))
FC_REFLECT(scorum::protocol::total_goals_home::under, (threshold))
FC_REFLECT(scorum::protocol::total_goals_away::over, (threshold))
FC_REFLECT(scorum::protocol::total_goals_away::under, (threshold))


// Copyright 2019 Intel Corporation.

#include "tile/lang/ast/gradient.h"

#include <map>
#include <memory>
#include <stack>
#include <unordered_set>

#include <boost/format.hpp>

#include "tile/lang/ast/traversal.h"

namespace vertexai {
namespace tile {
namespace lang {
namespace ast {

namespace {

struct UseInfo {
  ExprPtr expr;
  size_t idx;
};

class ComputeUses : public AstVisitor<void> {
 public:
  explicit ComputeUses(const ExprPtr& src) {
    stack_.push(src);
    while (stack_.size()) {
      auto expr = stack_.top();
      stack_.pop();
      if (!seen_.count(expr.get())) {
        seen_.insert(expr.get());
        expr->Accept(this);
      }
    }
  }

  const std::vector<UseInfo>& uses(const Expr* expr) const { return uses_.at(expr); }

 private:
  void Visit(const CallExpr& expr) final {
    for (size_t i = 0; i < expr.args.size(); i++) {
      Push(expr, expr.args[i], i);
    }
  }

  void Visit(const ContractionExpr& expr) final {
    for (size_t i = 0; i < expr.srcs.size(); i++) {
      Push(expr, expr.srcs[i]->ref, i);
    }
    if (expr.use_default) {
      Push(expr, expr.use_default, expr.srcs.size());
    }
  }

  void Visit(const DimExprExpr& expr) final {}
  void Visit(const FloatConst& expr) final {}
  void Visit(const IntConst& expr) final {}
  void Visit(const ParamExpr& expr) final {}

 private:
  void Push(const Expr& user, const ExprPtr& used, size_t idx) {
    IVLOG(4, "ComputeUses::Push> user: " << &user << ", used: " << used << ", idx: " << idx);
    auto ptr = std::const_pointer_cast<Expr>(user.as_ptr());
    uses_[used.get()].emplace_back(UseInfo{ptr, idx});
    stack_.push(used);
  }

 private:
  std::stack<ExprPtr> stack_;
  std::unordered_set<const Expr*> seen_;
  std::unordered_map<const Expr*, std::vector<UseInfo>> uses_;
};

class Gradient {
 public:
  explicit Gradient(const ExprPtr& err) : uses_(err) {
    IVLOG(4, "Gradient::Gradient> err: " << err);
    seen_[err.get()] = std::make_shared<FloatConst>(1.0);
  }

  ExprPtr GetDerivative(const ExprPtr& expr) {
    IVLOG(4, "Gradient::GetDerivative> " << expr);
    auto it = seen_.find(expr.get());
    if (it != seen_.end()) {
      IVLOG(5, "  returning: " << it->second);
      return it->second;
    }
    ExprPtr total;
    for (const auto& use : uses_.uses(expr.get())) {
      ExprPtr dop;
      auto dout = GetDerivative(use.expr);
      if (auto call_expr = std::dynamic_pointer_cast<CallExpr>(use.expr)) {
        dop = DeriveCall(dout, call_expr, use.idx);
      } else if (auto cion_expr = std::dynamic_pointer_cast<ContractionExpr>(use.expr)) {
        dop = DeriveContraction(dout, cion_expr, use.idx);
      } else {
        throw std::runtime_error("Invalid operation type in Gradient::GetDerivative");
      }
      if (!total) {
        total = dop;
      } else {
        total = MakeCall("add", {total, dop});
      }
    }
    if (!total) {
      total = std::make_shared<FloatConst>(0.0);
    } else if (total->shape.dims.size()) {
      total = MakeCall("simple_reduce", {total, expr});
    }
    IVLOG(4, "  Gradient::GetDerivative, final result -> " << total);
    seen_.emplace(expr.get(), total);
    return total;
  }

 private:
  ExprPtr DeriveContraction(const ExprPtr& dout, const std::shared_ptr<ContractionExpr>& expr, size_t idx) {
    if (expr->use_default && idx == expr->srcs.size()) {
      return dout;
    }
    if (expr->combo_op == CombinationOp::EQ) {
      return std::make_shared<IntConst>(0);
    }
    if (expr->agg_op == AggregationOp::SUM || expr->agg_op == AggregationOp::ASSIGN) {
      return DeriveSum(dout, expr, idx);
    }
    if (expr->agg_op == AggregationOp::MIN || expr->agg_op == AggregationOp::MAX) {
      return DeriveExtreme(dout, expr, idx);
    }
    if (expr->agg_op == AggregationOp::PROD) {
      throw std::runtime_error("PROD AggregationOp does not support differentiation");
    }
    throw std::runtime_error("Invalid ContractionExpr in DeriveContraction");
  }

  ExprPtr DeriveCall(const ExprPtr& dout, const std::shared_ptr<CallExpr>& op, size_t idx) {
    IVLOG(4, "Gradient::DeriveCall> dout=" << dout << ", op=" << op << ", fn=" << op->fn << ", idx=" << idx);
    if (op->fn == "reshape") {
      std::vector<ExprPtr> args = {dout};
      auto in = op->args[0];
      auto dim_exprs = in->shape.dims_as_exprs();
      for (size_t i = 0; i < in->shape.dims.size(); ++i) {
        args.push_back(std::make_shared<DimExprExpr>(dim_exprs[i]));
      }
      return MakeCall("reshape", args);
    }
    auto deriv = DerivRegistry::Instance()->Resolve(op->fn);
    return deriv.fn(op, dout, op->args, deriv.user_fn, deriv.user_ctx)[idx];
  }

  ExprPtr DeriveSum(const ExprPtr& dout, const std::shared_ptr<ContractionExpr>& op, size_t idx) {
    IVLOG(4, "Gradient::DeriveSum> dout=" << dout << ", op=" << op << ", idx=" << idx);
    auto dop = std::make_shared<ContractionExpr>();
    dop->agg_op = AggregationOp::SUM;
    dop->combo_op = CombinationOp::NONE;  // May be overridden below based on op->combo_op
    dop->constraints = op->constraints;
    // Anywhere the forward pass hits the default, the derivative w.r.t. any other tensor is 0;
    // thus, for the corresponding gradient, the default is everywhere zero i.e. the standard unspecified default
    if (idx == op->srcs.size()) {
      throw std::logic_error("A default tensor fell through to the DeriveSum case during Gradient");
    }
    for (size_t i = 0; i < op->srcs.size(); ++i) {
      if (idx == i) {
        dop->srcs.push_back(std::make_shared<IndexMapExpr>(dout, op->sink_idxs->idxs));
      } else {
        switch (op->combo_op) {
          case CombinationOp::MULTIPLY:
            // For *, we multiply by the other (non-differentiated) input
            dop->srcs.push_back(op->srcs[i]);
            dop->combo_op = CombinationOp::MULTIPLY;
            break;
          case CombinationOp::PLUS:
            // For +, we ignore the other (non-differentiated) input
            dop->combo_op = CombinationOp::NONE;
            break;
          case CombinationOp::COND:
            throw std::runtime_error("Gradient of sum of conditionals not supported");
          case CombinationOp::NONE:
            throw std::runtime_error(
                "Unexpected multiple inputs found when differentiating contraction with NONE combination op");
          case CombinationOp::EQ:
            throw std::runtime_error("Gradient of sum of equalities not supported");
          default:
            throw std::runtime_error("Failed to recognize combination op during differentiation");
        }
      }
    }
    auto input = op->srcs[idx];
    dop->sink_idxs = std::make_shared<IndexMapExpr>(nullptr, input->idxs);
    dop->sink_dims = std::make_shared<SizeMapExpr>(input->ref->shape.dims_as_exprs());
    dop->ComputeShape(input->ref->shape.layout);
    return dop;
  }

  ExprPtr DeriveExtreme(const ExprPtr& dout, const std::shared_ptr<ContractionExpr>& op, size_t idx) {
    // Given `O(oidxs) >= I(iidxs);` (or a MIN aggregation too), produce the derivative
    //  ```dI(iidxs) += (I(iidxs) == O(oidxs)) ? dO(oidxs);```
    // where the above notation is meant to represent a COND combination op
    IVLOG(4, "Gradient::DeriveExtreme> dout=" << dout << ", op=" << op << ", idx=" << idx);
    auto input = op->srcs[0];
    auto dop = std::make_shared<ContractionExpr>();
    dop->agg_op = AggregationOp::SUM;
    dop->combo_op = CombinationOp::COND;
    dop->constraints = op->constraints;
    // Anywhere the forward pass hits the default, the derivative w.r.t. any other tensor is 0;
    // thus, for the corresponding gradient, the default is everywhere zero i.e. the standard unspecified default
    dop->srcs.push_back(input);
    dop->srcs.push_back(std::make_shared<IndexMapExpr>(op, op->sink_idxs->idxs));
    dop->srcs.push_back(std::make_shared<IndexMapExpr>(dout, op->sink_idxs->idxs));
    dop->sink_idxs = std::make_shared<IndexMapExpr>(nullptr, input->idxs);
    dop->sink_dims = std::make_shared<SizeMapExpr>(input->ref->shape.dims_as_exprs());
    dop->ComputeShape(input->ref->shape.layout);
    return dop;
  }

 private:
  ComputeUses uses_;
  std::map<const Expr*, ExprPtr> seen_;
};

}  // namespace

std::vector<ExprPtr> ComputeGradients(const std::vector<ExprPtr>& wrts, const ExprPtr& loss) {
  ExprPtr value = loss;
  auto ndims = loss->shape.dims.size();
  if (ndims) {
    auto cion = std::make_shared<ContractionExpr>();
    cion->agg_op = AggregationOp::SUM;
    cion->combo_op = CombinationOp::NONE;
    std::vector<PolyExprPtr> idxs;
    for (size_t i = 0; i < ndims; i++) {
      idxs.push_back(std::make_shared<PolyIndex>(i));
    }
    cion->srcs = {std::make_shared<IndexMapExpr>(loss, idxs)};
    cion->sink_idxs = std::make_shared<IndexMapExpr>(nullptr, std::vector<PolyExprPtr>{});
    cion->sink_dims = std::make_shared<SizeMapExpr>(std::vector<DimExprPtr>{});
    cion->ComputeShape("");
    value = cion;
  }
  Gradient grad(value);
  std::vector<ExprPtr> ret(wrts.size());
  for (size_t i = 0; i < wrts.size(); i++) {
    ret[i] = grad.GetDerivative(wrts[i]);
  }
  return ret;
}

}  // namespace ast
}  // namespace lang
}  // namespace tile
}  // namespace vertexai


#include "utils.h"
#include "GL/glwrapper.h"
#include "ImGuizmo/ImGuizmo.h"

#include <Eigen/Dense>
#include <boost/program_options.hpp>
#include <thread>

glm::vec2 clicked_point;
float rot_x =0.0f;
float rot_y =0.0f;
bool drawing_buffer_dirty = true;
glm::vec3 view_translation{ 0,0,-30 };

void cursor_calback(GLFWwindow* window, double xpos, double ypos)
{
    ImGuiIO& io = ImGui::GetIO();
    if(!io.WantCaptureMouse) {
        const glm::vec2 p{-xpos, ypos};
        const auto d = clicked_point - p;
        if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) {
            rot_x += 0.01 * d[1];
            rot_y += 0.01 * d[0];
        }
        if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS) {
            view_translation[2] += 0.02 * d[1];
        }
        if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_3) == GLFW_PRESS) {
            view_translation[1] += 0.01 * d[1];
            view_translation[0] -= 0.01 * d[0];
        }
        clicked_point = p;
    }
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

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

    namespace po = boost::program_options;
    po::options_description desc("Allowed options");
    desc.add_options()
            ("help", "produce help message")
            ("mirror_config", po::value<std::string>()->required(), "path to mirror mesh PLY.");

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);
    if (vm.count("help"))
    {
        std::cout << desc << "\n";
        return false;
    }

    bool gl_dirty = false;

    // Loading mirrrors initial params
    std::vector<catoptric_livox::Mirror> mirrors = catoptric_livox::loadMirrorFromPLY(vm["mirror_config"].as<std::string>());

    std::vector<std::pair<double,double>> angle_mask;

    GLFWwindow *window;
    const char *glsl_version = "#version 130";
    if (!glfwInit())
        return -1;
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    window = glfwCreateWindow(960, 540, "Simulation", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetCursorPosCallback(window, cursor_calback);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    glfwSwapInterval(1);
    if (glewInit() != GLEW_OK) { return -1; }

    GLCall(glClearColor(1.0, 1.0, 1.0, 1));

    Renderer renderer;

    Shader shader(shader_simple_v, shader_simple_f);

    VertexBufferLayout layout;
    layout.Push<float>(3);
    layout.Push<float>(3);

    VertexArray va_co;
    VertexBuffer vb_co(gl_primitives::coordinate_system_vertex.data(),
                       gl_primitives::coordinate_system_vertex.size() * sizeof(float));
    va_co.AddBuffer(vb_co, layout);
    IndexBuffer ib_co(gl_primitives::coordinate_system_indices.data(), gl_primitives::coordinate_system_indices.size());

    std::vector<float> mirrors_vertices;
    std::vector<unsigned int> mirrors_indices;
    catoptric_livox::updateDrawingBuffer(mirrors, mirrors_vertices, mirrors_indices);
    VertexArray va_mirror;
    VertexBuffer vb_mirror(mirrors_vertices.data(),
                           mirrors_vertices.size() * sizeof(float));
    va_mirror.AddBuffer(vb_mirror, layout);
    IndexBuffer ib_mirror(mirrors_indices.data(), mirrors_indices.size());

    std::vector<float> ray_vertices;
    std::vector<unsigned int> ray_indicies;
    float all_points = 0;
    float classified = 0;

    for (float i = -38.4/2; i <0; i+=1)
    {
        for (float j = 0;j < 360; j+=0.25) {
            Eigen::Vector3f  color{0,0,0};
            Eigen::Affine3d mat1(Eigen::Matrix4d::Identity());
            Eigen::Affine3d mat2(Eigen::Matrix4d::Identity());

            mat1.rotate(Eigen::AngleAxisd(M_PI * i / 180.0, Eigen::Vector3d::UnitZ()));
            mat2.rotate(Eigen::AngleAxisd(M_PI * j / 180.0, Eigen::Vector3d::UnitX()));

            Eigen::Affine3d mat3 = mat1 * mat2;

            Eigen::Vector3d p_local{10*mat3(0, 0), 10*mat3(0, 1), 10*mat3(0, 2)};
            double p_local_l = p_local.norm();
            Eigen::Vector3d p_local_norm = p_local / p_local_l;

            int mirror_id = -1;
            for (int m = 0; m < mirrors.size();m++)
            {
                if (mirrors[m].checkIfRayIntersectMirror(Eigen::Vector3d::Zero(),p_local)) {
                    mirror_id = m;
                }
            }
            all_points++;
            if (mirror_id==-1) {continue;}

            classified++;
            color = catoptric_livox::mirror_colors[mirror_id];
            color = color *0.5;
            ray_vertices.push_back(0);
            ray_vertices.push_back(0);
            ray_vertices.push_back(0);
            ray_vertices.push_back(color.x());
            ray_vertices.push_back(color.y());
            ray_vertices.push_back(color.z());

            const Eigen::Vector4d plane = mirrors[mirror_id].getABCDofPlane();

            Eigen::Vector3d p_glob = catoptric_livox::getMirroredRayIntersection(p_local_norm, p_local_l,plane);

            ray_vertices.push_back(p_glob.x());
            ray_vertices.push_back(p_glob.y());
            ray_vertices.push_back(p_glob.z());
            ray_vertices.push_back(color.x());
            ray_vertices.push_back(color.y());
            ray_vertices.push_back(color.z());

            ray_indicies.push_back(ray_indicies.size());
            ray_indicies.push_back(ray_indicies.size());

            ray_vertices.push_back(p_glob.x());
            ray_vertices.push_back(p_glob.y());
            ray_vertices.push_back(p_glob.z());
            ray_vertices.push_back(color.x());
            ray_vertices.push_back(color.y());
            ray_vertices.push_back(color.z());

            Eigen::Vector3d p_glob2 = catoptric_livox::getMirroredRay(p_local_norm, p_local_l,plane);

            ray_vertices.push_back(p_glob2.x());
            ray_vertices.push_back(p_glob2.y());
            ray_vertices.push_back(p_glob2.z());
            ray_vertices.push_back(color.x());
            ray_vertices.push_back(color.y());
            ray_vertices.push_back(color.z());

            ray_indicies.push_back(ray_indicies.size());
            ray_indicies.push_back(ray_indicies.size());
        }
    }
    std::cout << " classified to all points "  << classified/all_points <<std::endl;

    VertexArray va_ray;
    VertexBuffer vb_ray(ray_vertices.data(),
                               ray_vertices.size() * sizeof(float));
    va_ray.AddBuffer(vb_ray, layout);
    IndexBuffer ib_ray(ray_indicies.data(), ray_indicies.size());

    auto mat = Eigen::Affine3d::Identity();
    mat.rotate(Eigen::AngleAxisd(-M_PI/2, Eigen::Vector3d::UnitX()));

    std::vector<float> nns_vertices;
    std::vector<unsigned int> nns_indices;
    VertexArray va_nns;
    VertexBuffer vb_nns(nns_vertices.data(),
                        nns_vertices.size() * sizeof(float));
    va_nns.AddBuffer(vb_nns, layout);
    IndexBuffer ib_nns(nns_indices.data(), nns_indices.size());

    ImGui::CreateContext();
    ImGui::StyleColorsDark();
    ImGui_ImplGlfw_InitForOpenGL(window, false);
    ImGui_ImplOpenGL3_Init(glsl_version);
    glm::mat4 glm_test {glm::mat4(1.0f)};

    while (!glfwWindowShouldClose(window)) {

        GLCall(glEnable(GL_DEPTH_TEST));
        GLCall(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        ImGuizmo::BeginFrame();
        ImGuizmo::Enable(true);
        ImGuiIO &io = ImGui::GetIO();
        ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);

        ImGuizmo::Enable(true);
        int width, height;
        glfwGetWindowSize(window, &width, &height);
        glm::mat4 proj = glm::perspective(30.f, 1.0f * width / height, 0.05f, 100.0f);
        glm::mat4 model_translate = glm::translate(glm::mat4(1.0f), view_translation);
        glm::mat4 model_rotation_0 = glm::rotate(model_translate, float(-0.5 * M_PI), glm::vec3(0.0f, 0.0f, 1.0f));

        glm::mat4 model_rotation_1 = glm::rotate(model_rotation_0, rot_x, glm::vec3(0.0f, 1.0f, 0.0f));
        glm::mat4 model_rotation_2 = glm::rotate(model_rotation_1, rot_y, glm::vec3(1.0f, 0.0f, 0.0f));

        shader.Bind(); // bind shader to apply uniform
        // draw reference frame
        GLCall(glPointSize(1));
        GLCall(glLineWidth(1));

        shader.setUniformMat4f("u_MVP", proj * model_rotation_2);
        renderer.Draw(va_ray, ib_ray, shader, GL_LINES);
        renderer.Draw(va_co, ib_co, shader, GL_LINES);
        GLCall(glLineWidth(3));
        renderer.Draw(va_mirror, ib_mirror, shader, GL_LINES);
        GLCall(glLineWidth(1));
        renderer.Draw(va_nns, ib_nns, shader, GL_LINES);

        for (int i = 0; i < mirrors.size(); i++) {
            glm::mat4 tr = glm::mat4(1.0f);
            glm::mat4 sc = glm::scale(glm::mat4(1.0f), glm::vec3(0.01f));
            Eigen::Map<Eigen::Matrix4f> tr_e(&tr[0][0]);
            tr_e = mirrors[i].getTransformation().matrix().cast<float>();
            shader.setUniformMat4f("u_MVP", proj * model_rotation_2 * tr * sc);
            renderer.Draw(va_co, ib_co, shader, GL_LINES);
        }

        ImGui::Begin("Calibration Demo");
        ImGui::End();
        ImGui::Render();

        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
    glfwTerminate();
    return 0;
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_GENERIC_FUNCTION_GENMASK_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_GENERIC_FUNCTION_GENMASK_HPP_INCLUDED

#include <boost/simd/function/bitwise_cast.hpp>
#include <boost/simd/meta/as_arithmetic.hpp>
#include <boost/simd/constant/allbits.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  // -----------------------------------------------------------------------------------------------
  // re-targeted genmask for other types
  BOOST_DISPATCH_OVERLOAD ( genmask_
                          , (typename A0, typename Target)
                          , bd::cpu_
                          , bd::scalar_< bd::unspecified_<A0> >
                          , bd::target_< bd::generic_<bd::unspecified_<Target> > >
                          )
  {
    using result_t = typename Target::type;
    BOOST_FORCEINLINE result_t operator()( A0 const& a0, Target const& ) const BOOST_NOEXCEPT
    {
      return (a0!=0) ? Allbits<result_t>() : Zero<result_t>();
    }
  };
} } }

#endif


#include <vector>
#include <map>
#include <string>
//#include <boost/python.hpp>

#include "SSDR.h"

#define DLLEXPORT extern "C" __declspec(dllexport)

SSDR::Output ssdrOutput;

DLLEXPORT double build(int numMinBones, int numMaxInfluences, int numMaxIterations, float *bindVertices, float *animVertices, int numVertices, int numFrames)
{
    SSDR::Parameter ssdrParam;
    ssdrParam.numMaxInfluences = numMaxInfluences;
    ssdrParam.numMaxIterations = numMaxIterations;
    ssdrParam.numMinBones = numMinBones;

    SSDR::Input ssdrInput;
    ssdrInput.numVertices = numVertices;
    ssdrInput.numExamples = numFrames;
    ssdrInput.bindModel.resize(ssdrInput.numVertices);
    ssdrInput.sample.resize(ssdrInput.numVertices * numFrames);
    for (int vid = 0; vid < ssdrInput.numVertices; ++vid)
    {
        ssdrInput.bindModel[vid].x = bindVertices[vid * 3 + 0];
        ssdrInput.bindModel[vid].y = bindVertices[vid * 3 + 1];
        ssdrInput.bindModel[vid].z = bindVertices[vid * 3 + 2];
    }
    for (int f = 0; f < numFrames; ++f)
    {
        for (int vid = 0; vid < ssdrInput.numVertices; ++vid)
        {
            ssdrInput.sample[f * numVertices + vid].x = animVertices[(f * numVertices + vid) * 3 + 0];
            ssdrInput.sample[f * numVertices + vid].y = animVertices[(f * numVertices + vid) * 3 + 1];
            ssdrInput.sample[f * numVertices + vid].z = animVertices[(f * numVertices + vid) * 3 + 2];
        }
    }

    double sqe = SSDR::Decompose(ssdrOutput, ssdrInput, ssdrParam);
    return std::sqrt(sqe / ssdrInput.numVertices);
}

DLLEXPORT double* getSkinningWeight()
{
    size_t num = ssdrOutput.weight.size();
    double *skinningWeight = (double *)malloc(sizeof(double) * num);
    if(skinningWeight==NULL)return NULL;

    size_t i=0;
    for (auto it = ssdrOutput.weight.begin(); it != ssdrOutput.weight.end(); ++it,i++)
    {
        skinningWeight[i]=*it;
    }
    return skinningWeight;
}

DLLEXPORT int* getSkinningIndex()
{
    size_t num = ssdrOutput.index.size();
    int *skinningIndex = (int *)malloc(sizeof(int) * num);
    if(skinningIndex==NULL)return NULL;

    size_t i=0;
    for (auto it = ssdrOutput.index.begin(); it != ssdrOutput.index.end(); ++it,i++)
    {
        skinningIndex[i]=*it;
    }
    return skinningIndex;
}

DLLEXPORT int getNumBones()
{
    return ssdrOutput.numBones;
}

DLLEXPORT float* getBoneTranslation(int boneIdx, int frame)
{
    float *retval = (float *)malloc(sizeof(float) * 3);
    if(retval==NULL)return NULL;

    retval[0]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Translation().x;
    retval[1]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Translation().y;
    retval[2]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Translation().z;
    return retval;
}

DLLEXPORT float* getBoneRotation(int boneIdx, int frame)
{
    float *retval = (float *)malloc(sizeof(float) * 4);
    if(retval==NULL)return NULL;

    retval[0]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Rotation().x;
    retval[1]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Rotation().y;
    retval[2]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Rotation().z;
    retval[3]=ssdrOutput.boneTrans[frame * ssdrOutput.numBones + boneIdx].Rotation().w;
    return retval;
}

DLLEXPORT void freeRetArr(void *p)
{
  free(p);
}


// Copyright (c) 2015-2018 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "str_period.hpp"

#include <iterator>
#include <algorithm>

#include <boost/variant.hpp>
#include <boost/optional.hpp>

#include "basics/tandem_repeat.hpp"
#include "io/variant/vcf_record.hpp"
#include "utils/repeat_finder.hpp"
#include "../facets/reference_context.hpp"

namespace octopus { namespace csr {

const std::string STRPeriod::name_ = "STR_PERIOD";

std::unique_ptr<Measure> STRPeriod::do_clone() const
{
    return std::make_unique<STRPeriod>(*this);
}

namespace {

struct RepeatContextLess
{
    bool operator()(const TandemRepeat& lhs, const TandemRepeat& rhs) const noexcept
    {
        // Expand to discount possible reference pad
        const auto expanded_lhs_region = expand_lhs(mapped_region(lhs), 1);
        const auto expanded_rhs_region = expand_lhs(mapped_region(rhs), 1);
        if (overlap_size(expanded_lhs_region, call_) != overlap_size(expanded_rhs_region, call_)) {
            return overlap_size(expanded_lhs_region, call_) < overlap_size(expanded_rhs_region, call_);
        }
        return ends_before(lhs, rhs);
    }
    RepeatContextLess(const VcfRecord& call) : call_ {call} {};
private:
    const VcfRecord& call_;
};

bool could_contain(const TandemRepeat& repeat, const VcfRecord& call)
{
    return contains(expand(mapped_region(repeat), 1), call);
}

boost::optional<TandemRepeat> find_repeat_context(const VcfRecord& call, const Haplotype& reference)
{
    const auto repeats = find_exact_tandem_repeats(reference.sequence(), reference.mapped_region(), 1, 20);
    const auto overlapping_repeats = overlap_range(repeats, expand(mapped_region(call), 1));
    boost::optional<TandemRepeat> result {};
    if (!empty(overlapping_repeats)) {
        for (const auto& repeat : repeats) {
            if (could_contain(repeat, call)) {
                if (result) {
                    result = std::max(repeat, *result, RepeatContextLess {call});
                } else {
                    result = repeat;
                }
            }
        }
        if (!result) {
            result = *max_overlapped(repeats, call);
        }
    }
    return result;
}

} // namespace

Measure::ResultType STRPeriod::do_evaluate(const VcfRecord& call, const FacetMap& facets) const
{
    int result {0};
    const auto& reference = get_value<ReferenceContext>(facets.at("ReferenceContext"));
    const auto repeat_context = find_repeat_context(call, reference);
    if (repeat_context) result = repeat_context->period();
    return result;
}

Measure::ResultCardinality STRPeriod::do_cardinality() const noexcept
{
    return ResultCardinality::one;
}

const std::string& STRPeriod::do_name() const
{
    return name_;
}

std::string STRPeriod::do_describe() const
{
    return "Length of overlapping STR";
}

std::vector<std::string> STRPeriod::do_requirements() const
{
    return {"ReferenceContext"};
}

} // namespace csr
} // namespace octopus