/*
 * Brainflyer.cpp
 *
 *  Created on: Apr 12, 2018
 *      Author: armin
 */

#include "Brainflyer.h"

#include <KeyUtils.h>
#include <ec_pubkey_fast.h>
#include <stddef.h>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mutex>
#include <stdexcept>

namespace bf {

using namespace std;

recursive_mutex m_cout;

Brainflyer::Brainflyer(int id, string host, int port, util::bytestr key)
    : id(id), comm(host, port), key(key) {}

void Brainflyer::operator()() {
  log("Started");

  log("Initializing batch point conversion structure");
  if (secp256k1_ec_pubkey_batch_init(4096) != 0) {
    log("Initialization of batch point conversion structure failed");
    return;
  }

  log("Retrieving work");

  comm::Work work;

  try {
    work = comm.getWork();
  } catch (runtime_error& e) {
    log("Failed to retrieve work: ", e.what());
    return;
  }

  log("Got work", work.repr());
  performWork(work);
}

void Brainflyer::performWork(comm::Work work) {
  // brainflayer compatibility args
  int nopt_mod = 1;

  // calculate batch of 4096 keys
  unsigned char pub_batch[4096][65];
  unsigned char priv_batch[4096][32];

  unsigned char priv[32];
  memset(priv, 0, 32);
  size_t wrk_len = work.from.size();
  memcpy(&priv[32 - wrk_len], work.from.byteArr(), wrk_len);

  unsigned char priv_to[32];
  memset(priv_to, 0, 32);
  wrk_len = work.to.size();
  memcpy(&priv_to[32 - wrk_len], work.to.byteArr(), wrk_len);

  bool work_end = false;
  long keys = 0;
  while (!work_end) {
    secp256k1_ec_pubkey_batch_incr(4096, nopt_mod, pub_batch, priv_batch, priv);

    memcpy(priv, priv_batch[4096 - 1], 32);  // set next starting key

    int i = checkBatch(pub_batch, 4096);
    if (i != -1) {
      util::bytestr b_priv(priv_batch[i], 32);
      log("Key found", util::bytestr(priv_batch[i], 32));
    }

    for (int i = 0; i < 32; i++) {
      if (priv[i] > priv_to[i]) {
        work_end = true;
        break;
      } else if (priv[i] < priv_to[i]) {
        work_end = false;
        break;
      }  // equal: continue
    }
    keys += 4096;
  }

  log(keys, "checked");
  assert(keys >= work.total);
}

void Brainflyer::dumpBatch(unsigned char (*pub_batch)[65],
                           unsigned char (*priv_batch)[32]) {
  ofstream dumpfile("/home/armin/Desktop/batchdump");
  if (dumpfile.is_open()) {
    for (size_t i = 0; i < 4096; i++) {
      util::bytestr pub(pub_batch[i], 65);
      util::bytestr priv(priv_batch[i], 32);

      dumpfile << '"' << priv << '"' << ";" << pub << ";"
               << util::KeyUtils::chash160(pub) << endl;
    }
  }
}

int Brainflyer::checkBatch(unsigned char (*pub_batch)[65], size_t batch_size) {
  for (size_t i = 0; i < batch_size; i++) {
    bool found = true;
    util::bytestr pub(&pub_batch[i][0], 65);
    util::bytestr pubc = util::KeyUtils::chash160(pub);

    for (size_t j = 0; j < key.size(); j++) {
      if (pubc[j] != key[j]) {
        found = false;
        break;
      }
    }

    if (found) return ((int)i);
  }

  return (-1);
}

template <typename... Args>
void Brainflyer::log(Args... args) {
  lock_guard<recursive_mutex> lock(m_cout);

  cout << "[Thread-" << id << "] ";
  rec_log(args...);
  cout << endl;
}

template <typename First, typename... Rest>
void Brainflyer::rec_log(First f, Rest... r) {
  lock_guard<recursive_mutex> lock(m_cout);
  cout << f << " ";
  rec_log(r...);
}

template <typename T>
void Brainflyer::rec_log(T v) {
  lock_guard<recursive_mutex> lock(m_cout);
  cout << v;
}

}  // namespace bf
