// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <vector>
#include <iostream>
#include <sstream>

#include "include_base_utils.h"

#include "console_handler.h"

#include "p2p/net_node.h"
#include "currency_core/currency_basic.h"
#include "currency_core/currency_basic_impl.h"
#include "currency_core/currency_format_utils.h"
#include "currency_core/miner.h"

#include "chaingen.h"

using namespace std;

using namespace epee;
using namespace currency;

#define DIFF_UP_TIMESTAMP_DELTA 90

void test_generator::get_block_chain(std::vector<const block_info*>& blockchain, const crypto::hash& head, size_t n) const
{
  crypto::hash curr = head;
  while (null_hash != curr && blockchain.size() < n)
  {
    auto it = m_blocks_info.find(curr);
    if (m_blocks_info.end() == it)
    {
      throw std::runtime_error("block hash wasn't found");
    }

    blockchain.push_back(&it->second);
    curr = it->second.b.prev_id;
  }

  std::reverse(blockchain.begin(), blockchain.end());
}

void test_generator::get_last_n_block_sizes(std::vector<size_t>& block_sizes, const crypto::hash& head, size_t n) const
{
  std::vector<const block_info*> blockchain;
  get_block_chain(blockchain, head, n);
  BOOST_FOREACH(auto& bi, blockchain)
  {
    block_sizes.push_back(bi->block_size);
  }
}

uint64_t test_generator::get_already_generated_coins(const crypto::hash& blk_id) const
{
  auto it = m_blocks_info.find(blk_id);
  if (it == m_blocks_info.end())
    throw std::runtime_error("block hash wasn't found");

  return it->second.already_generated_coins;
}

uint64_t test_generator::get_already_donated_coins(const crypto::hash& blk_id) const
{
  auto it = m_blocks_info.find(blk_id);
  if (it == m_blocks_info.end())
    throw std::runtime_error("block hash wasn't found");

  return it->second.already_donated;
}

currency::wide_difficulty_type test_generator::get_block_difficulty(const crypto::hash& blk_id) const
{
  auto it = m_blocks_info.find(blk_id);
  if (it == m_blocks_info.end())
    throw std::runtime_error("block hash wasn't found");
  
  auto it_prev = m_blocks_info.find(it->second.b.prev_id);
  if (it_prev == m_blocks_info.end())
    throw std::runtime_error("block hash wasn't found");

  return it->second.cumul_difficulty - it_prev->second.cumul_difficulty;
}

uint64_t test_generator::get_already_generated_coins(const currency::block& blk) const
{
  crypto::hash blk_hash;
  get_block_hash(blk, blk_hash);
  return get_already_generated_coins(blk_hash);
}

void test_generator::add_block(const currency::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, 
                               uint64_t already_generated_coins, 
                               uint64_t already_donated_coins, 
                               wide_difficulty_type cum_diff)
{
  const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx);
  uint64_t block_reward;
  uint64_t max_donation;
  get_block_reward(misc_utils::median(block_sizes), block_size, already_generated_coins, already_donated_coins, block_reward, max_donation);
  uint64_t donation_for_block = 0;
  m_blocks_info[get_block_hash(blk)] = block_info(blk, already_generated_coins + block_reward, already_donated_coins + donation_for_block, block_size, cum_diff);
}

bool test_generator::construct_block(currency::block& blk, uint64_t height, const crypto::hash& prev_id,
                                     const currency::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, uint64_t already_donated_coins,
                                     std::vector<size_t>& block_sizes, const std::list<currency::transaction>& tx_list, const currency::alias_info& ai)
{
  blk.major_version = CURRENT_BLOCK_MAJOR_VERSION;
  blk.minor_version = CURRENT_BLOCK_MINOR_VERSION;
  blk.flags = BLOCK_FLAGS_SUPPRESS_DONATION;
  blk.timestamp = timestamp;
  blk.prev_id = prev_id;

  blk.tx_hashes.reserve(tx_list.size());
  BOOST_FOREACH(const transaction &tx, tx_list)
  {
    crypto::hash tx_hash;
    get_transaction_hash(tx, tx_hash);
    blk.tx_hashes.push_back(tx_hash);
  }

  uint64_t total_fee = 0;
  size_t txs_size = 0;
  BOOST_FOREACH(auto& tx, tx_list)
  {
    uint64_t fee = 0;
    bool r = get_tx_fee(tx, fee);
    CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block");
    total_fee += fee;
    txs_size += get_object_blobsize(tx);
  }

  account_keys donation_acc = AUTO_VAL_INIT(donation_acc);
  account_keys royalty_acc = AUTO_VAL_INIT(royalty_acc);
  get_donation_accounts(donation_acc, royalty_acc);


  blk.miner_tx = AUTO_VAL_INIT(blk.miner_tx);
  size_t target_block_size = txs_size + get_object_blobsize(blk.miner_tx);
  while (true)
  {
    if (!construct_miner_tx(height, misc_utils::median(block_sizes), 
                                    already_generated_coins, 
                                    already_donated_coins, 
                                    target_block_size, 
                                    total_fee, 
                                    miner_acc.get_keys().m_account_address, 
                                    donation_acc.m_account_address,
                                    royalty_acc.m_account_address,
                                    blk.miner_tx, 
                                    blobdata(), 
                                    10,
                                    0, 
                                    ai))
      return false;

    size_t actual_block_size = txs_size + get_object_blobsize(blk.miner_tx);
    if (target_block_size < actual_block_size)
    {
      target_block_size = actual_block_size;
    }
    else if (actual_block_size < target_block_size)
    {
      size_t delta = target_block_size - actual_block_size;
      blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0);
      actual_block_size = txs_size + get_object_blobsize(blk.miner_tx);
      if (actual_block_size == target_block_size)
      {
        break;
      }
      else
      {
        CHECK_AND_ASSERT_MES(target_block_size < actual_block_size, false, "Unexpected block size");
        delta = actual_block_size - target_block_size;
        blk.miner_tx.extra.resize(blk.miner_tx.extra.size() - delta);
        actual_block_size = txs_size + get_object_blobsize(blk.miner_tx);
        if (actual_block_size == target_block_size)
        {
          break;
        }
        else
        {
          CHECK_AND_ASSERT_MES(actual_block_size < target_block_size, false, "Unexpected block size");
          blk.miner_tx.extra.resize(blk.miner_tx.extra.size() + delta, 0);
          target_block_size = txs_size + get_object_blobsize(blk.miner_tx);
        }
      }
    }
    else
    {
      break;
    }
  }

  //blk.tree_root_hash = get_tx_tree_hash(blk);
  std::vector<const block_info*> blocks;
  get_block_chain(blocks, blk.prev_id, std::numeric_limits<size_t>::max());

  wide_difficulty_type a_diffic = get_difficulty_for_next_block(blocks);
  // Nonce search...
  blk.nonce = 0;
  while (!find_nounce(blk, blocks, a_diffic, height))
    blk.timestamp++;

  add_block(blk, txs_size, block_sizes, already_generated_coins, already_donated_coins, blocks.size() ? blocks.back()->cumul_difficulty + a_diffic: a_diffic);

  return true;
}

currency::wide_difficulty_type test_generator::get_difficulty_for_next_block(const crypto::hash& head_id)
{
  std::vector<const block_info*> blocks;
  get_block_chain(blocks, head_id, std::numeric_limits<size_t>::max());

  return get_difficulty_for_next_block(blocks);
}

currency::wide_difficulty_type test_generator::get_difficulty_for_next_block(const std::vector<const block_info*>& blocks)
{
  std::vector<uint64_t> timestamps;
  std::vector<wide_difficulty_type> commulative_difficulties;
  size_t offset = blocks.size() - std::min(blocks.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT));
  if(!offset)
    ++offset;//skip genesis block
  for(; offset < blocks.size(); offset++)
  {
    timestamps.push_back(blocks[offset]->b.timestamp);
    commulative_difficulties.push_back(blocks[offset]->cumul_difficulty);
  }
  return next_difficulty(timestamps, commulative_difficulties);
}


bool test_generator::find_nounce(block& blk, std::vector<const block_info*>& blocks, wide_difficulty_type dif, uint64_t height)
{
  if(height != blocks.size())
    throw std::runtime_error("wrong height in find_nounce");
  std::vector<crypto::hash> scratchpad_local;
  size_t count = 1;
  for(auto& i: blocks)
  {
    push_block_scratchpad_data(i->b, scratchpad_local);
#ifdef ENABLE_HASHING_DEBUG
    LOG_PRINT2("block_generation.log", "SCRATCHPAD_SHOT FOR H=" << count << ENDL << dump_scratchpad(scratchpad_local), LOG_LEVEL_3);
#endif
    ++count;
  }
  
  bool r = miner::find_nonce_for_given_block(blk, dif, height, [&](uint64_t index) -> crypto::hash&
  {
    return scratchpad_local[index%scratchpad_local.size()];
  });
#ifdef ENABLE_HASHING_DEBUG
  size_t call_no = 0;
  std::stringstream ss;
  crypto::hash pow = get_block_longhash(blk, height, [&](uint64_t index) -> crypto::hash&
  {
    ss << "[" << call_no << "][" << index << "%" << scratchpad_local.size() <<"(" << index%scratchpad_local.size() << ")]" << scratchpad_local[index%scratchpad_local.size()] << ENDL;
    ++call_no;
    return scratchpad_local[index%scratchpad_local.size()];
  });
  LOG_PRINT2("block_generation.log", "ID: " << get_block_hash(blk) << "[" << height <<  "]" << ENDL << "POW:" << pow << ENDL << ss.str(), LOG_LEVEL_3);
#endif
  return r;
}

bool test_generator::construct_block(currency::block& blk, const currency::account_base& miner_acc, uint64_t timestamp, const currency::alias_info& ai)
{
  std::vector<size_t> block_sizes;
  std::list<currency::transaction> tx_list;
  return construct_block(blk, 0, null_hash, miner_acc, timestamp, 0, 0, block_sizes, tx_list, ai);
}

bool test_generator::construct_block(currency::block& blk, const currency::block& blk_prev,
                                     const currency::account_base& miner_acc,
                                     const std::list<currency::transaction>& tx_list, const currency::alias_info& ai)
{
  uint64_t height = boost::get<txin_gen>(blk_prev.miner_tx.vin.front()).height + 1;
  crypto::hash prev_id = get_block_hash(blk_prev);
  // Keep push difficulty little up to be sure about PoW hash success
  uint64_t timestamp = height > 10 ? blk_prev.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN: blk_prev.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN - DIFF_UP_TIMESTAMP_DELTA;
  uint64_t already_generated_coins = get_already_generated_coins(prev_id);
  uint64_t already_donated_coins = get_already_donated_coins(prev_id);
  std::vector<size_t> block_sizes;
  get_last_n_block_sizes(block_sizes, prev_id, CURRENCY_REWARD_BLOCKS_WINDOW);

  return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, already_donated_coins, block_sizes, tx_list, ai);
}

bool test_generator::construct_block_manually(block& blk, const block& prev_block, const account_base& miner_acc,
                                              int actual_params/* = bf_none*/, uint8_t major_ver/* = 0*/,
                                              uint8_t minor_ver/* = 0*/, uint64_t timestamp/* = 0*/,
                                              const crypto::hash& prev_id/* = crypto::hash()*/, const wide_difficulty_type& diffic/* = 1*/,
                                              const transaction& miner_tx/* = transaction()*/,
                                              const std::vector<crypto::hash>& tx_hashes/* = std::vector<crypto::hash>()*/,
                                              size_t txs_sizes/* = 0*/)
{
  size_t height = get_block_height(prev_block) + 1;
  blk.flags = BLOCK_FLAGS_SUPPRESS_DONATION;
  blk.major_version = actual_params & bf_major_ver ? major_ver : CURRENT_BLOCK_MAJOR_VERSION;
  blk.minor_version = actual_params & bf_minor_ver ? minor_ver : CURRENT_BLOCK_MINOR_VERSION;
  blk.timestamp     = actual_params & bf_timestamp ? timestamp : (height > 10 ? prev_block.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN: prev_block.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN-DIFF_UP_TIMESTAMP_DELTA); // Keep difficulty unchanged
  blk.prev_id       = actual_params & bf_prev_id   ? prev_id   : get_block_hash(prev_block);
  blk.tx_hashes     = actual_params & bf_tx_hashes ? tx_hashes : std::vector<crypto::hash>();

  
  uint64_t already_generated_coins = get_already_generated_coins(prev_block);
  uint64_t already_donated_coins = get_already_donated_coins(get_block_hash(prev_block));
  std::vector<size_t> block_sizes;
  get_last_n_block_sizes(block_sizes, get_block_hash(prev_block), CURRENCY_REWARD_BLOCKS_WINDOW);
  if (actual_params & bf_miner_tx)
  {
    blk.miner_tx = miner_tx;
  }
  else
  {
    size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx);
    // TODO: This will work, until size of constructed block is less then CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE
    if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), 1))
      return false;
  }

  //blk.tree_root_hash = get_tx_tree_hash(blk);
  std::vector<const block_info*> blocks;
  get_block_chain(blocks, blk.prev_id, std::numeric_limits<size_t>::max());

  wide_difficulty_type a_diffic = actual_params & bf_diffic ? diffic : get_difficulty_for_next_block(blocks);
  find_nounce(blk, blocks, a_diffic, height);

  add_block(blk, txs_sizes, block_sizes, already_generated_coins, already_donated_coins, blocks.size() ? blocks.back()->cumul_difficulty + a_diffic: a_diffic);

  return true;
}

bool test_generator::construct_block_manually_tx(currency::block& blk, const currency::block& prev_block,
                                                 const currency::account_base& miner_acc,
                                                 const std::vector<crypto::hash>& tx_hashes, size_t txs_size)
{
  return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, transaction(), tx_hashes, txs_size);
}


struct output_index {
    const currency::txout_target_v out;
    uint64_t amount;
    size_t blk_height; // block height
    size_t tx_no; // index of transaction in block
    size_t out_no; // index of out in transaction
    size_t idx;
    bool spent;
    const currency::block *p_blk;
    const currency::transaction *p_tx;

    output_index(const currency::txout_target_v &_out, uint64_t _a, size_t _h, size_t tno, size_t ono, const currency::block *_pb, const currency::transaction *_pt)
        : out(_out), amount(_a), blk_height(_h), tx_no(tno), out_no(ono), idx(0), spent(false), p_blk(_pb), p_tx(_pt) { }

    output_index(const output_index &other)
        : out(other.out), amount(other.amount), blk_height(other.blk_height), tx_no(other.tx_no), out_no(other.out_no), idx(other.idx), spent(other.spent), p_blk(other.p_blk), p_tx(other.p_tx) {  }

    const std::string toString() const {
        std::stringstream ss;

        ss << "output_index{blk_height=" << blk_height
           << " tx_no=" << tx_no
           << " out_no=" << out_no
           << " amount=" << amount
           << " idx=" << idx
           << " spent=" << spent
           << "}";

        return ss.str();
    }

    output_index& operator=(const output_index& other)
    {
      new(this) output_index(other);
      return *this;
    }
};

typedef std::map<uint64_t, std::vector<size_t> > map_output_t;
typedef std::map<uint64_t, std::vector<output_index> > map_output_idx_t;
typedef pair<uint64_t, size_t>  outloc_t;

namespace
{
  uint64_t get_inputs_amount(const vector<tx_source_entry> &s)
  {
    uint64_t r = 0;
    BOOST_FOREACH(const tx_source_entry &e, s)
    {
      r += e.amount;
    }

    return r;
  }
}

bool init_output_indices(map_output_idx_t& outs, std::map<uint64_t, std::vector<size_t> >& outs_mine, const std::vector<currency::block>& blockchain, const map_hash2tx_t& mtx, const currency::account_base& from) {

    BOOST_FOREACH (const block& blk, blockchain) {
        vector<const transaction*> vtx;
        vtx.push_back(&blk.miner_tx);

        BOOST_FOREACH(const crypto::hash &h, blk.tx_hashes) {
            const map_hash2tx_t::const_iterator cit = mtx.find(h);
            if (mtx.end() == cit)
                throw std::runtime_error("block contains an unknown tx hash");

            vtx.push_back(cit->second);
        }

        //vtx.insert(vtx.end(), blk.);
        // TODO: add all other txes
        for (size_t i = 0; i < vtx.size(); i++) {
            const transaction &tx = *vtx[i];

            for (size_t j = 0; j < tx.vout.size(); ++j) {
                const tx_out &out = tx.vout[j];

                output_index oi(out.target, out.amount, boost::get<txin_gen>(*blk.miner_tx.vin.begin()).height, i, j, &blk, vtx[i]);

                if (2 == out.target.which()) { // out_to_key
                    outs[out.amount].push_back(oi);
                    size_t tx_global_idx = outs[out.amount].size() - 1;
                    outs[out.amount][tx_global_idx].idx = tx_global_idx;
                    // Is out to me?
                    if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), j)) {
                        outs_mine[out.amount].push_back(tx_global_idx);
                    }
                }
            }
        }
    }

    return true;
}

bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, const std::vector<currency::block>& blockchain, const map_hash2tx_t& mtx, const currency::account_base& from) {

    BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) {
        for (size_t i = 0; i < o.second.size(); ++i) {
            output_index &oi = outs[o.first][o.second[i]];

            // construct key image for this output
            crypto::key_image img;
            keypair in_ephemeral;
            generate_key_image_helper(from.get_keys(), get_tx_pub_key_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img);

            // lookup for this key image in the events vector
            BOOST_FOREACH(auto& tx_pair, mtx) {
                const transaction& tx = *tx_pair.second;
                BOOST_FOREACH(const txin_v &in, tx.vin) {
                    if (typeid(txin_to_key) == in.type()) {
                        const txin_to_key &itk = boost::get<txin_to_key>(in);
                        if (itk.k_image == img) {
                            oi.spent = true;
                        }
                    }
                }
            }
        }
    }

    return true;
}

bool fill_output_entries(std::vector<output_index>& out_indices,
                         size_t sender_out, size_t nmix, uint64_t& real_entry_idx,
                         std::vector<tx_source_entry::output_entry>& output_entries)
{
  if (out_indices.size() <= nmix)
    return false;

  bool sender_out_found = false;
  size_t rest = nmix;
  for (size_t i = 0; i < out_indices.size() && (0 < rest || !sender_out_found); ++i)
  {
    const output_index& oi = out_indices[i];
    if (oi.spent)
      continue;

    bool append = false;
    if (i == sender_out)
    {
      append = true;
      sender_out_found = true;
      real_entry_idx = output_entries.size();
    }
    else if (0 < rest)
    {
      if(boost::get<txout_to_key>(oi.out).mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX || boost::get<txout_to_key>(oi.out).mix_attr > nmix+1)
        continue;

      --rest;
      append = true;
    }

    if (append)
    {
      const txout_to_key& otk = boost::get<txout_to_key>(oi.out);
      output_entries.push_back(tx_source_entry::output_entry(oi.idx, otk.key));
    }
  }

  return 0 == rest && sender_out_found;
}

bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<test_event_entry>& events,
                     const block& blk_head, const currency::account_base& from, uint64_t amount, size_t nmix, bool check_for_spends = true)
{
    map_output_idx_t outs;
    map_output_t outs_mine;

    std::vector<currency::block> blockchain;
    map_hash2tx_t mtx;
    if (!find_block_chain(events, blockchain, mtx, get_block_hash(blk_head)))
        return false;

    if (!init_output_indices(outs, outs_mine, blockchain, mtx, from))
        return false;

    if(check_for_spends)
    {
      if (!init_spent_output_indices(outs, outs_mine, blockchain, mtx, from))
        return false;
    }

    // Iterate in reverse is more efficiency
    uint64_t sources_amount = 0;
    bool sources_found = false;
    BOOST_REVERSE_FOREACH(const map_output_t::value_type o, outs_mine)
    {
        for (size_t i = 0; i < o.second.size() && !sources_found; ++i)
        {
            size_t sender_out = o.second[i];
            const output_index& oi = outs[o.first][sender_out];
            if (oi.spent)
                continue;

            currency::tx_source_entry ts;
            ts.amount = oi.amount;
            ts.real_output_in_tx_index = oi.out_no;
            ts.real_out_tx_key = get_tx_pub_key_from_extra(*oi.p_tx); // incoming tx public key
            if (!fill_output_entries(outs[o.first], sender_out, nmix, ts.real_output, ts.outputs))
              continue;

            sources.push_back(ts);

            sources_amount += ts.amount;
            sources_found = amount <= sources_amount;
        }

        if (sources_found)
            break;
    }

    return sources_found;
}

bool fill_tx_destination(tx_destination_entry &de, const currency::account_base &to, uint64_t amount) {
    de.addr = to.get_keys().m_account_address;
    de.amount = amount;
    return true;
}

void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const block& blk_head,
                                      const currency::account_base& from, const currency::account_base& to,
                                      uint64_t amount, uint64_t fee, size_t nmix, std::vector<tx_source_entry>& sources,
                                      std::vector<tx_destination_entry>& destinations,
                                      bool check_for_spends)
{
  sources.clear();
  destinations.clear();

  if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix, check_for_spends))
    throw std::runtime_error("couldn't fill transaction sources");

  tx_destination_entry de;
  if (!fill_tx_destination(de, to, amount))
    throw std::runtime_error("couldn't fill transaction destination");
  destinations.push_back(de);

  tx_destination_entry de_change;
  uint64_t cache_back = get_inputs_amount(sources) - (amount + fee);
  if (0 < cache_back)
  {
    if (!fill_tx_destination(de_change, from, cache_back))
      throw std::runtime_error("couldn't fill transaction cache back destination");
    destinations.push_back(de_change);
  }
}

/*
void fill_nonce(currency::block& blk, const wide_difficulty_type& diffic, uint64_t height)
{
  blk.nonce = 0;
  while (!miner::find_nonce_for_given_block(blk, diffic, height))
    blk.timestamp++;
}*/

bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins,
                                 const account_public_address& miner_address, transaction& tx, uint64_t fee,
                                 keypair* p_txkey/* = 0*/)
{
  keypair txkey;
  txkey = keypair::generate();
  add_tx_pub_key_to_extra(tx, txkey.pub);

  if (0 != p_txkey)
    *p_txkey = txkey;

  txin_gen in;
  in.height = height;
  tx.vin.push_back(in);

  // This will work, until size of constructed block is less then CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE
  uint64_t block_reward;
  uint64_t max_donation;
  if (!get_block_reward(0, 0, already_generated_coins, 0, block_reward, max_donation))
  {
    LOG_PRINT_L0("Block is too big");
    return false;
  }
  block_reward += fee;

  crypto::key_derivation derivation;
  crypto::public_key out_eph_public_key;
  crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation);
  crypto::derive_public_key(derivation, 0, miner_address.m_spend_public_key, out_eph_public_key);

  tx_out out;
  out.amount = block_reward;
  out.target = txout_to_key(out_eph_public_key);
  tx.vout.push_back(out);

  tx.version = CURRENT_TRANSACTION_VERSION;
  tx.unlock_time = height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW;

  return true;
}

bool construct_tx_to_key(const std::vector<test_event_entry>& events, currency::transaction& tx, const block& blk_head,
                         const currency::account_base& from, const currency::account_base& to, uint64_t amount,
                         uint64_t fee, size_t nmix, uint8_t mix_attr, bool check_for_spends)
{
  vector<tx_source_entry> sources;
  vector<tx_destination_entry> destinations;
  fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations, check_for_spends);

  return construct_tx(from.get_keys(), sources, destinations, tx, 0, mix_attr);
}

transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head,
                                  const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee)
{
  transaction tx;
  construct_tx_to_key(events, tx, blk_head, acc_from, acc_to, amount, fee, 0);
  events.push_back(tx);
  return tx;
}

uint64_t get_balance(const currency::account_base& addr, const std::vector<currency::block>& blockchain, const map_hash2tx_t& mtx) {
    uint64_t res = 0;
    std::map<uint64_t, std::vector<output_index> > outs;
    std::map<uint64_t, std::vector<size_t> > outs_mine;

    map_hash2tx_t confirmed_txs;
    get_confirmed_txs(blockchain, mtx, confirmed_txs);

    if (!init_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr))
        return false;

    if (!init_spent_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr))
        return false;

    BOOST_FOREACH (const map_output_t::value_type &o, outs_mine) {
        for (size_t i = 0; i < o.second.size(); ++i) {
            if (outs[o.first][o.second[i]].spent)
                continue;

            res += outs[o.first][o.second[i]].amount;
        }
    }

    return res;
}

void get_confirmed_txs(const std::vector<currency::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs)
{
  std::unordered_set<crypto::hash> confirmed_hashes;
  BOOST_FOREACH(const block& blk, blockchain)
  {
    BOOST_FOREACH(const crypto::hash& tx_hash, blk.tx_hashes)
    {
      confirmed_hashes.insert(tx_hash);
    }
  }

  BOOST_FOREACH(const auto& tx_pair, mtx)
  {
    if (0 != confirmed_hashes.count(tx_pair.first))
    {
      confirmed_txs.insert(tx_pair);
    }
  }
}

bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<currency::block>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head) {
    std::unordered_map<crypto::hash, const block*> block_index;
    BOOST_FOREACH(const test_event_entry& ev, events)
    {
        if (typeid(block) == ev.type())
        {
            const block* blk = &boost::get<block>(ev);
            block_index[get_block_hash(*blk)] = blk;
        }
        else if (typeid(transaction) == ev.type())
        {
            const transaction& tx = boost::get<transaction>(ev);
            mtx[get_transaction_hash(tx)] = &tx;
        }
    }

    bool b_success = false;
    crypto::hash id = head;
    for (auto it = block_index.find(id); block_index.end() != it; it = block_index.find(id))
    {
        blockchain.push_back(*it->second);
        id = it->second->prev_id;
        if (null_hash == id)
        {
            b_success = true;
            break;
        }
    }
    reverse(blockchain.begin(), blockchain.end());

    return b_success;
}


void test_chain_unit_base::register_callback(const std::string& cb_name, verify_callback cb)
{
  m_callbacks[cb_name] = cb;
}
bool test_chain_unit_base::verify(const std::string& cb_name, currency::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
  auto cb_it = m_callbacks.find(cb_name);
  if(cb_it == m_callbacks.end())
  {
    LOG_ERROR("Failed to find callback " << cb_name);
    return false;
  }
  return cb_it->second(c, ev_index, events);
}
