#pragma once

#include <crab/analysis/graphs/cdg.hpp>
#include <crab/cfg/cfg.hpp>
#include <crab/cg/cg_bgl.hpp> // for sccg.hpp
#include <crab/domains/discrete_domains.hpp>
#include <crab/iterators/killgen_fixpoint_iterator.hpp>
#include <crab/support/debug.hpp>
#include <crab/support/stats.hpp>
#include <crab/types/indexable.hpp>

#include <boost/range/iterator_range.hpp>
#include <unordered_map>
#include <unordered_set>

/**
 *  Dataflow analysis that for each block b it computes facts <i,V>
 *  meaning that there exists a path emanating from b that will reach
 *  assertion with id=i and its evaluation depends on the set of
 *  variables V.
 **/
namespace crab {
namespace analyzer {

using namespace crab::iterators;
using namespace crab::domains;
using namespace crab::cfg;

namespace assertion_crawler_impl{
// Convenient wrapper for using an assert statement as a key in
// patricia tries.
template <typename CFG>
class assert_wrapper : public indexable {
public:
  using statement_t = statement<typename CFG::basic_block_label_t,
				typename CFG::number_t,
				typename CFG::varname_t>;
private:
  using this_type = assert_wrapper<CFG>;
  // unique identifier for the assert needed for being used as key
  ikos::index_t m_id;
  // the assert statement
  statement_t *m_assert;
public:
  assert_wrapper(ikos::index_t id, statement_t *assert)
    : m_id(id), m_assert(assert) {
    if (!m_assert) {
      CRAB_ERROR("assert_wrapper with null assert statement");
    }
    if (!(m_assert->is_assert() || m_assert->is_ref_assert() || m_assert->is_bool_assert())) {
      CRAB_ERROR("assert_wrapper must be assert, ref_assert, or bool_assert");
    }
  }

  statement_t &get() {
    return *m_assert;
  }
  const statement_t &get() const {
    return *m_assert;
  }
  virtual ikos::index_t index() const override {
    return m_id;
  }
  bool operator==(this_type o) const {
    return m_id == o.m_id;
  }
  bool operator<(this_type o) const {
    return m_id < o.m_id;
  }
  virtual void write(crab::crab_os &o) const override {
    o << "\"" << *m_assert << "\"";
  }
};

template<typename CFG>
class assertion_crawler_domain {
 public:
  using variable_t = typename CFG::variable_t;
  using number_t = typename CFG::number_t;
  using assert_wrapper_t = typename assertion_crawler_impl::assert_wrapper<CFG>;
  using var_dom_t = ikos::discrete_domain<variable_t>;

  using first_domain_t = discrete_pair_domain<assert_wrapper_t, var_dom_t>;
  using second_domain_t = discrete_pair_domain<variable_t, var_dom_t>;
  using this_type = assertion_crawler_domain<CFG>;

 private:
  // the dataflow solution: map assertions to set of variables.
  first_domain_t m_first;
  // for inter-procedural analysis: map output variables to the set of
  // variables whose data may flow to.
  second_domain_t m_second;

 public:

  assertion_crawler_domain(const std::vector<variable_t> &vars)
    : m_first(first_domain_t::bottom()), m_second(second_domain_t::bottom()) {

    for (variable_t v: vars) {
      m_second.set(v, var_dom_t(v));
    }
  }

  assertion_crawler_domain()
    : m_first(first_domain_t::bottom()), m_second(second_domain_t::bottom()) {}

  void set_to_bottom() {
    m_first = first_domain_t::bottom();
    m_second = second_domain_t::bottom();
  }

  void set_to_top() {
    m_first = first_domain_t::top();
    m_second = second_domain_t::top();
  }

  static this_type top() {
    this_type res;
    res.get_first() = first_domain_t::top();
    res.get_second() = second_domain_t::top();
    return res;
  }

  static this_type bottom() {
    this_type res;
    res.get_first() = first_domain_t::bottom();
    res.get_second() = second_domain_t::bottom();
    return res;
  }

  first_domain_t &get_first() {
    return m_first;
  }

  const first_domain_t &get_first() const {
    return m_first;
  }

  second_domain_t &get_second() {
    return m_second;
  }

  const second_domain_t &get_second() const {
    return m_second;
  }

  bool operator<=(const this_type& other) const {
    return (get_first() <= other.get_first() && get_second() <= other.get_second());
  }

  this_type merge(const this_type& other) const {
    this_type res(*this);
    res.get_first() = res.get_first() | other.get_first();
    res.get_second() = res.get_second() | other.get_second();
    return res;
  }

  void write(crab::crab_os &o) const {
    o << "(";
    m_first.write(o);
    o << ",";
    m_second.write(o);
    o << ")";
  }

  friend crab::crab_os &operator<<(crab::crab_os &o, const assertion_crawler_domain<CFG> &dom) {
    dom.write(o);
    return o;
  }
};

// Helper that applies a function to each pair of discrete_pair_domain.
template<typename Key, typename Value>
struct transform_discrete_pair_domain
  : public std::unary_function<discrete_pair_domain<Key,Value>,
			       discrete_pair_domain<Key,Value>> {
  using discrete_pair_domain_t = discrete_pair_domain<Key, Value>;

  template<typename Op>
  discrete_pair_domain_t apply_on_value(discrete_pair_domain_t sol, Op &op) {
    // All of this is needed because discrete_pair_domain_t cannot
    // be modified in-place
    if (sol.is_bottom() || sol.is_top()) {
      return sol;
    }
    for (auto kv : sol) {
      std::pair<Value,bool> p = op(kv.second);
      if (p.second) {
	sol.set(kv.first, p.first);
      }
    }
    return sol;
  }

  template<typename Op>
  discrete_pair_domain_t apply_on_key_and_value(discrete_pair_domain_t sol, Op &op) {
    // All of this is needed because discrete_pair_domain_t cannot
    // be modified in-place
    if (sol.is_bottom() || sol.is_top()) {
      return sol;
    }
    for (auto kv : sol) {
      std::pair<Value,bool> p = op(kv.first, kv.second);
      if (p.second) {
	sol.set(kv.first, p.first);
      }
    }
    return sol;
  }
};

// Wrapper to store a function summary that is represented by
// assertion_crawler_domain.
template<class CFG>
class summary {
public:
  using variable_t = typename CFG::variable_t;
  using abs_dom_t = assertion_crawler_domain<CFG>;

private:
  std::vector<variable_t> m_inputs;
  std::vector<variable_t> m_outputs;
  abs_dom_t m_sum;
public:
  summary(const std::vector<variable_t> &inputs,
	  const std::vector<variable_t> &outputs,
	  abs_dom_t sum)
    : m_sum(sum) {
    std::copy(inputs.begin(), inputs.end(), std::back_inserter(m_inputs));
    std::copy(outputs.begin(), outputs.end(), std::back_inserter(m_outputs));
  }
  const std::vector<variable_t> &get_inputs() const {
    return m_inputs;
  }
  const std::vector<variable_t> &get_outputs() const {
    return m_outputs;
  }
  const abs_dom_t &get_summary() const {
    return m_sum;
  }
  abs_dom_t &get_summary() {
    return m_sum;
  }

  void write(crab_os &o) const {
    o << "Inputs:{";
    for(unsigned i=0,sz=m_inputs.size();i<sz;) {
      o << m_inputs[i];
      ++i;
      if (i<sz) {
	o << ",";
      }
    }
    o << "} Outputs:{";
    for(unsigned i=0,sz=m_outputs.size();i<sz;) {
      o << m_outputs[i];
      ++i;
      if (i<sz) {
	o << ",";
      }
    }
    o << "} " << m_sum;
  }
};
} // end namespace assertion_crawler_impl

template<class CFG>
class assertion_crawler;

// Define the operations of the dataflow analysis
template <class CFG>
class assertion_crawler_operations
  : public killgen_operations_api<CFG, assertion_crawler_impl::assertion_crawler_domain<CFG>> {

  friend class assertion_crawler<CFG>;

private:
  using basic_block_label_t = typename CFG::basic_block_label_t;
  using basic_block_t = typename CFG::basic_block_t;
  using statement_t = typename basic_block_t::statement_t;
  using variable_t = typename CFG::variable_t;
  using varname_t = typename CFG::varname_t;
  using number_t = typename CFG::number_t;
  using fdecl_t = typename CFG::fdecl_t;
  using live_t = typename statement_t::live_t;
public:
  using assertion_crawler_domain_t = assertion_crawler_impl::assertion_crawler_domain<CFG>;
  using var_dom_t = typename assertion_crawler_domain_t::var_dom_t;
  using assert_wrapper_t = assertion_crawler_impl::assert_wrapper<CFG>;
  using assert_map_t = typename std::unordered_map<statement_t *, assert_wrapper_t>;
  // control-dependency graph: map a CFG block to the set of blocks
  // which control-dependent on it.
  using cdg_t = std::unordered_map<basic_block_label_t, std::vector<basic_block_label_t>>;
  // inter-procedural analysis
  using summary_map_t = callsite_or_fdecl_map<CFG, assertion_crawler_impl::summary<CFG>>;
private:
  using killgen_operations_api_t = killgen_operations_api<CFG, assertion_crawler_domain_t>;
  using assert_map_domain_t =  typename assertion_crawler_domain_t::first_domain_t;
  using summary_dependencies_domain_t =  typename assertion_crawler_domain_t::second_domain_t;
public:
  ////============================================================////
  //            Propagate data/control dependencies
  ////============================================================////
  class transfer_function : public statement_visitor<basic_block_label_t, number_t, varname_t> {
    using visitor_t = statement_visitor<basic_block_label_t, number_t, varname_t>;
    using bin_op_t = typename visitor_t::bin_op_t;
    using assign_t = typename visitor_t::assign_t;
    using assume_t = typename visitor_t::assume_t;
    using select_t = typename visitor_t::select_t;
    using assert_t = typename visitor_t::assert_t;
    using int_cast_t = typename visitor_t::int_cast_t;
    using havoc_t = typename visitor_t::havoc_t;
    using unreach_t = typename visitor_t::unreach_t;
    using callsite_t = typename visitor_t::callsite_t;
    using intrinsic_t = typename visitor_t::intrinsic_t;
    using make_ref_t = typename visitor_t::make_ref_t;
    using remove_ref_t = typename visitor_t::remove_ref_t;
    using region_init_t = typename visitor_t::region_init_t;
    using region_copy_t = typename visitor_t::region_copy_t;
    using region_cast_t = typename visitor_t::region_cast_t;
    using load_from_ref_t = typename visitor_t::load_from_ref_t;
    using store_to_ref_t = typename visitor_t::store_to_ref_t;
    using gep_ref_t = typename visitor_t::gep_ref_t;
    using assume_ref_t = typename visitor_t::assume_ref_t;
    using assert_ref_t = typename visitor_t::assert_ref_t;
    using select_ref_t = typename visitor_t::select_ref_t;
    using bool_bin_op_t = typename visitor_t::bool_bin_op_t;
    using bool_assign_cst_t = typename visitor_t::bool_assign_cst_t;
    using bool_assign_var_t = typename visitor_t::bool_assign_var_t;
    using bool_assume_t = typename visitor_t::bool_assume_t;
    using bool_select_t = typename visitor_t::bool_select_t;
    using bool_assert_t = typename visitor_t::bool_assert_t;
    using variable_t = typename CFG::variable_t;

    /** Add data-dependencies **/
    class add_data_deps:
      public std::unary_function<var_dom_t,  std::pair<var_dom_t, bool>> {

      var_dom_t m_uses;
      var_dom_t m_defs;
    public:
      add_data_deps(const live_t &l): m_uses(var_dom_t::bottom()), m_defs(var_dom_t::bottom()) {
        for (auto v : boost::make_iterator_range(l.uses_begin(), l.uses_end())) {
          m_uses += v;
	}
        for (auto v : boost::make_iterator_range(l.defs_begin(), l.defs_end())) {
          m_defs += v;
	}
      }

      add_data_deps(const var_dom_t &uses, const var_dom_t &defs) {
	for (auto v: uses) { m_uses += v;}
	for (auto v: defs) { m_defs += v;}
      }

      add_data_deps(const add_data_deps &o) = delete;

      std::pair<var_dom_t, bool> operator()(var_dom_t d) {
        bool change = false;
        if (m_defs.is_bottom() && !m_uses.is_bottom() && !(m_uses & d).is_bottom()) {
          d += m_uses;
          change = true;
        }
        if (!(d & m_defs).is_bottom()) {
          d -= m_defs;
          d += m_uses;
          change = true;
        }
        return std::make_pair(d, change);
      }
    };

    /** Add control-dependencies **/
    class add_control_deps :
      public std::binary_function<assert_wrapper_t, var_dom_t, std::pair<var_dom_t, bool>> {

      const cdg_t &cdg;
      const std::vector<basic_block_label_t> &roots;
      var_dom_t uses;

      // return true if we find a path in cdg from root to target
      // FIXME: do caching for the queries
      bool reach(const basic_block_label_t &root,
                 const basic_block_label_t &target,
                 std::unordered_set<basic_block_label_t> &visited) {
        if (root == target)
          return true;

        // break cycles
        if (visited.find(root) != visited.end())
          return false;

        visited.insert(root);
        auto it = cdg.find(root);
        if (it == cdg.end())
          return false;

        for (auto child : it->second) {
          if (reach(child, target, visited))
            return true;
        }
        return false;
      }

      bool reach(const basic_block_label_t &target) {
        std::unordered_set<basic_block_label_t> visited;
        for (auto r : roots)
          if (reach(r, target, visited))
            return true;
        return false;
      }

    public:
      add_control_deps(const cdg_t &_cdg,
                       const std::vector<basic_block_label_t> &_roots,
                       const live_t &l)
          : cdg(_cdg), roots(_roots), uses(var_dom_t::bottom()) {
        for (auto v : boost::make_iterator_range(l.uses_begin(), l.uses_end())) {
          uses += v;
	}
      }

      add_control_deps(const add_data_deps &o) = delete;

      std::pair<var_dom_t, bool> operator()(assert_wrapper_t w, var_dom_t d) {
        bool change = false;
        if (reach(w.get().get_parent()->label())) {
          d += uses;
          change = true;
        }
        return std::make_pair(d, change);
      }
    };

    /** Remove data-dependencies **/
    class remove_deps
      : public std::unary_function<var_dom_t, std::pair<var_dom_t, bool>> {

      var_dom_t vars;
    public:
      remove_deps(const variable_t &v) : vars(var_dom_t::bottom()) {
        vars += v;
      }

      remove_deps(const std::vector<variable_t> &vs)
          : vars(var_dom_t::bottom()) {
        for (auto v : vs) {
          vars += v;
        }
      }

      remove_deps(const remove_deps &o) = delete;

      std::pair<var_dom_t, bool> operator()(var_dom_t d) {
        bool change = false;
        if (!(d & vars).is_bottom()) {
          d -= vars;
          change = true;
        }
        return std::make_pair(d, change);
      }
    };

    using transform_sol_t =
	 assertion_crawler_impl::transform_discrete_pair_domain<assert_wrapper_t, var_dom_t>;
    using transform_sum_deps_t =
	 assertion_crawler_impl::transform_discrete_pair_domain<variable_t, var_dom_t>;

    assertion_crawler_domain_t m_sol;
    // map each assertion to a unique identifier
    assert_map_t &m_assert_map;
    // control-dependence graph (it can be empty)
    const cdg_t &m_cdg;
    // summaries from other functions
    summary_map_t &m_summaries;
    bool m_opt_ignore_region_offset;

    inline void process_assertion(statement_t &s) {
      assert(s.is_assert() || s.is_ref_assert() || s.is_bool_assert());

      auto it = m_assert_map.find(&s);
      if (it != m_assert_map.end())
        return;

      var_dom_t vdom = var_dom_t::bottom();
      auto const &l = s.get_live();
      for (auto v : boost::make_iterator_range(l.uses_begin(), l.uses_end())) {
        vdom += v;
      }

      unsigned id = m_assert_map.size();
      assert_wrapper_t val(id, &s);
      m_assert_map.insert(typename assert_map_t::value_type(&s, val));
      m_sol.get_first().set(val, vdom);
      CRAB_LOG("assertion-crawler-step", crab::outs()
                                             << "*** " << s << "\n"
                                             << "\tAdded " << vdom << "\n";);
    }

    inline void propagate_data(statement_t &s) {
      CRAB_LOG("assertion-crawler-step",
	       crab::outs() << "*** " << s << "\n" << "\tBEFORE: " << m_sol << "\n");

      if (m_opt_ignore_region_offset) {
	std::unique_ptr<add_data_deps> op = nullptr;

	if (s.is_ref_make()) {
	  auto make_ref = static_cast<make_ref_t*>(&s);
	  var_dom_t defs = var_dom_t::bottom();
	  var_dom_t uses = var_dom_t::bottom();
	  defs += make_ref->lhs();
	  uses += make_ref->region();
	  op.reset(new add_data_deps(uses, defs));
	} else if (s.is_ref_remove()) {
	  auto remove_ref = static_cast<remove_ref_t*>(&s);
	  var_dom_t defs = var_dom_t::bottom();
	  var_dom_t uses = var_dom_t::bottom();
	  uses += remove_ref->region();
	  op.reset(new add_data_deps(uses, defs));
	} else if (s.is_ref_load()) {
	  auto load_ref = static_cast<load_from_ref_t*>(&s);
	  var_dom_t defs = var_dom_t::bottom();
	  var_dom_t uses = var_dom_t::bottom();
	  defs += load_ref->lhs();
	  uses += load_ref->region();
	  op.reset(new add_data_deps(uses, defs));
	} else if (s.is_ref_store()) {
	  auto store_ref = static_cast<store_to_ref_t*>(&s);
	  var_dom_t defs = var_dom_t::bottom();
	  var_dom_t uses = var_dom_t::bottom();
	  defs += store_ref->region();
	  uses += store_ref->region();
	  if (store_ref->val().is_variable()) {
	    uses += store_ref->val().get_variable();
	  }
	  op.reset(new add_data_deps(uses, defs));
	} else if (s.is_ref_gep()) {
	  auto gep_ref = static_cast<gep_ref_t*>(&s);
	  if (gep_ref->lhs() != gep_ref->rhs()) {
	    var_dom_t defs = var_dom_t::bottom();
	    var_dom_t uses = var_dom_t::bottom();
	    defs += gep_ref->lhs();
	    uses += gep_ref->rhs();
	    op.reset(new add_data_deps(uses, defs));
	  }
	} else {
	  op.reset(new add_data_deps(s.get_live()));
	}
	if (op) {
	  transform_sol_t f1;
	  m_sol.get_first() = std::move(f1.apply_on_value(m_sol.get_first(), *op));
	  transform_sum_deps_t f2;
	  m_sol.get_second() = std::move(f2.apply_on_value(m_sol.get_second(), *op));
	}
      }  else {
	add_data_deps op(s.get_live());
	transform_sol_t f1;
	m_sol.get_first() = std::move(f1.apply_on_value(m_sol.get_first(), op));
	transform_sum_deps_t f2;
	m_sol.get_second() = std::move(f2.apply_on_value(m_sol.get_second(), op));
      }

      CRAB_LOG("assertion-crawler-step", crab::outs() << "\tAFTER " << m_sol << "\n";);
    }

    inline void propagate_data_and_control(statement_t &s) {
      CRAB_LOG("assertion-crawler-step", crab::outs()
	       << "*** " << s << "\n"
	       << "\tBEFORE: " << m_sol << "\n");
      // -- add data dependencies
      add_data_deps op(s.get_live());
      transform_sol_t df1;
      m_sol.get_first() = std::move(df1.apply_on_value(m_sol.get_first(), op));
      transform_sum_deps_t df2;
      m_sol.get_second() = std::move(df2.apply_on_value(m_sol.get_second(), op));
      CRAB_LOG("assertion-crawler-step",
               crab::outs() << "\tAFTER data-dep " << m_sol << "\n";);

      if (!m_cdg.empty()) {
	// -- add control dependencies
	for (auto const &pred : boost::make_iterator_range(s.get_parent()->prev_blocks())) {
	  // it->second is the set of basic blocks that control
	  // dependent on s' block
	  auto it = m_cdg.find(pred);
	  if (it != m_cdg.end()) {
	    auto const &children = it->second;
	    CRAB_LOG("assertion-crawler-step-control", crab::outs() << "{";
		     for (auto &c : children) {
		       crab::outs() << c << ";";
		     }
		     crab::outs() << "} control-dependent on " << pred << "\n";);
	    add_control_deps op(m_cdg, children, s.get_live());
	    transform_sol_t cf;
	    m_sol.get_first() = std::move(cf.apply_on_key_and_value(m_sol.get_first(), op));
	    CRAB_LOG("assertion-crawler-step-control",
		     crab::outs() << "\tAFTER control-dep " << m_sol << "\n";);
	  }
	}
      }
    }

  public:
    transfer_function(assertion_crawler_domain_t init,
		      const cdg_t &g,
		      assert_map_t &assert_map,
		      summary_map_t &summaries,
		      bool ignore_region_offset)
      : m_sol(init), m_assert_map(assert_map), m_cdg(g), m_summaries(summaries),
	m_opt_ignore_region_offset(ignore_region_offset) {}

    assertion_crawler_domain_t get_solution() const {
      return m_sol;
    }

    void visit(bin_op_t &s) {
      propagate_data(s);
    }

    void visit(assign_t &s) {
      propagate_data(s);
    }

    void visit(assume_t &s) {
      propagate_data_and_control(s);
    }

    void visit(select_t &s) {
      propagate_data(s);
    }

    void visit(assert_t &s) {
      process_assertion(s);
    }

    void visit(int_cast_t &s) {
      propagate_data(s);
    }

    void visit(unreach_t &) {
      m_sol.set_to_bottom();
    }

    void visit(havoc_t &s) {
      CRAB_LOG("assertion-crawler-step", crab::outs()
                                             << "*** " << s << "\n"
                                             << "\tBEFORE: " << m_sol << "\n");
      remove_deps op(s.get_variable());
      transform_sol_t f1;
      m_sol.get_first() = std::move(f1.apply_on_value(m_sol.get_first(), op));
      transform_sum_deps_t f2;
      m_sol.get_second() = std::move(f2.apply_on_value(m_sol.get_second(), op));
      CRAB_LOG("assertion-crawler-step", crab::outs()
                                             << "\tAFTER " << m_sol << "\n";);
    }

    // For each key-value pair rename from variables with to variables
    // *only* on the value. This is an expensive operation because it
    // creates a new discrete_pair_domain from scratch.
    template<typename Key>
    static void rename(discrete_pair_domain<Key, var_dom_t> &dpd,
		       const std::vector<variable_t> &from, const std::vector<variable_t> &to) {
      using discrete_pair_domain_t = discrete_pair_domain<Key, var_dom_t>;
      if (from.empty() || dpd.is_top() || dpd.is_bottom()) {
	return;
      }
      if (from.size() != to.size()) {
	CRAB_ERROR("discrete_pair_domain::rename with input vectors of different sizes");
      }
      discrete_pair_domain_t res = discrete_pair_domain_t::bottom();
      for (auto kv: dpd) {
	auto key = kv.first;
	auto dom = kv.second;
	dom.rename(from, to);
	res.set(key, dom);
      }
      std::swap(dpd, res);
    }

    // dpd is of the form [key1 -> {o1,x1,...}, key2 -> {o2,x2,...}, ...]
    //
    // sdd is a map that relates outputs with inputs.
    //     It is of the form [o1 -> {i1,i2}, o2  -> {i3,i4}]
    // This function replaces the occurrences of each sdd's key in dpd with its value.
    // That is, [key1 -> {i1,i2,x1,...}, key2 -> {i3,i4,x2,...}, ...]
    template<typename Key>
    static void apply_summary(discrete_pair_domain<Key, var_dom_t> &dpd,
			      summary_dependencies_domain_t &sdd) {
      auto outputs_to_inputs = [&sdd](const var_dom_t &var_dom) {
				 if (var_dom.is_top()) {
				   return var_dom_t::top();
				 } else if (var_dom.is_bottom()) {
				   return var_dom_t::bottom();
				 } else {
				   var_dom_t res = var_dom_t::bottom();
				   for (auto it=var_dom.begin(), et=var_dom.end(); it!=et; ++it) {
				     variable_t v = *it;
				     var_dom_t inputs(sdd[v]);
				     if (inputs.is_bottom()) {
				       // the callee doesn't know
				       // about v so we just propagate
				       // it as it's.
				       var_dom_t vs(v);
				       res += vs; // not in sdd
				     } else {
				       res += inputs;
				     }
				   }
				   return res;
				 }};

      using discrete_pair_domain_t = discrete_pair_domain<Key, var_dom_t>;
      discrete_pair_domain_t out = discrete_pair_domain_t::bottom();
      for (auto kv: dpd) {
	var_dom_t res = outputs_to_inputs(kv.second);
	out.set(kv.first, res);
      }
      std::swap(dpd, out);
    }

    void visit(callsite_t &s) {

      CRAB_LOG("assertion-crawler-step-cs", crab::outs()
	       << "*** " << s << "\n"
	       << "\tBEFORE: " << m_sol << "\n");

      auto it = m_summaries.find(&s);
      if (it != m_summaries.end()) {
	auto callee_summary_info = it->second;
	const std::vector<variable_t> &callsite_inputs = s.get_args();
	const std::vector<variable_t> &callsite_outputs = s.get_lhs();
	const std::vector<variable_t> &callee_inputs = callee_summary_info.get_inputs();
	const std::vector<variable_t> &callee_outputs = callee_summary_info.get_outputs();
	auto &callee_summary = callee_summary_info.get_summary();

	CRAB_LOG("assertion-crawler-step-cs",
		 crab::outs() << "\tSummary at the callee: "
		 << callee_summary.get_second() << "\n";
		 crab::outs() << "\tCallee input variables: {";
		 for (auto const& v: callee_inputs) {
		   crab::outs() << v << ";";
		 }
		 crab::outs() << "}\n\tCallee output variables: {";
		 for (auto const& v: callee_outputs) {
		   crab::outs() << v << ";";
		 }
		 crab::outs() << "}\n";);

	// -- Update assertion map domain at the caller
	assert_map_domain_t amd(m_sol.get_first());
	rename(amd, callsite_outputs, callee_outputs);
	apply_summary(amd, callee_summary.get_second());
	rename(amd, callee_inputs, callsite_inputs);
	// Propagate the assertion map domain from the callee to the caller
	assert_map_domain_t callee_amd(callee_summary.get_first());
	rename(callee_amd, callee_inputs, callsite_inputs);
	m_sol.get_first() = amd | callee_amd;

	// -- Update the summary dependencies at the caller
	summary_dependencies_domain_t sdm(m_sol.get_second());
	rename(sdm, callsite_outputs, callee_outputs);
	apply_summary(sdm, callee_summary.get_second());
	rename(sdm, callee_inputs, callsite_inputs);
	m_sol.get_second() = sdm;
      } else {
	CRAB_LOG("assertion-crawler-step-cs",
		 CRAB_WARN("assertion-crawler did not find summary for callee at ", s));

	// crab::outs() << "Summary table:\n";
	// for (auto &kv: m_summaries) {
	//   crab::outs() << "\t";
	//   crab::outs() << &(kv.first) << " ";
	//   kv.first.write(crab::outs());
	//   crab::outs() << " -> ";
	//   crab::outs() << &(kv.second) << " ";
	//   kv.second.write(crab::outs());
	//   crab::outs() << "\n";
	// }
	propagate_data(s);
      }

      CRAB_LOG("assertion-crawler-step-cs", crab::outs()
	       << "\tAFTER " << m_sol << "\n";);
    }

    void visit(intrinsic_t &s) {
      propagate_data(s);
    }

    void visit(make_ref_t &s) {
      propagate_data(s);
    }

    void visit(remove_ref_t &s) {
      propagate_data(s);
    }

    void visit(region_init_t &s) {
      propagate_data(s);
    }

    void visit(region_copy_t &s) {
      propagate_data(s);
    }

    void visit(region_cast_t &s) {
      propagate_data(s);
    }

    void visit(load_from_ref_t &s) {
      propagate_data(s);
    }

    void visit(store_to_ref_t &s) {
      propagate_data(s);
    }

    void visit(gep_ref_t &s) {
      propagate_data(s);
    }

    void visit(assume_ref_t &s) {
      propagate_data_and_control(s);
    }

    void visit(assert_ref_t &s) {
      process_assertion(s);
    }

    void visit(select_ref_t &s) {
      propagate_data(s);
    }

    void visit(bool_bin_op_t &s) {
      propagate_data(s);
    }

    void visit(bool_assign_var_t &s) {
      propagate_data(s);
    }

    void visit(bool_assign_cst_t &s) {
      propagate_data(s);
    }

    void visit(bool_select_t &s) {
      propagate_data(s);
    }

    void visit(bool_assume_t &s) {
      propagate_data_and_control(s);
    }

    void visit(bool_assert_t &s) {
      process_assertion(s);
    }
  };

private:
  assert_map_t &m_assert_map;
  summary_map_t &m_summaries;
  cdg_t m_cdg; // control dependencies

  // only data-dependencies (no control)
  bool m_opt_only_data;
  // ignore dependencies from references used to access regionsa
  bool m_opt_ignore_region_offset;

public:
  assertion_crawler_operations
  (CFG cfg, assert_map_t &assert_map, summary_map_t &summaries,
   bool only_data, bool ignore_region_offset)
    : killgen_operations_api_t(cfg),
      m_assert_map(assert_map),
      m_summaries(summaries),
      m_opt_only_data(only_data),
      m_opt_ignore_region_offset(ignore_region_offset) {}

  /* === Begin killgen_operations_api === */
  virtual bool is_forward() override {
    // This is a backward analysis
    return false;
  }

  virtual std::string name() override {
    return "assertion-crawler";
  }

  virtual void init_fixpoint() override {
    if (!m_opt_only_data) {
      crab::ScopedCrabStats __st__("Control-Dependency Graph");
      crab::analyzer::graph_algo::control_dep_graph(this->m_cfg, m_cdg);
    }
  }

  virtual assertion_crawler_domain_t entry() override {
    if (this->m_cfg.has_func_decl()) {
      return assertion_crawler_domain_t(this->m_cfg.get_func_decl().get_outputs());
    } else {
      return assertion_crawler_domain_t();
    }
  }

  virtual assertion_crawler_domain_t
  merge(assertion_crawler_domain_t d1, assertion_crawler_domain_t d2) override {
    return d1.merge(d2);
  }

  virtual assertion_crawler_domain_t analyze(const basic_block_label_t &bb_id,
					     assertion_crawler_domain_t out) override {
    auto &bb = this->m_cfg.get_node(bb_id);
    transfer_function vis(out, m_cdg, m_assert_map, m_summaries,
			  m_opt_ignore_region_offset);
    for (auto &s : boost::make_iterator_range(bb.rbegin(), bb.rend())) {
      s.accept(&vis);
    }
    return vis.get_solution();
  }
  /* ===  End killgen_operations_api === */

  const cdg_t &get_cdg() const {
    return m_cdg;
  }
  assert_map_t &get_assert_map() {
    return m_assert_map;
  }
  const assert_map_t &get_assert_map() const {
    return m_assert_map;
  }
  summary_map_t &get_summary_map() {
    return m_summaries;
  }
  const summary_map_t &get_summary_map() const {
    return m_summaries;
  }
};

template<typename CallGraph>
class inter_assertion_crawler;

/**
 * Intra-procedural assertion crawler dataflow analysis
 *
 * Compute for each basic block b a set of facts (i,V) such that
 * there exists a path from b that will check assertion i and its
 * evaluation depends on the set of variables V.
 **/
template <class CFG>
class assertion_crawler : public crab::iterators::killgen_fixpoint_iterator<
                              CFG, assertion_crawler_operations<CFG>> {

  template<typename T> friend class inter_assertion_crawler;
public:
  using basic_block_label_t = typename CFG::basic_block_label_t;
  using varname_t = typename CFG::varname_t;

private:
  using assertion_crawler_op_t = assertion_crawler_operations<CFG>;
  using fixpo_t = crab::iterators::killgen_fixpoint_iterator<CFG, assertion_crawler_op_t>;
  using basic_block_t = typename CFG::basic_block_t;

public:
  using assertion_crawler_domain_t = typename assertion_crawler_op_t::assertion_crawler_domain_t;
  using assert_map_domain_t = typename assertion_crawler_domain_t::first_domain_t;
  using assert_map_t = typename assertion_crawler_op_t::assert_map_t;
  using summary_map_t = typename assertion_crawler_op_t::summary_map_t;
  using results_map_t = std::unordered_map<basic_block_label_t, assertion_crawler_domain_t>;
private:

  results_map_t m_results;
  assertion_crawler_op_t m_assert_crawler_op;

  // used by inter_assertion_crawler
  assertion_crawler_domain_t &lookup_results(const basic_block_label_t &bb) {
    auto it = m_results.find(bb);
    if (it == m_results.end()) {
      auto res = m_results.insert({bb, assertion_crawler_domain_t::top()});
      return res.first->second;
    } else {
      return it->second;
    }
  }

public:
  assertion_crawler(CFG cfg, assert_map_t &assert_map, summary_map_t &summaries,
		    bool only_data = false,
		    bool ignore_region_offset = false)
    : fixpo_t(cfg, m_assert_crawler_op),
      m_assert_crawler_op(cfg, assert_map, summaries, only_data, ignore_region_offset) {
  }

  assertion_crawler(const assertion_crawler<CFG> &o) = delete;
  assertion_crawler<CFG> &operator=(const assertion_crawler<CFG> &o) = delete;

  void exec(void) {
    this->run();
    for (auto p : boost::make_iterator_range(this->in_begin(), this->in_end())) {
      m_results.insert(std::make_pair(p.first, p.second));
    }
    this->release_memory();
  }

  // return the dataflow facts that hold at the entry of the block bb
  assert_map_domain_t get_results(const basic_block_label_t &bb) const {
    auto it = m_results.find(bb);
    if (it == m_results.end()) {
      return assert_map_domain_t::top();
    } else {
      return it->second.get_first();
    }
  }

  // return the dataflow facts of the pre-state at each program point in bb
  void get_results(
      const basic_block_label_t &b,
      std::map<typename CFG::statement_t *, assert_map_domain_t> &res) const {
    auto it = m_results.find(b);
    if (it != m_results.end()) {
      if (!it->second.get_first().is_bottom()) {
        auto &bb = this->m_cfg.get_node(b);
        typename assertion_crawler_op_t::transfer_function vis
	  (it->second /* OUT dataflow facts */,
	   m_assert_crawler_op.m_cdg,
	   m_assert_crawler_op.m_assert_map,
	   m_assert_crawler_op.m_summaries,
	   m_assert_crawler_op.m_opt_ignore_region_offset);
        for (auto &s : boost::make_iterator_range(bb.rbegin(), bb.rend())) {
          s.accept(&vis);
          auto in = vis.get_solution().get_first();
          res.insert(std::make_pair(&s, in));
        }
      }
    }
  }

  void write(crab_os &o) const {
    o << "Assertion Crawler Analysis ";
    if (this->m_cfg.has_func_decl()) {
      auto const &fdecl = this->m_cfg.get_func_decl();
      o << "for " << fdecl.get_func_name() <<"\n";
    } else {
      o << "\n";
    }

    // Print invariants in DFS to enforce a fixed order
    std::set<basic_block_label_t> visited;
    std::vector<basic_block_label_t> worklist;
    worklist.push_back(this->m_cfg.entry());
    visited.insert(this->m_cfg.entry());
    while (!worklist.empty()) {
      auto cur_label = worklist.back();
      worklist.pop_back();
      auto it = m_results.find(cur_label);
      assert(it != m_results.end());
      auto inv = it->second.get_first();
      crab::outs() << basic_block_traits<basic_block_t>::to_string(cur_label)
                   << "=" << inv << "\n";
      auto const &cur_node = this->m_cfg.get_node(cur_label);
      for (auto const &kid_label :
           boost::make_iterator_range(cur_node.next_blocks())) {
        if (visited.insert(kid_label).second) {
          worklist.push_back(kid_label);
        }
      }
    }
  }
};

template <typename CFG>
inline crab_os &operator<<(crab_os &o, const assertion_crawler<CFG> &ac) {
  ac.write(o);
  return o;
}

/** Context-insensitive interprocedural analysis **/
template <typename CallGraph>
class inter_assertion_crawler {
  using cg_node_t = typename CallGraph::node_t;
  using cg_edge_t = typename CallGraph::edge_t;
  using this_type = inter_assertion_crawler<CallGraph>;

public:
  using cg_t = CallGraph;
  using cg_ref_t = crab::cg::call_graph_ref<cg_t>;
  using cfg_t = typename cg_node_t::cfg_t;
  using basic_block_label_t = typename cfg_t::basic_block_label_t;
  using varname_t = typename cfg_t::varname_t;
  using number_t = typename cfg_t::number_t;
  using variable_t = typename cfg_t::variable_t;
  using assertion_crawler_t = assertion_crawler<cfg_t>;
  using assertion_crawler_domain_t = typename assertion_crawler_t::assertion_crawler_domain_t;
  using assert_map_domain_t = typename assertion_crawler_t::assert_map_domain_t;
private:
  using summary_map_t = typename assertion_crawler_t::summary_map_t;
  using summary_t = typename summary_map_t::mapped_type;
  using assert_map_t = typename assertion_crawler_t::assert_map_t;
  using inter_results_map_t = std::unordered_map<cfg_t, std::unique_ptr<assertion_crawler_t>>;

  cg_t &m_cg; // the call graph
  assert_map_t m_assert_map; // to assign id's to assert statements
  summary_map_t m_summaries; // summaries
  inter_results_map_t m_inter_results; // the results of the analysis
  bool m_opt_only_data; // whether consider only data dependencies
  // ignore dependencies from references used to access regions
  bool m_opt_ignore_region_offset;
public:
  inter_assertion_crawler(cg_t &cg, bool only_data = false, bool ignore_region_offset = false)
    : m_cg(cg), m_opt_only_data(only_data), m_opt_ignore_region_offset(ignore_region_offset) {
  }

  inter_assertion_crawler(const this_type &other) = delete;
  this_type &operator=(const this_type &other) = delete;

  void run(bool do_type_checking = true) {
    if (do_type_checking) {
      CRAB_VERBOSE_IF(1, get_msg_stream() << "Type checking call graph ... ";);
      crab::CrabStats::resume("CallGraph type checking");
      m_cg.type_check();
      crab::CrabStats::stop("CallGraph type checking");
      CRAB_VERBOSE_IF(1, get_msg_stream() << "OK\n";);
    }

    CRAB_VERBOSE_IF(1, get_msg_stream()
                           << "Started inter-procedural assertion crawler analysis\n";);
    CRAB_LOG("assertion-crawler", m_cg.write(crab::outs()); crab::outs() << "\n");
    crab::ScopedCrabStats __st__("InterAssertionCrawler");

    auto analyze = [this](cfg_t cfg) {
		     assert(cfg.has_func_decl());
		     auto const &fdecl = cfg.get_func_decl();
		     const std::string &fun_name = fdecl.get_func_name();
		     CRAB_VERBOSE_IF(1, get_msg_stream() << "++ Analyzing " << fun_name << "...\n";);
		     // --- Run the intra-procedural analysis to compute summaries.
		     std::unique_ptr<assertion_crawler_t> intra_analysis
		       (new assertion_crawler_t(cfg, m_assert_map, m_summaries,
						m_opt_only_data, m_opt_ignore_region_offset));
		     intra_analysis->exec();
		     // --- Store the cfg's summary
		     assertion_crawler_domain_t &results = intra_analysis->lookup_results(cfg.entry());
		     summary_t summary(fdecl.get_inputs(), fdecl.get_outputs(), results);
		     auto it = m_summaries.find(&fdecl);
		     if (it == m_summaries.end()) {
		       m_summaries.insert(std::make_pair(&fdecl, std::move(summary)));
		     } else {
		       it->second = std::move(summary);
		     }

		     return std::move(intra_analysis);
		   };

    auto hasStabilized = [](assertion_crawler_t &old, assertion_crawler_t &current, cfg_t cfg) {
			     for (auto it = cfg.label_begin(), et=cfg.label_end(); it!=et; ++it) {
			       const basic_block_label_t &bb = *it;
			       const assertion_crawler_domain_t &old_d = old.lookup_results(bb);
			       const assertion_crawler_domain_t &current_d = current.lookup_results(bb);
			       if (!(current_d <= old_d)) {
				 return false;
			       }
			     }
			     return true;
			   };

    // If the fixpoint converges very slowly we give up after
    // max_fixpo_iters iterations
    const unsigned max_fixpo_iters = 10;

    std::vector<cg_node_t> rev_order;
    graph_algo::scc_graph<cg_ref_t> Scc_g(m_cg);
    graph_algo::rev_topo_sort(Scc_g, rev_order);
    for (auto const &n : rev_order) {
      std::vector<cg_node_t> &scc_mems = Scc_g.get_component_members(n);

      // The SCC is recursive if it has more than one element or
      // there is only one that calls directly to itself.
      bool isRecursive =
	(scc_mems.size() > 1) ||
	std::any_of(m_cg.succs(n).first, m_cg.succs(n).second,
		    [n](const cg_edge_t &e) { return (n == e.dest()); });

      if (isRecursive) {
	// standard fixpoint among all the SCC members
	unsigned iter = 0;
	bool change = true;
	while (change || iter > max_fixpo_iters) {
	  change = false;
	  for (unsigned i=0, num_sccs=scc_mems.size();i<num_sccs;++i) {
	    cfg_t cfg = scc_mems[i].get_cfg();
	    std::unique_ptr<assertion_crawler_t> current = analyze(cfg);
	    if (iter == 0) {
	      m_inter_results.insert(std::make_pair(cfg, std::move(current)));
	      change = true;
	    } else {
	      auto it = m_inter_results.find(cfg);
	      assert(it != m_inter_results.end());
	      assertion_crawler_t &last = *(it->second);
	      change |= !hasStabilized(last, *current, cfg);
	      it->second = std::move(current);
	    }
	  }
	  ++iter;
	}
	if (iter > max_fixpo_iters) {
	  CRAB_WARN("Inter-procedural Assertion Crawler analysis was abruptly stopped on a ",
		    "recursive SCC after ", max_fixpo_iters, " iterations");
	}
      } else {
	for (auto m : scc_mems) {
	  cfg_t cfg = m.get_cfg();
	  std::unique_ptr<assertion_crawler_t> intra_analysis = analyze(cfg);
	  m_inter_results.insert(std::make_pair(cfg, std::move(intra_analysis)));
	}
      }
    }

    CRAB_VERBOSE_IF(1, get_msg_stream()
		    << "Finished inter-procedural assertion crawler analysis\n";);
  }

  // Return the dataflow facts that hold at the entry of the basic block b in cfg.
  assert_map_domain_t get_results(const cfg_t &cfg, const basic_block_label_t &bb) const {
    auto it = m_inter_results.find(cfg);
    if (it != m_inter_results.end()) {
      return it->second->get_results(bb);
    } else {
      return assert_map_domain_t::top();
    }
  }

  void write(crab_os &o) const {
    for (auto &kv: m_inter_results) {
      kv.second->write(o);
    }
  }

};

} // namespace analyzer
} // namespace crab


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_BLOCK_MODE_CIPHER_FEEDBACK_HPP
#define CRYPTO3_BLOCK_MODE_CIPHER_FEEDBACK_HPP

#include <memory>
#include <limits>

#include <boost/static_assert.hpp>

#include <nil/crypto3/modes/mode.hpp>
#include <nil/crypto3/modes/cts.hpp>

namespace nil {
    namespace crypto3 {
        namespace block {
            namespace modes {
                namespace detail {
                    template<typename Cipher, typename Padding, template<typename> class Allocator = std::allocator>
                    struct cfb_policy {
                        typedef std::size_t size_type;

                        typedef Cipher cipher_type;
                        typedef Padding padding_type;

                        constexpr static const size_type block_bits = cipher_type::block_bits;
                        constexpr static const size_type block_words = cipher_type::block_words;
                        typedef typename cipher_type::block_type block_type;

                        static void shift_register(cipher_type &cipher) {
                            const size_t shift = feedback();
                            const size_t carryover = block_size() - shift;

                            if (carryover > 0) {
                                copy_mem(m_state.data(), &m_state[shift], carryover);
                            }
                            copy_mem(&m_state[carryover], m_keystream.data(), shift);
                            cipher().encrypt(m_state, m_keystream);
                            m_keystream_pos = 0;
                        }

                        static inline void xor_copy(uint8_t buf[], uint8_t key_buf[], size_t len) {
                            for (size_t i = 0; i != len; ++i) {
                                uint8_t k = key_buf[i];
                                key_buf[i] = buf[i];
                                buf[i] ^= k;
                            }
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding,
                             typename CiphertextStealingMode>
                    struct cfb_encryption_policy : public cfb_policy<Cipher, Padding> {};

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_encryption_policy<Cipher, FeedbackBits, Padding, cts<0, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const size_type block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const size_type block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            if (!valid_nonce_length(nonce_len)) {
                                throw invalid_iv_length(name(), nonce_len);
                            }

                            if (nonce_len == 0) {
                                if (m_state.empty()) {
                                    throw invalid_state("CFB requires a non-empty initial nonce");
                                }
                                // No reason to encipher state->keystream_buf, because no change
                            } else {
                                m_state.assign(nonce, nonce + nonce_len);
                                m_keystream.resize(m_state.size());
                                cipher().encrypt(m_state, m_keystream);
                                m_keystream_pos = 0;
                            }
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            const size_t shift = feedback();

                            size_t left = sz;

                            if (m_keystream_pos != 0) {
                                const size_t take = std::min<size_t>(left, shift - m_keystream_pos);

                                xor_buf(m_keystream.data() + m_keystream_pos, buf, take);
                                copy_mem(buf, m_keystream.data() + m_keystream_pos, take);

                                m_keystream_pos += take;
                                left -= take;
                                buf += take;

                                if (m_keystream_pos == shift) {
                                    shift_register();
                                }
                            }

                            while (left >= shift) {
                                xor_buf(m_keystream.data(), buf, shift);
                                copy_mem(buf, m_keystream.data(), shift);

                                left -= shift;
                                buf += shift;
                                shift_register();
                            }

                            if (left > 0) {
                                xor_buf(m_keystream.data(), buf, left);
                                copy_mem(buf, m_keystream.data(), left);
                                m_keystream_pos += left;
                            }

                            return sz;
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = {0};

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_encryption_policy<Cipher, FeedbackBits, Padding, cts<1, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const size_type block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const size_type block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = {0};

                            return cipher.encrypt(block);
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = {0};

                            return cipher.encrypt(block);
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = {0};

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_encryption_policy<Cipher, FeedbackBits, Padding, cts<2, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const size_type block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const size_type block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = {0};

                            return cipher.encrypt(block);
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = {0};

                            return cipher.encrypt(block);
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = {0};

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_encryption_policy<Cipher, FeedbackBits, Padding, cts<3, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const size_type block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const size_type block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = {0};

                            return cipher.encrypt(block);
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = {0};

                            return cipher.encrypt(block);
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = {0};

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding,
                             typename CiphertextStealingMode>
                    struct cfb_decryption_policy : public cfb_policy<Cipher, Padding> {};

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_decryption_policy<Cipher, FeedbackBits, Padding, cts<0, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const std::size_t block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const std::size_t block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(block, iv);

                            return block;
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            const size_t shift = feedback();

                            size_t left = sz;

                            if (m_keystream_pos != 0) {
                                const size_t take = std::min<size_t>(left, shift - m_keystream_pos);

                                xor_copy(buf, m_keystream.data() + m_keystream_pos, take);

                                m_keystream_pos += take;
                                left -= take;
                                buf += take;

                                if (m_keystream_pos == shift) {
                                    shift_register();
                                }
                            }

                            while (left >= shift) {
                                xor_copy(buf, m_keystream.data(), shift);
                                left -= shift;
                                buf += shift;
                                shift_register();
                            }

                            if (left > 0) {
                                xor_copy(buf, m_keystream.data(), left);
                                m_keystream_pos += left;
                            }

                            return sz;
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, iv);

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_decryption_policy<Cipher, FeedbackBits, Padding, cts<1, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const std::size_t block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const std::size_t block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(block, iv);

                            return block;
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, previous);

                            return result;
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, iv);

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_decryption_policy<Cipher, FeedbackBits, Padding, cts<2, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const std::size_t block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const std::size_t block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(block, iv);

                            return block;
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, previous);

                            return result;
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, iv);

                            return result;
                        }
                    };

                    template<typename Cipher, std::size_t FeedbackBits, typename Padding>
                    struct cfb_decryption_policy<Cipher, FeedbackBits, Padding, cts<3, Cipher, Padding>>
                        : public cfb_policy<Cipher, Padding> {
                        typedef typename cfb_policy<Cipher, Padding>::size_type size_type;

                        typedef typename cfb_policy<Cipher, Padding>::cipher_type cipher_type;
                        typedef typename cfb_policy<Cipher, Padding>::padding_type padding_type;

                        constexpr static const std::size_t block_bits = cfb_policy<Cipher, Padding>::block_bits;
                        constexpr static const std::size_t block_words = cfb_policy<Cipher, Padding>::block_words;
                        typedef typename cfb_policy<Cipher, Padding>::block_type block_type;

                        inline static block_type begin_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type block = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(block, iv);

                            return block;
                        }

                        inline static block_type process_block(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, previous);

                            return result;
                        }

                        inline static block_type end_message(const cipher_type &cipher, const block_type &plaintext) {
                            block_type result = cipher.decrypt(plaintext);

                            //                        codec::encode<codec::encoder::logic_xor>(result, iv);

                            return result;
                        }
                    };

                    template<typename Policy>
                    class cipher_feedback {
                        typedef Policy policy_type;

                    public:
                        typedef typename policy_type::cipher_type cipher_type;
                        typedef typename policy_type::padding_type padding_type;

                        typedef typename policy_type::size_type size_type;

                        typedef typename cipher_type::key_type key_type;
                        typedef typename policy_type::iv_type iv_type;

                        constexpr static const size_type block_bits = policy_type::block_bits;
                        constexpr static const size_type block_words = policy_type::block_words;
                        typedef typename cipher_type::block_type block_type;

                        cipher_feedback(const cipher_type &cipher) : cipher(cipher) {
                        }

                        block_type begin_message(const block_type &plaintext) {
                            previous = policy_type::begin_message(cipher, plaintext);
                            return previous;
                        }

                        block_type process_block(const block_type &plaintext) {
                            previous = policy_type::process_block(cipher, plaintext, previous);
                            return previous;
                        }

                        block_type end_message(const block_type &plaintext) {
                            return policy_type::end_message(cipher, plaintext);
                        }

                        inline static size_type required_output_size(size_type inputlen) {
                            return padding_type::required_output_size(inputlen);
                        }

                    protected:
                        block_type previous;
                        cipher_type cipher;
                    };
                }    // namespace detail

                /*!
                 * @brief Cipher Feedback Mode (CBC).
                 *
                 * @tparam Cipher
                 * @tparam FeedbackBits
                 * @tparam Padding
                 * @tparam CiphertextStealingMode
                 *
                 * @ingroup block_modes
                 */
                template<typename Cipher, std::size_t FeedbackBits, template<typename> class Padding,
                         template<typename, typename> class CiphertextStealingMode = cts0>
                struct cipher_feedback {
                    typedef Cipher cipher_type;
                    typedef Padding<Cipher> padding_type;
                    typedef CiphertextStealingMode<Cipher, Padding<Cipher>> ciphertext_stealing_type;

                    constexpr static const std::size_t feedback_bits = FeedbackBits;
                    BOOST_STATIC_ASSERT(feedback_bits <= cipher_type::block_bits);
                    BOOST_STATIC_ASSERT(feedback_bits % CHAR_BIT == 0);

                    typedef detail::cfb_encryption_policy<cipher_type, feedback_bits, padding_type,
                                                          ciphertext_stealing_type>
                        encryption_policy;
                    typedef detail::cfb_decryption_policy<cipher_type, feedback_bits, padding_type,
                                                          ciphertext_stealing_type>
                        decryption_policy;

                    template<template<typename, typename> class Policy>
                    struct bind {
                        typedef detail::cipher_feedback<Policy<cipher_type, padding_type>> type;
                    };
                };

                /*!
                 * @brief
                 * @tparam Cipher
                 * @tparam FeedbackBits
                 * @tparam Padding
                 * @tparam CiphertextStealingMode
                 *
                 * @ingroup block_modes
                 */
                template<typename Cipher, std::size_t FeedbackBits, template<typename> class Padding,
                         template<typename, typename> class CiphertextStealingMode = cts0>
                using cfb = cipher_feedback<Cipher, FeedbackBits, Padding, CiphertextStealingMode>;
            }    // namespace modes
        }        // namespace block
    }            // namespace crypto3
}    // namespace nil

#endif


/* VLFeat_SIFT.cpp : Defines the entry point for the console application.

Integration of the classifier Copyright (c) 2014
Wilfried Hartmann and Michal Havlena, IGP, D-BAUG, ETH Zurich.

When using the code either in source or binary form, please cite:
"W.Hartmann, M.Havlena, and K.Schindler: Predicting Matchability, CVPR 2014"
http://www.igp.ethz.ch/photogrammetry/publications/pdf_folder/CVPR2014_Hartmann.pdf

Random forest classifier code Copyright (c) 2014 Stefan Walk.
Code made available under the terms of the MIT license (see the LICENSE file).
*/

/** @internal
 ** @file     sift.c
 ** @author   Andrea Vedaldi
 ** @brief    Scale Invariant Feature Transform (SIFT) - Driver
 **/

/*
Copyright (C) 2007-12 Andrea Vedaldi and Brian Fulkerson.
All rights reserved.

This file is part of the VLFeat library and is made available under
the terms of the BSD license (see the COPYING file).
*/

extern "C" {
//#include <vl/generic.h>
//#include <vl/getopt_long.h>
}

#include <cstddef>
#include "random-forest/node-gini.hpp"
#include "random-forest/forest.hpp"

#include <boost/archive/text_iarchive.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <fstream>
#include <sstream>

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

#include <vector>
#include "keys2a.h"

using namespace std;

int main(int argc, char **argv)
{
	printf("\nLiblearning");
	fflush(stdout);
	liblearning::RandomForest::RandomForest< liblearning::RandomForest::NodeGini<liblearning::RandomForest::AxisAlignedSplitter> > rtrees ;

	std::ifstream ifs("rforest.gz", std::ios_base::in | std::ios_base::binary) ;

  	char     err_msg [1024] ;
	if (!ifs.is_open()) {
		snprintf(err_msg, sizeof(err_msg),
				"Could not open rforests for reading.") ;
		fprintf (stderr, "sift: err: %s\n", err_msg) ;
		return 1 ;
	}

	printf("sift: read classifier from 'rforests'\n") ;

	boost::iostreams::filtering_istream ins ;
	ins.push(boost::iostreams::gzip_decompressor()) ;
	ins.push(ifs) ;
	boost::archive::text_iarchive ias(ins) ;
	ias >> BOOST_SERIALIZATION_NVP(rtrees) ;

	unsigned char* desc;
	keypt_t* keys;
	int numKeys = ReadKeyFile(argv[1], &desc, &keys);

	printf("\nRead %d keys", numKeys);

	float cl_thresh = 0.525;

	printf("\nLiblearning");
	fflush(stdout);
	for(int i=0; i < numKeys; i++) {
		//printf("\nKey %d", i);
		fflush(stdout);
		std::vector<float> siftdesc(128) ;
		std::vector<float> results(rtrees.params.n_classes);

		unsigned char* des = desc+128*i;
		for (int l = 0; l < 128; ++l)
			siftdesc [l] = (float)(des[l]) ;

		//printf("\nKey %d copied", i);
		fflush(stdout);
		rtrees.evaluate(&siftdesc[0], &results[0]) ;

		//printf("\nKey %d evaluated", i);
		fflush(stdout);

		if (results[0] > 1.0 - cl_thresh) {
			continue ;
		} else {
			printf("%d\n", i);
		}
	}

	/* quit */
	return 0 ;
}


// Copyright (c) 2015-2016 Hypha

#include "hyphaplugins/deepnetvideo/deepnetvideo.h"

#include <cmath>
#include <cstdlib>
#include <exception>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <thread>
#include <vector>

#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>

#include <dlib/opencv.h>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>

#include <Poco/ClassLibrary.h>
#include <Poco/DateTime.h>
#include <Poco/DateTimeFormatter.h>
#include <Poco/LocalDateTime.h>
#include <Poco/Timezone.h>

#include <hypha/plugin/hyphaplugin.h>
#include <hypha/utils/logger.h>

using namespace hypha::utils;
using namespace hypha::plugin;
using namespace hypha::plugin::deepnetvideo;

using namespace cv;
using namespace cv::dnn;

using namespace std;

DeepNetVideo::~DeepNetVideo() {}

/* Find best class for the blob (i. e. class with maximal probability) */
void getMaxClass(dnn::Blob &probBlob, int *classId, double *classProb) {
  Mat probMat = probBlob.matRefConst().reshape(
      1, 1);  // reshape the blob to 1x1000 matrix
  Point classNumber;
  minMaxLoc(probMat, NULL, classProb, NULL, &classNumber);
  *classId = classNumber.x;
}

std::vector<String> readClassNames(const char *filename = "synset_words.txt") {
  std::vector<String> classNames;
  std::ifstream fp(filename);
  if (!fp.is_open()) {
    std::cerr << "File with classes labels not found: " << filename
              << std::endl;
    throw std::runtime_error("File with classes labels not found");
  }
  std::string name;
  while (!fp.eof()) {
    std::getline(fp, name);
    if (name.length()) classNames.push_back(name.substr(name.find(' ') + 1));
  }
  fp.close();
  return classNames;
}

void DeepNetVideo::doWork() {
  std::this_thread::sleep_for(std::chrono::seconds(1) / fps);
  try {
    std::thread captureT = std::thread([this] { captureCamera(); });
    if (captureT.joinable()) captureT.join();
    currentImage_mutex.lock();
    currentImage = captureMat;
    currentImage_mutex.unlock();
  } catch (cv::Exception &e) {
    Logger::error(e.what());
  } catch (std::exception e) {
    Logger::error(e.what());
  } catch (...) {
    Logger::error("Something terrible happened.");
  }

  std::vector<std::string> classes = classify();
  Logger::info("current class: ");
  for (std::string className : classes) Logger::info(className);
  // sendMessage(className);
}

void DeepNetVideo::captureCamera() {
  if (getFilmState() == FILM) {
    if (filmCounter++ >= 100) setFilmState(IDLE);
    if (capture_mutex.try_lock()) {
      try {
        cv::Mat capt;
        if (capture.isOpened()) {
          // read five times to clear buffered old images
          capture.read(capt);
          capture.read(capt);
          capture.read(capt);
          capture.read(capt);
          capture.read(capt);
        }
        if (captureMat_mutex.try_lock()) {
          captureMat = capt.clone();
          captureMat_mutex.unlock();
        }
        capture_mutex.unlock();
      } catch (cv::Exception &e) {
        Logger::error("Could not capture from camera");
        Logger::error(e.what());
      } catch (std::exception &e) {
        Logger::warning("Could not capture from camera");
      }
    }
  }
}

cv::Mat DeepNetVideo::rotateMat(cv::Mat mat) {
  if (mat.rows < 1) return mat;
  cv::Point2f src_center(mat.cols / 2.0F, mat.rows / 2.0F);
  cv::Mat rot_mat = cv::getRotationMatrix2D(src_center, 180, 1.0);
  cv::warpAffine(mat, mat, rot_mat, mat.size());
  return mat;
}

cv::Mat DeepNetVideo::fillMat(cv::Mat mat) {
  if (mat.rows != height)
    cv::vconcat(mat, cv::Mat(height - mat.rows, mat.cols, mat.type()), mat);
  if (mat.cols != width)
    cv::hconcat(mat, cv::Mat(mat.rows, width - mat.cols, mat.type()), mat);
  return mat;
}

void DeepNetVideo::setup() {
  if (!device.empty()) {
    capture.open(std::stoi(device));
    capture.set(CV_CAP_PROP_FRAME_WIDTH, width);
    capture.set(CV_CAP_PROP_FRAME_HEIGHT, height);
    capture.set(CV_CAP_PROP_FPS, fps);
    captureMat = cv::Mat(height, width, CV_8UC3);
  }
  filmCounter = 0;
  filmState = FILM;

  loadNet();
}

std::string DeepNetVideo::communicate(std::string UNUSED(message)) {
  return "SUCCESS";
}

void DeepNetVideo::loadConfig(std::string json) {
  boost::property_tree::ptree pt;
  std::stringstream ss(json);
  boost::property_tree::read_json(ss, pt);
  if (pt.get_optional<int>("fps")) {
    fps = pt.get<int>("fps");
  }
  if (pt.get_optional<int>("width")) {
    width = pt.get<int>("width");
  }
  if (pt.get_optional<int>("height")) {
    height = pt.get<int>("height");
  }
  if (pt.get_optional<std::string>("device")) {
    device = pt.get<std::string>("device");
  }

  if (pt.get_optional<std::string>("modelTxt")) {
    modelTxt = pt.get<std::string>("modelTxt");
  }

  if (pt.get_optional<std::string>("modelBin")) {
    modelBin = pt.get<std::string>("modelBin");
  }

  if (pt.get_optional<std::string>("classNamesFile")) {
    classNamesFile = pt.get<std::string>("classNamesFile");
  }
}

std::string DeepNetVideo::getConfig() { return "{}"; }

HyphaPlugin *DeepNetVideo::getInstance(std::string id) {
  DeepNetVideo *instance = new DeepNetVideo();
  instance->setId(id);
  return instance;
}

void DeepNetVideo::receiveMessage(std::string message) {
  try {
    boost::property_tree::ptree pt;
    std::stringstream ss(message);
    boost::property_tree::read_json(ss, pt);
    if (pt.get_optional<bool>("run")) {
      if (pt.get<bool>("run")) {
        setState(FILM);
      } else {
        setState(IDLE);
      }
    }
  } catch (std::exception &e) {
    Logger::error(e.what());
  }
}

void DeepNetVideo::loadNet() {
  deserialize(this->modelBin) >> net >> labels;
  snet.subnet() = net.subnet();
}

// ----------------------------------------------------------------------------------------

dlib::rectangle make_random_cropping_rect_resnet(const matrix<rgb_pixel> &img,
                                                 dlib::rand &rnd) {
  // figure out what rectangle we want to crop from the image
  double mins = 0.466666666, maxs = 0.875;
  auto scale = mins + rnd.get_random_double() * (maxs - mins);
  auto size = scale * std::min(img.nr(), img.nc());
  dlib::rectangle rect(size, size);
  // randomly shift the box around
  point offset(rnd.get_random_32bit_number() % (img.nc() - rect.width()),
               rnd.get_random_32bit_number() % (img.nr() - rect.height()));
  return move_rect(rect, offset);
}

// ----------------------------------------------------------------------------------------

void randomly_crop_images(const matrix<rgb_pixel> &img,
                          dlib::array<matrix<rgb_pixel>> &crops,
                          dlib::rand &rnd, long num_crops) {
  std::vector<chip_details> dets;
  for (long i = 0; i < num_crops; ++i) {
    auto rect = make_random_cropping_rect_resnet(img, rnd);
    dets.push_back(chip_details(rect, chip_dims(227, 227)));
  }

  extract_image_chips(img, dets, crops);

  for (auto &&img : crops) {
    // Also randomly flip the image
    if (rnd.get_random_double() > 0.5) img = fliplr(img);

    // And then randomly adjust the colors.
    apply_random_color_offset(img, rnd);
  }
}

dlib::matrix<rgb_pixel> MatToMatrix(cv::Mat mat) {
  dlib::matrix<rgb_pixel> result(mat.rows, mat.cols);

  rgb_pixel *p;
  for (int i = 0; i < mat.rows; i++) {
    p = mat.ptr<rgb_pixel>(i);
    for (int j = 0; j < mat.cols; j++) {
      result(i, j) = p[j];
    }
  }

  return result;
}

// ----------------------------------------------------------------------------------------

std::vector<std::string> DeepNetVideo::classify() {
  std::vector<std::string> tags;
  Mat cvimg = getCurrentImage();
  if (cvimg.empty()) {
    std::cerr << "can't get image" << std::endl;
    throw std::runtime_error("can't get image");
  }
  // cv_image<bgr_pixel> cimg(img);
  dlib::array<matrix<rgb_pixel>> images;
  matrix<rgb_pixel> mimg, crop;
  cv::cvtColor(cvimg, cvimg, CV_BGR2RGB);
  mimg = MatToMatrix(cvimg);
  const int num_crops = 8;
  // Grab 16 random crops from the image.  We will run all of them through the
  // network and average the results.
  randomly_crop_images(mimg, images, rnd, num_crops);
  // p(i) == the probability the image contains object of class i.
  matrix<float, 1, 1000> p =
      sum_rows(dlib::mat(snet(images.begin(), images.end()))) / num_crops;

  win.set_image(mimg);
  // Print the 5 most probable labels
  for (int k = 0; k < 1; ++k) {
    unsigned long predicted_label = index_of_max(p);
    cout << p(predicted_label) << ": " << labels[predicted_label] << endl;
    p(predicted_label) = 0;
  }
  return tags;
}

cv::Mat DeepNetVideo::getCurrentImage() {
  setFilmState(FILM);
  cv::Mat frame;
  currentImage_mutex.lock();
  frame = currentImage.clone();
  currentImage_mutex.unlock();
  return frame;
}

DeepNetVideo::State DeepNetVideo::getState() {
  State state = IDLE;
  state_mutex.lock();
  state = currentState;
  state_mutex.unlock();
  return state;
}

void DeepNetVideo::setState(State state) {
  state_mutex.lock();
  currentState = state;
  state_mutex.unlock();
}

DeepNetVideo::State DeepNetVideo::getFilmState() {
  State state = IDLE;
  state_mutex.lock();
  state = filmState;
  state_mutex.unlock();
  return state;
}

void DeepNetVideo::setFilmState(State state) {
  state_mutex.lock();
  if (filmState == IDLE && state == FILM) {
    filmState = FILM;
    if (!device.empty()) {
      capture.open(std::stoi(device));
      capture.set(CV_CAP_PROP_FRAME_WIDTH, width);
      capture.set(CV_CAP_PROP_FRAME_HEIGHT, height);
      capture.set(CV_CAP_PROP_FPS, fps);
      cameras++;
    }
  } else if (filmState == FILM && state == IDLE) {
    filmState = IDLE;
    if (!device.empty()) {
      capture.release();
    }
    cameras = 0;
  }
  state_mutex.unlock();
}

POCO_BEGIN_MANIFEST(HyphaPlugin)
POCO_EXPORT_CLASS(DeepNetVideo)
POCO_END_MANIFEST


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_TOOLBOX_OPERATOR_FUNCTIONS_SIMD_VMX_ALTIVEC_MULTIPLIES_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_OPERATOR_FUNCTIONS_SIMD_VMX_ALTIVEC_MULTIPLIES_HPP_INCLUDED
#ifdef BOOST_SIMD_HAS_VMX_SUPPORT

#include <boost/simd/toolbox/operator/functions/multiplies.hpp>
#include <boost/dispatch/meta/scalar_of.hpp>
#include <boost/dispatch/meta/upgrade.hpp>
#include <boost/simd/include/constants/digits.hpp>
#include <boost/simd/include/constants/mzero.hpp>
#include <boost/simd/include/constants/zero.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::multiplies_, boost::simd::tag::altivec_, (A0)
                            , ((simd_<single_<A0>,boost::simd::tag::altivec_>))
                              ((simd_<single_<A0>,boost::simd::tag::altivec_>))
                            )
  {
    typedef A0 result_type;

    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return vec_madd(a0(),a1(), boost::simd::Mzero<A0>()());
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::multiplies_, boost::simd::tag::altivec_, (A0)
                            , ((simd_<type16_<A0>,boost::simd::tag::altivec_>))
                              ((simd_<type16_<A0>,boost::simd::tag::altivec_>))
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return vec_mladd(a0(),a1(),Zero<A0>()());
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::multiplies_, boost::simd::tag::altivec_, (A0)
                            , ((simd_<type8_<A0>,boost::simd::tag::altivec_>))
                              ((simd_<type8_<A0>,boost::simd::tag::altivec_>))
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      typedef typename dispatch::meta::upgrade<A0>::type uptype;
      uptype l = vec_mule(a0(),a1());
      uptype r = vec_mulo(a0(),a1());
      return vec_mergel(vec_pack(l(),l()),vec_pack(r(),r()));
    }
  };

/*
 * TODO : FINISH THIS
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2,int32_t )
    {
      static inline type_t Multiply( const type_t& a, const type_t& b, const ttt::boxed<2>&, const true_t&  )
      {
        typedef __vu32_4 cast_t;
        type_t r  = type_t( av_multiply<__vu32_4>::Multiply(cast_t(a),cast_t(b), ttt::boxed<2>(), false_t()) );
        return vec_sel(r,vec_sub(vec_splat_u32(0), r),vec_cmpgt(vec_splat_u32(0), vec_xor(a, b)));
      }
    }

    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2,uint32_t )
    {

      static inline type_t Multiply( const type_t& a, const type_t& b, const ttt::boxed<2>&, const false_t&  )
      {
        typedef __vu16_8 short_t;
        type_t sixteen = type_t(vec_vspltisw (-16));
        type_t low = vec_mulo (short_t(a),short_t(b));
        type_t high = vec_msum (short_t(a),short_t(vec_rl(b, sixteen)),vec_splat_u32(0));
        return vec_add(vec_sl(high, sixteen),low);
      }

*/
} } }

#endif
#endif


#include "forces/qa/QuasiAdiabatic.hpp"
#include <Eigen/Core>
#include <algorithm>
using namespace mdk;
using namespace mdk::param;

void QuasiAdiabatic::bind(Simulation &simulation) {
    NonlocalForce::bind(simulation);

    stats = &simulation.var<Stats>();

    types = &simulation.data<Types>();
    chains = &simulation.data<Chains>();

    bb_lj = LennardJones(5.0 * angstrom, 1.0 * eps);
    bs_lj = LennardJones(6.8 * angstrom, 1.0 * eps);

    auto& params = simulation.data<param::Parameters>();
    for (auto const& [acids, r]: params.pairwiseMinDist) {
        auto acid1 = (int8_t)acids.first, acid2 = (int8_t)acids.second;
        auto sslj = SidechainLJ(1.0 * eps, r);
        ss_ljs[acid1][acid2] = ss_ljs[acid2][acid1] = sslj;
    }

    n = h = Vectors(state->n);

    formationMaxDistSq = 0.0;
    formationMaxDistSq = std::max(formationMaxDistSq, bb_lj.r_min);
    formationMaxDistSq = std::max(formationMaxDistSq, bs_lj.r_min);
    for (int8_t acid1 = 0; acid1 < AminoAcid::N; ++acid1) {
        for (int8_t acid2 = 0; acid2 < AminoAcid::N; ++acid2) {
            auto ss_r_min = ss_ljs[acid1][acid2].sink_max;
            formationMaxDistSq = std::max(formationMaxDistSq, ss_r_min);
        }
    }
    formationMaxDistSq = pow(formationMaxDistSq, 2.0);

    installIntoVL();
}

void QuasiAdiabatic::asyncPart(Dynamics &dyn) {
    computeNH();

    for (auto& cont: pairs) {
        if (cont.status == QAContact::Status::REMOVED)
            continue;

        double stage;
        if (cont.status == QAContact::Status::FORMING) {
            stage = std::min((state->t - cont.t0) / formationTime, 1.0);
        }
        else {
            stage = std::max(1.0 - (state->t - cont.t0) / breakingTime, 0.0);
        }

        Vector r = state->top(state->r[cont.i2] - state->r[cont.i1]);
        auto norm = r.norm();
        auto unit = r / norm;
        double r_min;

        if (stage > 0.0) {
            if (cont.type == Stats::Type::BB) {
                bb_lj.computeF(unit, norm, dyn.V, dyn.F[cont.i1],
                    dyn.F[cont.i2]);
                r_min = bb_lj.r_min;
            }
            else if (cont.type != Stats::Type::SS) {
                bs_lj.computeF(unit, norm, dyn.V, dyn.F[cont.i1],
                    dyn.F[cont.i2]);
                r_min = bs_lj.r_min;
            }
            else {
                auto const& ss_lj = ss_ljs[(*types)[cont.i1]][(*types)[cont.i2]];
                ss_lj.computeF(unit, norm, dyn.V, dyn.F[cont.i1], dyn.F[cont.i2]);
                r_min = ss_lj.sink_max;
            }

            if (cont.status == QAContact::Status::FORMING &&
                norm > breakingTolerance * pow(2.0, -1.0/6.0) * r_min) {

                cont.status = QAContact::Status::BREAKING;
                cont.t0 = state->t;
            }
        }

        if (cont.status == QAContact::Status::BREAKING && stage == 0.0) {
            cont.status = QAContact::Status::REMOVED;
            freePairs.emplace_back((QAFreePair) {
                .i1 = cont.i1, .i2 = cont.i2,
                .status = QAFreePair::Status::FREE
            });
        }
    }
}

vl::Spec QuasiAdiabatic::spec() const {
    double maxCutoff = 0.0;
    maxCutoff = std::max(maxCutoff, bb_lj.cutoff());
    maxCutoff = std::max(maxCutoff, bs_lj.cutoff());

    for (int8_t acid1 = 0; acid1 < AminoAcid::N; ++acid1) {
        for (int8_t acid2 = 0; acid2 < AminoAcid::N; ++acid2) {
            maxCutoff = std::max(maxCutoff, ss_ljs[acid1][acid2].cutoff());
        }
    }

    return (vl::Spec) {
        .cutoffSq = pow(maxCutoff, 2.0),
        .minBondSep = 3,
    };
}

static bool operator<(QAContact const& p1, std::pair<int, int> const& p2) {
    return std::make_pair(p1.i1, p1.i2) < p2;
}

static bool operator==(QAContact const& p1, std::pair<int, int> const& p2) {
    return std::make_pair(p1.i1, p1.i2) == p2;
}

void QuasiAdiabatic::vlUpdateHook() {
    std::swap(oldPairs, pairs);
    freePairs.clear();

    auto oldPairsIter = oldPairs.begin();
    auto oldPairsEnd = oldPairs.end();

    for (auto& pair : vl->pairs) {
        if (chains->isTerminal[pair.first] || chains->isTerminal[pair.second]
            || !chains->sepByAtLeastN(pair.first, pair.second, 3)) {

            continue;
        }

        while (oldPairsIter != oldPairsEnd && *oldPairsIter < pair)
            ++oldPairsIter;

        if (oldPairsIter != oldPairsEnd
            && oldPairsIter->status != QAContact::Status::REMOVED
            && *oldPairsIter == pair) {

            pairs.emplace_back(*oldPairsIter);
        }
        else {
            freePairs.emplace_back((QAFreePair) {
                .i1 = pair.first, .i2 = pair.second,
                .status = QAFreePair::Status::FREE
            });
        }
    }
}

bool QuasiAdiabatic::geometryPhase(vl::PairInfo const& p, QADiff &diff) const {
    Vector h1 = h[p.i1], h2 = h[p.i2];
    double cos_h1_r12 = h1.dot(p.unit), cos_h2_r12 = h2.dot(p.unit),
        cos_h1_h2 = h1.dot(h2);

    if (abs(cos_h1_r12) >= hr_abs_min && abs(cos_h2_r12) >= hr_abs_min &&
        abs(cos_h1_h2) >= hh_abs_min && p.norm <= bb_lj.r_min) {

        diff.cont.type = Stats::Type::BB;
        return true;
    }

    Vector n1 = n[p.i1];
    double cos_n1_r12 = n1.dot(p.unit);
    if (cos_n1_r12 <= nr_max && abs(cos_h1_r12) >= hr_abs_min &&
        p.norm <= bs_lj.r_min * formationTolerance) {

        diff.cont.type = Stats::Type::BS;
        return true;
    }

    Vector n2 = n[p.i2];
    double cos_n2_r12 = n2.dot(p.unit);
    if (cos_n2_r12 <= nr_max && abs(cos_h1_r12) >= hr_abs_min &&
        p.norm <= bs_lj.r_min * formationTolerance) {

        diff.cont.type = Stats::Type::SB;
        return true;
    }

    if (cos_n1_r12 <= nr_max && -cos_n2_r12 <= nr_max &&
        p.norm <= ss_ljs[(*types)[p.i1]][(*types)[p.i2]].sink_max * formationTolerance) {

        diff.cont.type = Stats::Type::SS;
        return true;
    }

    return false;
}

void QuasiAdiabatic::syncPart(Dynamics &dyn) {
    qaDiffs.clear();
    for (int i = 0; i < (int)freePairs.size(); ++i) {
        auto const& p = freePairs[i];

        if (p.status == QAFreePair::Status::TAKEN)
            continue;

        vl::PairInfo pairInfo;
        pairInfo.i1 = p.i1;
        pairInfo.i2 = p.i2;

        auto r = state->top(state->r[p.i1] - state->r[p.i2]);
        auto r_normsq = r.squaredNorm();
        if (r_normsq >= formationMaxDistSq)
            continue;

        pairInfo.norm = sqrt(r_normsq);
        pairInfo.unit = r / pairInfo.norm;

        QADiff diff;
        if (!geometryPhase(pairInfo, diff))
            continue;

        stats->creationDiffs(p.i1, p.i2, diff.cont.type, diff.statDiffs);

        auto stat1 = stats->stats[p.i1] + diff.statDiffs[0];
        if (!stat1.valid()) continue;

        auto stat2 = stats->stats[p.i2] + diff.statDiffs[1];
        if (!stat2.valid()) continue;

        diff.oldIdx = i;
        diff.cont.i1 = p.i1;
        diff.cont.i2 = p.i2;
        diff.cont.t0 = state->t;
        diff.cont.status = QAContact::Status::FORMING;

        qaDiffsMutex.lock();
        qaDiffs.emplace_back(diff);
        qaDiffsMutex.unlock();
    }

    std::sort(qaDiffs.begin(), qaDiffs.end());
    for (auto const& diff: qaDiffs) {
        auto& stat1 = stats->stats[diff.cont.i1];
        auto res1 = stat1 + diff.statDiffs[0];
        if (!res1.valid()) continue;

        auto& stat2 = stats->stats[diff.cont.i2];
        auto res2 = stat2 + diff.statDiffs[1];
        if (!res2.valid()) continue;

        freePairs[diff.oldIdx].status = QAFreePair::Status::TAKEN;
        stat1 = res1;
        stat2 = res2;
        pairs.push_back(diff.cont);
    }
}

void QuasiAdiabatic::computeNH() {
    for (int i = 0; i < (int)chains->triples.size(); ++i) {
        if (!chains->triples[i]) continue;

        auto r0 = state->r[i-1], r1 = state->r[i], r2 = state->r[i+1];
        auto v0 = r1 - r0, v1 = r2 - r1;
        n[i] = (v1 - v0).normalized();
        h[i] = (v1.cross(v0)).normalized();
    }
}


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

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <fstream>

#include "include_base_utils.h"
#include "account.h"
#include "warnings.h"
#include "crypto/crypto.h"
#include "currency_core/currency_format_utils.h"
#include "common/mnemonic-encoding.h"
#include "string_tools.h"

using namespace std;

DISABLE_VS_WARNINGS(4244 4345)

namespace currency
{

  //-----------------------------------------------------------------
  account_base::account_base()
  {
    set_null();
  }
  //-----------------------------------------------------------------
  void account_base::set_null()
  {
    m_seed.clear();
    m_keys = account_keys();
    m_creation_timestamp = 0;
  }
  //-----------------------------------------------------------------
  void account_base::generate()
  {
    //generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key);
    generate_brain_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, m_seed);
    dependent_key(m_keys.m_spend_secret_key, m_keys.m_view_secret_key);
    if (!crypto::secret_key_to_public_key(m_keys.m_view_secret_key, m_keys.m_account_address.m_view_public_key))
      throw std::runtime_error("Failed to create public view key");

    m_creation_timestamp = time(NULL);
  }
  //-----------------------------------------------------------------
  const account_keys& account_base::get_keys() const
  {
    return m_keys;
  }
  //-----------------------------------------------------------------
  std::string account_base::get_restore_data() const
  {
    return m_seed;
  }
  //-----------------------------------------------------------------
  std::string account_base::get_restore_braindata() const
  {
    std::string restore_string;
    if (!m_seed.empty())
    {
      std::string restore_buff = get_restore_data();
      restore_string = tools::mnemonic_encoding::binary2text(restore_buff);

      uint64_t date_offset = m_creation_timestamp > WALLET_BRAIN_DATE_OFFSET ? m_creation_timestamp - WALLET_BRAIN_DATE_OFFSET : 0;
      uint64_t weeks_count = date_offset / WALLET_BRAIN_DATE_QUANTUM;
      CHECK_AND_ASSERT_THROW_MES(weeks_count < std::numeric_limits<uint32_t>::max(), "internal error: unable to converto to uint32, val = " << weeks_count);
      uint32_t weeks_count_32 = static_cast<uint32_t>(weeks_count);

      restore_string.append(tools::mnemonic_encoding::word_by_num(weeks_count_32));
    }
    else
    {
      if (m_keys.is_watch_only())
      {
        return std::string("");
      }
      crypto::secret_key spend_key = get_keys().m_spend_secret_key;
      restore_string = epee::string_tools::pod_to_hex(spend_key);

      time_t timestamp = get_createtime();
      std::string date;
      epee::misc_utils::get_restore_str_from_time(date, timestamp);

      restore_string.append(" " + date);
    }

    return restore_string;
  }
  //-----------------------------------------------------------------
  bool account_base::restore_keys(const std::string& restore_data)
  {
    //CHECK_AND_ASSERT_MES(restore_data.size() == ACCOUNT_RESTORE_DATA_SIZE, false, "wrong restore data size");
    if (restore_data.size() == BRAINWALLET_DEFAULT_SEED_SIZE)
    {
      crypto::keys_from_default((unsigned char*)restore_data.data(), m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key);
    }
    else if(restore_data.size() == BRAINWALLET_SHORT_SEED_SIZE)
    {
      crypto::keys_from_short((unsigned char*)restore_data.data(), m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key);
    }
    else
    {
      LOG_ERROR("wrong restore data size=" << restore_data.size());
      return false;
    }
    m_seed = restore_data;
    crypto::dependent_key(m_keys.m_spend_secret_key, m_keys.m_view_secret_key);
    bool r = crypto::secret_key_to_public_key(m_keys.m_view_secret_key, m_keys.m_account_address.m_view_public_key);
    CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key for view key");
    return true;
  }
  //-----------------------------------------------------------------
  bool account_base::restore_keys_from_braindata(const std::string& restore_data)
  {
    set_createtime(0);
    std::stringstream restore_phrase_stream(restore_data);
    size_t words_count = std::distance(std::istream_iterator<std::string>(restore_phrase_stream), std::istream_iterator<std::string>());

    if (words_count == SPEND_KEY_WORDS_COUNT_WITH_TS)
    {
      time_t restore_create_time;
      std::istringstream date(restore_data.substr(1 + (restore_data.find_last_of(' '))));
      if (epee::misc_utils::get_restore_time_from_str(restore_create_time, date.str()))
      {
        set_createtime(restore_create_time);
      }
    }
    else if (words_count == PHRASE_WORDS_COUNT_WITH_TS)
    {
      auto index = restore_data.find_last_of(' ');
      std::string last_word = restore_data.substr(++index);

      uint64_t count_of_weeks = tools::mnemonic_encoding::num_by_word(last_word);
      time_t timestamp = count_of_weeks * WALLET_BRAIN_DATE_QUANTUM + WALLET_BRAIN_DATE_OFFSET;

      if (timestamp < time(nullptr) && timestamp > WALLET_BRAIN_DATE_OFFSET)
      {
        set_createtime(timestamp);
      }
    }

    if (words_count == SPEND_KEY_WORDS_COUNT || words_count == SPEND_KEY_WORDS_COUNT_WITH_TS)
    {
      return restore_keys_from_spend_key(restore_data);
    }
    else
    {
      string::size_type start = 0;
      string::size_type last = restore_data.find_first_of(" ");
      uint32_t copied_words_count = 0;
      std::string restore_string;
      while (start - 1 != restore_data.size() && copied_words_count < PHRASE_WORDS_COUNT)
      {
        if (last > start)
        {
          restore_string.append(restore_data.substr(start, last - start) + " ");
          copied_words_count++;
        }

        start = ++last;
        last = restore_data.find_first_of(" ", last);
        if (last == std::string::npos)
        {
          last = restore_data.size();
        }
      }
      std::string restore_buff = tools::mnemonic_encoding::text2binary(restore_string);
      if (!restore_buff.size())
        return false;
      return restore_keys(restore_buff);
    }

    return false;
  }
  //-----------------------------------------------------------------
  bool account_base::restore_keys_from_view_key(const std::string& restore_data)
  {
    m_seed.clear();
    m_keys.m_spend_secret_key = currency::null_skey;
    bool r = epee::string_tools::hex_to_pod(restore_data, m_keys.m_view_secret_key);
    CHECK_AND_ASSERT_MES(r, false, "failed to restore secret_view_key");
    r = crypto::secret_key_to_public_key(m_keys.m_view_secret_key, m_keys.m_account_address.m_view_public_key);
    CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key for view key");
    set_createtime(0);
    return true;
  }
  //-----------------------------------------------------------------
  bool account_base::restore_keys_from_spend_key(const std::string& restore_data)
  {
    m_seed.clear();
    std::string spend_key = restore_data.substr(0, restore_data.find(" "));

    bool r = epee::string_tools::hex_to_pod(spend_key, m_keys.m_spend_secret_key);
    CHECK_AND_ASSERT_MES(r, false, "failed to restore spend_secret_key");
    r = crypto::secret_key_to_public_key(m_keys.m_spend_secret_key, m_keys.m_account_address.m_spend_public_key);
    CHECK_AND_ASSERT_MES(r, false, "failed to secret_spend_key_to_public_key for view key");
    try
    {
      dependent_key(m_keys.m_spend_secret_key, m_keys.m_view_secret_key);
    }
    catch (...)
    {
      ASSERT_MES_AND_THROW("failed to depend spend key to view key");
      return false;
    }
    r = crypto::secret_key_to_public_key(m_keys.m_view_secret_key, m_keys.m_account_address.m_view_public_key);
    CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_view_key for view key");
    return true;
  }
  //-----------------------------------------------------------------
  std::string account_base::get_public_address_str()
  {
    //TODO: change this code into base 58
    return get_account_address_as_str(m_keys.m_account_address);
  }
  //-----------------------------------------------------------------
  void account_base::make_account_watch_only()
  {
    m_keys.m_spend_secret_key = currency::null_skey;
    m_seed.clear();
  }
  //-----------------------------------------------------------------
  std::string transform_addr_to_str(const account_public_address& addr)
  {
    return get_account_address_as_str(addr);
  }

  account_public_address transform_str_to_addr(const std::string& str)
  {
    account_public_address ad = AUTO_VAL_INIT(ad);
    if (!get_account_address_from_str(ad, str))
    {
      LOG_ERROR("cannot parse address from string: " << str);
    }
    return ad;
  }
}