#include <sstream>
#include <iostream>
#include <fstream>
#include <vector>
#include <tr1/unordered_map>

#include <boost/functional/hash.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/program_options.hpp>
#include <boost/program_options/variables_map.hpp>

#include "sampler.h"
#include "filelib.h"
#include "stringlib.h"
#include "weights.h"
#include "scorer.h"
#include "inside_outside.h"
#include "hg_io.h"
#include "kbest.h"
#include "viterbi.h"

// This is Figure 4 (Algorithm Sampler) from Hopkins&May (2011)

using namespace std;
namespace po = boost::program_options;

struct ApproxVectorHasher {
  static const size_t MASK = 0xFFFFFFFFull;
  union UType {
    double f;
    size_t i;
  };
  static inline double round(const double x) {
    UType t;
    t.f = x;
    size_t r = t.i & MASK;
    if ((r << 1) > MASK)
      t.i += MASK - r + 1;
    else
      t.i &= (1ull - MASK);
    return t.f;
  }
  size_t operator()(const SparseVector<double>& x) const {
    size_t h = 0x573915839;
    for (SparseVector<double>::const_iterator it = x.begin(); it != x.end(); ++it) {
      UType t;
      t.f = it->second;
      if (t.f) {
        size_t z = (t.i >> 32);
        boost::hash_combine(h, it->first);
        boost::hash_combine(h, z);
      }
    }
    return h;
  }
};

struct ApproxVectorEquals {
  bool operator()(const SparseVector<double>& a, const SparseVector<double>& b) const {
    SparseVector<double>::const_iterator bit = b.begin();
    for (SparseVector<double>::const_iterator ait = a.begin(); ait != a.end(); ++ait) {
      if (bit == b.end() ||
          ait->first != bit->first ||
          ApproxVectorHasher::round(ait->second) != ApproxVectorHasher::round(bit->second))
        return false;
      ++bit;
    }
    if (bit != b.end()) return false;
    return true;
  }
};

boost::shared_ptr<MT19937> rng;

void InitCommandLine(int argc, char** argv, po::variables_map* conf) {
  po::options_description opts("Configuration options");
  opts.add_options()
        ("reference,r",po::value<vector<string> >(), "[REQD] Reference translation (tokenized text)")
        ("weights,w",po::value<string>(), "[REQD] Weights files from current iterations")
        ("kbest_repository,K",po::value<string>()->default_value("./kbest"),"K-best list repository (directory)")
        ("input,i",po::value<string>()->default_value("-"), "Input file to map (- is STDIN)")
        ("source,s",po::value<string>()->default_value(""), "Source file (ignored, except for AER)")
        ("loss_function,l",po::value<string>()->default_value("ibm_bleu"), "Loss function being optimized")
        ("kbest_size,k",po::value<unsigned>()->default_value(1500u), "Top k-hypotheses to extract")
        ("candidate_pairs,G", po::value<unsigned>()->default_value(5000u), "Number of pairs to sample per hypothesis (Gamma)")
        ("best_pairs,X", po::value<unsigned>()->default_value(50u), "Number of pairs, ranked by magnitude of objective delta, to retain (Xi)")
        ("random_seed,S", po::value<uint32_t>(), "Random seed (if not specified, /dev/random will be used)")
        ("help,h", "Help");
  po::options_description dcmdline_options;
  dcmdline_options.add(opts);
  po::store(parse_command_line(argc, argv, dcmdline_options), *conf);
  bool flag = false;
  if (!conf->count("reference")) {
    cerr << "Please specify one or more references using -r <REF.TXT>\n";
    flag = true;
  }
  if (!conf->count("weights")) {
    cerr << "Please specify weights using -w <WEIGHTS.TXT>\n";
    flag = true;
  }
  if (flag || conf->count("help")) {
    cerr << dcmdline_options << endl;
    exit(1);
  }
}

struct HypInfo {
  HypInfo() : g_(-100.0) {}
  HypInfo(const vector<WordID>& h, const SparseVector<double>& feats) : hyp(h), g_(-100.0), x(feats) {}

  // lazy evaluation
  double g(const SentenceScorer& scorer) const {
    if (g_ == -100.0)
      g_ = scorer.ScoreCandidate(hyp)->ComputeScore();
    return g_;
  }
  vector<WordID> hyp;
  mutable double g_;
  SparseVector<double> x;
};

struct HypInfoCompare {
  bool operator()(const HypInfo& a, const HypInfo& b) const {
    ApproxVectorEquals comp;
    return (a.hyp == b.hyp && comp(a.x,b.x));
  }
};

struct HypInfoHasher {
  size_t operator()(const HypInfo& x) const {
    boost::hash<vector<WordID> > hhasher;
    ApproxVectorHasher vhasher;
    size_t ha = hhasher(x.hyp);
    boost::hash_combine(ha, vhasher(x.x));
    return ha;
  }
};

void WriteKBest(const string& file, const vector<HypInfo>& kbest) {
  WriteFile wf(file);
  ostream& out = *wf.stream();
  out.precision(10);
  for (int i = 0; i < kbest.size(); ++i) {
    out << TD::GetString(kbest[i].hyp) << endl;
    out << kbest[i].x << endl;
  }
}

void ParseSparseVector(string& line, size_t cur, SparseVector<double>* out) {
  SparseVector<double>& x = *out;
  size_t last_start = cur;
  size_t last_comma = string::npos;
  while(cur <= line.size()) {
    if (line[cur] == ' ' || cur == line.size()) {
      if (!(cur > last_start && last_comma != string::npos && cur > last_comma)) {
        cerr << "[ERROR] " << line << endl << "  position = " << cur << endl;
        exit(1);
      }
      const int fid = FD::Convert(line.substr(last_start, last_comma - last_start));
      if (cur < line.size()) line[cur] = 0;
      const double val = strtod(&line[last_comma + 1], NULL);
      x.set_value(fid, val);

      last_comma = string::npos;
      last_start = cur+1;
    } else {
      if (line[cur] == '=')
        last_comma = cur;
    }
    ++cur;
  }
}

void ReadKBest(const string& file, vector<HypInfo>* kbest) {
  //cerr << "Reading from " << file << endl;
  ReadFile rf(file);
  istream& in = *rf.stream();
  string cand;
  string feats;
  while(getline(in, cand)) {
    getline(in, feats);
    assert(in);
    kbest->push_back(HypInfo());
    TD::ConvertSentence(cand, &kbest->back().hyp);
    ParseSparseVector(feats, 0, &kbest->back().x);
  }
  //cerr << "  read " << kbest->size() << " hypotheses\n";
}

void Dedup(vector<HypInfo>* h) {
  // cerr << "Dedup in=" << h->size();
  tr1::unordered_set<HypInfo, HypInfoHasher, HypInfoCompare> u;
  while(h->size() > 0) {
    u.insert(h->back());
    h->pop_back();
  }
  tr1::unordered_set<HypInfo, HypInfoHasher, HypInfoCompare>::iterator it = u.begin();
  while (it != u.end()) {
    h->push_back(*it);
    it = u.erase(it);
  }
  //cerr << "  out=" << h->size() << endl;
}

struct ThresholdAlpha {
  explicit ThresholdAlpha(double t = 0.05) : threshold(t) {}
  double operator()(double mag) const {
    if (mag < threshold) return 0.0; else return 1.0;
  }
  const double threshold;
};

struct TrainingInstance {
  TrainingInstance(const SparseVector<double>& feats, bool positive, double diff) : x(feats), y(positive), gdiff(diff) {}
  SparseVector<double> x;
#undef DEBUGGING_PRO
#ifdef DEBUGGING_PRO
  vector<WordID> a;
  vector<WordID> b;
#endif
  bool y;
  double gdiff;
};
#ifdef DEBUGGING_PRO
ostream& operator<<(ostream& os, const TrainingInstance& d) {
  return os << d.gdiff << " y=" << d.y << "\tA:" << TD::GetString(d.a) << "\n\tB: " << TD::GetString(d.b) << "\n\tX: " << d.x;
}
#endif

struct DiffOrder {
  bool operator()(const TrainingInstance& a, const TrainingInstance& b) const {
    return a.gdiff > b.gdiff;
  }
};

void Sample(const unsigned gamma, const unsigned xi, const vector<HypInfo>& J_i, const SentenceScorer& scorer, const bool invert_score, vector<TrainingInstance>* pv) {
  vector<TrainingInstance> v1, v2;
  double avg_diff = 0;
  for (unsigned i = 0; i < gamma; ++i) {
    const size_t a = rng->inclusive(0, J_i.size() - 1)();
    const size_t b = rng->inclusive(0, J_i.size() - 1)();
    if (a == b) continue;
    double ga = J_i[a].g(scorer);
    double gb = J_i[b].g(scorer);
    bool positive = gb < ga;
    if (invert_score) positive = !positive;
    const double gdiff = fabs(ga - gb);
    if (!gdiff) continue;
    avg_diff += gdiff;
    SparseVector<double> xdiff = (J_i[a].x - J_i[b].x).erase_zeros();
    if (xdiff.empty()) {
      cerr << "Empty diff:\n  " << TD::GetString(J_i[a].hyp) << endl << "x=" << J_i[a].x << endl;
      cerr << "  " << TD::GetString(J_i[b].hyp) << endl << "x=" << J_i[b].x << endl;
      continue;
    }
    v1.push_back(TrainingInstance(xdiff, positive, gdiff));
#ifdef DEBUGGING_PRO
    v1.back().a = J_i[a].hyp;
    v1.back().b = J_i[b].hyp;
    cerr << "N: " << v1.back() << endl;
#endif
  }
  avg_diff /= v1.size();

  for (unsigned i = 0; i < v1.size(); ++i) {
    double p = 1.0 / (1.0 + exp(-avg_diff - v1[i].gdiff));
    // cerr << "avg_diff=" << avg_diff << "  gdiff=" << v1[i].gdiff << "  p=" << p << endl;
    if (rng->next() < p) v2.push_back(v1[i]);
  }
  vector<TrainingInstance>::iterator mid = v2.begin() + xi;
  if (xi > v2.size()) mid = v2.end();
  partial_sort(v2.begin(), mid, v2.end(), DiffOrder());
  copy(v2.begin(), mid, back_inserter(*pv));
#ifdef DEBUGGING_PRO
  if (v2.size() >= 5) {
    for (int i =0; i < (mid - v2.begin()); ++i) {
      cerr << v2[i] << endl;
    }
    cerr << pv->back() << endl;
  }
#endif
}

int main(int argc, char** argv) {
  po::variables_map conf;
  InitCommandLine(argc, argv, &conf);
  if (conf.count("random_seed"))
    rng.reset(new MT19937(conf["random_seed"].as<uint32_t>()));
  else
    rng.reset(new MT19937);
  const string loss_function = conf["loss_function"].as<string>();

  ScoreType type = ScoreTypeFromString(loss_function);
  DocScorer ds(type, conf["reference"].as<vector<string> >(), conf["source"].as<string>());
  cerr << "Loaded " << ds.size() << " references for scoring with " << loss_function << endl;
  Hypergraph hg;
  string last_file;
  ReadFile in_read(conf["input"].as<string>());
  istream &in=*in_read.stream();
  const unsigned kbest_size = conf["kbest_size"].as<unsigned>();
  const unsigned gamma = conf["candidate_pairs"].as<unsigned>();
  const unsigned xi = conf["best_pairs"].as<unsigned>();
  string weightsf = conf["weights"].as<string>();
  vector<double> weights;
  {
    Weights w;
    w.InitFromFile(weightsf);
    w.InitVector(&weights);
  }
  string kbest_repo = conf["kbest_repository"].as<string>();
  MkDirP(kbest_repo);
  while(in) {
    vector<TrainingInstance> v;
    string line;
    getline(in, line);
    if (line.empty()) continue;
    istringstream is(line);
    int sent_id;
    string file;
    // path-to-file (JSON) sent_id
    is >> file >> sent_id;
    ReadFile rf(file);
    ostringstream os;
    vector<HypInfo> J_i;
    os << kbest_repo << "/kbest." << sent_id << ".txt.gz";
    const string kbest_file = os.str();
    // read k-best hypotheses from previous iterations
    if (FileExists(kbest_file))
      ReadKBest(kbest_file, &J_i);
    // extract k-best for this iteration
    HypergraphIO::ReadFromJSON(rf.stream(), &hg);
    hg.Reweight(weights);
    KBest::KBestDerivations<vector<WordID>, ESentenceTraversal> kbest(hg, kbest_size);

    for (int i = 0; i < kbest_size; ++i) {
      const KBest::KBestDerivations<vector<WordID>, ESentenceTraversal>::Derivation* d =
        kbest.LazyKthBest(hg.nodes_.size() - 1, i);
      if (!d) break;
      J_i.push_back(HypInfo(d->yield, d->feature_values));
    }
    Dedup(&J_i);
    WriteKBest(kbest_file, J_i);

    Sample(gamma, xi, J_i, *ds[sent_id], (type == TER), &v);
    for (unsigned i = 0; i < v.size(); ++i) {
      const TrainingInstance& vi = v[i];
      cout << vi.y << "\t" << vi.x << endl;
      cout << (!vi.y) << "\t" << (vi.x * -1.0) << endl;
    }
  }
  return 0;
}


#include <pbcopper/data/FrameEncoders.h>

#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>

#include <algorithm>
#include <limits>

#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>

namespace PacBio {
namespace Data {
namespace {

static std::vector<uint16_t> framepoints;
static std::vector<uint8_t> frameToCode;
static uint16_t maxFramepoint;

static int InitIpdDownsampling()
{
    if (!framepoints.empty()) {
        return 0;
    }

    // liftover from kineticsTools

    const int B = 2;
    const int t = 6;
    const double T = std::pow(B, t);

    int next = 0;
    double grain;
    const int end = 256 / T;
    for (int i = 0; i < end; ++i) {
        grain = std::pow(B, i);
        std::vector<uint16_t> nextOnes;
        for (double j = 0; j < T; ++j) {
            nextOnes.push_back(j * grain + next);
        }
        next = nextOnes.back() + grain;
        framepoints.insert(framepoints.end(), nextOnes.cbegin(), nextOnes.cend());
    }
    assert(framepoints.size() - 1 <= std::numeric_limits<uint8_t>::max());

    const uint16_t maxElement = (*std::max_element(framepoints.cbegin(), framepoints.cend()));
    frameToCode.assign(maxElement + 1, 0);

    const int fpEnd = framepoints.size() - 1;
    uint8_t i = 0;
    uint16_t fl = 0;
    uint16_t fu = 0;
    for (; i < fpEnd; ++i) {
        fl = framepoints[i];
        fu = framepoints[i + 1];
        if (fu > fl + 1) {
            const int middle = (fl + fu) / 2;
            for (int f = fl; f < middle; ++f) {
                frameToCode[f] = i;
            }
            for (int f = middle; f < fu; ++f) {
                frameToCode[f] = i + 1;
            }
        } else {
            frameToCode[fl] = i;
        }
    }

    // this next line differs from the python implementation (there, it's "i+1")
    // our C++ for loop has incremented our index counter one more time than the indexes from python enumerate(...)
    frameToCode[fu] = i;
    maxFramepoint = fu;

    return 0;
}

static const int initV1Frames = InitIpdDownsampling();

}  // namespace

// ----------------
// V1 frame codec
// ----------------

Frames V1FrameEncoder::Decode(const std::vector<uint8_t>& encodedFrames) const
{
    std::vector<uint16_t> rawFrames;
    rawFrames.reserve(encodedFrames.size());
    std::transform(encodedFrames.cbegin(), encodedFrames.cend(), std::back_inserter(rawFrames),
                   [&](uint8_t code) { return framepoints[code]; });
    return rawFrames;
}

std::vector<uint8_t> V1FrameEncoder::Encode(const std::vector<uint16_t>& rawFrames) const
{
    std::vector<uint8_t> encoded;
    encoded.reserve(rawFrames.size());
    std::transform(rawFrames.cbegin(), rawFrames.cend(), std::back_inserter(encoded),
                   [&](uint16_t frame) { return frameToCode[std::min(maxFramepoint, frame)]; });
    return encoded;
}

std::string V1FrameEncoder::Name() const { return "CodecV1"; }

// ----------------
// V2 frame codec
// ----------------

V2FrameEncoder::V2FrameEncoder(int exponentBits, int mantissaBits)
    : exponentBits_{exponentBits}
    , mantissaBits_{mantissaBits}
    , base_(std::pow(2, mantissaBits_))
    , max_((1 << (exponentBits_ + mantissaBits_)) - 1)
{
}

Frames V2FrameEncoder::Decode(const std::vector<uint8_t>& encodedFrames) const
{
    std::vector<uint16_t> decoded;
    decoded.reserve(encodedFrames.size());
    std::transform(encodedFrames.cbegin(), encodedFrames.cend(), std::back_inserter(decoded),
                   [&](uint8_t x) -> uint16_t {
                       if (x > max_) {
                           throw std::runtime_error{"[pbcopper] invalid frame encoding ERROR: " +
                                                    std::to_string(x) + " is out of range [0," +
                                                    std::to_string(max_) + ']'};
                       }
                       const uint8_t mantissa = x & static_cast<uint8_t>((base_ - 1));
                       const uint8_t exponent = (x ^ mantissa) >> mantissaBits_;
                       return (base_ * (std::pow(2, exponent) - 1)) +
                              ((std::pow(2, exponent)) * mantissa);
                   });
    return Frames{decoded};
}

std::vector<uint8_t> V2FrameEncoder::Encode(const std::vector<uint16_t>& rawFrames) const
{
    // NOTE: nothing compressed here yet in output bytes, regardless of bitsPerPulse

    std::vector<uint8_t> encoded;
    encoded.reserve(rawFrames.size());
    std::transform(
        rawFrames.cbegin(), rawFrames.cend(), std::back_inserter(encoded), [&](uint16_t x) {
            const int exponent = std::log2(x / base_ + 1);
            const int mantissa =
                (x - base_ * (static_cast<uint8_t>(std::pow(2, exponent)) - 1)) >> exponent;
            const uint8_t result = (exponent << mantissaBits_) | mantissa;
            return std::min(result, max_);
        });
    return encoded;
}

int V2FrameEncoder::MantissaBits() const { return mantissaBits_; }

std::string V2FrameEncoder::Name() const
{
    return "CodecV2/" + std::to_string(exponentBits_) + '/' + std::to_string(mantissaBits_);
}

}  // namespace Data
}  // namespace PacBio


#pragma once

#include <vector>
#include <boost/graph/graph_concepts.hpp>
#include <caffe/ultinous/ImageClassificationModel.h>
#include <caffe/ultinous/FeatureMap.hpp>
#include <caffe/ultinous/AbstractTripletGenerator.hpp>
#include <caffe/ultinous/FeatureCollectorTripletGenerator.hpp>
#include "caffe/proto/caffe.pb.h"

namespace caffe {
namespace ultinous {

template <typename Dtype>
class HardTripletGenerator : public AbstractTripletGenerator
{
public:
  HardTripletGenerator(HardTripletParameter const &htp, BasicModel const &basicModel)
    : m_sampler(basicModel)
    , m_classesInSample(htp.sampledclasses())
    , m_imagesInSampleClass(htp.sampledpictures())
    , m_margin(htp.margin())
    , m_indexInSample(m_classesInSample*m_imagesInSampleClass)
    , m_featureMap(FeatureMapContainer<Dtype>::instance(htp.featuremapid()))
    , m_tooHardTriplets(htp.toohardtriplets())
    , m_hardestPositive(htp.hardestpositive())
    , m_hardestNegative(htp.hardestnegative())
    , m_isLastTripletHard(false)
  {
    CHECK_GT( m_classesInSample, 0 );
    CHECK_GT( m_imagesInSampleClass, 0 );
    ImageSampler::initSample( m_classesInSample, m_imagesInSampleClass, m_sample );

    for( size_t i = 0; i < m_classesInSample*m_imagesInSampleClass; ++i)
      m_shuffle.push_back(i);

    m_numImagesInModel = 0;
    for( int i = 0; i < basicModel.size(); ++i )
      m_numImagesInModel += basicModel[i].images.size();

    m_featureMap.resize( m_numImagesInModel );
    FeatureCollectorTripletGenerator<Dtype>::init( basicModel );
  }
private:
  typedef size_t ImageIndex;
  typedef size_t ClassIndex;
  typedef size_t SampleIndex;
  typedef ImageClassificationModel::ImageIndexes ImageIndexes;
public:

  Triplet nextTriplet()
  {
    static bool featuresCollected = false;

    if( !featuresCollected )
    {
      if( m_featureMap.numFeatures() != m_numImagesInModel )
      {
        m_isLastTripletHard = true;
        return FeatureCollectorTripletGenerator<Dtype>::getInstance().nextTriplet();
      }

      LOG(INFO) << "All features are collected!";
      featuresCollected = true;
    }

    if(classIndex(m_indexInSample) >= m_classesInSample)
      resample();

    Triplet t;
    m_isLastTripletHard = false;

    SampleIndex anchorIndex = m_shuffle[m_indexInSample];

    t.push_back(image(anchorIndex)); // anchor

    const Vec& dvec = m_distances[anchorIndex];
    SampleIndex posSampleBegin = classIndex(anchorIndex)*m_imagesInSampleClass;
    SampleIndex posSampleEnd = posSampleBegin + m_imagesInSampleClass;
    SampleIndex posIndex = 0;

    if( m_hardestPositive )
    {
      Dtype maxPosDistance = 0;
      for(size_t posSample = posSampleBegin; posSample<posSampleEnd; ++posSample)
      {
        if(posSample==anchorIndex)
          continue;
        if(dvec[posSample] > maxPosDistance)
        {
          maxPosDistance = dvec[posSample];
          posIndex = posSample;
        }
      }
    }
    else
    {
      posIndex = 1+anchorIndex;
      if( (posIndex % m_imagesInSampleClass) == 0 )
        posIndex = posSampleBegin;
    }

    t.push_back(image(posIndex)); // hard positive

    Dtype posDistance = dvec[posIndex];
    Dtype posDistanceWithMargin = posDistance+m_margin;

    size_t nSample = m_classesInSample*m_imagesInSampleClass;
    Dtype closeNegDistance = std::numeric_limits<Dtype>::max();
    size_t negIndex = std::numeric_limits<size_t>::max();

    std::vector<size_t> hardNegIndices;

    for(size_t negSample = 0; negSample<nSample; ++negSample)
    {
      if( negSample == posSampleBegin )
      {
        negSample = posSampleEnd-1;
        continue;
      }

      if(negIndex == std::numeric_limits<size_t>::max())
        negIndex = negSample; // the first negative selected as "random"

      if( dvec[negSample] >= posDistance && dvec[negSample] < posDistanceWithMargin )
      {
          m_isLastTripletHard = true;
          if( m_hardestNegative )
          {
            if( dvec[negSample] < closeNegDistance )
            {
              closeNegDistance = dvec[negSample];
              negIndex = negSample;
            }
          }
          else
          {
            hardNegIndices.push_back( negSample );
          }
      }
    }

    if( m_tooHardTriplets && !m_isLastTripletHard )
    {
      closeNegDistance = 0;
      for(size_t negSample = 0; negSample<nSample; ++negSample)
      {
        if( negSample == posSampleBegin )
        {
          negSample = posSampleEnd-1;
          continue;
        }

        if( dvec[negSample] < posDistance )
        {
          m_isLastTripletHard = true;
          if( m_hardestNegative )
          {
            if( dvec[negSample] > closeNegDistance )
            {
              closeNegDistance = dvec[negSample];
              negIndex = negSample;
            }
          }
          else
          {
            hardNegIndices.push_back( negSample );
          }
        }
      }
    }

    if( !m_hardestNegative )
    {
      if( hardNegIndices.size() > 0 )
      {
        negIndex = hardNegIndices[rand()%hardNegIndices.size()];
        m_isLastTripletHard = true;
      }
      else
      {
        negIndex = rand()%(nSample-m_imagesInSampleClass);
        if( negIndex >= posSampleBegin )
          negIndex += m_imagesInSampleClass;
        m_isLastTripletHard = false;
      }
    }

    t.push_back(image(negIndex)); // hard negative

    ++m_indexInSample;

    return t;
  }

  bool isLastTripletHard( ) const
  {
    return m_isLastTripletHard;
  }

  const FeatureMap<Dtype>& getFeatureMap( ) const
  {
    return m_featureMap;
  }

  Dtype getMargin( ) const
  {
    return m_margin;
  }
private:
  ClassIndex classIndex(SampleIndex idx) const { return idx/m_imagesInSampleClass; }
  ImageIndex imageIndex(SampleIndex idx) const { return idx%m_imagesInSampleClass; }
  const ImageIndexes& images(SampleIndex idx) const { return m_sample[classIndex(idx)].images; }
  ImageIndex image(SampleIndex idx) const { return images(idx)[imageIndex(idx)]; }
private:
  void resample()
  {
    m_sampler.sample(m_sample);
    recalcDistances();
    shuffle( m_shuffle.begin(), m_shuffle.end() );
    m_indexInSample = 0;
  }

  void recalcDistancesGPU(); // src/caffe/ultinous/HardTripletGenerator.cu

  void recalcDistancesCPU()
  {
    size_t const nSample = m_classesInSample * m_imagesInSampleClass;

    typename FeatureMap<Dtype>::FeatureVec sqr;

    for( size_t i = 0; i < nSample-1; i++ )
    {
      const typename FeatureMap<Dtype>::FeatureVec& feat1 = m_featureMap.getFeatureVec( image(i) );
      CHECK_GT( feat1.size(), 0);

      sqr.resize( feat1.size() );

      for( size_t j = i+1; j < nSample; j++ )
      {
        const typename FeatureMap<Dtype>::FeatureVec& feat2 = m_featureMap.getFeatureVec( image(j) );

        CHECK_GT( feat2.size(), 0 );
        CHECK_EQ( feat1.size(), feat2.size() );

        caffe_sub( feat1.size(), &(feat1[0]), &(feat2[0]), &(sqr[0]) );
        Dtype dist = caffe_cpu_dot( sqr.size(), &(sqr[0]), &(sqr[0]) );

        m_distances[i][j] = dist;
        m_distances[j][i] = dist;
      }
    }
  }
  void recalcDistances()
  {
    size_t const nSample = m_classesInSample * m_imagesInSampleClass;

    CHECK_GT( nSample, 0 );

    if( m_distances.size() != nSample )
      m_distances = Mat( nSample, Vec(nSample, 0) );

    CHECK_EQ( m_distances.size(), nSample );
    for( size_t i = 0; i < nSample; i++ )
      CHECK_EQ( m_distances[i].size(), nSample );

    #ifdef CPU_ONLY
    recalcDistancesCPU();
    #else
    recalcDistancesGPU();
    #endif
  }

private:
  typedef std::vector<Dtype> Vec;
  typedef std::vector<Vec> Mat;
  typedef ImageSampler::Sample Sample;
private:
  ImageSampler m_sampler;
  size_t m_classesInSample;
  size_t m_imagesInSampleClass;
  Mat m_distances;
  Dtype m_margin;
  Sample m_sample;
  SampleIndex m_indexInSample;
  FeatureMap<Dtype>& m_featureMap;
  bool m_tooHardTriplets;
  bool m_hardestPositive;
  bool m_hardestNegative;
  bool m_isLastTripletHard;

  std::vector<SampleIndex> m_shuffle;

  shared_ptr<SyncedMemory> m_syncedFeatures;
  shared_ptr<SyncedMemory> m_syncedDistances;

  size_t m_numImagesInModel;
};

} // namespace ultinous
} // namespace caffe


/* Author: Mincheul Kang */

#include <boost/chrono.hpp>
#include <boost/foreach.hpp>
#include <ompl/base/SpaceInformation.h>

#include <harmonious_sampling/StateSpaces.h>

StateValidityChecker::StateValidityChecker(const ompl::base::SpaceInformationPtr &si,
                                                   planning_scene::PlanningScenePtr& planning_scene,
                                                   std::string planning_group,
                                                   std::string collision_check_group):
        ompl::base::StateValidityChecker(si),
        state_space_(si->getStateSpace().get())
{
    planning_scene_ = planning_scene;
    planning_group_ = planning_group;

    collision_request_.group_name = collision_check_group;
    collision_request_.contacts = true;
    collision_request_.max_contacts = 100;
    collision_request_.max_contacts_per_pair = 1;
    collision_request_.verbose = false;
}

bool StateValidityChecker::isValid(const ompl::base::State *state) const {
    std::map<std::string, double> configuration;
    collision_detection::CollisionResult collision_result;
    std::vector<double> values;
    state_space_->copyToReals(values, state);

    collision_result.clear();

    robot_state::RobotState robot_state = planning_scene_->getCurrentStateNonConst();
    robot_state.setJointGroupPositions(planning_group_, values);
    planning_scene_->checkCollision(collision_request_, collision_result, robot_state);

    return !collision_result.collision;
}


// Copyright (c) 2017 - 2019 - The SmartCash Developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "consensus/consensus.h"
#include "init.h"
#include "smartrewards/rewards.h"
#include "smartrewards/rewardspayments.h"
#include "smarthive/hive.h"
#include "smartnode/spork.h"
#include "smartnode/smartnodepayments.h"
#include "ui_interface.h"
#include "validation.h"

#include <boost/thread.hpp>
#include <boost/range/irange.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>

CSmartRewards *prewards = NULL;

CCriticalSection cs_rewardsdb;
CCriticalSection cs_rewardrounds;

// Used for time conversions.
boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));

// Estimate or return the current block height.
int GetBlockHeight(const CBlockIndex *index)
{
    int64_t syncDiff = std::time(0) - index->GetBlockTime();
    int64_t firstTxDiff;
    if( MainNet() ) firstTxDiff = std::time(0) - nStartRewardTime; // Diff from the reward blocks start till now on mainnet.
    else firstTxDiff = std::time(0) - nFirstTxTimestamp_Testnet; // Diff from the first transaction till now on testnet.
    return syncDiff > 1200 ? firstTxDiff / 55 : index->nHeight; // If we are 20 minutes near now use the current height.
}

int ParseScript(const CScript &script, std::vector<CSmartAddress> &ids){

    std::vector<CTxDestination> addresses;
    txnouttype type;
    int nRequired;

    if (!ExtractDestinations(script, type, addresses, nRequired)) {
        return 0;
    }

    BOOST_FOREACH(const CTxDestination &d, addresses)
    {
        ids.push_back(CSmartAddress(d));
    }

    return nRequired;
}

void CalculateRewardRatio(CSmartRewardRound &round)
{
    int64_t time = GetTime();
    int64_t start = round.startBlockHeight;
    round.rewards = 0;

    while( start <= round.endBlockHeight) round.rewards += GetBlockValue(start++,0,time) * 0.15;

    round.percent = double(round.rewards) / ( round.eligibleSmart - round.disqualifiedSmart );
}

bool CSmartRewards::Verify()
{
    LOCK(cs_rewardsdb);
    return pdb->Verify(rewardHeight);
}

void CSmartRewards::UpdatePayoutParameter(CSmartRewardRound &round)
{

    int64_t nPayeeCount = round.eligibleEntries - round.disqualifiedEntries;
    int nFirst_1_3_Round = MainNet() ? nRewardsFirst_1_3_Round : nRewardsFirst_1_3_Round_Testnet;

    if( round.number < nFirst_1_3_Round ){

        round.nBlockPayees = nRewardPayouts_1_2_BlockPayees;
        round.nBlockInterval = nRewardPayouts_1_2_BlockInterval;

        if( TestNet() ){
            if( round.number < 68 ){
                round.nBlockPayees = nRewardPayoutsPerBlock_1_Testnet;
                round.nBlockInterval = nRewardPayoutBlockInterval_1_Testnet;
            }else{
                round.nBlockPayees = nRewardPayoutsPerBlock_2_Testnet;
                round.nBlockInterval = nRewardPayoutBlockInterval_2_Testnet;
            }
        }

    }else{

        int64_t nBlockStretch = MainNet() ? nRewardPayouts_1_3_BlockStretch :
                                   nRewardPayouts_1_3_BlockStretch_Testnet;
        int64_t nBlocksPerRound = MainNet() ? nRewardsBlocksPerRound_1_3 :
                                   nRewardsBlocksPerRound_1_3_Testnet;
        int64_t nBlockPayees = MainNet() ? nRewardPayouts_1_3_BlockPayees :
                                           nRewardPayouts_1_3_BlockPayees_Testnet;

        round.nBlockPayees = std::max<int>(nBlockPayees, (nPayeeCount / nBlockStretch * nBlockPayees) + 1);

        int64_t nStartDelayBlocks = MainNet() ? nRewardPayoutStartDelay : nRewardPayoutStartDelay_Testnet;
        int64_t nBlocksTarget = nStartDelayBlocks + nBlocksPerRound;
        round.nBlockInterval = ((nBlockStretch * round.nBlockPayees) / nPayeeCount) + 1;
        int64_t nStretchedLength = nPayeeCount / round.nBlockPayees * (round.nBlockInterval);

        if( nStretchedLength > nBlocksTarget ){
            round.nBlockInterval--;
        }else if( nStretchedLength < nBlockStretch ){
            round.nBlockInterval++;
        }
    }
}

bool CSmartRewards::Update(CBlockIndex *pindexNew, const CChainParams& chainparams, const int nCurrentRound, CSmartRewardsUpdateResult &result) {

    CSmartRewardEntry *rEntry = nullptr;
    CBlock block;
    int nHeight = pindexNew->nHeight;
    int nFirst1_3_Round = MainNet() ? nRewardsFirst_1_3_Round : nRewardsFirst_1_3_Round_Testnet;
    ReadBlockFromDisk(block, pindexNew, chainparams.GetConsensus());

    BOOST_FOREACH(const CTransaction &tx, block.vtx) {

        CSmartRewardTransaction testTx;
#ifdef DEBUG_LOCKORDER
        int nTime1 = GetTimeMicros();
#endif
        // First check if the transaction hash did already come up in the past.
        if( GetTransaction(tx.GetHash(),testTx)){
            // If yes we want to ignore it!
            LogPrintf("[%s] Double appearance! First in %d - Now in %d\n",testTx.hash.ToString(), testTx.blockHeight, pindexNew->nHeight);
            continue;
        }else{
            // If not save add it to the database.
            CSmartRewardTransaction saveTx(pindexNew->nHeight, tx.GetHash());
            AddTransaction(saveTx);
        }

        CSmartAddress *voteKeyRegistrationCheck = nullptr;

        // No reason to check the input here for new coins.
        if( !tx.IsCoinBase() ){

            CTransaction rTx;
            uint256 rBlockHash;

            BOOST_FOREACH(const CTxIn &in, tx.vin) {

                if( in.scriptSig.IsZerocoinSpend() ) continue;

                if(!::GetTransaction(in.prevout.hash,rTx,chainparams.GetConsensus(),rBlockHash)){
                    return error("%s: GetTransaction - %s\n Input: %s", __func__, tx.ToString(),in.prevout.hash.ToString());
                }

                CTxOut rOut = rTx.vout[in.prevout.n];

                std::vector<CSmartAddress> ids;

                int required = ParseScript(rOut.scriptPubKey ,ids);

                if( !required || required > 1 || ids.size() > 1 ){
                    LogPrint("smartrewards", "Process Inputs: Could't parse CSmartAddress: %s\n",rOut.ToString());
                    continue;
                }

                if(!GetCachedRewardEntry(ids.at(0),rEntry)){

                    rEntry = new CSmartRewardEntry(ids.at(0));

                    if(!ReadRewardEntry(rEntry->id, *rEntry)){
                        delete rEntry;
                        LogPrintf("%s: Spend without previous receive - %s", __func__, tx.ToString());
                        continue;
                    }

                    rewardEntries.insert(make_pair(ids.at(0), rEntry));
                }

                rEntry->balance -= rOut.nValue;

                if( rEntry->IsEligible() ){

                    // If its a votekey registration not instantly make the
                    // balance ineligible. First check if the change is sent back
                    // to the address or not to avoid exploiting fund sending
                    // with votekey registration transactions
                    if( nCurrentRound >= nFirst1_3_Round && tx.IsVoteKeyRegistration()  )
                        voteKeyRegistrationCheck = new CSmartAddress(rEntry->id);
                    else{
                        rEntry->fBalanceEligible = false;
                        result.disqualifiedEntries++;
                        result.disqualifiedSmart += rEntry->balanceOnStart;
                    }

                }

                if(rEntry->balance < 0 ){
                    LogPrintf("%s: Negative amount?! - %s", __func__, rEntry->ToString());
                    rEntry->balance = 0;
                }

            }
        }

#ifdef DEBUG_LOCKORDER
        int nTime2 = GetTimeMicros();
#endif

        BOOST_FOREACH(const CTxOut &out, tx.vout) {

            if(out.scriptPubKey.IsZerocoinMint() ) continue;

            std::vector<CSmartAddress> ids;
            int required = ParseScript(out.scriptPubKey ,ids);

            if( !required || required > 1 || ids.size() > 1 ){
                LogPrint("smartrewards", "Process Outputs: Could't parse CSmartAddress: %s\n",out.ToString());
                continue;
            }else{

                if(!GetCachedRewardEntry(ids.at(0),rEntry)){
                    rEntry = new CSmartRewardEntry(ids.at(0));
                    ReadRewardEntry(rEntry->id, *rEntry);
                    rewardEntries.insert(make_pair(ids.at(0), rEntry));
                }

                if( voteKeyRegistrationCheck ){

                    if( tx.IsVoteKeyRegistration() &&
                        !out.IsVoteKeyRegistrationData() &&
                        !(*voteKeyRegistrationCheck == rEntry->id) ){

                        CSmartRewardEntry *vkEntry = nullptr;

                        if(!GetCachedRewardEntry(*voteKeyRegistrationCheck,vkEntry)){
                            vkEntry = new CSmartRewardEntry(*voteKeyRegistrationCheck);
                            ReadRewardEntry(vkEntry->id, *vkEntry);
                            rewardEntries.insert(make_pair(vkEntry->id, vkEntry));
                        }

                        // Finally invalidate the balance since the change output went not
                        // back to the registered transaction! We don't want to allow
                        // a exploit to send around funds withouht breaking smartrewards.
                        vkEntry->fBalanceEligible = false;
                        result.disqualifiedEntries++;
                        result.disqualifiedSmart += vkEntry->balanceOnStart;
                    }

                    delete voteKeyRegistrationCheck;
                }

                rEntry->balance += out.nValue;

                // If we are in the 1.3 cycles check for node rewards to remove node addresses from lists
                if( nCurrentRound >= nFirst1_3_Round && tx.IsCoinBase() ){

                    int nInterval = SmartNodePayments::PayoutInterval(nHeight);
                    int nPayoutsPerBlock = SmartNodePayments::PayoutsPerBlock(nHeight);
                    // Just to avoid potential zero divisions
                    nPayoutsPerBlock = std::max(1,nPayoutsPerBlock);

                    CAmount nNodeReward = SmartNodePayments::Payment(nHeight) / nPayoutsPerBlock;

                    // If we have an interval check if this is a node payout block
                    if( nInterval && !(nHeight % nInterval) ){

                        // If the amount matches and the entry is not yet marked as node do it
                        if( abs(out.nValue - nNodeReward ) < 2 && !rEntry->fIsSmartNode ){

                            // If it is currently eligible adjust the round's results
                            if( rEntry->IsEligible() ){
                                result.disqualifiedEntries++;
                                result.disqualifiedSmart += rEntry->balanceOnStart;
                            }

                            rEntry->fIsSmartNode = true;
                        }
                    }
                }
            }
        }

#ifdef DEBUG_LOCKORDER
        int nTime3 = GetTimeMicros();
        int nTimeTx = nTime3 - nTime1;

        if( nTimeTx > 500000){
            LogPrint("smartrewards", "CSmartRewards::Update TX %s - %.2fms\n",HexStr(tx.GetHash()), nTimeTx * 0.001);
            LogPrint("smartrewards", " inputs - %.2fms\n", (nTime2 - nTime1) * 0.001);
            LogPrint("smartrewards", " outputs - %.2fms\n", (nTime3 - nTime2) * 0.001);
        }
#endif

    }

    uint256 blockHash = block.GetHash();
    result.block = CSmartRewardBlock(pindexNew->nHeight, blockHash, block.GetBlockTime());

    // Synt the data all nCacheEntires to the db.
    int preparedEntries = rewardEntries.size() + transactionEntries.size();

    return AddBlock(result.block, preparedEntries > nCacheEntires );
}

void CSmartRewards::EvaluateRound(CSmartRewardRound &current, CSmartRewardRound &next, CSmartRewardEntryList &entries, CSmartRewardSnapshotList &snapshots)
{
    LOCK(cs_rewardsdb);
    snapshots.clear();

    UpdatePayoutParameter(current);

    BOOST_FOREACH(CSmartRewardEntry &entry, entries) {

        if( current.number ) snapshots.push_back(CSmartRewardSnapshot(entry, current));

        entry.balanceOnStart = entry.balance;
        // Reset SmartNode flag with every cycle in case a node was shut down during the cycle.
        entry.fIsSmartNode = false;
        // Reset the voted flag with every cycle to force a new vote for eligibility
        entry.fVoteProved = false;
        // Evaluate the balance eligibilty
        entry.fBalanceEligible = entry.balanceOnStart >= SMART_REWARDS_MIN_BALANCE && !SmartHive::IsHive(entry.id);

        if( entry.IsEligible() ){
            ++next.eligibleEntries;
            next.eligibleSmart += entry.balanceOnStart;
        }
    }
}

bool CSmartRewards::StartFirstRound(const CSmartRewardRound &first, const CSmartRewardEntryList &entries)
{
    LOCK(cs_rewardsdb);
    return pdb->StartFirstRound(first,entries);
}

bool CSmartRewards::FinalizeRound(const CSmartRewardRound &current, const CSmartRewardRound &next, const CSmartRewardEntryList &entries, const CSmartRewardSnapshotList &snapshots)
{
    LOCK(cs_rewardsdb);
    return pdb->FinalizeRound(current, next, entries, snapshots);
}

bool CSmartRewards::GetRewardSnapshots(const int16_t round, CSmartRewardSnapshotList &snapshots)
{
    LOCK(cs_rewardsdb);
    return pdb->ReadRewardSnapshots(round, snapshots);
}

bool CSmartRewards::GetRewardPayouts(const int16_t round, CSmartRewardSnapshotList &payouts)
{
    LOCK(cs_rewardsdb);
    return pdb->ReadRewardPayouts(round, payouts);
}

bool CSmartRewards::GetRewardPayouts(const int16_t round, CSmartRewardSnapshotPtrList &payouts)
{
    LOCK(cs_rewardsdb);
    return pdb->ReadRewardPayouts(round, payouts);
}

// --- TBD ---
bool CSmartRewards::RestoreSnapshot(const int16_t round)
{
//    LOCK(cs_rewardsdb);

//    CSmartRewardRound restore;
//    CSmartRewardEntryList entries;

//    if( !pdb->ReadRound(round, restore) ) return false;
//    if( !pdb->ReadRewardSnapshots(round, entries) ) return false;
//    if( history.number != round) return false;

//    restore.disqualifiedEntries = 0;
//    restore.disqualifiedSmart = 0;
//    restore.rewards = 0;
//    restore.percent = 0;

//    CalculateRewardRatio(restore);

//    return pdb->ResetToRound(round, restore, entries);
    return false;
}

bool CSmartRewards::GetCachedRewardEntry(const CSmartAddress &id, CSmartRewardEntry *&entry)
{
    LOCK(cs_rewardsdb);

    // Return the entry if its already in cache.
    auto findResult = rewardEntries.find(id);

    if( findResult != rewardEntries.end() ){
        entry = findResult->second;
        return true;
    }

    return false;
}

bool CSmartRewards::ReadRewardEntry(const CSmartAddress &id, CSmartRewardEntry &entry)
{
    LOCK(cs_rewardsdb);
    return pdb->ReadRewardEntry(id,entry);
}

bool CSmartRewards::GetRewardEntry(const CSmartAddress &id, CSmartRewardEntry &entry)
{
    LOCK(cs_rewardsdb);

    CSmartRewardEntry * pReadEntry;

    if( GetCachedRewardEntry(id,pReadEntry) ){
        entry = *pReadEntry;
        return true;
    }

    return ReadRewardEntry(id,entry);
}

bool CSmartRewards::GetRewardEntries(CSmartRewardEntryList &entries)
{
    LOCK(cs_rewardsdb);
    return pdb->ReadRewardEntries(entries);
}

bool CSmartRewards::SyncPrepared()
{
    LOCK2(cs_rewardsdb, cs_rewardrounds);

    bool ret =  pdb->SyncBlocks(blockEntries,currentRound, rewardEntries, transactionEntries);

    for( std::pair<CSmartAddress, CSmartRewardEntry*> it : rewardEntries ){
        delete it.second;
    }

    rewardEntries.clear();
    blockEntries.clear();
    transactionEntries.clear();

    return ret;
}

bool CSmartRewards::IsSynced()
{
    int nSyncDistance = MainNet() ? nRewardsSyncDistance : nRewardsSyncDistance_Testnet;
    return (chainHeight - rewardHeight) <= nSyncDistance - 1;
}

double CSmartRewards::GetProgress()
{
    int nSyncDistance = MainNet() ? nRewardsSyncDistance : nRewardsSyncDistance_Testnet;
    double progress = chainHeight > nSyncDistance ? double(rewardHeight) / double(chainHeight - nSyncDistance) : 0.0;
    return progress > 1 ? 1 : progress;
}

int CSmartRewards::GetLastHeight()
{
    return rewardHeight;
}

int CSmartRewards::GetBlocksPerRound(const int nRound)
{
    LOCK(cs_rewardrounds);

    if( MainNet() ){

        if( nRound < nRewardsFirst_1_3_Round )
            return nRewardsBlocksPerRound_1_2;
        else
            return nRewardsBlocksPerRound_1_3;

    }else{

        if( nRound < nRewardsFirst_1_3_Round_Testnet )
            return nRewardsBlocksPerRound_1_2_Testnet;
        else
            return nRewardsBlocksPerRound_1_3_Testnet;

    }

}

bool CSmartRewards::AddBlock(const CSmartRewardBlock &block, bool sync)
{
    blockEntries.push_back(block);

#ifdef DEBUG_LOCKORDER
    int nTime1 = GetTimeMicros();
#endif

    if(sync) return SyncPrepared();

#ifdef DEBUG_LOCKORDER
    int nTimeSync = GetTimeMicros() - nTime1;
    if( nTimeSync > 300000){
        LogPrint("smartrewards", "CSmartRewards::AddBlock - Sync - Block: %d - Progress %.2fms\n",block.nHeight, nTimeSync * 0.001);
    }
#endif

    return true;
}

void CSmartRewards::AddTransaction(const CSmartRewardTransaction &transaction)
{
    LOCK(cs_rewardsdb);
    transactionEntries.push_back(transaction);
}

CSmartRewards::CSmartRewards(CSmartRewardsDB *prewardsdb)  : pdb(prewardsdb)
{
    LOCK(cs_rewardsdb);

    // Get the last written block of the rewards database.
    if(!pdb->ReadLastBlock(currentBlock)){
        // If there is no one available yet
        // Use 0 to get 1 as start below.
        currentBlock.nHeight = 0;
        currentBlock.blockHash = uint256();
        currentBlock.blockTime = 0;
    }

    pdb->ReadRounds(finishedRounds);

    std::sort(finishedRounds.begin(), finishedRounds.end());

    if( finishedRounds.size() ){
        lastRound = finishedRounds.back();
    }

    pdb->ReadCurrentRound(currentRound);
}

void CSmartRewards::Lock()
{
    LOCK(cs_rewardsdb);
    pdb->Lock();
}

bool CSmartRewards::IsLocked()
{
    LOCK(cs_rewardsdb);
    return pdb->IsLocked();
}

void CSmartRewards::CatchUp()
{
    const CChainParams& chainparams = Params();
    CBlockIndex* pHighestIndex = chainActive.Tip();
    CBlockIndex* pLastIndex = pHighestIndex;
    if( !pLastIndex ) return;
    // If the rewards db is higher than the chain.
    if( pLastIndex->nHeight <= currentBlock.nHeight ) return;

    // Search the index of the next missing bock in the
    // rewards database.
    while( pLastIndex->nHeight != currentBlock.nHeight + 1){
        pLastIndex = pLastIndex->pprev;
    }

    int nMinConfirmations = MainNet() ? nRewardsConfirmations : nRewardsConfirmations_Testnet;

    while( pLastIndex && ( currentBlock.nHeight - pLastIndex->nHeight ) < nMinConfirmations)
    {
        if( ShutdownRequested() ){
            SyncPrepared();
            return;
        }

        if( !(currentBlock.nHeight % 100) ){
            uiInterface.InitMessage(_("Creating SmartRewards database: ") + strprintf("%d/%d",currentBlock.nHeight, pHighestIndex->nHeight));
        }

        ProcessBlock(pLastIndex, chainparams);

        pLastIndex = chainActive.Next(pLastIndex);

    }

    prewards->UpdateHeights(GetBlockHeight(pHighestIndex), currentBlock.nHeight);
}

bool CSmartRewards::GetLastBlock(CSmartRewardBlock &block)
{
    LOCK(cs_rewardsdb);
    // Read the last block stored in the rewards database.
    return pdb->ReadLastBlock(block);
}

bool CSmartRewards::GetTransaction(const uint256 hash, CSmartRewardTransaction &transaction)
{
    // If the transaction is already in the cache use this one.
    BOOST_FOREACH(CSmartRewardTransaction t, transactionEntries) {
        if(t.hash == hash){
            transaction = t;
            return true;
        }
    }

    return pdb->ReadTransaction(hash, transaction);
}

const CSmartRewardRound& CSmartRewards::GetCurrentRound()
{
    return currentRound;
}

const CSmartRewardRound& CSmartRewards::GetLastRound()
{
    return lastRound;
}

const CSmartRewardRoundList& CSmartRewards::GetRewardRounds()
{
    return finishedRounds;
}

void CSmartRewards::UpdateHeights(const int nHeight, const int nRewardHeight)
{
    chainHeight = nHeight;
    rewardHeight = nRewardHeight;
}

void CSmartRewards::ProcessBlock(CBlockIndex* pLastIndex, const CChainParams& chainparams)
{
    int64_t nTime1 = 0, nTime2 = 0, nTime3 = 0;
    static int64_t nTimeTotal = 0;
    static int64_t nTimeUpdateRewardsTotal = 0;
    static int64_t nCountUpdateRewards = 0;

    if(pLastIndex->nHeight > sporkManager.GetSporkValue(SPORK_15_SMARTREWARDS_BLOCKS_ENABLED)){
        return;
    }

    int nMinConfirmations = MainNet() ? nRewardsConfirmations : nRewardsConfirmations_Testnet;

    if( ( pLastIndex->nHeight - currentBlock.nHeight ) > nMinConfirmations){

        int nCurrentRound;

        {
            LOCK(cs_rewardrounds);
            nCurrentRound = currentRound.number;
        }

        CBlockIndex* pNextIndex = pLastIndex;

        while(pNextIndex && pNextIndex->nHeight != currentBlock.nHeight + 1 ) pNextIndex = pNextIndex->pprev;

        if(!pNextIndex || pNextIndex->nHeight != currentBlock.nHeight + 1 ) throw runtime_error("Failed to find next block index!");

        nTime1 = GetTimeMicros();

        // Result of the block processing.
        CSmartRewardsUpdateResult result;
        // Process the block!
        if(!Update(pNextIndex, chainparams, nCurrentRound, result)) throw runtime_error(std::string(__func__) + ": rewards update failed");

        // Update the current block to the processed one
        currentBlock = result.block;

        nTime2 = GetTimeMicros();

        nTimeUpdateRewardsTotal += nTime2 - nTime1;
        ++nCountUpdateRewards;

        // For the first round we have special parameter..
        if( !currentRound.number ){

            if( (MainNet() && pNextIndex->GetBlockTime() > nFirstRoundStartTime) ||
                (TestNet() && pNextIndex->nHeight >= nFirstRoundStartBlock_Testnet) ){

                if( !SyncPrepared() ) throw runtime_error("Failed to sync current prepared entries!");

                // Create the very first smartrewards round.
                CSmartRewardRound first;
                first.number = 1;
                first.startBlockTime = pNextIndex->GetBlockTime();
                first.startBlockHeight = MainNet() ? nFirstRoundStartBlock : nFirstRoundStartBlock_Testnet;
                first.endBlockTime = MainNet() ? nFirstRoundEndTime : nFirstRoundEndTime_Testnet;
                // Estimate the block, gets updated on the end of the round to the real one.
                first.endBlockHeight = MainNet() ? nFirstRoundEndBlock : nFirstRoundEndBlock_Testnet;

                CSmartRewardEntryList entries;
                CSmartRewardSnapshotList snapshots;

                // Get the current entries
                if( !GetRewardEntries(entries) ) throw runtime_error("Failed to read all reward entries!");

                // Evaluate the round and update the next rounds parameter.
                EvaluateRound(currentRound, first, entries, snapshots );

                CalculateRewardRatio(first);

                if( !StartFirstRound(first, entries) ) throw runtime_error("Failed to finalize round!");

                currentRound = first;
            }

        }else if( result.disqualifiedEntries || result.disqualifiedSmart ){

            // If there were disqualification during the last block processing
            // update the current round stats.

            currentRound.disqualifiedEntries += result.disqualifiedEntries;
            currentRound.disqualifiedSmart += result.disqualifiedSmart;

            CalculateRewardRatio(currentRound);
        }

        // If just hit the next round threshold
        if( ( MainNet() && currentRound.number < nRewardsFirstAutomatedRound - 1 && pNextIndex->GetBlockTime() > currentRound.endBlockTime ) ||
            ( ( TestNet() || currentRound.number >= nRewardsFirstAutomatedRound - 1 ) && pNextIndex->nHeight >= currentRound.endBlockHeight ) ){

            if( !SyncPrepared() ) throw runtime_error("Failed to sync current prepared entries!");

            // Write the round to the history
            currentRound.endBlockHeight = pNextIndex->nHeight;
            currentRound.endBlockTime = pNextIndex->GetBlockTime();

            CSmartRewardEntryList entries;
            CSmartRewardSnapshotList snapshots;

            // Create the next round.
            CSmartRewardRound next;
            next.number = currentRound.number + 1;
            next.startBlockTime = currentRound.endBlockTime;
            next.startBlockHeight = currentRound.endBlockHeight + 1;

            int nBlocksPerRound = GetBlocksPerRound(next.number);
            time_t startTime = (time_t)next.startBlockTime;

            if( MainNet() ){

                if( next.number == nRewardsFirstAutomatedRound - 1 ){
                    // Let the round 12 end at height 574099 so that round 13 starts at 574100
                    next.endBlockHeight = HF_V1_2_SMARTREWARD_HEIGHT - 1;
                    next.endBlockTime = startTime + ( (next.endBlockHeight - next.startBlockHeight) * 55 );
                }else if(next.number < nRewardsFirstAutomatedRound){

                    boost::gregorian::date endDate = boost::posix_time::from_time_t(startTime).date();

                    endDate += boost::gregorian::months(1);
                    // End date at 00:00:00 + 25200 seconds (7 hours) to match the date at 07:00 UTC
                    next.endBlockTime = time_t((boost::posix_time::ptime(endDate, boost::posix_time::seconds(25200)) - epoch).total_seconds());
                    next.endBlockHeight = next.startBlockHeight + ( (next.endBlockTime - next.startBlockTime) / 55 );
                }else{
                    next.endBlockHeight = next.startBlockHeight + nBlocksPerRound - 1;
                    next.endBlockTime = startTime + nBlocksPerRound * 55;
                }

            }else{
                next.endBlockHeight = next.startBlockHeight + nBlocksPerRound - 1;
                next.endBlockTime = startTime + nBlocksPerRound * 55;
            }

            if( !SyncPrepared() ) throw runtime_error("Failed to sync current prepared entries!");

            // Get the current entries
            if( !GetRewardEntries(entries) ) throw runtime_error("Failed to read all reward entries!");

            CalculateRewardRatio(currentRound);

            // Evaluate the round and update the next rounds parameter.
            EvaluateRound(currentRound, next, entries, snapshots);

            CalculateRewardRatio(next);

            if( !FinalizeRound(currentRound, next, entries, snapshots) ) throw runtime_error("Failed to finalize round!");

            LOCK(cs_rewardrounds);

            finishedRounds.push_back(currentRound);
            lastRound = currentRound;
            currentRound = next;
        }

        prewards->UpdateHeights(GetBlockHeight(pLastIndex), currentBlock.nHeight);

        nTime3 = GetTimeMicros(); nTimeTotal += nTime3 - nTime1;
        int nTimeUpdateMean = nTimeUpdateRewardsTotal/nCountUpdateRewards;
        int nTimeUpdate = nTime2 - nTime1;

        if( nTimeUpdate > nTimeUpdateMean * 100){
            LogPrint("smartrewards", "Round %d - Block: %d - Progress %d%%\n",currentRound.number, currentBlock.nHeight, int(prewards->GetProgress() * 100));
            LogPrint("smartrewards", "  Update rewards: %.2fms [%.2fms]\n", nTimeUpdate * 0.001, (nTimeUpdateMean) * 0.001);
            LogPrint("smartrewards", "  Evaluate round: %.2fms\n", (nTime3 - nTime2) * 0.001);
            LogPrint("smartrewards", "  Total: %.2fms [%.2fs]\n", (nTime3 - nTime1) * 0.001, nTimeTotal * 0.000001);
        }
        // If we are synced notify the UI on each new block.
        // If not notify the UI every nRewardsUISyncUpdateRate blocks to let it update the
        // loading screen.
        if( IsSynced() || !(currentBlock.nHeight % nRewardsUISyncUpdateRate) )
            uiInterface.NotifySmartRewardUpdate();
    }
}


// Copyright (c) 2016 The Gulden developers
// Authored by: Malcolm MacLeod (mmacleod@webmail.co.za)
// Distributed under the GULDEN software license, see the accompanying
// file COPYING

#include "account.h"
#include "wallet/wallet.h"
#include <Gulden/mnemonic.h>
#include "base58.h"
#include "wallet/walletdb.h"
#include "wallet/crypter.h"

#include <map>

#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/nil_generator.hpp>

CHDSeed::CHDSeed()
    : m_nAccountIndex(HDDesktopStartIndex)
    , m_nAccountIndexMobi(HDMobileStartIndex)
    , encrypted(false)
    , m_readOnly(false)
{
}

CHDSeed::CHDSeed(SecureString mnemonic, SeedType type)
    : m_type(type)
    , m_UUID(boost::uuids::nil_generator()())
    , m_nAccountIndex(HDDesktopStartIndex)
    , m_nAccountIndexMobi(HDMobileStartIndex)
    , encrypted(false)
    , m_readOnly(false)
{

    unencryptedMnemonic = mnemonic;
    Init();
}

CHDSeed::CHDSeed(CExtPubKey& pubkey, SeedType type)
    : m_type(type)
    , m_UUID(boost::uuids::nil_generator()())
    , m_nAccountIndex(HDDesktopStartIndex)
    , m_nAccountIndexMobi(HDMobileStartIndex)
    , encrypted(false)
    , m_readOnly(true)
{
    unencryptedMnemonic = "";
    masterKeyPub = pubkey;

    masterKeyPriv.GetMutableKey().MakeNewKey(true);
    assert(masterKeyPriv.key.IsValid());
    cointypeKeyPriv.GetMutableKey().MakeNewKey(true);
    purposeKeyPriv.GetMutableKey().MakeNewKey(true);

    InitReadOnly();
}

void CHDSeed::Init()
{
    unsigned char* seed = seedFromMnemonic(unencryptedMnemonic);
    static const std::vector<unsigned char> hashkey = { 'G', 'u', 'l', 'd', 'e', 'n', ' ', 'b', 'i', 'p', '3', '2' };
    static const std::vector<unsigned char> hashkeylegacy = { 'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd' };

    if (m_type == BIP32Legacy || m_type == BIP44External) {
        masterKeyPriv.SetMaster(hashkeylegacy, seed, 64);
    } else {
        masterKeyPriv.SetMaster(hashkey, seed, 64);
    }

    switch (m_type) {
    case BIP32:
    case BIP32Legacy:
        masterKeyPriv.Derive(purposeKeyPriv, 100 | BIP32_HARDENED_KEY_LIMIT); //Unused - but we generate anyway so that we don't save predictable/blank encrypted data to disk (tiny security precaution)
        purposeKeyPriv.Derive(cointypeKeyPriv, 100 | BIP32_HARDENED_KEY_LIMIT); //Unused - but we generate anyway so that we don't save predictable/blank encrypted data to disk (tiny security precaution)
        break;
    case BIP44: {
        masterKeyPriv.Derive(purposeKeyPriv, 44 | BIP32_HARDENED_KEY_LIMIT); //m/44'
        purposeKeyPriv.Derive(cointypeKeyPriv, 87 | BIP32_HARDENED_KEY_LIMIT); //m/44'/87'
    } break;
    case BIP44NoHardening: {
        masterKeyPriv.Derive(purposeKeyPriv, 44); //m/44
        purposeKeyPriv.Derive(cointypeKeyPriv, 87); //m/44/87
    } break;
    default:
        assert(0);
    }
    masterKeyPub = masterKeyPriv.Neuter();
    purposeKeyPub = purposeKeyPriv.Neuter();
    cointypeKeyPub = cointypeKeyPriv.Neuter();

    if (m_UUID.is_nil()) {
        m_UUID = boost::uuids::random_generator()();
    }
}

void CHDSeed::InitReadOnly()
{
    assert(m_type == BIP44NoHardening);
    masterKeyPub.Derive(purposeKeyPub, 44); //m/44
    purposeKeyPub.Derive(cointypeKeyPub, 87); //m/44/87

    if (m_UUID.is_nil()) {
        m_UUID = boost::uuids::random_generator()();
    }
}

CAccountHD* CHDSeed::GenerateAccount(AccountSubType type, CWalletDB* Db)
{
    if (IsLocked())
        return NULL;

    CAccountHD* account = NULL;
    switch (type) {
    case Desktop:
        assert(m_nAccountIndex <= 99999);
        account = GenerateAccount(m_nAccountIndex++);
        break;
    case Mobi:
        account = GenerateAccount(m_nAccountIndexMobi++);
        break;
    default:
        assert(0);
        return NULL;
    }

    if (Db) {

        Db->WriteHDSeed(*this);
    }

    account->m_SubType = type;
    if (IsCrypted()) {
        account->Encrypt(vMasterKey);
    }
    return account;
}

CAccountHD* CHDSeed::GenerateAccount(int nAccountIndex)
{
    if (IsLocked())
        return NULL;

    CExtKey accountKey;
    if (IsReadOnly()) {
        CExtPubKey accountKeyPub;
        cointypeKeyPub.Derive(accountKeyPub, nAccountIndex); // m/44/87/n (BIP44)
        return new CAccountHD(accountKeyPub, m_UUID);
    } else {
        switch (m_type) {
        case BIP32:
        case BIP32Legacy:
            masterKeyPriv.Derive(accountKey, nAccountIndex | BIP32_HARDENED_KEY_LIMIT); // m/n' (BIP32)
            break;
        case BIP44:
        case BIP44External:
            cointypeKeyPriv.Derive(accountKey, nAccountIndex | BIP32_HARDENED_KEY_LIMIT); // m/44'/87'/n' (BIP44)
            break;
        case BIP44NoHardening:
            cointypeKeyPriv.Derive(accountKey, nAccountIndex); // m/44'/87'/n (BIP44 without hardening (for read only sync))
            break;
        default:
            assert(0);
        }
        return new CAccountHD(accountKey, m_UUID);
    }
}

std::string CHDSeed::getUUID() const
{
    return boost::uuids::to_string(m_UUID);
}

SecureString CHDSeed::getMnemonic()
{
    return unencryptedMnemonic;
}

SecureString CHDSeed::getPubkey()
{
    return CBitcoinSecretExt<CExtPubKey>(masterKeyPub).ToString().c_str();
}

bool CHDSeed::IsLocked() const
{
    if (unencryptedMnemonic.size() > 0 || m_readOnly) {
        return false;
    }
    return true;
}

bool CHDSeed::IsCrypted() const
{
    return encrypted;
}

bool CHDSeed::Lock()
{

    unencryptedMnemonic = "";

    masterKeyPriv = CExtKey();
    purposeKeyPriv = CExtKey();
    cointypeKeyPriv = CExtKey();

    vMasterKey.clear();

    return true;
}

bool CHDSeed::Unlock(const CKeyingMaterial& vMasterKeyIn)
{

    assert(sizeof(m_UUID) == WALLET_CRYPTO_IV_SIZE);
    CKeyingMaterial vchMnemonic;
    if (!DecryptSecret(vMasterKeyIn, encryptedMnemonic, std::vector<unsigned char>(m_UUID.begin(), m_UUID.end()), vchMnemonic))
        return false;
    unencryptedMnemonic = SecureString(vchMnemonic.begin(), vchMnemonic.end());

    CKeyingMaterial vchMasterKeyPrivEncoded;
    if (!DecryptSecret(vMasterKeyIn, masterKeyPrivEncrypted, masterKeyPub.pubkey.GetHash(), vchMasterKeyPrivEncoded))
        return false;
    masterKeyPriv.Decode(vchMasterKeyPrivEncoded.data());

    CKeyingMaterial vchPurposeKeyPrivEncoded;
    if (!DecryptSecret(vMasterKeyIn, purposeKeyPrivEncrypted, purposeKeyPub.pubkey.GetHash(), vchPurposeKeyPrivEncoded))
        return false;
    purposeKeyPriv.Decode(vchPurposeKeyPrivEncoded.data());

    CKeyingMaterial vchCoinTypeKeyPrivEncoded;
    if (!DecryptSecret(vMasterKeyIn, cointypeKeyPrivEncrypted, cointypeKeyPub.pubkey.GetHash(), vchCoinTypeKeyPrivEncoded))
        return false;
    cointypeKeyPriv.Decode(vchCoinTypeKeyPrivEncoded.data());

    vMasterKey = vMasterKeyIn;

    return true;
}

bool CHDSeed::Encrypt(CKeyingMaterial& vMasterKeyIn)
{

    assert(sizeof(m_UUID) == WALLET_CRYPTO_IV_SIZE);
    encryptedMnemonic.clear();
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(unencryptedMnemonic.begin(), unencryptedMnemonic.end()), std::vector<unsigned char>(m_UUID.begin(), m_UUID.end()), encryptedMnemonic))
        return false;

    SecureUnsignedCharVector masterKeyPrivEncoded(BIP32_EXTKEY_SIZE);
    masterKeyPriv.Encode(masterKeyPrivEncoded.data());
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(masterKeyPrivEncoded.begin(), masterKeyPrivEncoded.end()), masterKeyPub.pubkey.GetHash(), masterKeyPrivEncrypted))
        return false;

    SecureUnsignedCharVector purposeKeyPrivEncoded(BIP32_EXTKEY_SIZE);
    purposeKeyPriv.Encode(purposeKeyPrivEncoded.data());
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(purposeKeyPrivEncoded.begin(), purposeKeyPrivEncoded.end()), purposeKeyPub.pubkey.GetHash(), purposeKeyPrivEncrypted))
        return false;

    SecureUnsignedCharVector cointypeKeyPrivEncoded(BIP32_EXTKEY_SIZE);
    cointypeKeyPriv.Encode(cointypeKeyPrivEncoded.data());
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(cointypeKeyPrivEncoded.begin(), cointypeKeyPrivEncoded.end()), cointypeKeyPub.pubkey.GetHash(), cointypeKeyPrivEncrypted))
        return false;

    encrypted = true;
    vMasterKey = vMasterKeyIn;

    return true;
}

CAccountHD::CAccountHD(CExtKey accountKey_, boost::uuids::uuid seedID)
    : CAccount()
    , m_SeedID(seedID)
    , m_nIndex(accountKey_.nChild)
    , m_nNextChildIndex(0)
    , m_nNextChangeIndex(0)
    , encrypted(false)
    , accountKeyPriv(accountKey_)
{
    accountKeyPriv.Derive(primaryChainKeyPriv, 0); //a/0
    accountKeyPriv.Derive(changeChainKeyPriv, 1); //a/1
    primaryChainKeyPub = primaryChainKeyPriv.Neuter();
    changeChainKeyPub = changeChainKeyPriv.Neuter();
}

CAccountHD::CAccountHD(CExtPubKey accountKey_, boost::uuids::uuid seedID)
    : CAccount()
    , m_SeedID(seedID)
    , m_nIndex(accountKey_.nChild)
    , m_nNextChildIndex(0)
    , m_nNextChangeIndex(0)
    , encrypted(false)
{

    accountKeyPriv.GetMutableKey().MakeNewKey(true);
    primaryChainKeyPriv.GetMutableKey().MakeNewKey(true);
    changeChainKeyPriv.GetMutableKey().MakeNewKey(true);

    accountKey_.Derive(primaryChainKeyPub, 0); //a/0
    accountKey_.Derive(changeChainKeyPub, 1); //a/1
    m_readOnly = true;
}

void CAccountHD::GetKey(CExtKey& childKey, int nChain)
{
    assert(!m_readOnly);
    if (nChain == KEYCHAIN_EXTERNAL) {
        primaryChainKeyPriv.Derive(childKey, m_nNextChildIndex++);
    } else {
        changeChainKeyPriv.Derive(childKey, m_nNextChangeIndex++);
    }
}

bool CAccountHD::GetKey(const CKeyID& keyID, CKey& key) const
{
    if (IsLocked())
        return false;

    assert(!m_readOnly);

    int64_t nKeyIndex = -1;
    CExtKey privKey;
    if (externalKeyStore.GetKey(keyID, nKeyIndex)) {
        primaryChainKeyPriv.Derive(privKey, nKeyIndex);
        if (privKey.Neuter().pubkey.GetID() != keyID)
            assert(0);
        key = privKey.key;
        return true;
    }
    if (internalKeyStore.GetKey(keyID, nKeyIndex)) {
        changeChainKeyPriv.Derive(privKey, nKeyIndex);
        if (privKey.Neuter().pubkey.GetID() != keyID)
            assert(0);
        key = privKey.key;
        return true;
    }
    return false;
}

bool CAccountHD::GetKey(const CKeyID& address, std::vector<unsigned char>& encryptedKeyOut) const
{
    assert(0);
}

void CAccountHD::GetPubKey(CExtPubKey& childKey, int nChain) const
{
    if (nChain == KEYCHAIN_EXTERNAL) {
        primaryChainKeyPub.Derive(childKey, m_nNextChildIndex++);
    } else {
        changeChainKeyPub.Derive(childKey, m_nNextChangeIndex++);
    }
}

bool CAccountHD::GetPubKey(const CKeyID& address, CPubKey& vchPubKeyOut) const
{
    int64_t nKeyIndex = -1;
    if (externalKeyStore.GetKey(address, nKeyIndex)) {
        CExtPubKey extPubKey;
        primaryChainKeyPub.Derive(extPubKey, nKeyIndex);
        vchPubKeyOut = extPubKey.pubkey;
        return true;
    }
    if (internalKeyStore.GetKey(address, nKeyIndex)) {
        CExtPubKey extPubKey;
        changeChainKeyPub.Derive(extPubKey, nKeyIndex);
        vchPubKeyOut = extPubKey.pubkey;
        return true;
    }
    return false;
}

bool CAccountHD::Lock()
{
    if (!IsReadOnly()) {
        return true;
    }

    if (!encrypted)
        return false;

    accountKeyPriv = CExtKey();
    primaryChainKeyPriv = CExtKey();
    changeChainKeyPriv = CExtKey();

    return true;
}

bool CAccountHD::Unlock(const CKeyingMaterial& vMasterKeyIn)
{
    assert(sizeof(accountUUID) == WALLET_CRYPTO_IV_SIZE);

    if (IsReadOnly()) {
        return true;
    }

    CKeyingMaterial vchAccountKeyPrivEncoded;
    if (!DecryptSecret(vMasterKeyIn, accountKeyPrivEncrypted, std::vector<unsigned char>(accountUUID.begin(), accountUUID.end()), vchAccountKeyPrivEncoded))
        return false;
    accountKeyPriv.Decode(vchAccountKeyPrivEncoded.data());

    CKeyingMaterial vchPrimaryChainKeyPrivEncoded;
    if (!DecryptSecret(vMasterKeyIn, primaryChainKeyEncrypted, primaryChainKeyPub.pubkey.GetHash(), vchPrimaryChainKeyPrivEncoded))
        return false;
    primaryChainKeyPriv.Decode(vchPrimaryChainKeyPrivEncoded.data());

    CKeyingMaterial vchChangeChainKeyPrivEncoded;
    if (!DecryptSecret(vMasterKeyIn, changeChainKeyEncrypted, changeChainKeyPub.pubkey.GetHash(), vchChangeChainKeyPrivEncoded))
        return false;
    changeChainKeyPriv.Decode(vchChangeChainKeyPrivEncoded.data());

    return true;
}

bool CAccountHD::Encrypt(CKeyingMaterial& vMasterKeyIn)
{
    assert(sizeof(accountUUID) == WALLET_CRYPTO_IV_SIZE);

    if (IsReadOnly()) {
        return true;
    }

    SecureUnsignedCharVector accountKeyPrivEncoded(BIP32_EXTKEY_SIZE);
    accountKeyPriv.Encode(accountKeyPrivEncoded.data());
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(accountKeyPrivEncoded.begin(), accountKeyPrivEncoded.end()), std::vector<unsigned char>(accountUUID.begin(), accountUUID.end()), accountKeyPrivEncrypted))
        return false;

    SecureUnsignedCharVector primaryChainKeyPrivEncoded(BIP32_EXTKEY_SIZE);
    primaryChainKeyPriv.Encode(primaryChainKeyPrivEncoded.data());
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(primaryChainKeyPrivEncoded.begin(), primaryChainKeyPrivEncoded.end()), primaryChainKeyPub.pubkey.GetHash(), primaryChainKeyEncrypted))
        return false;

    SecureUnsignedCharVector changeChainKeyPrivEncoded(BIP32_EXTKEY_SIZE);
    changeChainKeyPriv.Encode(changeChainKeyPrivEncoded.data());
    if (!EncryptSecret(vMasterKeyIn, CKeyingMaterial(changeChainKeyPrivEncoded.begin(), changeChainKeyPrivEncoded.end()), changeChainKeyPub.pubkey.GetHash(), changeChainKeyEncrypted))
        return false;

    encrypted = true;

    return true;
}

bool CAccountHD::IsCrypted() const
{
    return encrypted;
}

bool CAccountHD::IsLocked() const
{
    return !accountKeyPriv.key.IsValid();
}

bool CAccountHD::AddKeyPubKey(const CKey& key, const CPubKey& pubkey, int keyChain)
{

    assert(0);
    return true;
}

bool CAccountHD::AddKeyPubKey(int64_t HDKeyIndex, const CPubKey& pubkey, int keyChain)
{
    if (keyChain == KEYCHAIN_EXTERNAL)
        return externalKeyStore.AddKeyPubKey(HDKeyIndex, pubkey);
    else
        return internalKeyStore.AddKeyPubKey(HDKeyIndex, pubkey);
}

CPubKey CAccountHD::GenerateNewKey(CWallet& wallet, int keyChain)
{
    CExtPubKey childKey;
    do {
        GetPubKey(childKey, keyChain);
    } while (wallet.HaveKey(childKey.pubkey.GetID())); ///

    LogPrintf("CAccount::GenerateNewKey(): NewHDKey [%s]\n", CBitcoinAddress(childKey.pubkey.GetID()).ToString());

    if (!wallet.AddKeyPubKey(childKey.nChild, childKey.pubkey, *this, keyChain))
        throw std::runtime_error("CAccount::GenerateNewKey(): AddKeyPubKey failed");

    return childKey.pubkey;
}

CExtKey* CAccountHD::GetAccountMasterPrivKey()
{
    if (IsLocked())
        return NULL;

    return &accountKeyPriv;
}

SecureString CAccountHD::GetAccountMasterPubKeyEncoded()
{
    if (IsLocked())
        return NULL;

    return CBitcoinSecretExt<CExtPubKey>(accountKeyPriv.Neuter()).ToString().c_str();
}

std::string CAccountHD::getSeedUUID() const
{
    return boost::uuids::to_string(m_SeedID);
}

uint32_t CAccountHD::getIndex()
{
    return m_nIndex;
}

CAccount::CAccount()
{
    SetNull();

    earliestPossibleCreationTime = GetTime();
    accountUUID = boost::uuids::random_generator()();
    parentUUID = boost::uuids::nil_generator()();
    m_Type = AccountType::Normal;
    m_SubType = AccountSubType::Desktop;
    m_readOnly = false;
}

void CAccount::SetNull()
{
    vchPubKey = CPubKey();
}

CPubKey CAccount::GenerateNewKey(CWallet& wallet, int keyChain)
{
    CKey secret;
    secret.MakeNewKey(true);

    CPubKey pubkey = secret.GetPubKey();
    assert(secret.VerifyPubKey(pubkey));

    if (!wallet.AddKeyPubKey(secret, pubkey, *this, keyChain))
        throw std::runtime_error("CAccount::GenerateNewKey(): AddKeyPubKey failed");

    return pubkey;
}

bool CAccount::HaveWalletTx(const CTransaction& tx)
{
    for (const CTxOut& txout : tx.vout) {
        isminetype ret = isminetype::ISMINE_NO;
        for (auto keyChain : { KEYCHAIN_EXTERNAL, KEYCHAIN_CHANGE }) {
            isminetype temp = (keyChain == KEYCHAIN_EXTERNAL ? IsMine(externalKeyStore, txout.scriptPubKey) : IsMine(internalKeyStore, txout.scriptPubKey));
            if (temp > ret)
                ret = temp;
        }
        if (ret >= isminetype::ISMINE_NO)
            return true;
    }
    for (const CTxIn& txin : tx.vin) {
        isminetype ret = isminetype::ISMINE_NO;
        std::map<uint256, CWalletTx>::const_iterator mi = pwalletMain->mapWallet.find(txin.prevout.hash);
        if (mi != pwalletMain->mapWallet.end()) {
            const CWalletTx& prev = (*mi).second;
            if (txin.prevout.n < prev.vout.size()) {
                for (const CTxOut& txout : prev.vout) {
                    for (auto keyChain : { KEYCHAIN_EXTERNAL, KEYCHAIN_CHANGE }) {
                        isminetype temp = (keyChain == KEYCHAIN_EXTERNAL ? IsMine(externalKeyStore, txout.scriptPubKey) : IsMine(internalKeyStore, txout.scriptPubKey));
                        if (temp > ret)
                            ret = temp;
                    }
                }
            }
        }
        if (ret >= isminetype::ISMINE_NO)
            return true;
    }
    return false;
}

bool CAccount::HaveKey(const CKeyID& address) const
{
    return externalKeyStore.HaveKey(address) || internalKeyStore.HaveKey(address);
}

bool CAccount::HaveWatchOnly(const CScript& dest) const
{
    return externalKeyStore.HaveWatchOnly(dest) || internalKeyStore.HaveWatchOnly(dest);
}

bool CAccount::HaveWatchOnly() const
{
    return externalKeyStore.HaveWatchOnly() || internalKeyStore.HaveWatchOnly();
}

bool CAccount::HaveCScript(const CScriptID& hash) const
{
    return externalKeyStore.HaveCScript(hash) || internalKeyStore.HaveCScript(hash);
}

bool CAccount::GetCScript(const CScriptID& hash, CScript& redeemScriptOut) const
{
    return externalKeyStore.GetCScript(hash, redeemScriptOut) || internalKeyStore.GetCScript(hash, redeemScriptOut);
}

bool CAccount::IsLocked() const
{
    return externalKeyStore.IsLocked() || internalKeyStore.IsLocked();
}

bool CAccount::IsCrypted() const
{
    return externalKeyStore.IsCrypted() || internalKeyStore.IsCrypted();
}

bool CAccount::Lock()
{

    vMasterKey.clear();

    return externalKeyStore.Lock() && internalKeyStore.Lock();
}

bool CAccount::Unlock(const CKeyingMaterial& vMasterKeyIn)
{
    vMasterKey = vMasterKeyIn;

    return externalKeyStore.Unlock(vMasterKeyIn) && internalKeyStore.Unlock(vMasterKeyIn);
}

bool CAccount::GetKey(const CKeyID& keyID, CKey& key) const
{
    return externalKeyStore.GetKey(keyID, key) || internalKeyStore.GetKey(keyID, key);
}

bool CAccount::GetKey(const CKeyID& address, std::vector<unsigned char>& encryptedKeyOut) const
{
    return externalKeyStore.GetKey(address, encryptedKeyOut) || internalKeyStore.GetKey(address, encryptedKeyOut);
}

void CAccount::GetKeys(std::set<CKeyID>& setAddress) const
{
    std::set<CKeyID> setExternal;
    externalKeyStore.GetKeys(setExternal);
    std::set<CKeyID> setInternal;
    internalKeyStore.GetKeys(setInternal);
    setAddress.clear();
    setAddress.insert(setExternal.begin(), setExternal.end());
    setAddress.insert(setInternal.begin(), setInternal.end());
}

bool CAccount::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
{
    if (!externalKeyStore.EncryptKeys(vMasterKeyIn))
        return false;
    if (!internalKeyStore.EncryptKeys(vMasterKeyIn))
        return false;

    if (pwalletMain) {
        if (!pwalletMain->fFileBacked)
            return true;
        {
            std::set<CKeyID> setAddress;
            GetKeys(setAddress);

            LOCK(pwalletMain->cs_wallet);
            for (const auto& keyID : setAddress) {
                CPubKey pubKey;
                if (!GetPubKey(keyID, pubKey)) {
                    LogPrintf("CAccount::EncryptKeys(): Failed to get pubkey");
                    return false;
                }
                if (pwalletMain->pwalletdbEncryption)
                    pwalletMain->pwalletdbEncryption->EraseKey(pubKey);
                else
                    CWalletDB(pwalletMain->strWalletFile).EraseKey(pubKey);

                std::vector<unsigned char> secret;
                if (!GetKey(keyID, secret)) {
                    LogPrintf("CAccount::EncryptKeys(): Failed to get crypted key");
                    return false;
                }
                if (pwalletMain->pwalletdbEncryption) {
                    if (!pwalletMain->pwalletdbEncryption->WriteCryptedKey(pubKey, secret, pwalletMain->mapKeyMetadata[keyID], getUUID(), KEYCHAIN_EXTERNAL)) {
                        LogPrintf("CAccount::EncryptKeys(): Failed to write key");
                        return false;
                    }
                } else {
                    if (!CWalletDB(pwalletMain->strWalletFile).WriteCryptedKey(pubKey, secret, pwalletMain->mapKeyMetadata[keyID], getUUID(), KEYCHAIN_EXTERNAL)) {
                        LogPrintf("CAccount::EncryptKeys(): Failed to write key");
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

bool CAccount::Encrypt(CKeyingMaterial& vMasterKeyIn)
{
    return EncryptKeys(vMasterKeyIn) /*&& SetCrypted()*/;
}

bool CAccount::GetPubKey(const CKeyID& address, CPubKey& vchPubKeyOut) const
{
    return externalKeyStore.GetPubKey(address, vchPubKeyOut) || internalKeyStore.GetPubKey(address, vchPubKeyOut);
}

bool CAccount::AddKeyPubKey(const CKey& key, const CPubKey& pubkey, int keyChain)
{
    std::vector<unsigned char> encryptedKeyOut;
    if (keyChain == KEYCHAIN_EXTERNAL) {
        return externalKeyStore.AddKeyPubKey(key, pubkey);
        /*if (externalKeyStore.AddKeyPubKey(key, pubkey))
        {
            if (externalKeyStore.GetKey(pubkey.GetID(), encryptedKeyOut))
            {
                return AddCryptedKey(pubkey, encryptedKeyOut, keyChain);
            }
        }
        return false;*/
    } else {
        return internalKeyStore.AddKeyPubKey(key, pubkey);
    }
}

bool CAccount::AddKeyPubKey(int64_t HDKeyIndex, const CPubKey& pubkey, int keyChain)
{

    assert(0);
    return true;
}

bool CAccount::AddWatchOnly(const CScript& dest)
{

    assert(0);
    return externalKeyStore.AddWatchOnly(dest);
}

bool CAccount::RemoveWatchOnly(const CScript& dest)
{

    assert(0);
    return externalKeyStore.RemoveWatchOnly(dest) || internalKeyStore.RemoveWatchOnly(dest);
}

bool CAccount::AddCScript(const CScript& redeemScript)
{
    return externalKeyStore.AddCScript(redeemScript);
}

bool CAccount::AddCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, int64_t nKeyChain)
{

    assert(!IsHD());

    if (nKeyChain == KEYCHAIN_EXTERNAL) {
        if (!externalKeyStore.AddCryptedKey(vchPubKey, vchCryptedSecret))
            return false;
    } else {
        if (!internalKeyStore.AddCryptedKey(vchPubKey, vchCryptedSecret))
            return false;
    }

    if (pwalletMain) {
        if (!pwalletMain->fFileBacked)
            return true;
        {
            LOCK(pwalletMain->cs_wallet);
            if (pwalletMain->pwalletdbEncryption)
                return pwalletMain->pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret, pwalletMain->mapKeyMetadata[vchPubKey.GetID()], getUUID(), nKeyChain);
            else
                return CWalletDB(pwalletMain->strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret, pwalletMain->mapKeyMetadata[vchPubKey.GetID()], getUUID(), nKeyChain);
        }
    } else {
        return true;
    }
    return false;
}

void CAccount::AddChild(CAccount* childAccount)
{
    childAccount->parentUUID = accountUUID;
}

void CAccount::possiblyUpdateEarliestTime(uint64_t creationTime, CWalletDB* Db)
{
    if (creationTime < earliestPossibleCreationTime)
        earliestPossibleCreationTime = creationTime;

    if (Db) {
        Db->WriteAccount(getUUID(), this);
    }
}

uint64_t CAccount::getEarliestPossibleCreationTime()
{
    return earliestPossibleCreationTime;
}

unsigned int CAccount::GetKeyPoolSize()
{
    AssertLockHeld(cs_keypool); // setKeyPool
    return setKeyPoolExternal.size();
}

std::string CAccount::getLabel() const
{
    return accountLabel;
}

void CAccount::setLabel(const std::string& label, CWalletDB* Db)
{
    accountLabel = label;
    if (Db) {
        Db->EraseAccountLabel(getUUID());
        Db->WriteAccountLabel(getUUID(), label);
    }
}

std::string CAccount::getUUID() const
{
    return boost::uuids::to_string(accountUUID);
}

void CAccount::setUUID(const std::string& stringUUID)
{
    accountUUID = boost::lexical_cast<boost::uuids::uuid>(stringUUID);
}

std::string CAccount::getParentUUID() const
{
    if (parentUUID == boost::uuids::nil_generator()())
        return "";
    return boost::uuids::to_string(parentUUID);
}


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================

#ifndef BOOST_SIMD_FUNCTION_SIMD_FNMA_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SIMD_FNMA_HPP_INCLUDED

#include <boost/simd/function/scalar/fnma.hpp>
#include <boost/simd/arch/common/generic/function/autodispatcher.hpp>
#include <boost/simd/arch/common/simd/function/fnma.hpp>

#if defined(BOOST_HW_SIMD_X86_OR_AMD_AVAILABLE)
#  if BOOST_HW_SIMD_X86_AMD_FMA4
#    include <boost/simd/arch/x86/fma4/simd/function/fnma.hpp>
#  endif
#  if BOOST_HW_SIMD_X86_FMA3
#    include <boost/simd/arch/x86/fma3/simd/function/fnma.hpp>
#  endif
#endif

#endif


#include <cctbx/boost_python/flex_fwd.h>

#include <scitbx/array_family/boost_python/flex_wrapper.h>
#include <scitbx/array_family/boost_python/flex_pickle_double_buffered.h>
#include <scitbx/array_family/boost_python/ref_pickle_double_buffered.h>
#include <boost/python/args.hpp>
#include <boost/python/return_internal_reference.hpp>

#include <spotfinder/core_toolbox/distl.h>

namespace scitbx { namespace af { namespace boost_python {

namespace {

  struct to_string_ir : pickle_double_buffered::to_string
  {
    using pickle_double_buffered::to_string::operator<<;

    to_string_ir()
    {
      int version = 1;
      *this << version;
    }

    to_string_ir& operator<<(Distl::icering const& val)
    {
      *this << val.lowerr2
            << val.upperr2
            << val.lowerresol
            << val.upperresol
            << val.strength;
      return *this;
    }
  };

  struct from_string_ir : pickle_double_buffered::from_string
  {
    from_string_ir(const char* str_ptr)
    : pickle_double_buffered::from_string(str_ptr)
    {
          *this >> version;
    }

    using pickle_double_buffered::from_string::operator>>;

    from_string_ir& operator>>(Distl::icering& val)
    {
      Distl::point lpeak;

      *this >> val.lowerr2
            >> val.upperr2
            >> val.lowerresol
            >> val.upperresol
            >> val.strength;
      return *this;
    }

    int version;
  };

  struct to_string : pickle_double_buffered::to_string
  {
    using pickle_double_buffered::to_string::operator<<;

    to_string()
    {
      int version = 2; //Current version for flex_distl_spot
      //printf("Pickle flex_distl_spot; version: %d\n", version);
      *this << version;
    }

    to_string& operator<<(scitbx::vec2<double> const& vec)
    {
      //special time saver for 2D profiles:  ignore z-direction
      *this << (float)vec[0] << (float)vec[1];
      return *this;
    }

    to_string& operator<<(spotfinder::distltbx::w_spot const& val)
    {
      *this << val.max_pxl_x()
            << val.max_pxl_y()
            << val.peakintensity
            << val.bodypixels.size();

      for (int i=0; i<val.bodypixels.size(); ++i){
      *this << val.bodypixels[i].x
            << val.bodypixels[i].y;
      }

      *this << val.total_mass;
      to_string::operator<<(val.model_m->center_of_mass());
      *this << (float)val.model_m->eigenvalue(0)
            << (float)val.model_m->eigenvalue(1);

      to_string::operator<<(val.model_m->eigenvector(0));
      to_string::operator<<(val.model_m->eigenvector(1));

      return *this;
    }
  };

  struct from_string : pickle_double_buffered::from_string
  {
    from_string(const char* str_ptr)
    : pickle_double_buffered::from_string(str_ptr)
    {
      *this >> version;
      //printf("Unpickle flex_distl_spot; version: %d\n", version);
    }

    from_string& operator>>(scitbx::vec2<double>& vec)
    {
      //special time saver for 2D profiles:  ignore z-direction
      float a,b;
      *this >> a >> b;
      vec[0] = (double)a; vec[1] = (double)b;
      return *this;
    }

    using pickle_double_buffered::from_string::operator>>;

    from_string& operator>>(spotfinder::distltbx::w_spot& val)
    {
      int iptx,ipty,bodylen;
      Distl::point lpeak;

      *this >> iptx
            >> ipty;
      lpeak = Distl::point(iptx,ipty);
      *this >> val.peakintensity
            >> bodylen;

      val.bodypixels = scitbx::af::shared<Distl::point>();
      for (int i=0; i<bodylen; ++i){
      *this >> iptx
            >> ipty;
        val.bodypixels.push_back(Distl::point(iptx,ipty));
      }

      val.setstate(lpeak);
      if (version == 2) {
        double mass;
        *this >> mass;
        scitbx::vec2<double> com,value,axis0,axis1;
        float a,b;
        from_string::operator>>(com);
        *this >> a >> b;
        value[0] = (double)a; value[1] = (double)b;
        from_string::operator>>(axis0);
        from_string::operator>>(axis1);
        val.ss_setstate(com,value,axis0,axis1,mass);
      }
      return *this;
    }

    int version;
  };

}}}} // namespace scitbx::af::boost_python::<anonymous>

namespace scitbx { namespace af { namespace boost_python {

  void wrap_flex_icering()
  {
    flex_wrapper<Distl::icering,
                 boost::python::return_internal_reference<>
                 >::plain("distl_icering")
    .def_pickle(flex_pickle_double_buffered<
                 Distl::icering, to_string_ir, from_string_ir>())
    ;
  }

  af::shared<double>
  ctr_mass_distances_from_direct_beam(
    af::const_ref<spotfinder::distltbx::w_spot> const& spots,
    scitbx::vec2<double> detector_size,
    scitbx::vec2<int> detector_pixels,
    scitbx::vec2<double> xy_beam)
  {
    af::shared<double> result(
      spots.size(),
      af::init_functor_null<double>());
    scitbx::vec2<double> sop;
    for(std::size_t i=0;i<2;i++) {
      sop[i] = detector_size[i] / detector_pixels[i];
    }
    double const* b = xy_beam.begin();
    for(std::size_t i=0;i<spots.size();i++) {
      spotfinder::distltbx::w_spot const& spot = spots[i];
      double dx = spot.ctr_mass_x() * sop[0] - b[0];
      double dy = spot.ctr_mass_y() * sop[1] - b[1];
      result[i] = std::sqrt(dx*dx + dy*dy);
    }
    return result;
  }

  void wrap_flex_w_spot()
  {
    using boost::python::arg;
    flex_wrapper<spotfinder::distltbx::w_spot,
                 boost::python::return_internal_reference<>
                 >::plain("distl_spot")
    .def_pickle(flex_pickle_double_buffered<
                 spotfinder::distltbx::w_spot, to_string, from_string>())
    .def("ctr_mass_distances_from_direct_beam",
      ctr_mass_distances_from_direct_beam, (
        arg("detector_size"),
        arg("detector_pixels"),
        arg("xy_beam")))
    ;
  }

  void wrap_flex_point()
  {
    flex_wrapper<Distl::point>::plain("distl_point");
  }

}}} // namespace scitbx::af::boost_python