//
// Created by mwo on 8/01/17.
//

#define MYSQLPP_SSQLS_NO_STATICS 1

#include "TxSearch.h"

#include "db/MySqlAccounts.h"

#include "db/ssqlses.h"

#include "CurrentBlockchainStatus.h"
#include "src/UniversalIdentifier.hpp"

namespace xmreg
{




TxSearch::TxSearch(XmrAccount const& _acc,
                   std::shared_ptr<CurrentBlockchainStatus> _current_bc_status)
    : current_bc_status {_current_bc_status}
{
    acc = make_shared<XmrAccount>(_acc);

    // creates an mysql connection for this thread
    xmr_accounts = make_shared<MySqlAccounts>(current_bc_status);

    network_type net_type = current_bc_status->get_bc_setup().net_type;


    if (!xmreg::parse_str_address(acc->address, address, net_type))
    {
        OMERROR << "Cant parse address: " + acc->address;
        throw TxSearchException("Cant parse address: " + acc->address);
    }

    if (!xmreg::parse_str_secret_key(acc->viewkey, viewkey))
    {
        OMERROR << "Cant parse private view key: " + acc->address;
        throw TxSearchException("Cant parse private view key: "
                                + acc->viewkey);
    }

    populate_known_outputs();

    // start searching from last block that we searched for
    // this accont
    searched_blk_no = acc->scanned_block_height;

    last_ping_timestamp = 0s;

    address_prefix = acc->address.substr(0, 6);

    ping();
}

void
TxSearch::operator()()
{

seconds current_timestamp = get_current_timestamp();

last_ping_timestamp = current_timestamp;

uint64_t blocks_lookahead
        = current_bc_status->get_bc_setup().blocks_search_lookahead;


auto current_bc_status_ptr = current_bc_status.get();

uint64_t account_id = acc->id.data;

searching_is_ongoing = true;

MicroCoreAdapter mcore_addapter {current_bc_status_ptr};

// we put everything in massive catch, as there are plenty ways in which
// an exceptions can be thrown here. Mostly from mysql.
// but because this is detatch thread, we cant catch them in main thread.
// program will blow up if exception is thrown. need to handle exceptions
// here.
try
{
while(continue_search)
{            

seconds loop_timestamp {current_timestamp};

uint64_t last_block_height = current_bc_status->current_height;

uint64_t h1 = searched_blk_no;
uint64_t h2 = std::min(h1 + blocks_lookahead - 1, last_block_height);

vector<block> blocks;

blocks = current_bc_status->get_blocks_range(h1, h2);

if (blocks.empty())
{

    if (h1 <= h2)
    {
        OMERROR << address_prefix  << ": cant get blocks from " << h1
                << " to " << h2;
        stop();
    }
    else
    {
        OMVLOG1 << address_prefix << ": waiting for new block. "
               << "Last scanned was " << h2;
    }

    std::this_thread::sleep_for(
            std::chrono::seconds(
                    current_bc_status->get_bc_setup()
                    .refresh_block_status_every)
    );

    loop_timestamp = get_current_timestamp();

    // if thread has lived longer than thread_search_li
    // without last_ping_timestamp being updated,
    // stop the thread
    if (loop_timestamp - last_ping_timestamp > thread_search_life)
    {
        OMINFO << address_prefix
                  + ": search thread stopped.";
        stop();
    }

    // update current_height of blockchain, as maybe top block(s)
    // were dropped due to reorganization.
    //CurrentBlockchainStatus::update_current_blockchain_height();

    // if any txs that we already indexed got orphaned as a
    // consequence of this
    // MySqlAccounts::select_txs_for_account_spendability_check
    // should
    // update database accordingly when get_address_txs is executed.

    continue;
}

OMVLOG2 << address_prefix  + ": analyzing "
        << blocks.size() << " blocks from "
        << h1 << " to " << h2
        << " out of " << last_block_height << " blocks";

vector<crypto::hash> txs_hashes_from_blocks;
vector<transaction> txs_in_blocks;
vector<CurrentBlockchainStatus::txs_tuple_t> txs_data;

if (!current_bc_status->get_txs_in_blocks(blocks,
                                          txs_hashes_from_blocks,
                                          txs_in_blocks,
                                          txs_data))
{
    OMERROR << address_prefix
               + ": cant get tx in blocks from "
            << h1 << " to " << h2;
    return;
}

// we will only create mysql DateTime object once, anything is found
// in a given block;
unique_ptr<DateTime> blk_timestamp_mysql_format;

// searching for our incoming and outgoing xmr has two components.
//
// FIRST. to search for the incoming xmr, we use address, viewkey and
// outputs public key. Its straight forward, as this is what viewkey was
// designed to do.
//
// SECOND. Searching for spendings (i.e., key images) is more difficult,
// because we dont have spendkey. But what we can do is, we can look for
// candidate key images. And this can be achieved by checking if any mixin
// in associated with the given key image, is our output. If it is our output,
// then we assume its our key image (i.e. we spend this output).
// Off course this is only
// assumption as our outputs can be used in key images of others for their
// mixin purposes. Thus, we sent to the front end the list of key images
// that we think are yours, and the frontend, because it has spend key,
// can filter out false positives.

size_t tx_idx {0};

for (auto const& tx_tuple: txs_data)
{
    crypto::hash const& tx_hash = txs_hashes_from_blocks[tx_idx];
    transaction const& tx       = txs_in_blocks[tx_idx++];
    uint64_t blk_height         = std::get<0>(tx_tuple);
    uint64_t blk_timestamp      = std::get<1>(tx_tuple);
    bool is_coinbase            = std::get<2>(tx_tuple);
    bool is_rct                 = (tx.version == 2);
    uint8_t rct_type            = (is_rct ? tx.rct_signatures.type : 0);


    // Class that is responsible for identification of our outputs
    // and inputs in a given tx.

    auto identifier = make_identifier(tx,
                        make_unique<Output>(&address, &viewkey),
                        make_unique<Input>(&address, &viewkey,
                                           &known_outputs_keys,
                                           &mcore_addapter));
    identifier.identify();

    // flag indicating whether the txs in the given block are
    // spendable.
    // this is true when block number is more than 10 blocks
    // from current blockchain height.
    // if tx.unlock_time is not given (its value is 0), we set it
    // here. For coinbase its always given, so no need to check
    // for that

    uint64_t tx_unlock_time = tx.unlock_time;

    if (tx_unlock_time == 0)
        tx_unlock_time = blk_height
            + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE;

    bool is_spendable = current_bc_status->is_tx_unlocked(
            tx_unlock_time, blk_height);

    // this is id of txs in lmdb blockchain table.
    // it will be used mostly to sort txs in the frontend.
    uint64_t blockchain_tx_id {0};

    // FIRSt step.
    //
    //oi_identification.identify_outputs();
    auto const& outputs_identified
        = identifier.get<Output>()->get();

    auto total_received = calc_total_xmr(outputs_identified);

    vector<uint64_t> amount_specific_indices;

    uint64_t tx_mysql_id {0};

    // create pointer to mysql transaction object
    // that we will initilize if we find something.
    unique_ptr<mysqlpp::Transaction> mysql_transaction;

    // if we identified some outputs as ours,
    // save them into mysql.
    if (!outputs_identified.empty())
    {
        auto tx_hash_str = pod_to_hex(tx_hash);
        auto tx_hash_prefix_str
            = pod_to_hex(get_transaction_prefix_hash(tx));
        auto tx_pub_key_str
            = pod_to_hex(identifier.get_tx_pub_key());
        uint64_t mixin_no {0};
        if (!is_coinbase)
            mixin_no = xmreg::get_mixin_no(tx);


        if (!blk_timestamp_mysql_format)
        {
            blk_timestamp_mysql_format
                    = unique_ptr<DateTime>(
                            new DateTime(
                            static_cast<time_t>(blk_timestamp)));
        }

        if (!mysql_transaction)
        {
            // start mysql transaction here
            mysql_transaction
                    = unique_ptr<mysqlpp::Transaction>(
                               new mysqlpp::Transaction(
                                       xmr_accounts->get_connection()
                                               ->get_connection()));

            // when we rescan blockchain some txs can already
            // be present in the mysql. So remove them, and their
            // associated data in that case to repopulate fresh
            // tx data
            if (!delete_existing_tx_if_exists(tx_hash_str))
                throw TxSearchException("Cant delete tx "
                             + tx_hash_str);

        }

        if (!current_bc_status->tx_exist(tx_hash, blockchain_tx_id))
        {
            OMERROR << "Tx " << tx_hash_str
                    << " not found in blockchain!";
            throw TxSearchException("Cant get tx from blockchain: "
                                    + tx_hash_str);
        }

        OMVLOG1 << address_prefix + ": found some outputs in block "
                << blk_height << ", tx: " << tx_hash_str;


        XmrTransaction tx_data;

        tx_data.id               = mysqlpp::null;
        tx_data.hash             = tx_hash_str;
        tx_data.prefix_hash      = tx_hash_prefix_str;
        tx_data.tx_pub_key       = tx_pub_key_str;
        tx_data.account_id       = account_id;
        tx_data.blockchain_tx_id = blockchain_tx_id;
        tx_data.total_received   = total_received;
        tx_data.total_sent       = 0; // at this stage we don't have
                                     //  anyinfo about spendings

                                     // this is current block
                                     // + unlock time
                                     // for regular tx,
                                     // the unlock time is
                                     // default of 10 blocks.
                                     // for coinbase tx it is 60 blocks
        tx_data.unlock_time      = tx_unlock_time;

        tx_data.height           = blk_height;
        tx_data.coinbase         = is_coinbase;
        tx_data.is_rct           = is_rct;
        tx_data.rct_type         = rct_type;
        tx_data.spendable        = is_spendable;
        tx_data.payment_id       = current_bc_status
                                    ->get_payment_id_as_string(tx);
        tx_data.mixin            = mixin_no;
        tx_data.timestamp        = *blk_timestamp_mysql_format;


        // insert tx_data into mysql's Transactions table
        tx_mysql_id = xmr_accounts->insert(tx_data);

        // get amount specific (i.e., global) indices of outputs
        if (!current_bc_status->get_amount_specific_indices(
                tx_hash, amount_specific_indices))
        {
            OMERROR << address_prefix
                       + ": cant get_amount_specific_indices!";
            throw TxSearchException(
                        "cant get_amount_specific_indices!");
        }

        if (tx_mysql_id == 0)
        {
            OMERROR << address_prefix
                       + ": tx_mysql_id is zero!"
                    << tx_data;
            throw TxSearchException("tx_mysql_id is zero!");
        }

        vector<XmrOutput> outputs_found;

        // now add the found outputs into Outputs tables
        for (auto&& out_info: outputs_identified)
        {
            XmrOutput out_data;

            out_data.id           = mysqlpp::null;
            out_data.account_id   = account_id;
            out_data.tx_id        = tx_mysql_id;
            out_data.out_pub_key  = pod_to_hex(out_info.pub_key);
            out_data.tx_pub_key   = tx_pub_key_str;
            out_data.amount       = out_info.amount;
            out_data.out_index    = out_info.idx_in_tx;
            out_data.rct_outpk    = pod_to_hex(out_info.rtc_outpk);
            out_data.rct_mask     = pod_to_hex(out_info.rtc_mask);
            out_data.rct_amount   = pod_to_hex(out_info.rtc_amount);
            out_data.global_index = amount_specific_indices
                    .at(out_data.out_index);
            out_data.mixin        = tx_data.mixin;
            out_data.timestamp    = tx_data.timestamp;

            outputs_found.push_back(std::move(out_data));

            {
                // add the outputs found into known_outputs_keys map
                std::lock_guard<std::mutex> lck (
                            getting_known_outputs_keys);
                known_outputs_keys.insert(
                    {out_info.pub_key, out_info.amount});
            }

        } //  for (auto& out_info: outputs_identified)


        // insert all outputs found into mysql's outputs table
        uint64_t no_rows_inserted
                = xmr_accounts->insert(outputs_found);

        if (no_rows_inserted == 0)
        {
            throw TxSearchException("no_rows_inserted is zero!");
        }

    } // if (!found_mine_outputs.empty())

    // SECOND component: Checking for our key images, i.e., inputs.

    // no need mutex here, as this will be exectued only after
    // the above. there is no threads here.

    auto const& inputs_identfied = identifier.get<Input>()->get();


    if (!inputs_identfied.empty())
    {
        // some inputs were identified as ours in a given tx.
        // so now, go over those inputs, and check
        // get detail info for each found mixin output from database

        auto tx_hash_str = pod_to_hex(tx_hash);
        auto tx_hash_prefix_str
            = pod_to_hex(get_transaction_prefix_hash(tx));
        auto tx_pub_key_str
            = pod_to_hex(identifier.get_tx_pub_key());
        uint64_t mixin_no {0};
        if (mixin_no == 0 && !is_coinbase)
            mixin_no = xmreg::get_mixin_no(tx);

        if (!blk_timestamp_mysql_format)
        {
            blk_timestamp_mysql_format
                    = unique_ptr<DateTime>(
                    new DateTime(
                            static_cast<time_t>(blk_timestamp)));
        }

        if (!mysql_transaction)
        {
            // start mysql transaction here if not already present
            mysql_transaction
                    = unique_ptr<mysqlpp::Transaction>(
                    new mysqlpp::Transaction(
                            xmr_accounts->get_connection()
                                    ->get_connection()));

            // when we rescan blockchain some txs can already
            // be present in the mysql. So remove them, and their
            // associated data in that case to repopulate fresh tx data

            // this will only execture if no outputs were found
            // above. So there is no risk of deleting same tx twice
            if (!delete_existing_tx_if_exists(tx_hash_str))
            {
                throw TxSearchException(
                        "Cant delete tx " + tx_hash_str);
            }
        }

        if (blockchain_tx_id == 0)
        {
            if (!current_bc_status
                    ->tx_exist(tx_hash, blockchain_tx_id))
            {
                OMERROR << address_prefix  + ": tx "
                        << tx_hash_str
                         << "not found in blockchain !";
                throw TxSearchException(
                            "Cant get tx from blockchain: "
                            + tx_hash_str);
            }
        }

        OMVLOG1 << address_prefix + ": found some possible "
                << "inputs in block " << blk_height << ", tx: "
                << tx_hash_str;

        vector<XmrInput> inputs_found;

        for (auto& in_info: inputs_identfied)
        {
            XmrOutput out;

            if (xmr_accounts->output_exists(
                        pod_to_hex(in_info.out_pub_key), out))
            {

                // seems that this key image is ours.
                // so get it information from database into XmrInput
                // database structure that will be written later
                // on into database.

                XmrInput in_data;

                in_data.id          = mysqlpp::null;
                in_data.account_id  = acc->id.data;
                in_data.tx_id       = 0; // later we set it
                in_data.output_id   = out.id.data;
                in_data.key_image   = pod_to_hex(in_info.key_img);
                in_data.amount      = out.amount;
                                        // must match corresponding
                                        // output's amount
                in_data.timestamp   = *blk_timestamp_mysql_format;

                inputs_found.push_back(in_data);

            } // if (xmr_accounts->output_exists(o

        } // for (auto& in_info: inputs_identfied)


        if (!inputs_found.empty())
        {
            // seems we have some inputs found. time
            // to write it to mysql. But first,
            // check if this tx is written in mysql.

            // calculate how much we preasumply spent.
            uint64_t total_sent {0};

            for (const XmrInput& in_data: inputs_found)
                total_sent += in_data.amount;

            if (tx_mysql_id == 0)
            {
                // this txs hasnt been seen in step first.
                // it means that it only contains potentially our
                // key images. It does not have our outputs.
                // so write it to mysql as ours, with
                // total received of 0.

                XmrTransaction tx_data;

                tx_data.id               = mysqlpp::null;
                tx_data.hash             = tx_hash_str;
                tx_data.prefix_hash      = tx_hash_prefix_str;
                tx_data.tx_pub_key       = tx_pub_key_str;
                tx_data.account_id       = acc->id.data;
                tx_data.blockchain_tx_id = blockchain_tx_id;
                tx_data.total_received   = 0; // because this is
                                              //spending,
                                              //total_recieved is 0
                tx_data.total_sent       = total_sent;
                tx_data.unlock_time      = tx_unlock_time;
                tx_data.height           = blk_height;
                tx_data.coinbase         = is_coinbase;
                tx_data.is_rct           = is_rct;
                tx_data.rct_type         = rct_type;
                tx_data.spendable        = is_spendable;
                tx_data.payment_id       = current_bc_status
                                ->get_payment_id_as_string(tx);
                tx_data.mixin            = mixin_no;
                tx_data.timestamp    = *blk_timestamp_mysql_format;

                // insert tx_data into mysql's Transactions table
                tx_mysql_id = xmr_accounts->insert(tx_data);

                if (tx_mysql_id == 0)
                {
                    OMERROR << address_prefix
                               + ": tx_mysql_id is zero!"
                            << tx_data;

                    //cerr << "tx_mysql_id is zero!" << endl;
                    throw TxSearchException("tx_mysql_id is zero!");
                    // it did not insert this tx, because maybe
                    // it already
                    // exisits in the MySQL. So maybe can now
                    // check if we have it and get tx_mysql_id this
                    // way.
                }

            } //   if (tx_mysql_id == 0)

            // save all input found into database at once
            // but first update tx_mysql_id for these inputs
            for (XmrInput& in_data: inputs_found)
                in_data.tx_id = tx_mysql_id; // set tx id now.
                                            //before we made it 0

            uint64_t no_rows_inserted
                    = xmr_accounts->insert(inputs_found);

            if (no_rows_inserted == 0)
            {
                throw TxSearchException(
                            "no_rows_inserted is zero!");
            }

        } //  if (!inputs_found.empty())

    } //  if (!oi_identification.identified_inputs.empty())

    // if we get to this point, we assume that all tx related
    // tables are ready
    // to be written, i.e., Transactions,
    // Outputs and Inputs. If so, write
    // all this into database.

    if (mysql_transaction)
        mysql_transaction->commit();

} // for (auto const& tx_pair: txs_map)

// update scanned_block_height every given interval
// or when we reached top of the blockchain

std::lock_guard<std::mutex> acc_lck(access_acc);

XmrAccount updated_acc = *acc;

updated_acc.scanned_block_height    = h2;
updated_acc.scanned_block_timestamp
        = DateTime(static_cast<time_t>(
                       blocks.back().timestamp));

if (xmr_accounts->update(*acc, updated_acc))
{
    // iff success, set acc to updated_acc;
    //cout << "scanned_block_height updated\n";
    *acc = updated_acc;
}

//current_timestamp = loop_timestamp;
// update this only when this variable is false
// otherwise a new search block value can
// be overwritten to h2, instead of the new value
if (!searched_block_got_updated)
    searched_blk_no = h2 + 1;

searched_block_got_updated = false;

} // while(continue_search)

}
catch(std::exception const& e)
{
    OMERROR << address_prefix
               + ": exception in TxSearch: "
            << e.what();
   set_exception_ptr();
}
catch(...)
{
    OMERROR << address_prefix
               + ": exception in TxSearch!";
    set_exception_ptr();
}

searching_is_ongoing = false;

// it will stop anyway, but just call it so we get info message pritened out
stop();


}

void
TxSearch::stop()
{
    OMINFO << address_prefix  + ": stopping the thread";
    continue_search = false;
}

TxSearch::~TxSearch()
{
    OMINFO << address_prefix
              + ": txSearch thread destroyed";
}

void
TxSearch::set_searched_blk_no(uint64_t new_value)
{
    searched_blk_no = new_value;
    searched_block_got_updated = true;
}

uint64_t
TxSearch::get_searched_blk_no() const
{
    return searched_blk_no;
}

inline seconds
TxSearch::get_current_timestamp() const
{
   return  chrono::duration_cast<seconds>(
          chrono::system_clock::now().time_since_epoch());
}

void
TxSearch::ping()
{
    //OMINFO << "New last_ping_timestamp: "
    //       << last_ping_timestamp.count();

    last_ping_timestamp = chrono::duration_cast<seconds>(
            chrono::system_clock::now().time_since_epoch());
}

bool
TxSearch::still_searching() const
{
    return searching_is_ongoing;
}

void
TxSearch::populate_known_outputs()
{
    vector<XmrOutput> outs;

    if (xmr_accounts->select(acc->id.data, outs))
    {
        for (const XmrOutput& out: outs)
        {
            public_key out_pub_key;

            hex_to_pod(out.out_pub_key, out_pub_key);

            known_outputs_keys[out_pub_key] = out.amount;
        }
    }
}

TxSearch::known_outputs_t
TxSearch::get_known_outputs_keys()
{
    std::lock_guard<std::mutex> lck (getting_known_outputs_keys);
    return known_outputs_keys;
};

void
TxSearch::find_txs_in_mempool(
        TxSearch::pool_txs_t mempool_txs,
        json* j_transactions)
{   

*j_transactions = json::array();

uint64_t current_height = current_bc_status
        ->get_current_blockchain_height();

auto current_bc_status_ptr = current_bc_status.get();

known_outputs_t known_outputs_keys_copy = get_known_outputs_keys();

// since find_txs_in_mempool can be called outside of this thread,
// we need to use local connection. we cant use connection that the
// main search method is using, as we can end up wtih mysql errors
// mysql will blow up when two queries are done at the same
// time in a single connection.
// so we create local connection here, only to be used in this method.

auto local_xmr_accounts = make_shared<MySqlAccounts>(current_bc_status);

MicroCoreAdapter mcore_addapter {current_bc_status_ptr};

for (auto const& mtx: mempool_txs)
{

uint64_t recieve_time = mtx.first;

const transaction& tx = mtx.second;

const crypto::hash tx_hash = get_transaction_hash(tx);
bool is_rct                 = (tx.version == 2);
uint8_t rct_type            = (is_rct ? tx.rct_signatures.type : 0);

// Class that is resposnible for idenficitaction of our outputs
// and inputs in a given tx.

auto identifier = make_identifier(tx, 
                    make_unique<Output>(&address, &viewkey),
                    make_unique<Input>(&address, &viewkey, 
                                       &known_outputs_keys, 
                                       &mcore_addapter));

identifier.identify();

// FIRSt step. to search for the incoming xmr, we use address,
// viewkey and
// outputs public key.

auto const& outputs_identified 
    = identifier.get<Output>()->get();

auto total_received = calc_total_xmr(outputs_identified);

//vector<uint64_t> amount_specific_indices;

// if we identified some outputs as ours,
// save them into json to be returned.
if (!outputs_identified.empty())
{
    auto tx_hash_str = pod_to_hex(tx_hash);
    auto tx_hash_prefix_str
        = pod_to_hex(get_transaction_prefix_hash(tx));
    auto tx_pub_key_str 
        = pod_to_hex(identifier.get_tx_pub_key());
    auto is_coinbase = cryptonote::is_coinbase(tx);
    uint64_t mixin_no {0};
    if (!is_coinbase)
        mixin_no = xmreg::get_mixin_no(tx);

    json j_tx;

    j_tx["id"]             = 0; // dont have any database id
                                //for tx in mempool
                                // this id is used for
                                // sorting txs in the frontend.

    j_tx["hash"]           = tx_hash_str;
    j_tx["tx_pub_key"]     = tx_pub_key_str;
    j_tx["timestamp"]      = recieve_time*1e3; // when it got into mempool
    j_tx["total_received"] = std::to_string(total_received);
    j_tx["total_sent"]     = "0"; // to be set later when looking for key images
    j_tx["unlock_time"]    = "0"; // for mempool we set it to zero
                                // since we dont have block_height to work with
    j_tx["height"]         = current_height; // put current blockchain height,
                                // just to indicate to frontend that this
                                // tx is younger than 10 blocks so that
                                // it shows unconfirmed message.
    j_tx["payment_id"]     = current_bc_status->get_payment_id_as_string(tx);
    j_tx["coinbase"]       = false; // pool tx are not coinbase, so always false
    j_tx["is_rct"]         = is_rct;
    j_tx["rct_type"]       = rct_type;
    j_tx["mixin"]          = mixin_no;
    j_tx["mempool"]        = true;

    j_transactions->push_back(j_tx);
}


// SECOND step: Checking for our key images, i.e., inputs.

// no need mutex here, as this will be exectued only after
// the above. there is no threads here.

auto const& inputs_identfied = identifier.get<Input>()->get();

if (!inputs_identfied.empty())
{
    // if we find something we need to construct spent_outputs json
    // array that will be appended into j_tx above. or in case
    // this is
    // only spending tx, i.e., no outputs were found, we need to
    // construct new j_tx.
    
    auto tx_hash_str = pod_to_hex(tx_hash);
    auto tx_hash_prefix_str
        = pod_to_hex(get_transaction_prefix_hash(tx));
    auto tx_pub_key_str 
        = pod_to_hex(identifier.get_tx_pub_key());
    uint64_t mixin_no {0};
    auto is_coinbase = cryptonote::is_coinbase(tx);
    if (!is_coinbase)
        mixin_no = xmreg::get_mixin_no(tx);

    json spend_keys;
    uint64_t total_sent {0};

    for (auto& in_info: inputs_identfied)
    {
        // need to get output info from mysql, as we need
        // to know output's amount, its orginal
        // tx public key and its index in that tx
        XmrOutput out;

        if (local_xmr_accounts->output_exists(
                    pod_to_hex(in_info.out_pub_key), out))
        {
            total_sent += out.amount;

            spend_keys.push_back({
                  {"key_image" , pod_to_hex(in_info.key_img)},
                  {"amount"    , std::to_string(out.amount)},
                  {"tx_pub_key", out.tx_pub_key},
                  {"out_index" , out.out_index},
                  {"mixin"     , out.mixin},
            });
        }
    }


    if (!spend_keys.empty())
    {
        // check if we got outputs in this tx. if yes
        // we use exising j_tx. If not, we need to construct new
        // j_tx object.

        if (!outputs_identified.empty())
        {
            // we have outputs in this tx as well, so use
            // exisiting j_tx. we add spending info
            // to j_tx created before.

            json& j_tx = j_transactions->back();

            j_tx["total_sent"]    = std::to_string(total_sent);
            j_tx["spent_outputs"] = spend_keys;
        }
        else
        {
            // we dont have any outputs in this tx as
            // this is spend only tx, so we need to create new
            // j_tx.

            json j_tx;

            j_tx["id"]             = 0; // dont have any database
                                        // id for tx in mempool
                                        // this id is used for
                                        // sorting txs in the
                                        // frontend.

            j_tx["hash"]           = tx_hash_str;
            j_tx["tx_pub_key"]     = tx_pub_key_str;
            j_tx["timestamp"]      = recieve_time*1e3; // when it got into mempool
            j_tx["total_received"] = "0";          // we did not recive any outputs/xmr
            j_tx["total_sent"]     = std::to_string(total_sent); // to be set later when looking for key images
            j_tx["unlock_time"]    = 0;          // for mempool we set it to zero
                                                 // since we dont have block_height to work with
            j_tx["height"]         = current_height; // put current blockchain height,
                                                // just to indicate to frontend that this
                                                // tx is younger than 10 blocks so that
                                                // it shows unconfirmed message.
            j_tx["payment_id"]     = current_bc_status
                    ->get_payment_id_as_string(tx);
            j_tx["coinbase"]       = false;     // mempool tx are not coinbase, so always false
            j_tx["is_rct"]         = is_rct;
            j_tx["rct_type"]       = rct_type;
            j_tx["mixin"]          = mixin_no;
            j_tx["mempool"]        = true;
            j_tx["spent_outputs"]  = spend_keys;

            j_transactions->push_back(j_tx);

        } // else of if (!identified_outputs.empty())

    } //  if (!spend_keys.empty())

} // if (!identified_inputs.empty())

} // for (const transaction& tx: txs_to_check)
}


TxSearch::addr_view_t
TxSearch::get_xmr_address_viewkey() const
{
    return make_pair(address, viewkey);
}


void
TxSearch::set_search_thread_life(seconds life_seconds)
{
    thread_search_life = life_seconds;
}

bool
TxSearch::delete_existing_tx_if_exists(string const& tx_hash)
{
    XmrTransaction tx_data_existing;

    if (xmr_accounts->tx_exists(acc->id.data, tx_hash, tx_data_existing))
    {
        OMVLOG1 << '\n' << address_prefix
                  + ": tx " << tx_hash
                << " already present in db, so remove it";

        // if tx is already present for that user,
        // we remove it, as we get it data from scrach

        if (xmr_accounts->delete_tx(tx_data_existing.id.data) == 0)
        {
            OMERROR << address_prefix  + ": cant remove tx "
                    << tx_hash;
            return false;
        }
    }

    return true;
}

void
TxSearch::update_acc(XmrAccount const& _acc)
{
    std::lock_guard<std::mutex> acc_lck(access_acc);
    *acc = _acc;
}


// default value of static veriables
seconds TxSearch::thread_search_life {600};

}
