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

#include <miner.h>

#include <chain.h>
#include <chainparams.h>
#include <coins.h>
#include <config.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <policy/policy.h>
#include <pubkey.h>
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <validation.h>

#include <test/setup_common.h>

#include <boost/test/unit_test.hpp>

#include <memory>

#include <protocol.h>
#include <rpc/protocol.h>
#include <univalue.h>
#include <streams.h>
#include <primitives/block.h>
#include <pow.h>
// we need increased inflation to have larger coinbase
struct TestnetSetup : public TestingSetup {
    TestnetSetup() : TestingSetup(CBaseChainParams::TESTNET) {}
};
BOOST_FIXTURE_TEST_SUITE(miner_tests, TestnetSetup)

// BOOST_CHECK_EXCEPTION predicates to check the specific validation error
class HasReason {
public:
    explicit HasReason(const std::string &reason) : m_reason(reason) {}
    bool operator()(const std::runtime_error &e) const {
        return std::string(e.what()).find(m_reason) != std::string::npos;
    };

private:
    const std::string m_reason;
};

static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB);

static BlockAssembler AssemblerForTest(const CChainParams &params,
                                       const CTxMemPool &mempool) {
    BlockAssembler::Options options;
    options.blockMinFeeRate = blockMinFeeRate;
    return BlockAssembler(params, mempool, options);
}

static struct {
    uint8_t extranonce;
    uint32_t nonce;
} blockinfo[] = {
    {4, 0xa4a3e223}, {2, 0x15c32f9e}, {1, 0x0375b547}, {1, 0x7004a8a5},
    {2, 0xce440296}, {2, 0x52cfe198}, {1, 0x77a72cd0}, {2, 0xbb5d6f84},
    {2, 0x83f30c2c}, {1, 0x48a73d5b}, {1, 0xef7dcd01}, {2, 0x6809c6c4},
    {2, 0x0883ab3c}, {1, 0x087bbbe2}, {2, 0x2104a814}, {2, 0xdffb6daa},
    {1, 0xee8a0a08}, {2, 0xba4237c1}, {1, 0xa70349dc}, {1, 0x344722bb},
    {3, 0xd6294733}, {2, 0xec9f5c94}, {2, 0xca2fbc28}, {1, 0x6ba4f406},
    {2, 0x015d4532}, {1, 0x6e119b7c}, {2, 0x43e8f314}, {2, 0x27962f38},
    {2, 0xb571b51b}, {2, 0xb36bee23}, {2, 0xd17924a8}, {2, 0x6bc212d9},
    {1, 0x630d4948}, {2, 0x9a4c4ebb}, {2, 0x554be537}, {1, 0xd63ddfc7},
    {2, 0xa10acc11}, {1, 0x759a8363}, {2, 0xfb73090d}, {1, 0xe82c6a34},
    {1, 0xe33e92d7}, {3, 0x658ef5cb}, {2, 0xba32ff22}, {5, 0x0227a10c},
    {1, 0xa9a70155}, {5, 0xd096d809}, {1, 0x37176174}, {1, 0x830b8d0f},
    {1, 0xc6e3910e}, {2, 0x823f3ca8}, {1, 0x99850849}, {1, 0x7521fb81},
    {1, 0xaacaabab}, {1, 0xd645a2eb}, {5, 0x7aea1781}, {5, 0x9d6e4b78},
    {1, 0x4ce90fd8}, {1, 0xabdc832d}, {6, 0x4a34f32a}, {2, 0xf2524c1c},
    {2, 0x1bbeb08a}, {1, 0xad47f480}, {1, 0x9f026aeb}, {1, 0x15a95049},
    {2, 0xd1cb95b2}, {2, 0xf84bbda5}, {1, 0x0fa62cd1}, {1, 0xe05f9169},
    {1, 0x78d194a9}, {5, 0x3e38147b}, {5, 0x737ba0d4}, {1, 0x63378e10},
    {1, 0x6d5f91cf}, {2, 0x88612eb8}, {2, 0xe9639484}, {1, 0xb7fabc9d},
    {2, 0x19b01592}, {1, 0x5a90dd31}, {2, 0x5bd7e028}, {2, 0x94d00323},
    {1, 0xa9b9c01a}, {1, 0x3a40de61}, {1, 0x56e7eec7}, {5, 0x859f7ef6},
    {1, 0xfd8e5630}, {1, 0x2b0c9f7f}, {1, 0xba700e26}, {1, 0x7170a408},
    {1, 0x70de86a8}, {1, 0x74d64cd5}, {1, 0x49e738a1}, {2, 0x6910b602},
    {0, 0x643c565f}, {1, 0x54264b3f}, {2, 0x97ea6396}, {2, 0x55174459},
    {2, 0x03e8779a}, {1, 0x98f34d8f}, {1, 0xc07b2b07}, {1, 0xdfe29668},
    {1, 0x3141c7c1}, {1, 0xb3b595f4}, {1, 0x735abf08}, {5, 0x623bfbce},
    {2, 0xd351e722}, {1, 0xf4ca48c9}, {1, 0x5b19c670}, {1, 0xa164bf0e},
    {2, 0xbbbeb305}, {2, 0xfe1c810a},
};

using CBlockIndexPtr = std::unique_ptr<CBlockIndex>;

static CBlockIndexPtr CreateBlockIndex(int nHeight) {
    CBlockIndexPtr index(new CBlockIndex);
    index->nHeight = nHeight;
    index->pprev = ::ChainActive().Tip();
    return index;
}

static bool TestSequenceLocks(const CTransaction &tx, int flags)
    EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
    LOCK(::g_mempool.cs);
    return CheckSequenceLocks(::g_mempool, tx, flags);
}

// Test suite for ancestor feerate transaction selection.
// Implemented as an additional function, rather than a separate test case, to
// allow reusing the blockchain created in CreateNewBlock_validity.
static void TestPackageSelection(const CChainParams &chainparams,
                                 const CScript &scriptPubKey,
                                 const std::vector<CTransactionRef> &txFirst)
    EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::g_mempool.cs) {
    // Test the ancestor feerate transaction selection.
    TestMemPoolEntryHelper entry;

    // Test that a medium fee transaction will be selected after a higher fee
    // rate package with a low fee rate parent.
    CMutableTransaction tx;
    tx.vin.resize(1);
    tx.vin[0].scriptSig = CScript() << OP_1;
    tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0);
    tx.vout.resize(1);
    tx.vout[0].nValue = int64_t(5000000000LL - 1000) * FIXOSHI;
    // This tx has a low fee: 1000 satoshis.
    // Save this txid for later use.
    TxId parentTxId = tx.GetId();
    g_mempool.addUnchecked(entry.Fee(1000 * FIXOSHI)
                               .Time(GetTime())
                               .SpendsCoinbase(true)
                               .FromTx(tx));

    // This tx has a medium fee: 10000 satoshis.
    tx.vin[0].prevout = COutPoint(txFirst[1]->GetId(), 0);
    tx.vout[0].nValue = int64_t(5000000000LL - 10000) * FIXOSHI;
    TxId mediumFeeTxId = tx.GetId();
    g_mempool.addUnchecked(entry.Fee(10000 * FIXOSHI)
                               .Time(GetTime())
                               .SpendsCoinbase(true)
                               .FromTx(tx));

    // This tx has a high fee, but depends on the first transaction.
    tx.vin[0].prevout = COutPoint(parentTxId, 0);
    // 50k satoshi fee.
    tx.vout[0].nValue = int64_t(5000000000LL - 1000 - 50000) * FIXOSHI;
    TxId highFeeTxId = tx.GetId();
    g_mempool.addUnchecked(entry.Fee(50000 * FIXOSHI)
                               .Time(GetTime())
                               .SpendsCoinbase(false)
                               .FromTx(tx));

    std::unique_ptr<CBlockTemplate> pblocktemplate =
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey);
    BOOST_CHECK(pblocktemplate->block.vtx[1]->GetId() == parentTxId);
    BOOST_CHECK(pblocktemplate->block.vtx[2]->GetId() == highFeeTxId);
    BOOST_CHECK(pblocktemplate->block.vtx[3]->GetId() == mediumFeeTxId);

    // Test that a package below the block min tx fee doesn't get included
    tx.vin[0].prevout = COutPoint(highFeeTxId, 0);
    // 0 fee.
    tx.vout[0].nValue = int64_t(5000000000LL - 1000 - 50000) * FIXOSHI;
    TxId freeTxId = tx.GetId();
    g_mempool.addUnchecked(entry.Fee(Amount::zero()).FromTx(tx));
    size_t freeTxSize = GetSerializeSize(tx, PROTOCOL_VERSION);

    // Calculate a fee on child transaction that will put the package just
    // below the block min tx fee (assuming 1 child tx of the same size).
    Amount feeToUse = blockMinFeeRate.GetFee(2 * freeTxSize) - FIXOSHI;

    tx.vin[0].prevout = COutPoint(freeTxId, 0);
    tx.vout[0].nValue =
        int64_t(5000000000LL - 1000 - 50000) * FIXOSHI - feeToUse;
    TxId lowFeeTxId = tx.GetId();
    g_mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx));
    pblocktemplate =
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey);
    // Verify that the free tx and the low fee tx didn't get selected.
    for (const auto &txn : pblocktemplate->block.vtx) {
        BOOST_CHECK(txn->GetId() != freeTxId);
        BOOST_CHECK(txn->GetId() != lowFeeTxId);
    }

    // Test that packages above the min relay fee do get included, even if one
    // of the transactions is below the min relay fee. Remove the low fee
    // transaction and replace with a higher fee transaction
    g_mempool.removeRecursive(CTransaction(tx));
    // Now we should be just over the min relay fee.
    tx.vout[0].nValue -= 2 * FIXOSHI;
    lowFeeTxId = tx.GetId();
    g_mempool.addUnchecked(entry.Fee(feeToUse + 2 * FIXOSHI).FromTx(tx));
    pblocktemplate =
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey);
    BOOST_CHECK(pblocktemplate->block.vtx[4]->GetId() == freeTxId);
    BOOST_CHECK(pblocktemplate->block.vtx[5]->GetId() == lowFeeTxId);

    // Test that transaction selection properly updates ancestor fee
    // calculations as ancestor transactions get included in a block. Add a
    // 0-fee transaction that has 2 outputs.
    tx.vin[0].prevout = COutPoint(txFirst[2]->GetId(), 0);
    tx.vout.resize(2);
    tx.vout[0].nValue = int64_t(5000000000LL - 100000000) * FIXOSHI;
    // 1BCC output.
    tx.vout[1].nValue = 100000000 * FIXOSHI;
    TxId freeTxId2 = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(Amount::zero()).SpendsCoinbase(true).FromTx(tx));

    // This tx can't be mined by itself.
    tx.vin[0].prevout = COutPoint(freeTxId2, 0);
    tx.vout.resize(1);
    feeToUse = blockMinFeeRate.GetFee(freeTxSize);
    tx.vout[0].nValue = int64_t(5000000000LL - 100000000) * FIXOSHI - feeToUse;
    TxId lowFeeTxId2 = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
    pblocktemplate =
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey);

    // Verify that this tx isn't selected.
    for (const auto &txn : pblocktemplate->block.vtx) {
        BOOST_CHECK(txn->GetId() != freeTxId2);
        BOOST_CHECK(txn->GetId() != lowFeeTxId2);
    }

    // This tx will be mineable, and should cause lowFeeTxId2 to be selected as
    // well.
    tx.vin[0].prevout = COutPoint(freeTxId2, 1);
    // 10k satoshi fee.
    tx.vout[0].nValue = (100000000 - 10000) * FIXOSHI;
    g_mempool.addUnchecked(entry.Fee(10000 * FIXOSHI).FromTx(tx));
    pblocktemplate =
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey);
    BOOST_CHECK(pblocktemplate->block.vtx[8]->GetId() == lowFeeTxId2);
}

void TestCoinbaseMessageEB(uint64_t eb, const std::string &cbmsg) {
    GlobalConfig config;
    config.SetExcessiveBlockSize(eb);

    CScript scriptPubKey =
        CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909"
                              "a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112"
                              "de5c384df7ba0b8d578a4c702b6bf11d5f")
                  << OP_CHECKSIG;

    std::unique_ptr<CBlockTemplate> pblocktemplate =
        BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey);

    CBlock *pblock = &pblocktemplate->block;

    // IncrementExtraNonce creates a valid coinbase and merkleRoot
    unsigned int extraNonce = 0;
    IncrementExtraNonce(pblock, ::ChainActive().Tip(), config.GetExcessiveBlockSize(),
                        extraNonce);
    unsigned int nHeight = ::ChainActive().Tip()->nHeight + 1;
    std::vector<uint8_t> vec(cbmsg.begin(), cbmsg.end());
    BOOST_CHECK(pblock->vtx[0]->vin[0].scriptSig ==
                ((CScript() << nHeight << CScriptNum(extraNonce) << vec) +
                 COINBASE_FLAGS));
}

// Coinbase scriptSig has to contains the correct EB value
// converted to MB, rounded down to the first decimal
BOOST_AUTO_TEST_CASE(CheckCoinbase_EB) {
    TestCoinbaseMessageEB(1000001, "/EB1.0/");
    TestCoinbaseMessageEB(2000000, "/EB2.0/");
    TestCoinbaseMessageEB(8000000, "/EB8.0/");
    TestCoinbaseMessageEB(8320000, "/EB8.3/");
}

#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/thread.h>
#include <event2/http.h>
#include <event2/util.h>

static uint8_t lastExtraNonce = 0;
static std::map<uint256, uint8_t> merkleRootToExtraNonce;
static event_base *mine_event_base;
static size_t blockIdx = 0;

void updateExtraNonce(CBlock *pblock, uint8_t extraNonce) {
    CMutableTransaction txCoinbase(*pblock->vtx[0]);
    txCoinbase.nVersion = 1;
    txCoinbase.vin[0].scriptSig[0] = extraNonce;
    pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
    pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
}

static void mine_request_cb(struct evhttp_request *req, void *arg) {
    CBlock *pblock = (CBlock *)arg;
    struct evbuffer *buf = evhttp_request_get_input_buffer(req);
    if (!buf) {
        return;
    }
    size_t size = evbuffer_get_length(buf);
    const char *data = (const char *)evbuffer_pullup(buf, size);
    if (!data) {
        return;
    }
    std::string body(data, size);
    evbuffer_drain(buf, size);
    UniValue jsonBody;
    jsonBody.read(body);
    BOOST_TEST_MESSAGE("d not empty " << UniValue::stringify(jsonBody));
    const UniValue::Array &d = jsonBody["params"].get_array();
    std::string reply;
    bool stop = false;

    if (!d.empty()) {
        BOOST_TEST_MESSAGE("d not empty " << UniValue::stringify(d));
        std::vector<uint8_t> powData = ParseHex(d[0].get_str());
        BOOST_TEST_MESSAGE("Reading d");
        std::vector<uint8_t> headerData(powData.begin(), powData.begin() + 80);
        for (size_t idx = 0; idx < headerData.size(); idx += 4) {
            std::reverse(headerData.begin() + idx, headerData.begin() + idx + 4);
        }
        CDataStream headerStream(headerData, SER_NETWORK, PROTOCOL_VERSION);
        CBlockHeader header;
        headerStream >> header;
        LogPrintf("Got hash: %s\n", header.GetHash());
        BOOST_TEST_MESSAGE("Got hash: " << header.GetHash());
        std::map<uint256, uint8_t>::iterator nonceEntry = merkleRootToExtraNonce.find(header.hashMerkleRoot);
        if (nonceEntry != merkleRootToExtraNonce.end()) {
            LogPrintf("Got PoW: extranonce=%d, nonce=%d\n", nonceEntry->second, header.nNonce);
            pblock->nNonce = header.nNonce;
            updateExtraNonce(pblock, nonceEntry->second);
            LogPrintf("Merkle roots: pblock=%s, header=%s\n", pblock->hashMerkleRoot.ToString(), header.hashMerkleRoot.ToString());
            if (pblock->hashMerkleRoot != header.hashMerkleRoot) {
                reply = "{\"result\":false}";
            } else {
                reply = "{\"result\":true}";
                stop = true;
                blockinfo[blockIdx] = {nonceEntry->second, header.nNonce};
                LogPrintf("New blockinfo: \n");
                for (size_t i = 0; i < sizeof(blockinfo) / sizeof(*blockinfo); ++i) {
                    LogPrintf("{%d, 0x%x},\n", blockinfo[i].extranonce, blockinfo[i].nonce);
                }
            }
        } else {
            LogPrintf("Unknown merkle root: %s\n", header.hashMerkleRoot.ToString());
            reply = "{\"result\":false}";
        }
    } else {
        lastExtraNonce++;
        updateExtraNonce(pblock, lastExtraNonce);
        merkleRootToExtraNonce[pblock->hashMerkleRoot] = lastExtraNonce;
        CDataStream headerStream(SER_NETWORK, PROTOCOL_VERSION);
        headerStream << pblock->GetBlockHeader();
        for (size_t idx = 0; idx < headerStream.size(); idx += 4) {
            std::reverse(headerStream.begin() + idx, headerStream.begin() + idx + 4);
        }

        arith_uint256 bnTarget;
        bool fNegative;
        bool fOverflow;
        bnTarget.SetCompact(pblock->nBits, &fNegative, &fOverflow);

        std::ostringstream replyStream;
        replyStream << "{\"result\":{\"data\":\"";
        replyStream << HexStr(headerStream);
        replyStream << "000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000";
        replyStream << "\",\"target\":\"";
        replyStream << HexStr(ArithToUint256(bnTarget));
        replyStream << "\"},\"error\": null}";

        reply = replyStream.str();
    }

    struct evbuffer *evb = evhttp_request_get_output_buffer(req);
    assert(evb);
    evbuffer_add(evb, reply.data(), reply.size());
    evhttp_send_reply(req, 200, nullptr, nullptr);
    if (stop) {
        sleep(1);
        event_base_loopbreak(mine_event_base);
    }
}

/** libevent event log callback */
static void libevent_log_cb(int severity, const char *msg) {
#ifndef EVENT_LOG_WARN
// EVENT_LOG_WARN was added in 2.0.19; but before then _EVENT_LOG_WARN existed.
#define EVENT_LOG_WARN _EVENT_LOG_WARN
#endif
    // Log warn messages and higher without debug category.
    if (severity >= EVENT_LOG_WARN) {
        LogPrintf("libevent: %s\n", msg);
    } else {
        LogPrint(BCLog::LIBEVENT, "libevent: %s\n", msg);
    }
}

// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) {
    BOOST_TEST_MESSAGE("Validity test\n");
    // Note that by default, these tests run with size accounting enabled.
    GlobalConfig config;
    const CChainParams &chainparams = config.GetChainParams();
    CScript scriptPubKey =
        CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909"
                              "a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112"
                              "de5c384df7ba0b8d578a4c702b6bf11d5f")
                  << OP_CHECKSIG;
    std::unique_ptr<CBlockTemplate> pblocktemplate;
    CMutableTransaction tx;
    CScript script;
    TestMemPoolEntryHelper entry;
    entry.nFee = 11 * FIXOSHI;
    entry.nHeight = 11;

    fCheckpointsEnabled = false;
    // Simple block creation, nothing special yet:
    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));

    // We can't make transactions until we have inputs.
    // Therefore, load 110 blocks :)
    int baseheight = 0;
    std::vector<CTransactionRef> txFirst;
    for (size_t i = 0; i < sizeof(blockinfo) / sizeof(*blockinfo); ++i) {
        blockIdx = i;
        // pointer for convenience.
        CBlock *pblock = &pblocktemplate->block;
        {
            LOCK(cs_main);
            pblock->nVersion = 1;
            pblock->nTime = ::ChainActive().Tip()->GetMedianTimePast() + 1;
            CMutableTransaction txCoinbase(*pblock->vtx[0]);
            txCoinbase.nVersion = 1;
            txCoinbase.vin[0].scriptSig = CScript();
            txCoinbase.vin[0].scriptSig.push_back(blockinfo[i].extranonce);
            txCoinbase.vin[0].scriptSig.push_back(::ChainActive().Height());
            txCoinbase.vout.resize(1);
            txCoinbase.vout[0].scriptPubKey = CScript();
            CBlockHeader header = pblock->GetBlockHeader();
            pblock->nBits = GetNextWorkRequired(::ChainActive().Tip(), &header, chainparams.GetConsensus());
            txCoinbase.vout[0].nValue = GetBlockSubsidy(pblock->nBits, 0, chainparams.GetConsensus());
            pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
            if (txFirst.size() == 0) {
                baseheight = ::ChainActive().Height();
            }
            if (txFirst.size() < 4) {
                txFirst.push_back(pblock->vtx[0]);
            }
            BOOST_TEST_MESSAGE("nBits  "<< pblock->nBits);
            pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
            pblock->nNonce = blockinfo[i].nonce;

        }
        arith_uint256 bnTarget;
        bool fNegative;
        bool fOverflow;
        bnTarget.SetCompact(pblock->nBits, &fNegative, &fOverflow);
        bool alreadyValid;
        alreadyValid = UintToArith256(pblock->GetHash()) <= bnTarget;
        if(!alreadyValid) {
            event_set_log_callback(&libevent_log_cb);
            evthread_use_pthreads();
            mine_event_base = event_base_new();
            evhttp *mine_http = evhttp_new(mine_event_base);
            evhttp_set_gencb(mine_http, mine_request_cb, pblock);
            //run an old cgminer to GPU mine those blocks
            evhttp_bind_socket(mine_http, "127.0.0.1", 3000);
            BOOST_TEST_MESSAGE("Waiting for getwork requests...\n");
            LogPrintf("Waiting for getwork requests...\n");
            event_base_dispatch(mine_event_base);
            merkleRootToExtraNonce.clear();
            lastExtraNonce = 0;
            evhttp_free(mine_http);
            event_base_free(mine_event_base);
        }
        std::shared_ptr<const CBlock> shared_pblock =
            std::make_shared<const CBlock>(*pblock);
        CDataStream headerStream(SER_NETWORK, PROTOCOL_VERSION);
        headerStream << shared_pblock->GetBlockHeader();
        LogPrintf("Made header: %s\n", HexStr(headerStream));
        ProcessNewBlock(config, shared_pblock, true, nullptr);
        pblock->hashPrevBlock = pblock->GetHash();
    }

    LOCK(cs_main);
    LOCK(::g_mempool.cs);

    // Just to make sure we can still make simple blocks.
    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));
    CBlock *pblock = &pblocktemplate->block;
    const Amount BLOCKSUBSIDY = GetBlockSubsidy(pblock->nBits, 0, chainparams.GetConsensus());
    const Amount LOWFEE = CENT;
    const Amount HIGHFEE = COIN;
    const Amount HIGHERFEE = 4 * COIN;

    // block size > limit
    tx.vin.resize(1);
    tx.vin[0].scriptSig = CScript();
    // 18 * (520char + DROP) + OP_1 = 9433 bytes
    std::vector<uint8_t> vchData(520);
    for (unsigned int i = 0; i < 18; ++i) {
        tx.vin[0].scriptSig << vchData << OP_DROP;
    }

    tx.vin[0].scriptSig << OP_1;
    tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0);
    tx.vout.resize(1);
    tx.vout[0].nValue = BLOCKSUBSIDY;
    BOOST_TEST_MESSAGE("BLOCKSUBSIDY " << BLOCKSUBSIDY);
    BOOST_TEST_MESSAGE("LOWFEE " << LOWFEE);
    for (unsigned int i = 0; i < 1; ++i) {
        tx.vout[0].nValue -= LOWFEE;
        const TxId txid = tx.GetId();
        // Only first tx spends coinbase.
        bool spendsCoinbase = i == 0;
        g_mempool.addUnchecked(entry.Fee(LOWFEE)
                                   .Time(GetTime())
                                   .SpendsCoinbase(spendsCoinbase)
                                   .FromTx(tx));
        tx.vin[0].prevout = COutPoint(txid, 0);
    }

    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));
    g_mempool.clear();

    // Orphan in mempool, template creation fails.
    g_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx));
    BOOST_CHECK_EXCEPTION(
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey),
        std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
    g_mempool.clear();

    // Child with higher priority than parent.
    tx.vin[0].scriptSig = CScript() << OP_1;
    tx.vin[0].prevout = COutPoint(txFirst[1]->GetId(), 0);
    tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
    TxId txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
    tx.vin[0].prevout = COutPoint(txid, 0);
    tx.vin.resize(2);
    tx.vin[1].scriptSig = CScript() << OP_1;
    tx.vin[1].prevout = COutPoint(txFirst[0]->GetId(), 0);
    // First txn output + fresh coinbase - new txn fee.
    tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE;
    txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));
    g_mempool.clear();

    // Coinbase in mempool, template creation fails.
    tx.vin.resize(1);
    tx.vin[0].prevout = COutPoint();
    tx.vin[0].scriptSig = CScript() << OP_0 << OP_1;
    tx.vout[0].nValue = Amount::zero();
    txid = tx.GetId();
    // Give it a fee so it'll get mined.
    g_mempool.addUnchecked(
        entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
    // Should throw bad-tx-coinbase
    BOOST_CHECK_EXCEPTION(
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey),
        std::runtime_error, HasReason("bad-tx-coinbase"));
    g_mempool.clear();

    // Double spend txn pair in mempool, template creation fails.
    tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0);
    tx.vin[0].scriptSig = CScript() << OP_1;
    tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
    tx.vout[0].scriptPubKey = CScript() << OP_1;
    txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
    tx.vout[0].scriptPubKey = CScript() << OP_2;
    txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
    BOOST_CHECK_EXCEPTION(
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey),
        std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
    g_mempool.clear();

    // Subsidy changing.
    int nHeight = ::ChainActive().Height();
    // Create an actual 209999-long block chain (without valid blocks).
    while (::ChainActive().Tip()->nHeight < 209999) {
        CBlockIndex *prev = ::ChainActive().Tip();
        CBlockIndex *next = new CBlockIndex();
        next->phashBlock = new BlockHash(InsecureRand256());
        pcoinsTip->SetBestBlock(next->GetBlockHash());
        next->pprev = prev;
        next->nHeight = prev->nHeight + 1;
        next->BuildSkip();
        ::ChainActive().SetTip(next);
    }
    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));
    // Extend to a 210000-long block chain.
    while (::ChainActive().Tip()->nHeight < 210000) {
        CBlockIndex *prev = ::ChainActive().Tip();
        CBlockIndex *next = new CBlockIndex();
        next->phashBlock = new BlockHash(InsecureRand256());
        pcoinsTip->SetBestBlock(next->GetBlockHash());
        next->pprev = prev;
        next->nHeight = prev->nHeight + 1;
        next->BuildSkip();
        ::ChainActive().SetTip(next);
    }

    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));

    // Invalid p2sh txn in mempool, template creation fails
    tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0);
    tx.vin[0].scriptSig = CScript() << OP_1;
    tx.vout[0].nValue = BLOCKSUBSIDY - LOWFEE;
    script = CScript() << OP_0;
    tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script));
    txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
    tx.vin[0].prevout = COutPoint(txid, 0);
    tx.vin[0].scriptSig = CScript()
                          << std::vector<uint8_t>(script.begin(), script.end());
    tx.vout[0].nValue -= LOWFEE;
    txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
    // Should throw blk-bad-inputs
    BOOST_CHECK_EXCEPTION(
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey),
        std::runtime_error, HasReason("blk-bad-inputs"));
    g_mempool.clear();

    // Delete the dummy blocks again.
    while (::ChainActive().Tip()->nHeight > nHeight) {
        CBlockIndex *del = ::ChainActive().Tip();
        ::ChainActive().SetTip(del->pprev);
        pcoinsTip->SetBestBlock(del->pprev->GetBlockHash());
        delete del->phashBlock;
        delete del;
    }

    // non-final txs in mempool
    SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1);
    uint32_t flags = LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST;
    // height map
    std::vector<int> prevheights;

    // Relative height locked.
    tx.nVersion = 2;
    tx.vin.resize(1);
    prevheights.resize(1);
    // Only 1 transaction.
    tx.vin[0].prevout = COutPoint(txFirst[0]->GetId(), 0);
    tx.vin[0].scriptSig = CScript() << OP_1;
    // txFirst[0] is the 2nd block
    tx.vin[0].nSequence = ::ChainActive().Tip()->nHeight + 1;
    prevheights[0] = baseheight + 1;
    tx.vout.resize(1);
    tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
    tx.vout[0].scriptPubKey = CScript() << OP_1;
    tx.nLockTime = 0;
    txid = tx.GetId();
    g_mempool.addUnchecked(
        entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));

    const Consensus::Params &params = chainparams.GetConsensus();

    {
        // Locktime passes.
        CValidationState state;
        BOOST_CHECK(ContextualCheckTransactionForCurrentBlock(
            params, CTransaction(tx), state, flags));
    }

    // Sequence locks fail.
    BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags));
    // Sequence locks pass on 2nd block.
    BOOST_CHECK(
        SequenceLocks(CTransaction(tx), flags, &prevheights,
                      *CreateBlockIndex(::ChainActive().Tip()->nHeight + 2)));

    // Relative time locked.
    tx.vin[0].prevout = COutPoint(txFirst[1]->GetId(), 0);
    // txFirst[1] is the 3rd block.
    tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG |
                          (((::ChainActive().Tip()->GetMedianTimePast() + 1 -
                             ::ChainActive()[1]->GetMedianTimePast()) >>
                            CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) +
                           1);
    prevheights[0] = baseheight + 2;
    txid = tx.GetId();
    g_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx));

    {
        // Locktime passes.
        CValidationState state;
        BOOST_CHECK(ContextualCheckTransactionForCurrentBlock(
            params, CTransaction(tx), state, flags));
    }

    // Sequence locks fail.
    BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags));

    for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) {
        // Trick the MedianTimePast.
        ::ChainActive()
            .Tip()
            ->GetAncestor(::ChainActive().Tip()->nHeight - i)
            ->nTime += 512;
    }
    // Sequence locks pass 512 seconds later.
    BOOST_CHECK(
        SequenceLocks(CTransaction(tx), flags, &prevheights,
                      *CreateBlockIndex(::ChainActive().Tip()->nHeight + 1)));
    for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) {
        // Undo tricked MTP.
        ::ChainActive()
            .Tip()
            ->GetAncestor(::ChainActive().Tip()->nHeight - i)
            ->nTime -= 512;
    }

    // Absolute height locked.
    tx.vin[0].prevout = COutPoint(txFirst[2]->GetId(), 0);
    tx.vin[0].nSequence = CTxIn::SEQUENCE_FINAL - 1;
    prevheights[0] = baseheight + 3;
    tx.nLockTime = ::ChainActive().Tip()->nHeight + 1;
    txid = tx.GetId();
    g_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx));

    {
        // Locktime fails.
        CValidationState state;
        BOOST_CHECK(!ContextualCheckTransactionForCurrentBlock(
            params, CTransaction(tx), state, flags));
        BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-nonfinal");
    }

    // Sequence locks pass.
    BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags));

    {
        // Locktime passes on 2nd block.
        CValidationState state;
        int64_t nMedianTimePast = ::ChainActive().Tip()->GetMedianTimePast();
        BOOST_CHECK(ContextualCheckTransaction(
            params, CTransaction(tx), state, ::ChainActive().Tip()->nHeight + 2,
            nMedianTimePast, nMedianTimePast));
    }

    // Absolute time locked.
    tx.vin[0].prevout = COutPoint(txFirst[3]->GetId(), 0);
    tx.nLockTime = ::ChainActive().Tip()->GetMedianTimePast();
    prevheights.resize(1);
    prevheights[0] = baseheight + 4;
    txid = tx.GetId();
    g_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx));

    {
        // Locktime fails.
        CValidationState state;
        BOOST_CHECK(!ContextualCheckTransactionForCurrentBlock(
            params, CTransaction(tx), state, flags));
        BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-nonfinal");
    }

    // Sequence locks pass.
    BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags));

    {
        // Locktime passes 1 second later.
        CValidationState state;
        int64_t nMedianTimePast =
            ::ChainActive().Tip()->GetMedianTimePast() + 1;
        BOOST_CHECK(ContextualCheckTransaction(
            params, CTransaction(tx), state, ::ChainActive().Tip()->nHeight + 1,
            nMedianTimePast, nMedianTimePast));
    }

    // mempool-dependent transactions (not added)
    tx.vin[0].prevout = COutPoint(txid, 0);
    prevheights[0] = ::ChainActive().Tip()->nHeight + 1;
    tx.nLockTime = 0;
    tx.vin[0].nSequence = 0;

    {
        // Locktime passes.
        CValidationState state;
        BOOST_CHECK(ContextualCheckTransactionForCurrentBlock(
            params, CTransaction(tx), state, flags));
    }

    // Sequence locks pass.
    BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags));
    tx.vin[0].nSequence = 1;
    // Sequence locks fail.
    BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags));
    tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG;
    // Sequence locks pass.
    BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags));
    tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1;
    // Sequence locks fail.
    BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags));

    pblocktemplate =
        AssemblerForTest(chainparams, g_mempool).CreateNewBlock(scriptPubKey);
    BOOST_CHECK(pblocktemplate);

    // None of the of the absolute height/time locked tx should have made it
    // into the template because we still check IsFinalTx in CreateNewBlock, but
    // relative locked txs will if inconsistently added to g_mempool. For now
    // these will still generate a valid template until BIP68 soft fork.
    BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3UL);
    // However if we advance height by 1 and time by 512, all of them should be
    // mined.
    for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) {
        // Trick the MedianTimePast.
        ::ChainActive()
            .Tip()
            ->GetAncestor(::ChainActive().Tip()->nHeight - i)
            ->nTime += 512;
    }
    ::ChainActive().Tip()->nHeight++;
    SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1);

    BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams, g_mempool)
                                     .CreateNewBlock(scriptPubKey));
    BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5UL);

    ::ChainActive().Tip()->nHeight--;
    SetMockTime(0);
    g_mempool.clear();

    TestPackageSelection(chainparams, scriptPubKey, txFirst);

    fCheckpointsEnabled = true;
}

void CheckBlockMaxSize(const Config &config, uint64_t size, uint64_t expected) {
    gArgs.ForceSetArg("-blockmaxsize", std::to_string(size));

    BlockAssembler ba(config, g_mempool);
    BOOST_CHECK_EQUAL(ba.GetMaxGeneratedBlockSize(), expected);
}

BOOST_AUTO_TEST_CASE(BlockAssembler_construction) {
    GlobalConfig config;

    // We are working on a fake chain and need to protect ourselves.
    LOCK(cs_main);

    // Test around historical 1MB (plus one byte because that's mandatory)
    config.SetExcessiveBlockSize(ONE_MEGABYTE + 1);
    CheckBlockMaxSize(config, 0, 1000);
    CheckBlockMaxSize(config, 1000, 1000);
    CheckBlockMaxSize(config, 1001, 1001);
    CheckBlockMaxSize(config, 12345, 12345);

    CheckBlockMaxSize(config, ONE_MEGABYTE - 1001, ONE_MEGABYTE - 1001);
    CheckBlockMaxSize(config, ONE_MEGABYTE - 1000, ONE_MEGABYTE - 1000);
    CheckBlockMaxSize(config, ONE_MEGABYTE - 999, ONE_MEGABYTE - 999);
    CheckBlockMaxSize(config, ONE_MEGABYTE, ONE_MEGABYTE - 999);

    // Test around default cap
    config.SetExcessiveBlockSize(DEFAULT_EXCESSIVE_BLOCK_SIZE);

    // Now we can use the default max block size.
    CheckBlockMaxSize(config, DEFAULT_EXCESSIVE_BLOCK_SIZE - 1001,
                      DEFAULT_EXCESSIVE_BLOCK_SIZE - 1001);
    CheckBlockMaxSize(config, DEFAULT_EXCESSIVE_BLOCK_SIZE - 1000,
                      DEFAULT_EXCESSIVE_BLOCK_SIZE - 1000);
    CheckBlockMaxSize(config, DEFAULT_EXCESSIVE_BLOCK_SIZE - 999,
                      DEFAULT_EXCESSIVE_BLOCK_SIZE - 1000);
    CheckBlockMaxSize(config, DEFAULT_EXCESSIVE_BLOCK_SIZE,
                      DEFAULT_EXCESSIVE_BLOCK_SIZE - 1000);

    // If the parameter is not specified, we use
    // DEFAULT_MAX_GENERATED_BLOCK_SIZE
    {
        gArgs.ClearArg("-blockmaxsize");
        BlockAssembler ba(config, g_mempool);
        BOOST_CHECK_EQUAL(ba.GetMaxGeneratedBlockSize(),
                          DEFAULT_MAX_GENERATED_BLOCK_SIZE);
    }
}

BOOST_AUTO_TEST_CASE(TestCBlockTemplateEntry) {
    CTransactionRef txRef = MakeTransactionRef();
    CBlockTemplateEntry txEntry(txRef, 1 * FIXOSHI, 10);
    BOOST_CHECK_MESSAGE(txEntry.tx == txRef, "Transactions did not match");
    BOOST_CHECK_EQUAL(txEntry.fees, 1 * FIXOSHI);
    BOOST_CHECK_EQUAL(txEntry.sigOpCount, 10);
}

BOOST_AUTO_TEST_SUITE_END()


#include "skadi/sample.h"

#include <cmath>
#include <cstddef>
#include <fstream>
#include <future>
#include <limits>
#include <list>
#include <mutex>
#include <regex>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>

#include <boost/optional.hpp>
#include <lz4frame.h>
#include <sys/stat.h>

#include "baldr/compression_utils.h"
#include "filesystem.h"
#include "midgard/logging.h"
#include "midgard/pointll.h"
#include "midgard/sequence.h"

namespace {
// srtmgl1 holds 1x1 degree tiles but oversamples the egde of the tile
// by .5 seconds on all sides. that means that the center of pixel 0 is
// located at the tiles lat,lon (which is important for bilinear filtering)
// it also means that there are 3601 pixels per row and per column
constexpr size_t HGT_DIM = 3601;
constexpr size_t HGT_PIXELS = HGT_DIM * HGT_DIM;
constexpr size_t HGT_BYTES = sizeof(int16_t) * HGT_PIXELS;
constexpr int16_t NO_DATA_VALUE = -32768;
constexpr int16_t NO_DATA_HIGH = 16384;
constexpr int16_t NO_DATA_LOW = -16384;
constexpr size_t TILE_COUNT = 180 * 360;
constexpr int8_t UNPACKED_TILES_COUNT = 50;

// macro is faster than inline function for this..
#define out_of_range(v) v > NO_DATA_HIGH || v < NO_DATA_LOW

std::vector<std::string> get_files(const std::string& root_dir) {
  std::vector<std::string> files;
  if (filesystem::exists(root_dir) && filesystem::is_directory(root_dir)) {
    for (filesystem::recursive_directory_iterator i(root_dir), end; i != end; ++i) {
      if (i->is_regular_file() || i->is_symlink()) {
        files.push_back(i->path().string());
      }
    }
  }
  // couldn't get data
  if (files.empty()) {
    LOG_WARN(root_dir + " currently has no elevation tiles");
  }
  return files;
}

int16_t flip(int16_t value) {
  return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF);
}

uint64_t file_size(const std::string& file_name) {
  // TODO: detect gzip and actually validate the uncompressed size?
  struct stat s {};
  int rc = stat(file_name.c_str(), &s);
  return rc == 0 ? s.st_size : -1;
}

} // namespace

namespace valhalla {
namespace skadi {

enum class format_t { UNKNOWN = 0, RAW = 1, GZIP = 2, LZ4 = 3 };

class cache_item_t {
private:
  format_t format;
  valhalla::midgard::mem_map<char> data;
  int usages;
  const char* unpacked;

public:
  cache_item_t() : format(format_t::UNKNOWN), usages(0), unpacked(nullptr) {
  }
  cache_item_t(cache_item_t&&) = default;
  ~cache_item_t() {
    free((void*)unpacked);
  }

  bool init(const std::string& path, format_t format) {
    auto size = file_size(path);
    if (format == format_t::RAW && size != HGT_BYTES) {
      return false;
    }
    this->format = format;
    data.map(path, size, POSIX_MADV_SEQUENTIAL, true);
    return true;
  }

  inline const char* get_data() const {
    return data.get();
  }

  inline format_t get_format() const {
    return format;
  }

  inline int& get_usages() {
    return usages;
  }

  inline const char* get_unpacked() {
    return unpacked;
  }

  inline const char* detach_unpacked() {
    auto rv = unpacked;
    unpacked = nullptr;
    return rv;
  }

  bool unpack(const char* unpacked) {
    this->unpacked = unpacked;

    if (format == format_t::GZIP) {
      // for setting where to read compressed data from
      auto src_func = [this](z_stream& s) -> void {
        s.next_in = static_cast<Byte*>(static_cast<void*>(data.get()));
        s.avail_in = static_cast<unsigned int>(data.size());
      };

      // for setting where to write the uncompressed data to
      auto dst_func = [this](z_stream& s) -> int {
        s.next_out = (Byte*)(this->unpacked);
        s.avail_out = HGT_BYTES;
        return Z_FINISH; // we know the output will hold all the input
      };

      // we have to unzip it
      if (!baldr::inflate(src_func, dst_func)) {
        LOG_WARN("Corrupt gzip elevation data");
        format = format_t::UNKNOWN;
        return false;
      }
    } else if (format == format_t::LZ4) {
      LZ4F_decompressionContext_t decode;
      LZ4F_decompressOptions_t options;
      LZ4F_createDecompressionContext(&decode, LZ4F_VERSION);

      // Take these two values locally, since LZ4F_decompress expects pointers...
      size_t src_size = data.size();
      size_t dest_size = HGT_BYTES;
      size_t result;

      do {
        result = LZ4F_decompress(decode, const_cast<char*>(this->unpacked), &dest_size, data.get(),
                                 &src_size, &options);
        if (LZ4F_isError(result)) {
          LZ4F_freeDecompressionContext(decode);
          LOG_WARN("Corrupt lz4 elevation data");
          format = format_t::UNKNOWN;
          return false;
        }
      } while (result != 0);

      LZ4F_freeDecompressionContext(decode);
    } else {
      LOG_WARN("Corrupt elevation data of unknown type");
      format = format_t::UNKNOWN;
      return false;
    }

    return true;
  }

  static boost::optional<std::pair<uint16_t, format_t>> parse_hgt_name(const std::string& name) {
    std::smatch m;
    std::regex e(".*/([NS])([0-9]{2})([WE])([0-9]{3})\\.hgt(\\.(gz|lz4))?$");
    if (std::regex_search(name, m, e)) {
      // enum class format_t{ UNKNOWN = 0, GZIP = 1, RAW = 3, LZ4 = 4 };
      format_t fmt;
      if (m[5].matched) {
        if (m[5] == ".gz") {
          fmt = format_t::GZIP;
        } else if (m[5] == ".lz4") {
          fmt = format_t::LZ4;
        } else {
          fmt = format_t::UNKNOWN;
        }
      } else {
        fmt = format_t::RAW;
      }

      auto lon = std::stoi(m[4]) * (m[3] == "E" ? 1 : -1) + 180;
      auto lat = std::stoi(m[2]) * (m[1] == "N" ? 1 : -1) + 90;
      if (lon >= 0 && lon < 360 && lat >= 0 && lat < 180) {
        return std::make_pair(uint16_t(lat * 360 + lon), fmt);
      }
    }
    return boost::none;
  }
};

// tile_data object holds unpacked elevation tile data
class tile_data {
private:
  cache_t* c;
  const int16_t* data;
  uint16_t index;
  bool reusable;

public:
  tile_data() : c(nullptr), index(0), reusable(false), data(nullptr) {
  }

  tile_data(const tile_data& other) : c(nullptr) {
    *this = other;
  }

  tile_data(cache_t* c, uint16_t index, bool reusable, const int16_t* data);
  ~tile_data();
  tile_data& operator=(const tile_data& other);

  tile_data& operator=(tile_data&& other) {
    std::swap(c, other.c);
    std::swap(data, other.data);
    std::swap(index, other.index);
    std::swap(reusable, other.reusable);

    return *this;
  }

  inline explicit operator bool() const {
    return data != nullptr;
  }

  double get(double u, double v) const {
    // integer pixel
    size_t x = std::floor(u);
    size_t y = std::floor(v);

    // coefficients
    double u_ratio = u - x;
    double v_ratio = v - y;
    double u_inv = 1 - u_ratio;
    double v_inv = 1 - v_ratio;
    double a_coef = u_inv * v_inv;
    double b_coef = u_ratio * v_inv;
    double c_coef = u_inv * v_ratio;
    double d_coef = u_ratio * v_ratio;

    // values
    double adjust = 0;
    auto a = flip(data[y * HGT_DIM + x]);
    auto b = flip(data[y * HGT_DIM + x + 1]);
    if (out_of_range(a)) {
      a_coef = 0;
    }
    if (out_of_range(b)) {
      b_coef = 0;
    }

    // first part of the bilinear interpolation
    auto value = a * a_coef + b * b_coef;
    adjust += a_coef + b_coef;
    // LOG_INFO('{' + std::to_string(y * HGT_DIM + x) + ',' + std::to_string(a) + '}');
    // LOG_INFO('{' + std::to_string(y * HGT_DIM + x + 1) + ',' + std::to_string(b) + '}');
    // only need the second part if you aren't right on the row
    // this also protects from a corner case where you sample past the end of the image
    if (y < HGT_DIM - 1) {
      auto c = flip(data[(y + 1) * HGT_DIM + x]);
      auto d = flip(data[(y + 1) * HGT_DIM + x + 1]);
      if (out_of_range(c)) {
        c_coef = 0;
      }
      if (out_of_range(d)) {
        d_coef = 0;
      }
      // LOG_INFO('{' + std::to_string((y + 1) * HGT_DIM + x) + ',' + std::to_string(c) + '}');
      // LOG_INFO('{' + std::to_string((y + 1) * HGT_DIM + x + 1) + ',' + std::to_string(d) + '}');
      value += c * c_coef + d * d_coef;
      adjust += c_coef + d_coef;
    }
    // if we are missing everything then give up
    if (adjust == 0) {
      return NO_DATA_VALUE;
    }
    // if we were missing some we need to adjust by that
    return value / adjust;
  }
};

struct cache_t {
  // Cached tiles
  std::vector<cache_item_t> cache;
  // Set of reusable tile indexes
  std::unordered_set<uint16_t> reusable;
  // Map of pending tiles. No matter how many requests received, only one inflate job per tile
  // started.
  std::unordered_map<uint16_t, std::shared_future<tile_data>> pending_tiles;
  // Guards access to the pending_tiles
  std::recursive_mutex mutex;
  // Elevation tile path
  std::string data_source;

  void increment_usages(uint16_t index) {
    std::lock_guard<std::recursive_mutex> lock(mutex);
    cache[index].get_usages()++;
  }

  void decrement_usages(uint16_t index) {
    std::lock_guard<std::recursive_mutex> lock(mutex);
    cache[index].get_usages()--;
  }

  tile_data source(uint16_t index);
};

tile_data::tile_data(cache_t* c, uint16_t index, bool reusable, const int16_t* data)
    : c(c), data(data), index(index), reusable(reusable) {
  if (reusable)
    c->increment_usages(index);
}

tile_data::~tile_data() {
  if (reusable)
    c->decrement_usages(index);
}

tile_data& tile_data::operator=(const tile_data& other) {
  if (c && reusable)
    c->decrement_usages(index);

  c = other.c;
  data = other.data;
  index = other.index;
  reusable = other.reusable;

  if (c && reusable)
    c->increment_usages(index);
  return *this;
}

tile_data cache_t::source(uint16_t index) {
  // bail if it's out of bounds
  if (index >= TILE_COUNT) {
    return {};
  }

  // if we don't have anything maybe it's lazy loaded
  auto& item = cache[index];
  if (item.get_data() == nullptr) {
    auto f = data_source + sample::get_hgt_file_name(index);
    item.init(f, format_t::RAW);
  }

  // it wasn't in cache and when we tried to load it the file was of unknown type
  if (item.get_format() == format_t::UNKNOWN) {
    return {};
  }

  // we have it raw or we don't
  if (item.get_format() == format_t::RAW) {
    return {this, index, false, (const int16_t*)item.get_data()};
  }

  // we were able to load it but the format wasn't RAW, which only leaves compressed formats
  mutex.lock();
  auto it = pending_tiles.find(index);
  if (it != pending_tiles.end()) {
    auto future = it->second;
    mutex.unlock();
    return future.get();
  }

  // item in cache is already unpacked
  const char* unpacked = item.get_unpacked();
  if (unpacked) {
    auto rv = tile_data(this, index, true, (const int16_t*)unpacked);
    mutex.unlock();
    return rv;
  }

  std::promise<tile_data> promise;
  it = pending_tiles.emplace(index, promise.get_future()).first;

  if (reusable.size() >= UNPACKED_TILES_COUNT) {
    for (auto i : reusable) {
      if (cache[i].get_usages() <= 0) {
        reusable.erase(i);
        unpacked = cache[i].detach_unpacked();
        break;
      }
    }
  }

  if (!unpacked) {
    unpacked = (char*)malloc(HGT_BYTES);
  }
  reusable.insert(index);
  auto rv = tile_data(this, index, true, (const int16_t*)unpacked);
  mutex.unlock();

  if (!item.unpack(unpacked)) {
    rv = tile_data();
  }

  mutex.lock();
  promise.set_value(rv);
  pending_tiles.erase(it);
  mutex.unlock();
  return rv;
}

sample::sample(const std::string& data_source) {
  cache_ = new cache_t();
  cache_->data_source = data_source;

  // messy but needed
  while (!cache_->data_source.empty() &&
         cache_->data_source.back() == filesystem::path::preferred_separator) {
    cache_->data_source.pop_back();
  }

  // If data_source is empty, do not allocate/resize mapped cache.
  if (data_source.empty()) {
    LOG_DEBUG("No elevation data_source was provided");
    return;
  }
  cache_->cache.resize(TILE_COUNT);

  // check the directory for files that look like what we need
  auto files = get_files(data_source);
  for (const auto& f : files) {
    // make sure its a valid index
    auto data = cache_item_t::parse_hgt_name(f);
    if (data && data->second != format_t::UNKNOWN) {
      if (!cache_->cache[data->first].init(f, data->second)) {
        LOG_WARN("Corrupt elevation data: " + f);
      }
    }
  }
}

sample::~sample() {
  delete cache_;
}

template <class coord_t> double sample::get(const coord_t& coord) {
  // check the cache and load
  auto lon = std::floor(coord.first);
  auto lat = std::floor(coord.second);
  auto index = static_cast<uint16_t>(lat + 90) * 360 + static_cast<uint16_t>(lon + 180);

  // get the proper source of the data
  const auto& tile = cache_->source(index);
  if (!tile) {
    return NO_DATA_VALUE;
  }

  // figure out what row and column we need from the array of data
  // NOTE: data is arranged from upper left to bottom right, so y is flipped

  // fractional pixel
  double u = (coord.first - lon) * (HGT_DIM - 1);
  double v = (1.0 - (coord.second - lat)) * (HGT_DIM - 1);

  return tile.get(u, v);
}

template <class coords_t> std::vector<double> sample::get_all(const coords_t& coords) {
  std::vector<double> values;
  values.reserve(coords.size());
  uint16_t curIndex = TILE_COUNT;
  tile_data tile(cache_, TILE_COUNT, false, nullptr);
  for (const auto& coord : coords) {
    auto lon = std::floor(coord.first);
    auto lat = std::floor(coord.second);
    auto index = static_cast<uint16_t>(lat + 90) * 360 + static_cast<uint16_t>(lon + 180);

    if (index != curIndex) {
      tile = cache_->source(index);
      curIndex = index;
    }

    double value = NO_DATA_VALUE;
    if (tile) {
      double u = (coord.first - lon) * (HGT_DIM - 1);
      double v = (1.0 - (coord.second - lat)) * (HGT_DIM - 1);
      value = tile.get(u, v);
    }

    values.emplace_back(value);
  }
  return values;
}

double sample::get_no_data_value() {
  return NO_DATA_VALUE;
}

template <class coord_t> uint16_t sample::get_tile_index(const coord_t& coord) {
  auto lon = std::floor(coord.first);
  auto lat = std::floor(coord.second);
  return static_cast<uint16_t>(lat + 90) * 360 + static_cast<uint16_t>(lon + 180);
}

void sample::add_single_tile(const std::string& path) {
  cache_->cache.front().init(path, format_t::RAW);
}

std::string sample::get_hgt_file_name(uint16_t index) {
  auto x = (index % 360) - 180;
  auto y = (index / 360) - 90;

  std::string name(y < 0 ? "/S" : "/N");
  y = std::abs(y);
  if (y < 10) {
    name.push_back('0');
  }
  name.append(std::to_string(y));
  name.append(name);

  name.append(x < 0 ? "W" : "E");
  x = std::abs(x);
  if (x < 100) {
    name.push_back('0');
  }
  if (x < 10) {
    name.push_back('0');
  }
  name.append(std::to_string(x));
  name.append(".hgt");

  return name;
}

// explicit instantiations for templated get
template double sample::get<std::pair<double, double>>(const std::pair<double, double>&);
template double sample::get<std::pair<float, float>>(const std::pair<float, float>&);
template double sample::get<midgard::PointLL>(const midgard::PointLL&);
template double sample::get<midgard::Point2>(const midgard::Point2&);
template std::vector<double>
sample::get_all<std::list<std::pair<double, double>>>(const std::list<std::pair<double, double>>&);
template std::vector<double> sample::get_all<std::vector<std::pair<double, double>>>(
    const std::vector<std::pair<double, double>>&);
template std::vector<double>
sample::get_all<std::list<std::pair<float, float>>>(const std::list<std::pair<float, float>>&);
template std::vector<double>
sample::get_all<std::vector<std::pair<float, float>>>(const std::vector<std::pair<float, float>>&);
template std::vector<double>
sample::get_all<std::list<midgard::PointLL>>(const std::list<midgard::PointLL>&);
template std::vector<double>
sample::get_all<std::vector<midgard::PointLL>>(const std::vector<midgard::PointLL>&);
template std::vector<double>
sample::get_all<std::list<midgard::Point2>>(const std::list<midgard::Point2>&);
template std::vector<double>
sample::get_all<std::vector<midgard::Point2>>(const std::vector<midgard::Point2>&);
template uint16_t
sample::get_tile_index<std::pair<double, double>>(const std::pair<double, double>& coord);
template uint16_t
sample::get_tile_index<std::pair<float, float>>(const std::pair<float, float>& coord);
template uint16_t sample::get_tile_index<midgard::PointLL>(const midgard::PointLL& coord);
template uint16_t sample::get_tile_index<midgard::Point2>(const midgard::Point2& coord);

} // namespace skadi
} // namespace valhalla


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

namespace boost { namespace simd { namespace tag
  {
   /*!
     @brief predecessor generic tag

     Represents the predecessor function in generic contexts.

     @par Models:
        Hierarchy
   **/
    struct predecessor_ : ext::elementwise_<predecessor_>
    {
      /// @brief Parent hierarchy
      typedef ext::elementwise_<predecessor_> parent;
      template<class... Args>
      static BOOST_FORCEINLINE BOOST_AUTO_DECLTYPE dispatch(Args&&... args)
      BOOST_AUTO_DECLTYPE_BODY( dispatching_predecessor_( ext::adl_helper(), static_cast<Args&&>(args)... ) )
    };
  }
  namespace ext
  {
   template<class Site>
   BOOST_FORCEINLINE generic_dispatcher<tag::predecessor_, Site> dispatching_predecessor_(adl_helper, boost::dispatch::meta::unknown_<Site>, ...)
   {
     return generic_dispatcher<tag::predecessor_, Site>();
   }
   template<class... Args>
   struct impl_predecessor_;
  }
  /*!
    With one parameter it is equivalent to \c prev
    It is in the type \c A0, the greatest  \c A0 elementwise strictly less than  \c a0.
    \par
    With two parameters, the second is an integer value  \c n
    and the result is equivalent to applying \c prev \c abs(n) times to  \c a0.

    @par Semantic:

    @code
    T r = predecessor(x);
    @endcode

    computes the greatest value strictly less than x in its type

    @param a0

    @return a value of same type as the inputs
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::predecessor_, predecessor, 1)
  /*!
    Returns the n-th greatest element strictly less than the parameter

    @par Semantic:

    @code
    T r = predecessor(x,n);
    @endcode

    computes the @c n-th greatest value strictly less than x in its type.
    n must be positive or null.
    For integer it saturate at Valmin, for floating point numbers Minf
    strict predecessors are Nan

    @param a0

    @param a1

    @return a value of same type as the inputs
  **/
    BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::predecessor_, predecessor, 2)
} }

#endif


#include <iostream>
#include <boost/format.hpp>
#include <vector>

#include <boost/range/algorithm_ext/erase.hpp>
#include <vector>
#include <fstream>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <data_type.hpp>
#include <image_algorithm/compare.hpp>
#include <image_algorithm/adjacent.hpp>
#include <image_algorithm/Murakami.hpp>
#include <./gui.hpp>
#include <boost/timer.hpp>
Murakami::Murakami(question_raw_data const& data, compared_type const& comp,bool const w_mode)
	: data_(data), comp_(comp)
{
}
/*蓮メリちゅっちゅ*/
std::vector<answer_type_y> Murakami::operator() (){
	bool const w_mode = true;
	auto const width = data_.split_num.first;
	auto const height = data_.split_num.second;
	//sort_compare();//sorted_comparation作成
	/*
	#ifdef _OPENMP
	//OpenMPを使ったコード
	std::cout << "use openMP" << std::endl;
	#else
	//OpenMPを使わない場合のコード
	std::cout << "no use openMP" << std::endl;
	#endif
	*/
	make_sorted_comparation();
	//boost::timer t;
	std::vector<block_data_> block_list(height * width);
	unsigned int unique_number_cnt = 0;
	//ブロックの左上に分割画像を配置するループ
	for (int i = 0; i < height; i++){
		for (int j = 0; j < width; j++){
			//block_list[i * width + j][0][0].y = i;
			//block_list[i * width + j][0][0].x = j;
			block_list[i * width + j].block = { { { j, i } } };//std::vector < std::vector<point_type> > {std::vector < point_type > {point_type{ j, i } }};
			block_list[i * width + j].u_this_number = unique_number_cnt;
			unique_number_cnt++;
		}
	}
		//std::cout << "a";
		//block_combination b;
	for (int i = 0; i < block_list.size(); i++){//ブロック比較ループ改良
		block_combination b;
		for (int j = i; j < block_list.size(); j++){
			if (i == j) continue;
			b = w_mode ? eval_block(block_list[i].block, block_list[j].block) : eval_block2(block_list[i].block, block_list[j].block);
			block_list[i].score_data_[block_list[j].u_this_number] = std::move(score_data{ b.score, b.shift_x, b.shift_y });
		}
	}
	int a;//デバッグ用の変数
	try{
		while (block_list.size() != 1){//メインループ
			a = block_list.size();
			block_combination best_block_combination;//一番いいblockの組み合わせいれるところ
			best_block_combination.score = std::numeric_limits<bigint>::min();
			block_combination b;
			for (int i = 0; i < block_list.size(); i++){//ブロック比較ループ改良
				for (int j = i; j < block_list.size(); j++){
					if (i == j) continue;
					std::unordered_map<unsigned int, score_data>::iterator m_it = block_list[i].score_data_.find(block_list[j].u_this_number);
					if (m_it != block_list[i].score_data_.end()){
						//TODO ここを参照渡しにする
						b.block1 = block_list[i].block;
						b.block2 = block_list[j].block;
						b.score = block_list[i].score_data_[m_it->first].score;
						b.shift_x = block_list[i].score_data_[m_it->first].shift_x;
						b.shift_y = block_list[i].score_data_[m_it->first].shift_y;

					}
					else{
						b = w_mode ? eval_block(block_list[i].block, block_list[j].block) : eval_block2(block_list[i].block, block_list[j].block);
					}
					if (best_block_combination.score < b.score)best_block_combination = std::move(b);
				}
			}

			if (best_block_combination.score == std::numeric_limits<bigint>::min()){
				//std::cout << "本当に結合するブロックがなかったので解を出さずにMurakamiは終了します";
				throw std::runtime_error("Murakami_runtime_error");
			}
			block_type combined_block = std::move(combine_block(best_block_combination));//ブロックを結合する
			boost::remove_erase_if(block_list, [&best_block_combination](block_data_ it){//block_listから結合する前のブロックを消す
				return (it.block == best_block_combination.block1 || it.block == best_block_combination.block2);
			});
			block_data_ p_b;
			p_b.block = combined_block;
			block_list.push_back(p_b);//結合したのを入れる
			//std::cout << "***" << block_list.size() << "***" << "\r" << std::flush;

			/*
			for (const auto& i : block_list){
			for (const auto& j : i.block){
			for (const auto& k : j){
			//std::cout << k.x << "," << k.y << " ";
			std::cout << boost::format("(%2d,%2d)") % k.x % k.y;
			}
			std::cout << "\n";
			}
			std::cout << "\n";
			}
			*/
			/*デバッグ画像表示
			std::vector<std::vector<std::vector<point_type>>> raw_block_list;
			for (auto const& block_it : block_list){
			raw_block_list.push_back(block_it.block);
			}
			gui::combine_show_image(data_, comp_, raw_block_list);
			*/
		}
	}catch (...){
		std::cerr << "ダメ_Murakami_だめ" << std::endl;
			//std::cout << t.elapsed() << "s経過した(Murakami内で計測)" << std::endl;
			std::vector<std::vector<point_type>>exception_array;
			std::vector<block_type> temp_array;
			for (auto hoge : block_list){
				temp_array.push_back(hoge.block);
			}
			exception_array = std::move(force_combine_block(temp_array));
			return std::vector<answer_type_y>{ { std::move(exception_array), 0, cv::Mat() } };

		}
		//std::cout << t.elapsed() << "s経過した(Murakami内で計測)" << std::endl;
		/*デバッグ表示
		for (int i = 0; i < height; i++){
			for (int j = 0; j < width; j++){
				std::cout << block_list[0].block[i][j].x << "," << block_list[0].block[i][j].y << "|";
			}
			std::cout << "\n";
		}
		*/
		return std::vector<answer_type_y>{ { block_list.at(0).block, 0, cv::Mat() } };

}
Murakami::block_combination Murakami::eval_block(const block_type& block1, const block_type& block2){

	auto const width = data_.split_num.first;
	auto const height = data_.split_num.second;
	int const b1_width = block1[0].size();
	int const b1_height = block1.size();
	int const b2_width = block2[0].size();
	int const b2_height = block2.size();

	auto const block1_exists = [b1_height, b1_width, &block1](int y, int x){
		return ((x >= 0 && x < b1_width && y >= 0 && y < b1_height) && (block1[y][x].x != -1 || block1[y][x].y != -1));
	};
	auto const block2_exists = [b2_height, b2_width, &block2](int y, int x){
		return ((x >= 0 && x < b2_width && y >= 0 && y < b2_height) && (block2[y][x].x != -1 || block2[y][x].y != -1));

	};
	auto const block_size_check = [b1_width, b1_height, b2_width, b2_height, width, height](int shift_y,int shift_x){
		return (std::max(b1_width, shift_x + b2_width) - std::min(0, shift_x) <= width && std::max(b1_height, shift_y + b2_height) - std::min(0, shift_y) <= height);
	};
	bigint best_block_c = std::numeric_limits<bigint>::min();

	int best_shift_i = std::numeric_limits<int>::min();
	int best_shift_j = std::numeric_limits<int>::min();
	for (int i = -b2_height -1; i <= b1_height + b2_height + 1; i++){
		for (int j = -b2_width -1; j <= b1_width + b2_width + 1; j++){
			bool confliction = false;
			if (!block_size_check(i, j)){
				continue;
			}
			bigint block_c = 0;
			bool empty_block_c = true;
			int rank1_num = 0;
			for (int k = 0; k < b1_height; k++){
				for (int l = 0; l < b1_width; l++){
					if (block2_exists(k - i, l - j) && block1_exists(k, l)){
							confliction = true;
							break;
					}else if (block1_exists(k, l) && !block2_exists(k - i, l - j)){
						//int_fast64_t piece_c = 0;
						if (block2_exists(k - i - 1, l - j)){//上
							block_c += eval_piece(block1[k][l], block2[k - i - 1][l - j], up);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][up][1] == block2[k - i - 1][l - j]) rank1_num++;
						}
						if (block2_exists(k - i, l - j - 1)){//左
							block_c += eval_piece(block1[k][l], block2[k - i][l - j - 1], left);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][left][1] == block2[k - i][l - j - 1]) rank1_num++;
						}
						if (block2_exists(k - i + 1, l - j)){//下
							block_c += eval_piece(block1[k][l], block2[k - i + 1][l - j], down);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][down][1] == block2[k - i + 1][l - j]) rank1_num++;
						}
						if (block2_exists(k - i, l - j + 1)){//右
							block_c += eval_piece(block1[k][l], block2[k - i][l - j + 1], right);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][right][1] == block2[k - i][l - j + 1]) rank1_num++;
						}
					}
				}

				if (confliction)break;
			}
			if (!confliction && !empty_block_c){
				++rank1_num;
				//block_c *= rank1_num; //0を掛けるのは怖い
				if(block_c < 0)block_c = -pow(block_c, rank1_num);
				if (block_c > 0)block_c = pow(block_c, rank1_num);
				if (block_c >= best_block_c){
					//block_size_check(i, j)
					best_block_c = block_c;
					best_shift_i = i;
					best_shift_j = j;
				}
			}
		}
	}
	/*
	if (best_shift_i == std::numeric_limits<int>::min()){
		std::cout << "結合すべきブロックがなかった" << std::endl;
	}
	*/
	block_combination return_struct{
		std::move(block1),
		std::move(block2),
		best_shift_j,
		best_shift_i,
		best_block_c
	};
	return std::move(return_struct);
}
Murakami::block_combination Murakami::eval_block2(const block_type& block1, const block_type& block2){

	auto const width = data_.split_num.first;
	auto const height = data_.split_num.second;
	int const b1_width = block1[0].size();
	int const b1_height = block1.size();
	int const b2_width = block2[0].size();
	int const b2_height = block2.size();

	auto const block1_exists = [b1_height, b1_width, &block1](int y, int x){
		return ((x >= 0 && x < b1_width && y >= 0 && y < b1_height) && (block1[y][x].x != -1 || block1[y][x].y != -1));
	};
	auto const block2_exists = [b2_height, b2_width, &block2](int y, int x){
		return ((x >= 0 && x < b2_width && y >= 0 && y < b2_height) && (block2[y][x].x != -1 || block2[y][x].y != -1));

	};
	auto const block_size_check = [b1_width, b1_height, b2_width, b2_height, width, height](int shift_y, int shift_x){
		return (std::max(b1_width, shift_x + b2_width) - std::min(0, shift_x) <= width && std::max(b1_height, shift_y + b2_height) - std::min(0, shift_y) <= height);
	};
	bigint best_block_c = std::numeric_limits<bigint>::min();

	int best_shift_i = std::numeric_limits<int>::min();
	int best_shift_j = std::numeric_limits<int>::min();
	for (int i = -b2_height - 1; i <= b1_height + b2_height + 1; i++){
		for (int j = -b2_width - 1; j <= b1_width + b2_width + 1; j++){
			bool confliction = false;
			if (!block_size_check(i, j)){
				continue;
			}
			bigint block_c = 0;
			bool empty_block_c = true;
			int rank1_num = 0;
			for (int k = 0; k < b1_height; k++){
				for (int l = 0; l < b1_width; l++){
					if (block2_exists(k - i, l - j) && block1_exists(k, l)){
						confliction = true;
						break;
					}
					else if (block1_exists(k, l) && !block2_exists(k - i, l - j)){
						//int_fast64_t piece_c = 0;
						if (block2_exists(k - i - 1, l - j)){//上
							block_c += eval_piece(block1[k][l], block2[k - i - 1][l - j], up);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][up][1] == block2[k - i - 1][l - j]) rank1_num++;
						}
						if (block2_exists(k - i, l - j - 1)){//左
							block_c += eval_piece(block1[k][l], block2[k - i][l - j - 1], left);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][left][1] == block2[k - i][l - j - 1]) rank1_num++;
						}
						if (block2_exists(k - i + 1, l - j)){//下
							block_c += eval_piece(block1[k][l], block2[k - i + 1][l - j], down);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][down][1] == block2[k - i + 1][l - j]) rank1_num++;
						}
						if (block2_exists(k - i, l - j + 1)){//右
							block_c += eval_piece(block1[k][l], block2[k - i][l - j + 1], right);
							empty_block_c = false;
							if (sorted_comparation[block1[k][l]][right][1] == block2[k - i][l - j + 1]) rank1_num++;
						}
					}
				}

				if (confliction)break;
			}
			if (!confliction && !empty_block_c){
				++rank1_num;
				block_c *= rank1_num; //0を掛けるのは怖い
				//if (block_c < 0)block_c = -pow(block_c, rank1_num);
				//if (block_c > 0)block_c = pow(block_c, rank1_num);
				if (block_c >= best_block_c){
					//block_size_check(i, j)
					best_block_c = block_c;
					best_shift_i = i;
					best_shift_j = j;
				}
			}
		}
	}
	/*
	if (best_shift_i == std::numeric_limits<int>::min()){
	std::cout << "結合すべきブロックがなかった" << std::endl;
	}
	*/
	block_combination return_struct{
		std::move(block1),
		std::move(block2),
		best_shift_j,
		best_shift_i,
		best_block_c
	};
	return std::move(return_struct);
}
std::int_fast64_t Murakami::eval_piece(const point_type& p1, const point_type& p2, direction dir){
	int_fast64_t score = 0;
	Murakami::direction dir2;
	switch (dir)
	{
	case Murakami::up:
		dir2 = Murakami::down;
		break;
	case Murakami::right:
		dir2 = Murakami::left;
		break;
	case Murakami::down:
		dir2 = Murakami::up;
		break;
	case Murakami::left:
		dir2 = Murakami::right;
		break;
	default:
		break;
	}
	score += eval_comp_(p1, p2, dir);
	score += eval_comp_(p2, p1, dir2);
	return score;
}
std::int_fast64_t Murakami::eval_comp_(const point_type& p1, const point_type& p2, direction dir){
	int rank = 0;
	std::int_fast64_t score = 0;
	/*この方がかっこいいけどちょっと遅い
	std::vector<point_type>::iterator it = find(sorted_comparation[p1][dir].begin(), sorted_comparation[p1][dir].end(), p2);
	rank = it - sorted_comparation[p1][dir].begin();
	*/

	for (const auto& it : sorted_comparation[p1][dir]){
		if (it == p2)break;
		rank++;
	}

	if (rank == 0)throw std::runtime_error("断片画像が重複しています");
	if (rank == 1){
		switch (dir)
		{
		case Murakami::up:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][up][2].y][sorted_comparation[p1][up][2].x].up - comp_[p1.y][p1.x][p2.y][p2.x].up;
			break;
		case Murakami::right:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][right][2].y][sorted_comparation[p1][right][2].x].right - comp_[p1.y][p1.x][p2.y][p2.x].right;
			break;
		case Murakami::down:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][down][2].y][sorted_comparation[p1][down][2].x].down - comp_[p1.y][p1.x][p2.y][p2.x].down;
			break;
		case Murakami::left:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][left][2].y][sorted_comparation[p1][left][2].x].left - comp_[p1.y][p1.x][p2.y][p2.x].left;
			break;
		default:
			break;
		}
	}
	else if (rank > 1){
		switch (dir)
		{
		case Murakami::up:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][up][1].y][sorted_comparation[p1][up][1].x].up - comp_[p1.y][p1.x][p2.y][p2.x].up;
			break;
		case Murakami::right:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][right][1].y][sorted_comparation[p1][right][1].x].right - comp_[p1.y][p1.x][p2.y][p2.x].right;
			break;
		case Murakami::down:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][down][1].y][sorted_comparation[p1][down][1].x].down - comp_[p1.y][p1.x][p2.y][p2.x].down;
			break;
		case Murakami::left:
			score = comp_[p1.y][p1.x][sorted_comparation[p1][left][1].y][sorted_comparation[p1][left][1].x].left - comp_[p1.y][p1.x][p2.y][p2.x].left;
			break;
		default:
			break;
		}
	}
	else{

	}
	return score;
}

void Murakami::make_sorted_comparation(){
	auto width = data_.split_num.first;
	auto height = data_.split_num.second;
	struct point_type_score{
		point_type point;
		int_fast64_t score;
		bool operator<(const point_type_score& right) const {
			return (score < right.score);
		}
	};

	std::unordered_map<point_type, std::vector<std::vector<point_type>>> sorted_point_score_dir_point;
	for (int k = 0; k < height; k++){
		for (int l = 0; l < width; l++){
			point_type point_k_l{ l, k };
			std::vector<std::vector<point_type>> sorted_point_score_dir;
			for (int dir_l = 0; dir_l < 4; dir_l++){
				std::vector<point_type_score> sorted_point_score;
				for (int i = 0; i < height; i++){
					for (int j = 0; j < width; j++){
						point_type p{ j, i };
						point_type_score t;
						t.point = p;
						switch (dir_l)
						{
						case up:
							t.score = comp_[k][l][i][j].up;
							break;
						case right:
							t.score = comp_[k][l][i][j].right;
							break;
						case down:
							t.score = comp_[k][l][i][j].down;
							break;
						case left:
							t.score = comp_[k][l][i][j].left;
							break;
						}
						sorted_point_score.push_back(t);
					}
				}
				std::sort(sorted_point_score.begin(), sorted_point_score.end());
				std::vector<point_type> p_vec;
				for (int a = 0; a < height * width; a++){
					p_vec.push_back(sorted_point_score[a].point);
				}
				sorted_point_score_dir.push_back(p_vec);
			}
			sorted_point_score_dir_point[point_k_l] = sorted_point_score_dir;
		}

	}

	sorted_comparation = std::move(sorted_point_score_dir_point);
}
Murakami::block_type Murakami::combine_block(const block_combination& block_comb){

	if (block_comb.block1.size() == 0)throw std::runtime_error("結合予定ブロックのサイズが0です,block1");
	if (block_comb.block2.size() == 0)throw std::runtime_error("結合予定ブロックのサイズが0です,block2");
	int const b1_width = block_comb.block1[0].size();
	int const b1_height = block_comb.block1.size();
	int const b2_width = block_comb.block2[0].size();
	int const b2_height = block_comb.block2.size();
	point_type lu, rd; //lu left_up,左上 rd right_down,右下
	lu.x = std::min(0, block_comb.shift_x);
	lu.y = std::min(0, block_comb.shift_y);
	rd.x = std::max(b1_width, block_comb.shift_x + b2_width);
	rd.y = std::max(b1_height, block_comb.shift_y + b2_height);
	block_size_type comb_block_size = rd - lu;
	auto const block1_exists = [b1_height, b1_width, &block_comb](int y, int x){
		return ((x >= 0 && x < b1_width && y >= 0 && y < b1_height) && (block_comb.block1[y][x].x != -1 || block_comb.block1[y][x].y != -1));
	};
	auto const block2_exists = [b2_height, b2_width, &block_comb](int y, int x){
		return ((x >= 0 && x < b2_width && y >= 0 && y < b2_height) && (block_comb.block2[y][x].x != -1 || block_comb.block2[y][x].y != -1));

	};
	auto const width = data_.split_num.first;
	auto const height = data_.split_num.second;
	int l_x = 0, l_y = 0;
	if (block_comb.shift_x < 0)l_x = -block_comb.shift_x;
	if (block_comb.shift_y < 0)l_y = -block_comb.shift_y;
	std::vector<std::vector<point_type>> return_combined_block(comb_block_size.y, std::vector<point_type>(comb_block_size.x, { -1, -1 }));

	for (int i = lu.y; i < rd.y; i++){
		for (int j = lu.x; j < rd.x; j++){
			if (block1_exists(i,j)){
				return_combined_block[i + l_y][j + l_x] = block_comb.block1[i][j];
			}else if (block2_exists(i - block_comb.shift_y, j - block_comb.shift_x)){
				return_combined_block[i + l_y][j + l_x] = block_comb.block2[i - block_comb.shift_y][j - block_comb.shift_x];
			}
		}
	}

	return return_combined_block;

}

Murakami::block_type Murakami::force_combine_block(std::vector<Murakami::block_type>& block_list){
	auto max_block_it = std::max_element(block_list.begin(), block_list.end(), [](const block_type& a, const block_type& b){
		return(a.size() + a[0].size() < b.size() + b[0].size());
	});
	block_type max_block = (*max_block_it);

	boost::remove_erase_if(block_list, [&max_block](block_type it){//block_listから結合する前のブロックを消す
		return (it == max_block);
	});

	std::for_each(
		max_block.begin(), max_block.end(),
		[this](std::vector<point_type>& elem)
		{
			elem.resize(data_.split_num.first, point_type{ -1, -1 });
		});

	max_block.resize(data_.split_num.second, std::vector<point_type>(data_.split_num.first, point_type{-1,-1}));

	for (auto &i : max_block){
		for (auto &j : i){
			if (j.x == -1 || j.y == -1){
				for (auto &list_i : block_list){
					for (auto &list_j : list_i){
						for (auto &list_k : list_j){
							if (list_k.x != -1 || list_k.y != -1){
								j = list_k;
								list_k.x = -1;
								list_k.y = -1;
								goto KIRISAME_MARISA;
							}
						}
					}
				}
			}
		KIRISAME_MARISA:;
		}
	}

	return max_block;
	/*
	auto pick_up_without_it = [&block_list](const block_type){

	};
	for (auto i : (*max_block_it)){
		for (auto j : i){
			if (!(j.x == -1) || !(j.y == -1))continue;
			pick_up_without_it(max_block_it);
		}
	}
	*/
}


// Gazebo hardware interface
// Author: Max Schwarz <max.schwarz@uni-bonn.de>

#include <robotcontrol/hw/gazebointerface.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <eigen_conversions/eigen_msg.h>
#include <tf_conversions/tf_eigen.h>
#include <pluginlib/class_list_macros.h>

namespace robotcontrol
{

GazeboInterface::GazeboInterface()
{
}

GazeboInterface::~GazeboInterface()
{
}

bool GazeboInterface::init(RobotModel* model)
{
	m_model = model;

	if(!ROSControlInterface::init(model))
		return false;

	ros::NodeHandle nh("~");
	if(!nh.getParam("modelName", m_modelName))
	{
		ROS_FATAL("modelName parameter is mandatory for GazeboInterface!");
		return false;
	}

	nh.param("/gazebo_perfect_odometry", m_publishOdom, false);

	m_sub_modelStates = nh.subscribe("/gazebo/model_states", 1, &GazeboInterface::handleModelStates, this);

	m_initTime = ros::Time::now();

	return true;
}

void GazeboInterface::handleModelStates(const gazebo_msgs::ModelStatesConstPtr& ms)
{
	// Sadly, gazebo does not provide a stamp.
	m_last_modelStatesStamp = ros::Time::now();
	m_last_modelStates = ms;
}

double signedAngleBetween(const Eigen::Vector3d& dir1, const Eigen::Vector3d& dir2, const Eigen::Vector3d& norm)
{
	double dot = dir1.dot(dir2);

	if(dot < -1.0)
		dot = -1.0;
	if(dot > 1.0)
		dot = 1.0;

	double angle = acos(dot);

	Eigen::Vector3d cross = dir1.cross(dir2);
	double sign = norm.dot(cross);
	if(sign < 0)
		angle *= -1.0;

	return angle;
}

bool GazeboInterface::readJointStates()
{
	if(!ROSControlInterface::readJointStates())
		return false;

	if(m_last_modelStates)
	{
		std::vector<std::string>::const_iterator it = std::find(
			m_last_modelStates->name.begin(), m_last_modelStates->name.end(),
			m_modelName
		);

		if(it != m_last_modelStates->name.end())
		{
			int idx = it - m_last_modelStates->name.begin();

			//
			// Orientation feedback
			//

			// Retrieve the robot pose
			const geometry_msgs::Pose& pose = m_last_modelStates->pose[idx];

			// Set the robot orientation
			Eigen::Quaterniond quat;
			tf::quaternionMsgToEigen(pose.orientation, quat);
			quat.normalize();
			m_model->setRobotOrientation(quat);

			// Provide /odom if wanted. Convention: /odom is on the floor.
			if(m_publishOdom && m_last_modelStatesStamp - m_initTime > ros::Duration(3.0))
			{
				tf::StampedTransform trans;
				trans.frame_id_ = "/odom";
				trans.child_frame_id_ = "/ego_rot";
				trans.stamp_ = m_last_modelStatesStamp;
				trans.setIdentity();

				tf::Vector3 translation;
				tf::pointMsgToTF(pose.position, translation);

				trans.setOrigin(translation);

				Eigen::Quaterniond rot;
				rot = Eigen::AngleAxisd(m_model->robotEYaw(), Eigen::Vector3d::UnitZ());

				tf::Quaternion quat;
				tf::quaternionEigenToTF(rot, quat);

				trans.setRotation(quat);

				ROS_DEBUG("robot pos: Z = %f, yaw: %f, stamp: %f", translation.z(), m_model->robotEYaw(), trans.stamp_.toSec());
				ROS_DEBUG("robot pos tf: %f %f %f %f %f %f %f",
					trans.getOrigin().x(), trans.getOrigin().y(), trans.getOrigin().z(),
					trans.getRotation().w(), trans.getRotation().x(), trans.getRotation().y(), trans.getRotation().z()
				);

				m_pub_tf.sendTransform(trans);
			}

			//
			// Angular velocity feedback
			//

			// Retrieve the robot twist
			const geometry_msgs::Twist& twist = m_last_modelStates->twist[idx];

			// Retrieve the robot angular velocity in global coordinates
			Eigen::Vector3d globalAngularVelocity;
			tf::vectorMsgToEigen(twist.angular, globalAngularVelocity);

			// Set the measured angular velocity (local coordinates)
			m_model->setRobotAngularVelocity(quat.conjugate() * globalAngularVelocity);

			//
			// Acceleration feedback
			//

			// We'd need an extra Gazebo plugin for acceleration sensing.
			// For now we can do without and simply calculate the current
			// acceleration due to gravity (neglect inertial accelerations).
			Eigen::Vector3d globalGravityAcceleration(0.0, 0.0, 9.81);

			// Set the measured acceleration (local coordinates)
			m_model->setAccelerationVector(quat.conjugate() * globalGravityAcceleration);

			//
			// Magnetic field vector feedback
			//

			// We assume that north is positive X. The values of 0.20G (horiz)
			// and 0.44G (vert) are approximately valid for central europe.
			Eigen::Vector3d globalMagneticVector(0.20, 0.00, -0.44); // In gauss

			// Set the measured magnetic field vector (local coordinates)
			m_model->setMagneticFieldVector(quat.conjugate() * globalMagneticVector);
		}
	}

	// Return success
	return true;
}

}

PLUGINLIB_EXPORT_CLASS(robotcontrol::GazeboInterface, robotcontrol::HardwareInterface)


// Copyright © 2011, Université catholique de Louvain
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// *  Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// *  Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifndef __BOOSTENVBIGINT_DECL_H
#define __BOOSTENVBIGINT_DECL_H

#include "boostenv-decl.hh"

#ifdef USE_GMP
#include <boost/multiprecision/gmp.hpp>
#else
#include <boost/multiprecision/cpp_int.hpp>
#endif

namespace mozart { namespace boostenv {

#ifdef USE_GMP
typedef boost::multiprecision::mpz_int mp_int;
#else
typedef boost::multiprecision::cpp_int mp_int;
#endif

class BoostBigInt : public BigIntImplem {
public:
  BoostBigInt(nativeint value) : _value(value) {};
  BoostBigInt(double value) : _value(value) {};
  BoostBigInt(const std::string& value) : _value(value) {};
  BoostBigInt(const mp_int& value) : _value(value) {};

  BoostBigInt(const BoostBigInt& src) = delete; // prevent copy

  mp_int value() { return _value; }

  inline
  std::shared_ptr<BigIntImplem> operator-();

  inline
  std::shared_ptr<BigIntImplem> operator+(std::shared_ptr<BigIntImplem> b);

  inline
  std::shared_ptr<BigIntImplem> operator+(nativeint b);

  inline
  std::shared_ptr<BigIntImplem> operator-(std::shared_ptr<BigIntImplem> b);

  inline
  std::shared_ptr<BigIntImplem> operator*(std::shared_ptr<BigIntImplem> b);

  inline
  std::shared_ptr<BigIntImplem> operator/(std::shared_ptr<BigIntImplem> b);

  inline
  std::shared_ptr<BigIntImplem> operator%(std::shared_ptr<BigIntImplem> b);

  inline
  int compare(nativeint b);

  inline
  int compare(std::shared_ptr<BigIntImplem> b);

  inline
  nativeint nativeintValue();

  inline
  double doubleValue();

  inline
  std::string str();

  inline
  void printReprToStream(VM vm, std::ostream& out, int depth, int width);

public:
  template <class T>
  static std::shared_ptr<BigIntImplem> make_shared_ptr(const T& value) {
    return std::static_pointer_cast<BigIntImplem>(std::make_shared<BoostBigInt>(value));
  }

private:
  static std::shared_ptr<BoostBigInt> cast(std::shared_ptr<BigIntImplem> b) {
    return std::static_pointer_cast<BoostBigInt>(b);
  }

private:
  mp_int _value;
};

} }

#endif // __BOOSTENVBIGINT_DECL_H


//  Copyright John Maddock 2020.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//
// This tests two things: that multiprecision::number meets our
// conceptual requirements, and that we can instantiate
// all our distributions and special functions on this type.
//
#define BOOST_MATH_ASSERT_UNDEFINED_POLICY false
#define TEST_GROUP_15

#ifdef _MSC_VER
#  pragma warning(disable:4800)
#  pragma warning(disable:4512)
#  pragma warning(disable:4127)
#  pragma warning(disable:4512)
#  pragma warning(disable:4503) // decorated name length exceeded, name was truncated
#endif

#include <boost/multiprecision/cpp_dec_float.hpp>
#include "compile_test/instantiate.hpp"

using namespace boost::multiprecision;

typedef number<cpp_dec_float<50>, et_on> test_type;

// We get sporadic internal compiler errors from gcc-7.x when CI testing
// that don't appear to be reproducible locally.  gcc-6.x and gcc-8.x are fine
// so for now it's a <shrug> and move on...
#if ! (defined(BOOST_GCC) && (__GNUC__ == 7))

void foo()
{
   instantiate(test_type());
}

#endif

int main()
{
   //BOOST_CONCEPT_ASSERT((boost::math::concepts::RealTypeConcept<test_type>));
}


#ifndef HOPS_LINEARPROGRAMGUROBIIMPL_HPP
#define HOPS_LINEARPROGRAMGUROBIIMPL_HPP

#include "LinearProgram.hpp"
#include <Eigen/Core>

#ifdef HOPS_GUROBI_FOUND

#include <gurobi_c++.h>
#include <memory>

namespace hops {
    class LinearProgramGurobiImpl : public LinearProgram {
    public:
        LinearProgramGurobiImpl(const Eigen::MatrixXd &A, const Eigen::VectorXd &b);

        LinearProgramGurobiImpl(const LinearProgramGurobiImpl &other);

        LinearProgramGurobiImpl &operator=(const LinearProgramGurobiImpl &other);

        [[nodiscard]] LinearProgramSolution solve(const Eigen::VectorXd &objective) const override;

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd> removeRedundantConstraints(double tolerance) override;

        [[nodiscard]] LinearProgramSolution computeChebyshevCenter() const override;

        [[nodiscard]] std::vector<long> computeUnconstrainedDimensions() const override;

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd>
        addBoxConstraintsToUnconstrainedDimensions(double lb, double ub) override;

    private:
        std::unique_ptr<GRBModel> model;
        std::vector<GRBVar> variables;
    };
}

#else //HOPS_GUROBI_FOUND

namespace hops {
    class LinearProgramGurobiImpl : public LinearProgram {
    public:
        LinearProgramGurobiImpl(const Eigen::MatrixXd &A, const Eigen::VectorXd &b) : LinearProgram(A, b) {
            throw std::runtime_error("HOPS did not find gurobi during compilation.");
        }

        [[nodiscard]] LinearProgramSolution solve(const Eigen::VectorXd &) const override {
            throw std::runtime_error("HOPS did not find gurobi during compilation.");
        }

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd> removeRedundantConstraints(double) override {
            throw std::runtime_error("HOPS did not find gurobi during compilation.");
        }

        [[nodiscard]] LinearProgramSolution computeChebyshevCenter() const override {
            throw std::runtime_error("HOPS did not find gurobi during compilation.");
        }

        [[nodiscard]] std::vector<long> computeUnconstrainedDimensions() const override {
            throw std::runtime_error("HOPS did not find gurobi during compilation.");
        }

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd>
        addBoxConstraintsToUnconstrainedDimensions(double, double) override {
            throw std::runtime_error("HOPS did not find gurobi during compilation.");
        }
    };
}

#endif //HOPS_GUROBI_FOUND
#endif //HOPS_LINEARPROGRAMGUROBIIMPL_HPP