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

#define MYSQLPP_SSQLS_NO_STATICS 1


#include "OpenMoneroRequests.h"

#include "db/ssqlses.h"
#include "OutputInputIdentification.h"

namespace xmreg
{


handel_::handel_(const fetch_func_t& callback):
        request_callback {callback}
{}

void
handel_::operator()(const shared_ptr< Session > session)
{
    const auto request = session->get_request( );
    size_t content_length = request->get_header( "Content-Length", 0);
    session->fetch(content_length, this->request_callback);
}



OpenMoneroRequests::OpenMoneroRequests(
        shared_ptr<MySqlAccounts> _acc, 
        shared_ptr<CurrentBlockchainStatus> _current_bc_status):
    xmr_accounts {_acc}, current_bc_status {_current_bc_status}
{

}


void
OpenMoneroRequests::login(const shared_ptr<Session> session, const Bytes & body)
{
    json j_response;
    json j_request;

    vector<string> required_values {"address", "view_key"};

    if (!parse_request(body, required_values, j_request, j_response))
    {
        session_close(session, j_response);
        return;
    }

    string xmr_address;
    string view_key;

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];
    }
    catch (json::exception const& e)
    {
        cerr << "json exception: " << e.what() << '\n';
        session_close(session, j_response);
        return;
    }

    // a placeholder for exciting or new account data
    XmrAccount acc;

    uint64_t acc_id {0};

    // marks if this is new account creation or not
    bool new_account_created {false};

    // first check if new account
    // select this account if its existing one
    if (!xmr_accounts->select(xmr_address, acc))
    {
        // account does not exist, so create new one
        // for this address

        uint64_t current_blockchain_height = get_current_blockchain_height();

        // initialize current blockchain timestamp with current time
        // in a moment we will try to get last block timestamp
        // to replace this value. But if it fails, we just use current
        // timestamp
        uint64_t current_blockchain_timestamp = std::time(nullptr);

        // get last block so we have its timestamp when
        // createing the account
        block last_blk;

        if (current_bc_status->get_block(current_blockchain_height, last_blk))
        {
            current_blockchain_timestamp = last_blk.timestamp;
        }

        DateTime blk_timestamp_mysql_format
                = XmrTransaction::timestamp_to_DateTime(
                    current_blockchain_timestamp);


        // create new account
        XmrAccount new_account(
                       mysqlpp::null,
                       xmr_address,
                       make_hash(view_key),
                       current_blockchain_height, /* for scanned_block_height */
                       blk_timestamp_mysql_format,
                       current_blockchain_height);

        // insert the new account into the mysql
        if ((acc_id = xmr_accounts->insert(new_account)) == 0)
        {
            // if creating account failed
            j_response = json {{"status", "error"},
                               {"reason", "Account creation failed"}};

            session_close(session, j_response);
            return;
        }

        // set this flag to indicate that we have just created a
        // new account in mysql. this information is sent to front-end
        // as it can disply some greeting window to new users upon
        // their first install
        new_account_created = true;

    } // if (!xmr_accounts->select(xmr_address, acc))


    // so by now new account has been created or it already exists
    // so we just login into it.

    if (login_and_start_search_thread(xmr_address, view_key, acc, j_response))
    {
       // if successfuly logged in and created search thread
        j_response["status"]      = "success";

        // we overwrite what ever was sent in login_and_start_search_thread
        // for the j_response["new_address"].
        j_response["new_address"] = new_account_created;
    }
    else
    {
        // some error with loggin in or search thread start
        session_close(session, j_response);
        return;

    } // else  if (login_and_start_search_thread(xmr_address,


    session_close(session, j_response);
}

void
OpenMoneroRequests::get_address_txs(
        const shared_ptr< Session > session, const Bytes & body)
{
    json j_response;
    json j_request;

    vector<string> requested_values {"address", "view_key"};

    if (!parse_request(body, requested_values, j_request, j_response))
    {
        session_close(session, j_response);
        return;
    }

    string xmr_address;
    string view_key;

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];
    }
    catch (json::exception const& e)
    {
        OMERROR << "json exception: " << e.what();
        session_close(session, j_response, UNPROCESSABLE_ENTITY);
        return;
    }

    // make hash of the submited viewkey. we only store
    // hash of viewkey in database, not acctual viewkey.
    string viewkey_hash = make_hash(view_key);

    // initialize json response
    j_response = json {
            {"total_received"         , 0},    // calculated in this function
            {"total_received_unlocked", 0},    // calculated in this function
            {"scanned_height"         , 0},    // not used. just to match mymonero
            {"scanned_block_height"   , 0},    // taken from Accounts table
            {"scanned_block_timestamp", 0},    // taken from Accounts table
            {"start_height"           , 0},    // blockchain height whencreated
            {"blockchain_height"      , 0},    // current blockchain height
            {"transactions"           , json::array()}
    };

    // a placeholder for exciting or new account data
    xmreg::XmrAccount acc;

    // for this to continue, search thread must have already been
    // created and still exisits.
//    if (current_bc_status->search_thread_exist(xmr_address))
//    {
      if (login_and_start_search_thread(xmr_address, view_key, acc, j_response))
      {
        // populate acc and check view_key
//        if (!login_and_start_search_thread(xmr_address, view_key, acc, j_response))
//        {
//            // some error with loggin in or search thread start
//            session_close(session, j_response.dump());
//            return;
//        }

        // before fetching txs, check if provided view key
        // is correct. this is simply to ensure that
        // we cant fetch an account's txs using only address.
        // knowlage of the viewkey is also needed.



        uint64_t total_received {0};
        uint64_t total_received_unlocked {0};

        j_response["total_received"]          = total_received;
        j_response["start_height"]            = acc.start_height;
        j_response["scanned_block_height"]    = acc.scanned_block_height;
        j_response["scanned_block_timestamp"] = static_cast<uint64_t>(
                    acc.scanned_block_timestamp);
        j_response["blockchain_height"]  = get_current_blockchain_height();

        vector<XmrTransaction> txs;

        xmr_accounts->select(acc.id.data, txs);

        if (xmr_accounts->select_txs_for_account_spendability_check(
                    acc.id.data, txs))
        {
            json j_txs = json::array();

            for (XmrTransaction const& tx: txs)
            {
                json j_tx {
                        {"id"             , tx.blockchain_tx_id},
                        {"coinbase"       , bool {tx.coinbase}},
                        {"tx_pub_key"     , tx.tx_pub_key},
                        {"hash"           , tx.hash},
                        {"height"         , tx.height},
                        {"mixin"          , tx.mixin},
                        {"payment_id"     , tx.payment_id},
                        {"unlock_time"    , tx.unlock_time},                  
                        {"total_sent"     , 0}, // to be field when checking for spent_outputs below
                        {"total_received" , std::to_string(tx.total_received)},
                        {"timestamp"      , static_cast<uint64_t>(tx.timestamp)*1000},
                        {"mempool"        , false} // tx in database are never from mempool
                };

                vector<XmrInput> inputs;

                if (xmr_accounts->select_for_tx(tx.id.data, inputs))
                {
                    json j_spent_outputs = json::array();

                    uint64_t total_spent {0};

                    for (XmrInput input: inputs)
                    {
                        XmrOutput out;

                        if (xmr_accounts->select_by_primary_id(
                                    input.output_id, out))
                        {
                            total_spent += input.amount;

                            j_spent_outputs.push_back({
                              {"amount"     , std::to_string(input.amount)},
                              {"key_image"  , input.key_image},
                              {"tx_pub_key" , out.tx_pub_key},
                              {"out_index"  , out.out_index},
                              {"mixin"      , out.mixin}});
                        }
                    }

                    j_tx["total_sent"] = std::to_string(total_spent);

                    j_tx["spent_outputs"] = j_spent_outputs;

                } // if (xmr_accounts->select_inputs_for_tx(tx.id, inputs))

                total_received += tx.total_received;

                if (bool {tx.spendable})
                {
                    total_received_unlocked += tx.total_received;
                }

                j_txs.push_back(j_tx);

            } // for (XmrTransaction tx: txs)

            j_response["total_received"]          = std::to_string(total_received);
            j_response["total_received_unlocked"] = std::to_string(total_received_unlocked);

            j_response["transactions"] = j_txs;

        } // if (xmr_accounts->select_txs_for_ac

    } // if (current_bc_status->search_thread_exist(xmr_address))
    else
    {        
        j_response = json {{"status", "error"},
                           {"reason", "Search thread does not exist."}};

        // some error with loggin in or search thread start
        session_close(session, j_response);
        return;
    }

    // append txs found in mempool to the json returned

    json j_mempool_tx;

    if (current_bc_status->find_txs_in_mempool(
            xmr_address, j_mempool_tx))
    {
        if(!j_mempool_tx.empty())
        {
            uint64_t total_received_mempool {0};
            uint64_t total_sent_mempool {0};

            // get last tx id (i.e., index) so that we can
            // set some ids for the mempool txs. These ids are
            // used for sorting in the frontend. Since we want mempool
            // tx to be first, they need to be higher than last_tx_id_db
            uint64_t last_tx_id_db {0};

            if (!j_response["transactions"].empty())
            {
                last_tx_id_db = j_response["transactions"].back()["id"];
            }


            for (json& j_tx: j_mempool_tx)
            {
                //cout << "mempool j_tx[\"total_received\"]: "
                //     << j_tx["total_received"] << endl;

                j_tx["id"] = ++last_tx_id_db;

                total_received_mempool += boost::lexical_cast<uint64_t>(
                            j_tx["total_received"].get<string>());
                total_sent_mempool     += boost::lexical_cast<uint64_t>(
                            j_tx["total_sent"].get<string>());

                j_response["transactions"].push_back(j_tx);
            }

            // we account for mempool txs when providing final
            // unlocked and locked balances.

            j_response["total_received"]
                    = std::to_string(
                        boost::lexical_cast<uint64_t>(
                            j_response["total_received"].get<string>())
                                           + total_received_mempool - total_sent_mempool);

            j_response["total_received_unlocked"]
                    = std::to_string(
                        boost::lexical_cast<uint64_t>(
                            j_response["total_received_unlocked"].get<string>())
                                          + total_received_mempool - total_sent_mempool);
        }

    }

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close(OK, response_body, response_headers);
}

void
OpenMoneroRequests::get_address_info(
        const shared_ptr< Session > session, const Bytes & body)
{
    json j_response;
    json j_request;

    vector<string> requested_values {"address" , "view_key"};

    if (!parse_request(body, requested_values, j_request, j_response))
    {
        session_close(session, j_response);
        return;
    }

    string xmr_address;
    string view_key;

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];
    }
    catch (json::exception const& e)
    {
        cerr << "json exception: " << e.what() << '\n';
        session_close(session, j_response);
        return;
    }

    // make hash of the submited viewkey. we only store
    // hash of viewkey in database, not acctual viewkey.
    string viewkey_hash = make_hash(view_key);

    j_response = json {
            {"locked_funds"           , "0"},    // locked xmr (e.g., younger than 10 blocks)
            {"total_received"         , "0"},    // calculated in this function
            {"total_sent"             , "0"},    // calculated in this function
            {"scanned_height"         , 0},    // not used. it is here to match mymonero
            {"scanned_block_height"   , 0},    // taken from Accounts table
            {"scanned_block_timestamp", 0},    // taken from Accounts table
            {"start_height"           , 0},    // not used, but available in Accounts table.
                                               // it is here to match mymonero
            {"blockchain_height"      , 0},    // current blockchain height
            {"spent_outputs"          , nullptr} // list of spent outputs that we think
                                               // user has spent. client side will
                                               // filter out false positives since
                                               // only client has spent key
    };

    // a placeholder for exciting or new account data
    xmreg::XmrAccount acc;

    // for this to continue, search thread must have already been
    // created and still exisits.
    if (login_and_start_search_thread(xmr_address, view_key, acc, j_response))
    {
        uint64_t total_received {0};

        // ping the search thread that we still need it.
        // otherwise it will finish after some time.
        current_bc_status->ping_search_thread(xmr_address);

        uint64_t current_searched_blk_no {0};

        if (current_bc_status->get_searched_blk_no(
                    xmr_address, current_searched_blk_no))
        {
            // if current_searched_blk_no is higher than what is in mysql, update it
            // in the search thread. This may occure when manually editing scanned_block_height
            // in Accounts table to import txs or rescan txs.
            // we use the minumum difference of 10 blocks, for this update to happen

            if (current_searched_blk_no > acc.scanned_block_height + 10)
            {
                current_bc_status->set_new_searched_blk_no(
                            xmr_address, acc.scanned_block_height);
            }
        }

        j_response["total_received"]          = std::to_string(total_received);
        j_response["start_height"]            = acc.start_height;
        j_response["scanned_block_height"]    = acc.scanned_block_height;
        j_response["scanned_block_timestamp"]
                = static_cast<uint64_t>(acc.scanned_block_timestamp);
        j_response["blockchain_height"]  = get_current_blockchain_height();

        uint64_t total_sent {0};

        vector<XmrTransaction> txs;

        // get all txs of for the account
        xmr_accounts->select(acc.id.data, txs);

        // now, filter out or updated transactions from txs vector that no
        // longer exisit in the recent blocks. Update is done to check for their
        // spendability status.
        if (xmr_accounts->select_txs_for_account_spendability_check(
                    acc.id.data, txs))
        {
            json j_spent_outputs = json::array();

            for (XmrTransaction tx: txs)
            {
                vector<XmrOutput> outs;

                if (xmr_accounts->select_for_tx(tx.id.data, outs))
                {
                    for (XmrOutput &out: outs)
                    {
                        // check if the output, has been spend
                        vector<XmrInput> ins;

                        if (xmr_accounts->select_inputs_for_out(
                                    out.id.data, ins))
                        {
                            for (XmrInput& in: ins)
                            {
                                j_spent_outputs.push_back({
                                    {"amount"     , std::to_string(in.amount)},
                                    {"key_image"  , in.key_image},
                                    {"tx_pub_key" , out.tx_pub_key},
                                    {"out_index"  , out.out_index},
                                    {"mixin"      , out.mixin},
                                });

                                total_sent += in.amount;
                            }
                        }

                        total_received += out.amount;

                    } //  for (XmrOutput &out: outs)

                } //  if (xmr_accounts->select_outputs_for_tx(tx.id, outs))

            } // for (XmrTransaction tx: txs)


            j_response["total_received"] = std::to_string(total_received);
            j_response["total_sent"]     = std::to_string(total_sent);

            j_response["spent_outputs"]  = j_spent_outputs;

        } // if (xmr_accounts->select_txs_for_account_spendability_check(acc.id, txs))

    } // if (current_bc_status->search_thread_exist(xmr_address))
    else
    {
        j_response = json {{"status", "error"},
                           {"reason", "Search thread does not exist."}};

        // some error with loggin in or search thread start
        session_close(session, j_response);
        return;
    }

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}


void
OpenMoneroRequests::get_unspent_outs(
        const shared_ptr< Session > session,
        const Bytes & body)
{
    json j_response;
    json j_request;

    vector<string> requested_values {"address" , "view_key", "mixin",
                                     "use_dust", "dust_threshold", "amount"};

    if (!parse_request(body, requested_values, j_request, j_response))
    {
        session_close(session, j_response);
        return;
    }

    string xmr_address;
    string view_key;
    uint64_t mixin {4};
    bool use_dust {false};
    uint64_t dust_threshold {1000000000};
    uint64_t amount {0};

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];

        mixin       = j_request["mixin"];
        use_dust    = j_request["use_dust"];

        dust_threshold = boost::lexical_cast<uint64_t>(
                    j_request["dust_threshold"].get<string>());
        amount  = boost::lexical_cast<uint64_t>(
                    j_request["amount"].get<string>());
    }
    catch (json::exception const& e)
    {
        cerr << "json exception: " << e.what() << '\n';
        session_close(session, j_response);
        return;
    }
    catch (boost::bad_lexical_cast const& e)
    {
        cerr << "Bed lexical cast" << e.what() << '\n';
        session_close(session, j_response);
        return;
    }

    // make hash of the submited viewkey. we only store
    // hash of viewkey in database, not acctual viewkey.
    string viewkey_hash = make_hash(view_key);

    j_response = json  {
            {"amount" , "0"},          // total value of the outputs
            {"outputs", json::array()} // list of outputs
                                       // exclude those without require
                                       // no of confirmation
    };

    // a placeholder for exciting or new account data
    xmreg::XmrAccount acc;

    // select this account if its existing one

    // for this to continue, search thread must have already been
    // created and still exisits.
    if (current_bc_status->search_thread_exist(xmr_address))
    {
        // populate acc and check view_key
        if (!login_and_start_search_thread(xmr_address, view_key, acc, j_response))
        {
            // some error with loggin in or search thread start
            session_close(session, j_response);
            return;
        }

        uint64_t total_outputs_amount {0};

//        uint64_t current_blockchain_height
//                = current_bc_status->get_current_blockchain_height();

        vector<XmrTransaction> txs;

        // retrieve txs from mysql associated with the given address
        if (xmr_accounts->select(acc.id.data, txs))
        {
            // we found some txs.

            json& j_outputs = j_response["outputs"];

            for (XmrTransaction& tx: txs)
            {
                // we skip over locked outputs
                // as they cant be spent anyway.
                // thus no reason to return them to the frontend
                // for constructing a tx.

                if (!current_bc_status->is_tx_unlocked(
                            tx.unlock_time, tx.height))
                {
                    continue;
                }

//                if (!bool {tx.coinbase})
//                {
//                    continue;
//                }

                vector<XmrOutput> outs;

                if (xmr_accounts->select_for_tx(tx.id.data, outs))
                {
                    for (XmrOutput &out: outs)
                    {
                        // skip outputs considered as dust
                        if (out.amount < dust_threshold)
                        {
                            continue;
                        }

                        // need to check for rct commintment
                        // coinbase ringct txs dont have
                        // rct filed in them. Thus
                        // we need to make them.

                        uint64_t global_amount_index = out.global_index;

                        string rct = out.get_rct();

                        // coinbase rct txs require speciall treatment
                        if (tx.coinbase && tx.is_rct)
                        {
                            uint64_t amount  = (tx.is_rct ? 0 : out.amount);

                            output_data_t od =
                                    current_bc_status->get_output_key(
                                            amount, global_amount_index);

                            string rtc_outpk  = pod_to_hex(od.commitment);
                            string rtc_mask   = pod_to_hex(rct::identity());
                            string rtc_amount(64, '0');

                            rct = rtc_outpk + rtc_mask + rtc_amount;
                        }

                        json j_out{
                                {"amount"          , std::to_string(out.amount)},
                                {"public_key"      , out.out_pub_key},
                                {"index"           , out.out_index},
                                {"global_index"    , out.global_index},
                                {"rct"             , rct},
                                {"tx_id"           , out.tx_id},
                                {"tx_hash"         , tx.hash},
                                {"tx_prefix_hash"  , tx.prefix_hash},
                                {"tx_pub_key"      , tx.tx_pub_key},
                                {"timestamp"       , static_cast<uint64_t>(
                                            out.timestamp*1e3)},
                                {"height"          , tx.height},
                                {"spend_key_images", json::array()}
                        };

                        vector<XmrInput> ins;

                        if (xmr_accounts->select_inputs_for_out(
                                    out.id.data, ins))
                        {
                            json& j_ins = j_out["spend_key_images"];

                            for (XmrInput& in: ins)
                            {
                                j_ins.push_back(in.key_image);
                            }
                        }

                        j_outputs.push_back(j_out);

                        total_outputs_amount += out.amount;

                    }  //for (XmrOutput &out: outs)

                } // if (xmr_accounts->select_outputs_for_tx(tx.id, outs))

            } // for (XmrTransaction& tx: txs)

        } //  if (xmr_accounts->select_txs(acc.id, txs))

        j_response["amount"] = std::to_string(total_outputs_amount);


        // need proper per_kb_fee estimate as
        // it is already using dynanamic fees. frontend
        // uses old fixed fees.

        j_response["per_byte_fee"] = current_bc_status
                                            ->get_dynamic_base_fee_estimate();


    } // if (current_bc_status->search_thread_exist(xmr_address))
    else
    {
        j_response = json {{"status", "error"},
                           {"reason", "Search thread does not exist."}};

        // some error with loggin in or search thread start
        session_close(session, j_response);
        return;

    }

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}

void
OpenMoneroRequests::get_random_outs(
        const shared_ptr< Session > session, const Bytes & body)
{
    json j_request;
    json j_response;

    vector<string> requested_values;

    if (!parse_request(body, requested_values, j_request, j_response))
    {
        session_close(session, j_response);
        return;
    }

    uint64_t count;

    try
    {
        count = j_request["count"];
    }
    catch (json::exception const& e)
    {
        cerr << "json exception: " << e.what() << '\n';
        session_close(session, j_response);
        return;
    };

    if (count > 41)
    {
        cerr << "Request ring size too big" << '\n';
        j_response["status"] = "error";
        j_response["error"]  = "Request ring size too large";
        session_close(session, j_response);
    }

    vector<uint64_t> amounts;

    try
    {
        // populate amounts vector so that we can pass it directly to
        // daeamon to get random outputs for these amounts
        for (json amount: j_request["amounts"])
        {
            amounts.push_back(boost::lexical_cast<uint64_t>(
                                  amount.get<string>()));

            //            amounts.push_back(0);
        }
    }
    catch (boost::bad_lexical_cast& e)
    {
        cerr << "Bed lexical cast" << '\n';
        session_close(session, j_response);
        return;
    }

    vector<RandomOutputs::outs_for_amount> found_outputs;

    if (current_bc_status->get_random_outputs(amounts, count, found_outputs))
    {
        json& j_amount_outs = j_response["amount_outs"];

        for (const auto& outs: found_outputs)
        {
            json j_outs {{"amount", std::to_string(outs.amount)},
                         {"outputs", json::array()}};


            json& j_outputs = j_outs["outputs"];

            for (auto  const& out: outs.outs)
            {
                tuple<string, string, string>
                        rct_field = current_bc_status
                            ->construct_output_rct_field(
                                    out.global_amount_index, outs.amount);

                string rct = std::get<0>(rct_field)    // rct_pk
                             + std::get<1>(rct_field)  // rct_mask
                             + std::get<2>(rct_field); // rct_amount

                json out_details {
                        {"global_index", out.global_amount_index},
                        {"public_key"  , pod_to_hex(out.out_key)},
                        {"rct"         , rct}
                };

                j_outputs.push_back(out_details);

            } // for (const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AM

            j_amount_outs.push_back(j_outs);

        } // for (const COMMAND_RPC_GET_RANDOM_OUTPUTS_

    } // if (current_bc_status->get_random_outputs(amounts,
    else
    {
        j_response["status"] = "error";
        j_response["error"]  = "Error getting random "
                               "outputs from monero deamon";
    }

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}


void
OpenMoneroRequests::submit_raw_tx(
        const shared_ptr< Session > session, const Bytes & body)
{
    json j_request = body_to_json(body);

    string raw_tx_blob = j_request["tx"];

    json j_response;

    string error_msg;

    // before we submit the tx into the deamon, we are going to do a few checks.
    // first, we parse the hexbuffer submmited by the frontend into
    // binary buffer block
    // second, we are going to check if we can construct
    // valid transaction object
    // fromt that binary buffer.
    // third, we are going to check if any key image in this tx is
    // in any of the txs
    // in the mempool. This allows us to make a clear comment that the tx
    // uses outputs just spend. This happens we a users submits few txs,
    // one after another
    // before previous txs get included in a block and are still
    // present in the mempool.

    std::string tx_blob;

    if(!epee::string_tools::parse_hexstr_to_binbuff(raw_tx_blob, tx_blob))
    {
        j_response["status"] = "error";
        j_response["Error"]  = "Tx faild parse_hexstr_to_binbuff";

        OMERROR << j_response["Error"];

        session_close(session, j_response,
                      UNPROCESSABLE_ENTITY);
        return;
    }

    transaction tx_to_be_submitted;

    if (!parse_and_validate_tx_from_blob(tx_blob, tx_to_be_submitted))
    {
        j_response["status"] = "error";
        j_response["Error"]  = "Tx faild parse_and_validate_tx_from_blob";

        OMERROR << j_response["Error"];

        session_close(session, j_response,
                      UNPROCESSABLE_ENTITY);
        return;
    }

    if (current_bc_status->find_key_images_in_mempool(tx_to_be_submitted))
    {
        j_response["status"] = "error";
        j_response["Error"]  = "Tx uses your outputs that area already "
                               "in the mempool. "
                               "Please wait till your previous tx(s) "
                               "get mined";

        OMERROR << j_response["Error"];

        session_close(session, j_response,
                      UNPROCESSABLE_ENTITY);
        return;
    }

    if (!current_bc_status->commit_tx(
            raw_tx_blob, error_msg,
            current_bc_status->get_bc_setup().do_not_relay))
    {
        j_response["status"] = "error";
        j_response["Error"]  = error_msg;

        OMERROR << j_response["Error"];

        session_close(session, j_response,
                      UNPROCESSABLE_ENTITY);
        return;
    }

    j_response["status"] = "success";


    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}

void
OpenMoneroRequests::import_wallet_request(
        const shared_ptr< Session > session, const Bytes & body)
{

    json j_response;
    json j_request;

    vector<string> requested_values {"address" , "view_key"};

    if (!parse_request(body, requested_values,
                       j_request, j_response))
    {
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      "Cant parse json body!");
        return;
    }

    string xmr_address;
    string view_key;

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];
    }
    catch (json::exception const& e)
    {
        OMERROR << "json exception: " << e.what();
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      e.what());
        return;
    }


    j_response["request_fulfilled"] = false;
    j_response["import_fee"]        = std::to_string(
                                            current_bc_status->get_bc_setup()
                                                .import_fee);
    j_response["status"] = "error";
    j_response["error"]  = "Some error occured";

    // get account from mysql db if exists
    auto xmr_account = select_account(xmr_address);

    if (!xmr_account)
    {
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      "The account does not exists!");
        return;
    }

    // if import_fee is zero, we just import the wallet.
    // we dont care about any databases or anything, as importing
    // wallet is free.
    // just reset the scanned block height in mysql and finish.
    if (current_bc_status->get_bc_setup().import_fee == 0)
    {
        // change search blk number in the search thread
        if (!current_bc_status->set_new_searched_blk_no(xmr_address, 0))
        {
            session_close(session, j_response, UNPROCESSABLE_ENTITY,
                          "Updating searched_blk_no failed!");
            return;
        }

        j_response["request_fulfilled"] = true;
        j_response["status"]            = "Import will start shortly";
        j_response["new_request"]       = true;
        j_response["error"]             = "";

        session_close(session, j_response);

        return;
    }

    // payment fee is not zero, so we need to
    // ask for the payment. So we first get payment details
    // associated with the given account.

    auto xmr_payment = select_payment(*xmr_account);

    // something went wrong.
    if (!xmr_payment)
    {
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      "Selecting payment details failed!");
        return;
    }

    // payment id is null
    if (xmr_payment->id == mysqlpp::null)
    {
        // no current payment record exist,
        // so we have to create new one.

        // payment request is new, so create its entry in
        // Payments table

        uint64_t payment_table_id {0};

        crypto::hash8 random_payment_id8 = crypto::rand<crypto::hash8>();

        string integrated_address =
                current_bc_status->get_account_integrated_address_as_str(
                        random_payment_id8);

        xmr_payment->account_id        = xmr_account->id.data;
        xmr_payment->payment_id        = pod_to_hex(random_payment_id8);
        xmr_payment->import_fee        = current_bc_status
                                            ->get_bc_setup().import_fee; // xmr
        xmr_payment->request_fulfilled = false;
        xmr_payment->tx_hash           = ""; // no tx_hash yet with the payment
        xmr_payment->payment_address   = integrated_address;

        if ((payment_table_id = xmr_accounts->insert(*xmr_payment)) == 0)
        {
            OMERROR << xmr_address.substr(0, 6)
                       + ": failed to create new payment record!";

            session_close(session, j_response, UNPROCESSABLE_ENTITY,
                          "Failed to create new payment record!");
            return;
        }

        // payment entry created

        j_response["payment_id"]        = payment_table_id;
        j_response["import_fee"]        = std::to_string(
                                            xmr_payment->import_fee);
        j_response["new_request"]       = true;
        j_response["request_fulfilled"]
                                        = bool {xmr_payment->request_fulfilled};
        j_response["payment_address"]   = xmr_payment->payment_address;
        j_response["status"]            = "Payment not yet received";
        j_response["error"]             = "";

        session_close(session, j_response);
        return;
    } // if (xmr_payment->id == mysqlpp::null)

    // payment id is not null, so it means that
    // we have already payment record in our db for that
    // account.

    bool request_fulfilled = bool {xmr_payment->request_fulfilled};

    if (request_fulfilled)
    {
        // if payment has been made, and we get new request to import txs
        // indicate that this is new requeest, but request was fulfiled.
        // front end should give proper message in this case

        j_response["request_fulfilled"] = request_fulfilled;
        j_response["status"]            = "Wallet already imported or "
                                          "in the progress.";
        j_response["new_request"]       = false;
        j_response["error"]             = "";

        session_close(session, j_response);
        return;
    }

    // payment has not been yet done, so we are going
    // to check if it has just been done and update
    // db accordingly

    string integrated_address =
            current_bc_status->get_account_integrated_address_as_str(
                    xmr_payment->payment_id);

    if (integrated_address.empty())
    {
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      "get_account_integrated_address_as_str failed!");
        return;
    }

    j_response["payment_id"]        = xmr_payment->payment_id;
    j_response["import_fee"]        = std::to_string(xmr_payment->import_fee);
    j_response["new_request"]       = false;
    j_response["request_fulfilled"] = request_fulfilled;
    j_response["payment_address"]   = integrated_address;
    j_response["status"]            = "Payment not yet received";

    string tx_hash_with_payment;

    // if payment has not yet been done
    // check if it has just been done now
    // if yes, mark it in mysql

    if(current_bc_status->search_if_payment_made(
            xmr_payment->payment_id,
            xmr_payment->import_fee,
            tx_hash_with_payment))
    {
        XmrPayment updated_xmr_payment = *xmr_payment;

        // updated values
        updated_xmr_payment.request_fulfilled = true;
        updated_xmr_payment.tx_hash           = tx_hash_with_payment;

        // save to mysql
        if (!xmr_accounts->update(*xmr_payment, updated_xmr_payment))
        {

            OMERROR << xmr_address.substr(0,6) +
                        "Updating payment db failed!\n";

            session_close(session, j_response, UNPROCESSABLE_ENTITY,
                          "Updating payment db failed!");
            return;
        }

        XmrAccount updated_acc = *xmr_account;

        updated_acc.scanned_block_height = 0;

        // set scanned_block_height	to 0 to begin
        // scanning entire blockchain

        if (!xmr_accounts->update(*xmr_account, updated_acc))
        {
            OMERROR << xmr_address.substr(0,6) +
                        "Updating scanned_block_height failed!\n";

            session_close(session, j_response, UNPROCESSABLE_ENTITY,
                          "Updating scanned_block_height failed!");
            return;
        }

        // if success, set acc to updated_acc;
        request_fulfilled = true;

        // change search blk number in the search thread
        if (!current_bc_status
                ->set_new_searched_blk_no(xmr_address, 0))
        {
            session_close(session, j_response, UNPROCESSABLE_ENTITY,
                          "updating searched_blk_no failed!");
            return;

        }

        j_response["request_fulfilled"]
                = request_fulfilled;
        j_response["status"]
                = "Payment received. Thank you.";
        j_response["new_request"]       = true;
        j_response["error"]             = "";

    } // if(current_bc_status->search_if_payment_made(


    session_close(session, j_response);
}



void
OpenMoneroRequests::import_recent_wallet_request(
        const shared_ptr< Session > session, const Bytes & body)
{
    json j_response;
    json j_request;

    bool request_fulfilled {false};

    j_response["request_fulfilled"] = false;

    vector<string> requested_values {"address" , "view_key",
                                     "no_blocks_to_import"};

    if (!parse_request(body, requested_values,
                       j_request, j_response))
    {
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      "Cant parse json body!");
        return;
    }

    string xmr_address;
    string view_key;

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];
    }
    catch (json::exception const& e)
    {
        OMERROR << "json exception: " << e.what();
        session_close(session, j_response, UNPROCESSABLE_ENTITY,
                      e.what());
        return;
    }

    uint64_t no_blocks_to_import {1000};

    try
    {
        no_blocks_to_import
                = boost::lexical_cast<uint64_t>(
                    j_request["no_blocks_to_import"].get<string>());
    }
    catch (std::exception const& e)
    {
        string msg = "Cant parse "
                + j_request["no_blocks_to_import"].get<string>()
                + " into number";

        cerr << msg << '\n';

        j_response["Error"] = msg;
        session_close(session, j_response);
        return;
    }

    // make sure that we dont import more that the maximum alowed no of blocks
    no_blocks_to_import = std::min(no_blocks_to_import,
                                   current_bc_status->get_bc_setup()
                                   .max_number_of_blocks_to_import);

    no_blocks_to_import
            = std::min(no_blocks_to_import,
                      current_bc_status->get_current_blockchain_height());

    XmrAccount acc;

    if (xmr_accounts->select(xmr_address, acc))
    {
        XmrAccount updated_acc = acc;

        // make sure scanned_block_height is larger than
        // no_blocks_to_import so we dont
        // end up with overflowing uint64_t.

        if (updated_acc.scanned_block_height >= no_blocks_to_import)
        {
            // repetead calls to import_recent_wallet_request will be
            // moving the scanning backward.
            // not sure yet if any protection is needed to
            // make sure that a user does not
            // go back too much back by importing his/hers
            // wallet multiple times in a row.
            updated_acc.scanned_block_height
                    = updated_acc.scanned_block_height - no_blocks_to_import;

            if (xmr_accounts->update(acc, updated_acc))
            {
                // change search blk number in the search thread
                if (!current_bc_status
                        ->set_new_searched_blk_no(xmr_address,
                                    updated_acc.scanned_block_height))
                {
                    cerr << "Updating searched_blk_no failed!" << endl;
                    j_response["Error"]  = "Updating searched_blk_no failed!";
                }
                else
                {
                    // if success, makre that request was successful;
                    request_fulfilled = true;
                }
            }

        }  // if (updated_acc.scanned_block_height > no_blocks_to_import)
    }
    else
    {
        cerr << "Updating account with new scanned_block_height failed!\n";
        j_response["status"]
                = "Updating account with new scanned_block_height failed!";
    }

    if (request_fulfilled)
    {
        j_response["request_fulfilled"] = request_fulfilled;
        j_response["status"]  = "Updating account with for"
                                " importing recent txs successeful.";
    }

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            std::to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}



void
OpenMoneroRequests::get_tx(
        const shared_ptr< Session > session, const Bytes & body)
{
    json j_response;
    json j_request;

    vector<string> requested_values {"address" , "view_key", "tx_hash"};

    if (!parse_request(body, requested_values, j_request, j_response))
    {
        session_close(session, j_response);
        return;
    }

    string xmr_address;
    string view_key;
    string tx_hash_str;

    try
    {
        xmr_address = j_request["address"];
        view_key    = j_request["view_key"];
        tx_hash_str = j_request["tx_hash"];
    }
    catch (json::exception const& e)
    {
        cerr << "json exception: " << e.what() << '\n';
        session_close(session, j_response);
        return;
    }

    j_response["status"] = "error";
    j_response["error"]  = "Some error occured";


    crypto::hash tx_hash;

    if (!hex_to_pod(tx_hash_str, tx_hash))
    {
        cerr << "Cant parse tx hash! : " << tx_hash_str  << '\n';
        j_response["status"] = "Cant parse tx hash! : " + tx_hash_str;
        session_close(session, j_response);
        return;
    }

    transaction tx;

    uint64_t default_timestamp {0};

    bool tx_found {false};

    bool tx_in_mempool {false};

    if (!current_bc_status->get_tx(tx_hash, tx))
    {
        // if tx not found in the blockchain, check if its in mempool

                // recieved_time, tx
        vector<pair<uint64_t, transaction>> mempool_txs =
                current_bc_status->get_mempool_txs();

        //cout << "serach mempool" << endl;

        for (auto const& mtx: mempool_txs)
        {
            //cout << (get_transaction_hash(mtx.second)) << '\n';

            if (get_transaction_hash(mtx.second) == tx_hash)
            {
                tx = mtx.second;
                tx_found = true;
                tx_in_mempool = true;
                default_timestamp = mtx.first;
                break;
            }
        }
    }
    else
    {
        tx_found = true;
    }

    if (tx_found)
    {
        crypto::hash tx_hash = get_transaction_hash(tx);

        // return tx hash. can be used to check if we acctually
        // delivered the tx that was requested
        j_response["tx_hash"]  = pod_to_hex(tx_hash);

        j_response["pub_key"]  = pod_to_hex(
                    xmreg::get_tx_pub_key_from_received_outs(tx));


        bool coinbase = is_coinbase(tx);

        j_response["coinbase"] = coinbase;

        // key images of inputs
        vector<txin_to_key> input_key_imgs;

        // public keys and xmr amount of outputs
        vector<pair<txout_to_key, uint64_t>> output_pub_keys;

        uint64_t xmr_inputs;
        uint64_t xmr_outputs;
        uint64_t num_nonrct_inputs;
        uint64_t fee {0};
        uint64_t mixin_no;
        uint64_t size;

        // sum xmr in inputs and ouputs in the given tx
        array<uint64_t, 4> const& sum_data = xmreg::summary_of_in_out_rct(
                tx, output_pub_keys, input_key_imgs);

        xmr_outputs       = sum_data[0];
        xmr_inputs        = sum_data[1];
        mixin_no          = sum_data[2];
        num_nonrct_inputs = sum_data[3];

        j_response["xmr_outputs"]    = xmr_outputs;
        j_response["xmr_inputs"]     = xmr_inputs;
        j_response["mixin_no"]       = mixin_no;
        j_response["num_of_outputs"] = output_pub_keys.size();
        j_response["num_of_inputs"]  = input_key_imgs.size();

        if (!coinbase &&  tx.vin.size() > 0)
        {
            // check if not miner tx
            // i.e., for blocks without any user transactions
            if (tx.vin.at(0).type() != typeid(txin_gen))
            {
                // get tx fee
                fee = get_tx_fee(tx);
            }
        }

        j_response["fee"] = fee;

        // get tx size in bytes
        size = get_object_blobsize(tx);

        j_response["size"] = size;

        // to be field later on using data from OutputInputIdentification
        j_response["total_sent"] = "0";
        j_response["total_received"] = "0";

        int64_t tx_height {-1};

        int64_t no_confirmations {-1};

        if (current_bc_status->get_tx_block_height(tx_hash, tx_height))
        {
            // get the current blockchain height. Just to check
            uint64_t bc_height = get_current_blockchain_height();

            no_confirmations = bc_height - tx_height;
        }

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

        j_response["payment_id"] = string {};
        j_response["timestamp"]  = default_timestamp;

        address_parse_info address_info;
        secret_key viewkey;

        // to get info about recived xmr in this tx, we calculate it from
        // scrach, i.e., search for outputs. We could get this info
        // directly from the database, but doing it again here, is a good way
        // to double check tx data in the frontend, and also maybe try doing
        // it differently than before. Its not great, since we reinvent
        // the wheel
        // but its worth double checking
        // the mysql data, and also allows for new
        // implementation in the frontend.
        if (current_bc_status->get_xmr_address_viewkey(
                    xmr_address, address_info, viewkey))
        {
            OutputInputIdentification oi_identification {
                &address_info, &viewkey, &tx, tx_hash,
                        coinbase};

            oi_identification.identify_outputs();

            uint64_t total_received {0};

            // we just get total amount recieved. we have viewkey,
            // so this must be correct and front end does not
            // need to do anything to check this.
            for (auto& out_info: oi_identification.identified_outputs)
            {
                total_received += out_info.amount;
            }

            j_response["total_received"] = std::to_string(total_received);

            json j_spent_outputs = json::array();

            // to get spendings, we need to have our key_images. but
            // the backend does not have spendkey, so it cant determine
            // which key images are really ours or not. this is the task
            // for the frontend. however, backend can only provide guesses and
            // nessessery data to the frontend to filter out incorrect
            // guesses.
            //
            // for input identification, we will use our mysql. its just much
            // faster to use it here, than before. but first we need to
            // get account id of the user asking for tx details.

            // a placeholder for exciting or new account data
            XmrAccount acc;

            // select this account if its existing one
            if (xmr_accounts->select(xmr_address, acc))
            {
                // if user exist, get tx data from database
                // this will work only for tx in the blockchain,
                // not those in the mempool.

                if (!tx_in_mempool)
                {
                    // if not in mempool, but in blockchain, just
                    // get data aout key images from the mysql

                    XmrTransaction xmr_tx;

                    if (xmr_accounts->tx_exists(
                                acc.id.data, tx_hash_str, xmr_tx))
                    {
                        j_response["payment_id"] = xmr_tx.payment_id;
                        j_response["timestamp"]
                                = static_cast<uint64_t>(xmr_tx.timestamp*1e3);

                        vector<XmrInput> inputs;

                        if (xmr_accounts->select_for_tx(
                                    xmr_tx.id.data, inputs))
                        {
                            json j_spent_outputs = json::array();

                            uint64_t total_spent {0};

                            for (XmrInput input: inputs)
                            {
                                XmrOutput out;

                                if (xmr_accounts
                                        ->select_by_primary_id(
                                            input.output_id, out))
                                {
                                    total_spent += input.amount;

                                    j_spent_outputs.push_back({
                                          {"amount"     , std::to_string(input.amount)},
                                          {"key_image"  , input.key_image},
                                          {"tx_pub_key" , out.tx_pub_key},
                                          {"out_index"  , out.out_index},
                                          {"mixin"      , out.mixin}});
                                }

                            } // for (XmrInput input: inputs)

                            j_response["total_sent"]    = std::to_string(total_spent);

                            j_response["spent_outputs"] = j_spent_outputs;

                        } // if (xmr_accounts->select_inputs_

                    }  // if (xmr_accounts->tx_exists(acc.id

                } // if (!tx_in_mempool)
                else
                {
                    // if tx in mempool, mysql will not have this tx, so
                    // we cant pull info about key images from mysql for this tx.

                    // we have to redo this info from basically from scrach.

                    unordered_map<public_key, uint64_t> known_outputs_keys;

                    if (current_bc_status->get_known_outputs_keys(
                            xmr_address, known_outputs_keys))
                    {
                        // we got known_outputs_keys from the search thread.
                        // so now we can use OutputInputIdentification to
                        // get info about inputs.

                        // Class that is resposnible for idenficitaction
                        // of our outputs
                        // and inputs in a given tx.
                        OutputInputIdentification oi_identification
                                {&address_info, &viewkey, &tx, tx_hash,
                                    coinbase};

                        // no need mutex here, as this will be exectued only
                        // after the above. there is no threads here.
                        oi_identification.identify_inputs(known_outputs_keys,
                                                          current_bc_status.get());

                        json j_spent_outputs = json::array();

                        uint64_t total_spent {0};

                        for (auto& in_info: oi_identification.identified_inputs)
                        {

                            // 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;

                            string out_pub_key
                                    = pod_to_hex(in_info.out_pub_key);

                            if (xmr_accounts->output_exists(out_pub_key, out))
                            {
                                total_spent += out.amount;

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

                        } //  for (auto& in_info: oi_identification

                        j_response["total_sent"]    = std::to_string(total_spent);

                        j_response["spent_outputs"] = j_spent_outputs;

                    } //if (current_bc_status->get_known_outputs_keys(
                      //    xmr_address, known_outputs_keys))

                } //  else

            } //  if (xmr_accounts->select(xmr_address, acc))

        } //  if (current_bc_status->get_xmr_add

        j_response["tx_height"]         = tx_height;
        j_response["no_confirmations"]  = no_confirmations;
        j_response["status"]            = "OK";
        j_response["error"]             = "";
    }
    else
    {
        cerr << "Cant get tx details for tx hash! : " << tx_hash_str  << '\n';
        j_response["status"] = "Cant get tx details for tx hash! : " + tx_hash_str;
    }

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}


void
OpenMoneroRequests::get_version(
        const shared_ptr< Session > session,
        const Bytes & body)
{

    (void) body;

    json j_response {
        {"last_git_commit_hash", string {GIT_COMMIT_HASH}},
        {"last_git_commit_date", string {GIT_COMMIT_DATETIME}},
        {"git_branch_name"     , string {GIT_BRANCH_NAME}},
        {"monero_version_full" , string {MONERO_VERSION_FULL}},
        {"api"                 , OPENMONERO_RPC_VERSION},
        {"testnet"             , current_bc_status->get_bc_setup().net_type
                    == network_type::TESTNET},
        {"network_type"        , current_bc_status->get_bc_setup().net_type},
        {"blockchain_height"   , get_current_blockchain_height()}
    };

    string response_body = j_response.dump();

    auto response_headers = make_headers({{ "Content-Length",
                                            to_string(response_body.size())}});

    session->close( OK, response_body, response_headers);
}


shared_ptr<Resource>
OpenMoneroRequests::make_resource(
        function< void (OpenMoneroRequests&, const shared_ptr< Session >,
                        const Bytes& ) > handle_func,
        const string& path)
{
    auto a_request = std::bind(handle_func, *this,
                               std::placeholders::_1,
                               std::placeholders::_2);

    shared_ptr<Resource> resource_ptr = make_shared<Resource>();

    resource_ptr->set_path(path);
    resource_ptr->set_method_handler( "OPTIONS", generic_options_handler);
    resource_ptr->set_method_handler( "POST"   , handel_(a_request) );

    return resource_ptr;
}


void
OpenMoneroRequests::generic_options_handler(
        const shared_ptr< Session > session )
{
    const auto request = session->get_request( );

    size_t content_length = request->get_header( "Content-Length", 0);

    session->fetch(content_length,
                   [](const shared_ptr< Session > session,
                   const Bytes &)
    {
        session->close( OK, string{}, make_headers());
    });
}


multimap<string, string>
OpenMoneroRequests::make_headers(
        const multimap<string, string>& extra_headers)
{
    multimap<string, string> headers {
            {"Access-Control-Allow-Origin"     , "*"},
            {"Access-Control-Allow-Headers"    , "Content-Type"},
            {"Content-Type"                    , "application/json"}
    };

    headers.insert(extra_headers.begin(), extra_headers.end());

    return headers;
};

void
OpenMoneroRequests::print_json_log(const string& text, const json& j)
{
    cout << text << '\n' << j.dump(4) << endl;
}


string
OpenMoneroRequests::body_to_string(const Bytes & body)
{
    return string(reinterpret_cast<const char *>(body.data()), body.size());
}

json
OpenMoneroRequests::body_to_json(const Bytes & body)
{
    json j = json::parse(body_to_string(body));
    return j;
}


uint64_t
OpenMoneroRequests::get_current_blockchain_height()
{
    return current_bc_status->get_current_blockchain_height();
}

bool
OpenMoneroRequests::login_and_start_search_thread(
                        const string& xmr_address,
                        const string& view_key,
                        XmrAccount& acc,
                        json& j_response)
{

    // select this account if its existing one
    if (xmr_accounts->select(xmr_address, acc))
    {
        // we got accunt from the database. we double check
        // if hash of provided viewkey by the frontend, matches
        // what we have in database.

        // make hash of the submited viewkey. we only store
        // hash of viewkey in database, not acctual viewkey.
        string viewkey_hash = make_hash(view_key);

        if (viewkey_hash == acc.viewkey_hash)
        {
            // if match, than save the viewkey in account object
            // and proceed to checking if a search thread exisits
            // for this account. if not, then create new thread

            acc.viewkey = view_key;

            // so we have an account now. Either existing or
            // newly created. Thus, we can start a thread
            // which will scan for transactions belonging to
            // that account, using its address and view key.
            // the thread will scan the blockchain for txs belonging
            // to that account and updated mysql database whenever it
            // will find something.
            //
            // The other client (i.e., a webbrowser) will query other
            // functions to retrieve
            // any belonging transactions in a loop.
            // Thus the thread does not need
            // to do anything except looking for tx and updating mysql
            // with relevant tx information

            if (!current_bc_status->search_thread_exist(acc.address))
            {
                std::unique_ptr<TxSearch> tx_search;

                try
                {
                    tx_search
                            = std::make_unique<TxSearch>(acc,
                                                         current_bc_status);
                }
                catch (std::exception const& e)
                {
                    OMERROR << "TxSearch construction faild.";
                    j_response = json {{"status", "error"},
                                       {"reason", "Failed to construct "
                                                  "TxSearch object"}};
                    return false;
                }


                if (current_bc_status->start_tx_search_thread(
                            acc, std::move(tx_search)))
                {
                    j_response["status"]      = "success";
                    j_response["new_address"] = false;

                    // thread has been started
                    // everything seems fine.

                    return true;
                }
            }
            else
            {
                j_response["status"]      = "success";
                j_response["new_address"] = false;

                // thread already exists
                // everything seems fine.

                return true;
            }

            j_response = json {{"status", "error"},
                               {"reason", "Failed created search "
                                          "thread for this account"}};
        }
        else
        {
            j_response = json {{"status", "error"},
                               {"reason", "Viewkey provided is incorrect"}};
        }
    }

    return false;
}


bool
OpenMoneroRequests::parse_request(
        const Bytes& body,
        vector<string>& values_map,
        json& j_request,
        json& j_response)
{
    try
    {
        j_request = body_to_json(body);

        // parsing was successful
        // now check if all required values are there.

        for (const auto& v: values_map)
        {
            if (j_request.count(v) == 0)
            {
                cerr << v + " value not provided" << endl;

                j_response["status"] = "error";
                j_response["reason"] = v + " value not provided";

                return false;
            }
        }

        return true;
    }
    catch (std::exception& e)
    {
        cerr << "OpenMoneroRequests::parse_request: " << e.what() << endl;

        j_response["status"] = "error";
        j_response["reason"] = "reqest json parsing failed";

        return false;
    }
}

boost::optional<XmrAccount>
OpenMoneroRequests::select_account(
        string const& xmr_address) const
{
    boost::optional<XmrAccount> acc = XmrAccount{};

    if (!xmr_accounts->select(xmr_address, *acc))
    {
        OMERROR << xmr_address.substr(0,6) +
                   ": address does not exists!";

        return acc;
    }

    return acc;
}

boost::optional<XmrPayment>
OpenMoneroRequests::select_payment(
        XmrAccount const& xmr_account) const
{
     vector<XmrPayment> xmr_payments;

     if (!xmr_accounts->select(xmr_account.id.data,
                               xmr_payments))
     {
         OMINFO << xmr_account.address.substr(0,6) +
                    ": no payment record found!";

         // so create empty record to be inserted into
         // db after.
         XmrPayment xmr_payment;
         xmr_payment.id = mysqlpp::null;

         return xmr_payment;
     }

     if (xmr_payments.size() > 1)
     {
         OMERROR << xmr_account.address.substr(0,6) +
                    ": more than one payment record found!";
         return {};
     }

     // if xmr_payments is empty it means
     // that the given account has no import
     // paymnet record created. so new
     // paymnet will be created
     if (xmr_payments.empty())
     {                  
         OMINFO << xmr_account.address.substr(0,6) +
                    ": no payment record found!";

         // so create empty record to be inserted into
         // db after.
         XmrPayment xmr_payment;
         xmr_payment.id = mysqlpp::null;

         return xmr_payment;
     }

     return xmr_payments.at(0);
}

void
OpenMoneroRequests::session_close(
        const shared_ptr< Session > session,
        json& j_response,
        int return_code,
        string error_msg) const
{
    if (return_code != OK)
    {
        j_response["Error"] = error_msg;
    }

    string response_body = j_response.dump();

    auto response_headers
            = make_headers({{ "Content-Length",
                              std::to_string(response_body.size())}});

    session->close( return_code,
                    response_body, response_headers);
}

}



