// DEx & MetaDEx

#include "base58.h"
#include "rpcserver.h"
#include "init.h"
#include "util.h"
#include "wallet.h"

#include <stdint.h>
#include <string.h>

#include <map>
#include <set>

#include <fstream>
#include <algorithm>

#include <vector>

#include <utility>
#include <string>

#include <boost/assign/list_of.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/find.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include "json/json_spirit_utils.h"
#include "json/json_spirit_value.h"

#include "leveldb/db.h"
#include "leveldb/write_batch.h"

#include <openssl/sha.h>

#include <boost/math/constants/constants.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>

using boost::multiprecision::int128_t;
using boost::multiprecision::cpp_int;
using boost::multiprecision::cpp_dec_float;
using boost::multiprecision::cpp_dec_float_100;

using namespace std;
using namespace boost;
using namespace boost::assign;
using namespace json_spirit;
using namespace leveldb;

#include "mastercore.h"

using namespace mastercore;

#include "mastercore_convert.h"
#include "mastercore_dex.h"
#include "mastercore_tx.h"

extern int msc_debug_metadex1, msc_debug_metadex2, msc_debug_metadex3;

md_PropertiesMap mastercore::metadex;

md_PricesMap* mastercore::get_Prices(unsigned int prop)
{
md_PropertiesMap::iterator it = metadex.find(prop);

  if (it != metadex.end()) return &(it->second);

  return (md_PricesMap *) NULL;
}

md_Set* mastercore::get_Indexes(md_PricesMap *p, XDOUBLE price)
{
md_PricesMap::iterator it = p->find(price);

  if (it != p->end()) return &(it->second);

  return (md_Set *) NULL;
}

enum MatchReturnType
{
  NOTHING             = 0,
  TRADED              = 1,
  TRADED_MOREINSELLER,
  TRADED_MOREINBUYER,
  ADDED,
  CANCELLED,
};

const string getTradeReturnType(MatchReturnType ret)
{
  switch (ret)
  {
    case NOTHING: return string("NOTHING");
    case TRADED: return string("TRADED");
    case TRADED_MOREINSELLER: return string("TRADED_MOREINSELLER");
    case TRADED_MOREINBUYER: return string("TRADED_MOREINBUYER");
    case ADDED: return string("ADDED");
    case CANCELLED: return string("CANCELLED");
    default: return string("* unknown *");
  }
}

bool operator==(XDOUBLE first, XDOUBLE second)
{
  return (first.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed) == second.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed));
}

bool operator!=(XDOUBLE first, XDOUBLE second)
{
  return !(first == second);
}

bool operator<=(XDOUBLE first, XDOUBLE second)
{
  return ((first.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed) < second.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed)) || (first == second));
}

bool operator>=(XDOUBLE first, XDOUBLE second)
{
  return ((first.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed) > second.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed)) || (first == second));
}

static void PriceCheck(const string &label, XDOUBLE left, XDOUBLE right)
{
const bool bOK = (left == right);

  file_log("PRICE CHECK %s: buyer = %s , inserted = %s : %s\n", label,
   left.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed),
   right.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), bOK ? "good":"PROBLEM!");
}

// find the best match on the market
// NOTE: sometimes I refer to the older order as seller & the newer order as buyer, in this trade
// INPUT: property, desprop, desprice = of the new order being inserted; the new object being processed
// RETURN:
static MatchReturnType x_Trade(CMPMetaDEx *newo)
{
const CMPMetaDEx *p_older = NULL;
md_PricesMap *prices = NULL;
const unsigned int prop = newo->getProperty();
const unsigned int desprop = newo->getDesProperty();
MatchReturnType NewReturn = NOTHING;
bool bBuyerSatisfied = false;
const XDOUBLE buyersprice = newo->effectivePrice();
const XDOUBLE desprice = (1/buyersprice); // inverse, to be matched against that of the existing older order

  if (msc_debug_metadex1)
  {
    file_log("%s(%s: prop=%u, desprop=%u, desprice= %s);newo: %s\n",
     __FUNCTION__, newo->getAddr(), prop, desprop, desprice.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), newo->ToString());
  }

  prices = get_Prices(desprop);

  // nothing for the desired property exists in the market, sorry!
  if (!prices)
  {
    file_log("%s()=%u:%s NOT FOUND ON THE MARKET\n", __FUNCTION__, NewReturn, getTradeReturnType(NewReturn));
    return NewReturn;
  }

  // within the desired property map (given one property) iterate over the items looking at prices
  for (md_PricesMap::iterator my_it = prices->begin(); my_it != prices->end(); ++my_it)
  { // check all prices
  XDOUBLE sellers_price = (my_it->first);

    if (msc_debug_metadex2) file_log("comparing prices: desprice %s needs to be GREATER THAN OR EQUAL TO %s\n",
     desprice.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), sellers_price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed));

    // Is the desired price check satisfied? The buyer's inverse price must be larger than that of the seller.
    if (desprice < sellers_price) continue;

    md_Set *indexes = &(my_it->second);

    // at good (single) price level and property iterate over offers looking at all parameters to find the match
    md_Set::iterator iitt;
    for (iitt = indexes->begin(); iitt != indexes->end();)
    { // specific price, check all properties
      p_older = &(*iitt);

      if (msc_debug_metadex1) file_log("Looking at existing: %s (its prop= %u, its des prop= %u) = %s\n",
       sellers_price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), p_older->getProperty(), p_older->getDesProperty(), p_older->ToString());

      // is the desired property correct?
        if (p_older->getDesProperty() != prop)
        {
          ++iitt;
          continue;
        }

      if (msc_debug_metadex1) file_log("MATCH FOUND, Trade: %s = %s\n", sellers_price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), p_older->ToString());

        // All Matched ! Trade now.
        // p_older is the old order pointer
        // newo is the new order pointer
        // the price in the older order is used
        const int64_t seller_amountForSale = p_older->getAmountForSale();
        const int64_t seller_amountWanted = p_older->getAmountDesired();
        const int64_t buyer_amountOffered = newo->getAmountForSale();

        if (msc_debug_metadex1) file_log("$$ trading using price: %s; seller: forsale= %ld, wanted= %ld, buyer amount offered= %ld\n",
         sellers_price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), seller_amountForSale, seller_amountWanted, buyer_amountOffered);

        if (msc_debug_metadex1) file_log("$$ old: %s\n", p_older->ToString());
        if (msc_debug_metadex1) file_log("$$ new: %s\n", newo->ToString());

        int64_t seller_amountGot = seller_amountWanted;

        if (buyer_amountOffered < seller_amountWanted)
        {
          seller_amountGot = buyer_amountOffered;
        }

        const int64_t buyer_amountStillForSale = buyer_amountOffered - seller_amountGot;

///////////////////////////
        XDOUBLE x_buyer_got = (XDOUBLE) seller_amountGot / sellers_price;

        x_buyer_got += (XDOUBLE) 0.5; // ROUND UP

        std::string str_buyer_got = x_buyer_got.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed);
        std::string str_buyer_got_int_part = str_buyer_got.substr(0, str_buyer_got.find_first_of("."));
        const int64_t buyer_amountGot = boost::lexical_cast<int64_t>( str_buyer_got_int_part );

        const int64_t seller_amountLeft = p_older->getAmountForSale() - buyer_amountGot;

        if (msc_debug_metadex1) file_log("$$ buyer_got= %ld, seller_got= %ld, seller_left_for_sale= %ld, buyer_still_for_sale= %ld\n",
         buyer_amountGot, seller_amountGot, seller_amountLeft, buyer_amountStillForSale);

        XDOUBLE seller_amount_stilldesired = (XDOUBLE) seller_amountLeft * sellers_price;

        seller_amount_stilldesired += (XDOUBLE) 0.5; // ROUND UP

        std::string str_amount_stilldesired = seller_amount_stilldesired.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed);
        std::string str_stilldesired_int_part = str_amount_stilldesired.substr(0, str_amount_stilldesired.find_first_of("."));

///////////////////////////
        CMPMetaDEx seller_replacement = *p_older;

        seller_replacement.setAmountForSale(seller_amountLeft, "seller_replacement");
        seller_replacement.setAmountDesired(boost::lexical_cast<int64_t>( str_stilldesired_int_part ), "seller_replacement");

        // transfer the payment property from buyer to seller
        // TODO: do something when failing here............
        // FIXME
        // ...
        if (update_tally_map(newo->getAddr(), newo->getProperty(), - seller_amountGot, BALANCE))
        {
          if (update_tally_map(p_older->getAddr(), p_older->getDesProperty(), seller_amountGot, BALANCE))
          {
          }
        }

        // transfer the market (the one being sold) property from seller to buyer
        // TODO: do something when failing here............
        // FIXME
        // ...
        if (update_tally_map(p_older->getAddr(), p_older->getProperty(), - buyer_amountGot, METADEX_RESERVE))
        {
          update_tally_map(newo->getAddr(), newo->getDesProperty(), buyer_amountGot, BALANCE);
        }

        NewReturn = TRADED;

        XDOUBLE will_pay = (XDOUBLE) buyer_amountStillForSale * newo->effectivePrice();

        will_pay += (XDOUBLE) 0.5;  // ROUND UP

        std::string str_will_pay = will_pay.str(INTERNAL_PRECISION_LEN, std::ios_base::fixed);
        std::string str_will_pay_int_part = str_will_pay.substr(0, str_will_pay.find_first_of("."));

        newo->setAmountForSale(buyer_amountStillForSale, "buyer");
        newo->setAmountDesired(boost::lexical_cast<int64_t>( str_will_pay_int_part ), "buyer");

        if (0 < buyer_amountStillForSale)
        {
          NewReturn = TRADED_MOREINBUYER;

          PriceCheck(getTradeReturnType(NewReturn), buyersprice, newo->effectivePrice());
        }
        else
        {
          bBuyerSatisfied = true;
        }

        if (0 < seller_amountLeft)  // done with all loops, update the seller, buyer is fully satisfied
        {
          NewReturn = TRADED_MOREINSELLER;
          bBuyerSatisfied = true;

          PriceCheck(getTradeReturnType(NewReturn), p_older->effectivePrice(), seller_replacement.effectivePrice());
        }

        if (msc_debug_metadex1) file_log("==== TRADED !!! %u=%s\n", NewReturn, getTradeReturnType(NewReturn));

        t_tradelistdb->recordTrade(p_older->getHash(), newo->getHash(),
         p_older->getAddr(), newo->getAddr(), p_older->getDesProperty(), newo->getDesProperty(), seller_amountGot, buyer_amountGot, newo->getBlock());

      if (msc_debug_metadex1) file_log("++ erased old: %s\n", iitt->ToString());
      // erase the old seller element
      indexes->erase(iitt++);

      if (bBuyerSatisfied)
      {
        // insert the updated one in place of the old
        if (0 < seller_replacement.getAmountForSale())
        {
          file_log("++ inserting seller_replacement: %s\n", seller_replacement.ToString());
          indexes->insert(seller_replacement);
        }
        break;
      }
    } // specific price, check all properties

    if (bBuyerSatisfied) break;
  } // check all prices

  file_log("%s()=%u:%s\n", __FUNCTION__, NewReturn, getTradeReturnType(NewReturn));

  return NewReturn;
}

void mastercore::MetaDEx_debug_print(bool bShowPriceLevel, bool bDisplay)
{
  file_log("<<<\n");
  for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it)
  {
    unsigned int prop = my_it->first;

    file_log(" ## property: %u\n", prop);
    md_PricesMap & prices = my_it->second;

    for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it)
    {
      XDOUBLE price = (it->first);
      md_Set & indexes = (it->second);

      if (bShowPriceLevel) file_log("  # Price Level: %s\n", price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed));

      for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it)
      {
      CMPMetaDEx obj = *it;

        if (bDisplay) printf("%s= %s\n", price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed).c_str() , obj.ToString().c_str());
        else file_log("%s= %s\n", price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed) , obj.ToString());

        // extra checks: price or either of the amounts is 0
//        assert((XDOUBLE)0 != obj.effectivePrice());
//        assert(obj.getAmountForSale());
//        assert(obj.getAmountDesired());
      }
    }
  }
  file_log(">>>\n");
}

void CMPMetaDEx::Set(const string &sa, int b, unsigned int c, uint64_t nValue, unsigned int cd, uint64_t ad, const uint256 &tx, unsigned int i, unsigned char suba)
{
  addr = sa;
  block = b;
  txid = tx;
  property = c;
  amount_forsale = nValue;
  desired_property = cd;
  amount_desired = ad;
  idx = i;
  subaction = suba;
}

CMPMetaDEx::CMPMetaDEx(const string &addr, int b, unsigned int c, uint64_t nValue, unsigned int cd, uint64_t ad, const uint256 &tx, unsigned int i, unsigned char suba, uint64_t lfors)
{
  still_left_forsale = lfors;
  Set(addr, b,c,nValue,cd,ad,tx,i,suba);
}

std::string CMPMetaDEx::ToString() const
{
  return strprintf("%s:%34s in %d/%03u, txid: %s , trade #%u %s for #%u %s",
   effectivePrice().str(DISPLAY_PRECISION_LEN, std::ios_base::fixed),
   addr.c_str(), block, idx, txid.ToString().substr(0,10).c_str(),
   property, FormatMP(property, amount_forsale), desired_property, FormatMP(desired_property, amount_desired));
}

// check to see if such a sell offer exists
bool mastercore::DEx_offerExists(const string &seller_addr, unsigned int prop)
{
//  if (msc_debug_dex) file_log("%s()\n", __FUNCTION__);
const string combo = STR_SELLOFFER_ADDR_PROP_COMBO(seller_addr);
OfferMap::iterator my_it = my_offers.find(combo);

  return !(my_it == my_offers.end());
}

// getOffer may replace DEx_offerExists() in the near future
// TODO: locks are needed around map's insert & erase
CMPOffer *mastercore::DEx_getOffer(const string &seller_addr, unsigned int prop)
{
  if (msc_debug_dex) file_log("%s(%s, %u)\n", __FUNCTION__, seller_addr, prop);
const string combo = STR_SELLOFFER_ADDR_PROP_COMBO(seller_addr);
OfferMap::iterator my_it = my_offers.find(combo);

  if (my_it != my_offers.end()) return &(my_it->second);

  return (CMPOffer *) NULL;
}

// TODO: locks are needed around map's insert & erase
CMPAccept *mastercore::DEx_getAccept(const string &seller_addr, unsigned int prop, const string &buyer_addr)
{
  if (msc_debug_dex) file_log("%s(%s, %u, %s)\n", __FUNCTION__, seller_addr, prop, buyer_addr);
const string combo = STR_ACCEPT_ADDR_PROP_ADDR_COMBO(seller_addr, buyer_addr);
AcceptMap::iterator my_it = my_accepts.find(combo);

  if (my_it != my_accepts.end()) return &(my_it->second);

  return (CMPAccept *) NULL;
}

// returns 0 if everything is OK
int mastercore::DEx_offerCreate(string seller_addr, unsigned int prop, uint64_t nValue, int block, uint64_t amount_des, uint64_t fee, unsigned char btl, const uint256 &txid, uint64_t *nAmended)
{
int rc = DEX_ERROR_SELLOFFER;

  // sanity check our params are OK
  if ((!btl) || (!amount_des)) return (DEX_ERROR_SELLOFFER -101); // time limit or amount desired empty

  if (DEx_getOffer(seller_addr, prop)) return (DEX_ERROR_SELLOFFER -10);  // offer already exists

  const string combo = STR_SELLOFFER_ADDR_PROP_COMBO(seller_addr);

  if (msc_debug_dex)
   file_log("%s(%s|%s), nValue=%lu)\n", __FUNCTION__, seller_addr, combo, nValue);

  const uint64_t balanceReallyAvailable = getMPbalance(seller_addr, prop, BALANCE);

  // if offering more than available -- put everything up on sale
  if (nValue > balanceReallyAvailable)
  {
  double BTC;

    // AND we must also re-adjust the BTC desired in this case...
    BTC = amount_des * balanceReallyAvailable;
    BTC /= (double)nValue;
    amount_des = rounduint64(BTC);

    nValue = balanceReallyAvailable;

    if (nAmended) *nAmended = nValue;
  }

  if (update_tally_map(seller_addr, prop, - nValue, BALANCE)) // subtract from what's available
  {
    update_tally_map(seller_addr, prop, nValue, SELLOFFER_RESERVE); // put in reserve

    my_offers.insert(std::make_pair(combo, CMPOffer(block, nValue, prop, amount_des, fee, btl, txid)));

    rc = 0;
  }

  return rc;
}

// returns 0 if everything is OK
int mastercore::DEx_offerDestroy(const string &seller_addr, unsigned int prop)
{
const uint64_t amount = getMPbalance(seller_addr, prop, SELLOFFER_RESERVE);

  if (!DEx_offerExists(seller_addr, prop)) return (DEX_ERROR_SELLOFFER -11); // offer does not exist

  const string combo = STR_SELLOFFER_ADDR_PROP_COMBO(seller_addr);

  OfferMap::iterator my_it;

  my_it = my_offers.find(combo);

  if (amount)
  {
    update_tally_map(seller_addr, prop, amount, BALANCE);   // give back to the seller from SellOffer-Reserve
    update_tally_map(seller_addr, prop, - amount, SELLOFFER_RESERVE);
  }

  // delete the offer
  my_offers.erase(my_it);

  if (msc_debug_dex)
   file_log("%s(%s|%s)\n", __FUNCTION__, seller_addr, combo);

  return 0;
}

// returns 0 if everything is OK
int mastercore::DEx_offerUpdate(const string &seller_addr, unsigned int prop, uint64_t nValue, int block, uint64_t desired, uint64_t fee, unsigned char btl, const uint256 &txid, uint64_t *nAmended)
{
int rc = DEX_ERROR_SELLOFFER;

  file_log("%s(%s, %d)\n", __FUNCTION__, seller_addr, prop);

  if (!DEx_offerExists(seller_addr, prop)) return (DEX_ERROR_SELLOFFER -12); // offer does not exist

  rc = DEx_offerDestroy(seller_addr, prop);

  if (!rc)
  {
    rc = DEx_offerCreate(seller_addr, prop, nValue, block, desired, fee, btl, txid, nAmended);
  }

  return rc;
}

// returns 0 if everything is OK
int mastercore::DEx_acceptCreate(const string &buyer, const string &seller, int prop, uint64_t nValue, int block, uint64_t fee_paid, uint64_t *nAmended)
{
int rc = DEX_ERROR_ACCEPT - 10;
OfferMap::iterator my_it;
const string selloffer_combo = STR_SELLOFFER_ADDR_PROP_COMBO(seller);
const string accept_combo = STR_ACCEPT_ADDR_PROP_ADDR_COMBO(seller, buyer);
uint64_t nActualAmount = getMPbalance(seller, prop, SELLOFFER_RESERVE);

  my_it = my_offers.find(selloffer_combo);

  if (my_it == my_offers.end()) return DEX_ERROR_ACCEPT -15;

  CMPOffer &offer = my_it->second;

  if (msc_debug_dex) file_log("%s(offer: %s)\n", __FUNCTION__, offer.getHash().GetHex());

  // here we ensure the correct BTC fee was paid in this acceptance message, per spec
  if (fee_paid < offer.getMinFee())
  {
    file_log("ERROR: fee too small -- the ACCEPT is rejected! (%lu is smaller than %lu)\n", fee_paid, offer.getMinFee());
    return DEX_ERROR_ACCEPT -105;
  }

  file_log("%s(%s) OFFER FOUND\n", __FUNCTION__, selloffer_combo);

  // the older accept is the valid one: do not accept any new ones!
  if (DEx_getAccept(seller, prop, buyer))
  {
    file_log("%s() ERROR: an accept from this same seller for this same offer is already open !!!!!\n", __FUNCTION__);
    return DEX_ERROR_ACCEPT -205;
  }

  if (nActualAmount > nValue)
  {
    nActualAmount = nValue;

    if (nAmended) *nAmended = nActualAmount;
  }

  // TODO: think if we want to save nValue -- as the amount coming off the wire into the object or not
  if (update_tally_map(seller, prop, - nActualAmount, SELLOFFER_RESERVE))
  {
    if (update_tally_map(seller, prop, nActualAmount, ACCEPT_RESERVE))
    {
      // insert into the map !
      my_accepts.insert(std::make_pair(accept_combo, CMPAccept(nActualAmount, block,
       offer.getBlockTimeLimit(), offer.getProperty(), offer.getOfferAmountOriginal(), offer.getBTCDesiredOriginal(), offer.getHash() )));

      rc = 0;
    }
  }

  return rc;
}

// this function is called by handler_block() for each Accept that has expired
// this function is also called when the purchase has been completed (the buyer bought everything he was allocated)
//
// returns 0 if everything is OK
int mastercore::DEx_acceptDestroy(const string &buyer, const string &seller, int prop, bool bForceErase)
{
int rc = DEX_ERROR_ACCEPT - 20;
CMPOffer *p_offer = DEx_getOffer(seller, prop);
CMPAccept *p_accept = DEx_getAccept(seller, prop, buyer);
bool bReturnToMoney; // return to BALANCE of the seller, otherwise return to SELLOFFER_RESERVE
const string accept_combo = STR_ACCEPT_ADDR_PROP_ADDR_COMBO(seller, buyer);

  if (!p_accept) return rc; // sanity check

  const uint64_t nActualAmount = p_accept->getAcceptAmountRemaining();

  // if the offer is gone ACCEPT_RESERVE should go back to BALANCE
  if (!p_offer)
  {
    bReturnToMoney = true;
  }
  else
  {
    file_log("%s() HASHES: offer=%s, accept=%s\n", __FUNCTION__, p_offer->getHash().GetHex(), p_accept->getHash().GetHex());

    // offer exists, determine whether it's the original offer or some random new one
    if (p_offer->getHash() == p_accept->getHash())
    {
      // same offer, return to SELLOFFER_RESERVE
      bReturnToMoney = false;
    }
    else
    {
      // old offer is gone !
      bReturnToMoney = true;
    }
  }

  if (bReturnToMoney)
  {
    if (update_tally_map(seller, prop, - nActualAmount, ACCEPT_RESERVE))
    {
      update_tally_map(seller, prop, nActualAmount, BALANCE);
      rc = 0;
    }
  }
  else
  {
    // return to SELLOFFER_RESERVE
    if (update_tally_map(seller, prop, - nActualAmount, ACCEPT_RESERVE))
    {
      update_tally_map(seller, prop, nActualAmount, SELLOFFER_RESERVE);
      rc = 0;
    }
  }

  // can only erase when is NOT called from an iterator loop
  if (bForceErase)
  {
  const AcceptMap::iterator my_it = my_accepts.find(accept_combo);

    if (my_accepts.end() !=my_it) my_accepts.erase(my_it);
  }

  return rc;
}

// incoming BTC payment for the offer
// TODO: verify proper partial payment handling
int mastercore::DEx_payment(uint256 txid, unsigned int vout, string seller, string buyer, uint64_t BTC_paid, int blockNow, uint64_t *nAmended)
{
//  if (msc_debug_dex) file_log("%s()\n", __FUNCTION__);
int rc = DEX_ERROR_PAYMENT;
CMPAccept *p_accept;
int prop;

prop = OMNI_PROPERTY_MSC; //test for MSC accept first
p_accept = DEx_getAccept(seller, prop, buyer);

  if (!p_accept)
  {
    prop = OMNI_PROPERTY_TMSC; //test for TMSC accept second
    p_accept = DEx_getAccept(seller, prop, buyer);
  }

  if (msc_debug_dex) file_log("%s(%s, %s)\n", __FUNCTION__, seller, buyer);

  if (!p_accept) return (DEX_ERROR_PAYMENT -1);  // there must be an active Accept for this payment

  const double BTC_desired_original = p_accept->getBTCDesiredOriginal();
  const double offer_amount_original = p_accept->getOfferAmountOriginal();

  if (0==(double)BTC_desired_original) return (DEX_ERROR_PAYMENT -2);  // divide by 0 protection

  double perc_X = (double)BTC_paid/BTC_desired_original;
  double Purchased = offer_amount_original * perc_X;

  uint64_t units_purchased = rounduint64(Purchased);

  const uint64_t nActualAmount = p_accept->getAcceptAmountRemaining();  // actual amount desired, in the Accept

  if (msc_debug_dex)
   file_log("BTC_desired= %30.20lf , offer_amount=%30.20lf , perc_X= %30.20lf , Purchased= %30.20lf , units_purchased= %lu\n",
   BTC_desired_original, offer_amount_original, perc_X, Purchased, units_purchased);

  // if units_purchased is greater than what's in the Accept, the buyer gets only what's in the Accept
  if (nActualAmount < units_purchased)
  {
    units_purchased = nActualAmount;

    if (nAmended) *nAmended = units_purchased;
  }

  if (update_tally_map(seller, prop, - units_purchased, ACCEPT_RESERVE))
  {
      update_tally_map(buyer, prop, units_purchased, BALANCE);
      rc = 0;
      bool bValid = true;
      p_txlistdb->recordPaymentTX(txid, bValid, blockNow, vout, prop, units_purchased, buyer, seller);

      file_log("#######################################################\n");
  }

  // reduce the amount of units still desired by the buyer and if 0 must destroy the Accept
  if (p_accept->reduceAcceptAmountRemaining_andIsZero(units_purchased))
  {
  const uint64_t selloffer_reserve = getMPbalance(seller, prop, SELLOFFER_RESERVE);
  const uint64_t accept_reserve = getMPbalance(seller, prop, ACCEPT_RESERVE);

    DEx_acceptDestroy(buyer, seller, prop, true);

    // delete the Offer object if there is nothing in its Reserves -- everything got puchased and paid for
    if ((0 == selloffer_reserve) && (0 == accept_reserve))
    {
      DEx_offerDestroy(seller, prop);
    }
  }

  return rc;
}

unsigned int eraseExpiredAccepts(int blockNow)
{
unsigned int how_many_erased = 0;
AcceptMap::iterator my_it = my_accepts.begin();

  while (my_accepts.end() != my_it)
  {
    // my_it->first = key
    // my_it->second = value

    CMPAccept &mpaccept = my_it->second;

    if ((blockNow - mpaccept.block) >= (int) mpaccept.getBlockTimeLimit())
    {
      file_log("%s() FOUND EXPIRED ACCEPT, erasing: blockNow=%d, offer block=%d, blocktimelimit= %d\n",
       __FUNCTION__, blockNow, mpaccept.block, mpaccept.getBlockTimeLimit());

      // extract the seller, buyer & property from the Key
      std::vector<std::string> vstr;
      boost::split(vstr, my_it->first, boost::is_any_of("-+"), token_compress_on);
      string seller = vstr[0];
      int property = atoi(vstr[1]);
      string buyer = vstr[2];

      DEx_acceptDestroy(buyer, seller, property);

      my_accepts.erase(my_it++);

      ++how_many_erased;
    }
    else my_it++;

  }

  return how_many_erased;
}

// pretty much directly linked to the ADD TX21 command off the wire
int mastercore::MetaDEx_ADD(const string &sender_addr, unsigned int prop, uint64_t amount, int block, unsigned int property_desired, uint64_t amount_desired, const uint256 &txid, unsigned int idx)
{
int rc = METADEX_ERROR -1;

  // MetaDEx implementation phase 1 check
  if ((prop != OMNI_PROPERTY_MSC) && (property_desired != OMNI_PROPERTY_MSC) &&
   (prop != OMNI_PROPERTY_TMSC) && (property_desired != OMNI_PROPERTY_TMSC))
  {
    return METADEX_ERROR -800;
  }

    // store the data into the temp MetaDEx object here
    CMPMetaDEx new_mdex(sender_addr, block, prop, amount, property_desired, amount_desired, txid, idx, CMPTransaction::ADD);
    XDOUBLE neworder_buyersprice = new_mdex.effectivePrice();

    if (msc_debug_metadex1) file_log("%s(); buyer obj: %s\n", __FUNCTION__, new_mdex.ToString());

    // given the property & the price find the proper place for insertion

    // TODO: reconsider for boost::multiprecision
    // FIXME
    if (0 >= neworder_buyersprice)
    {
      // do not work with 0 prices
      return METADEX_ERROR -66;
    }

    if (msc_debug_metadex3) MetaDEx_debug_print();

    // TRADE, check matches, remainder of the order will be put into the order book
    x_Trade(&new_mdex);

    if (msc_debug_metadex3) MetaDEx_debug_print();

#if 0
    // if anything is left in the new order, INSERT
    if ((0 < new_mdex.getAmountForSale()) && (!disable_Combo))
    {
      x_AddOrCancel(&new_mdex); // straight match to ADD
    }
#endif

    if (msc_debug_metadex3) MetaDEx_debug_print();

    // plain insert
    if (0 < new_mdex.getAmountForSale())
    { // not added nor subtracted, insert as new or post-traded amounts
    md_PricesMap temp_prices, *p_prices = get_Prices(prop);
    md_Set temp_indexes, *p_indexes = NULL;
    std::pair<md_Set::iterator,bool> ret;

      if (p_prices)
      {
        p_indexes = get_Indexes(p_prices, neworder_buyersprice);
      }

      if (!p_indexes) p_indexes = &temp_indexes;

      ret = p_indexes->insert(new_mdex);

      if (false == ret.second)
      {
        file_log("%s() ERROR: ALREADY EXISTS, line %d, file: %s\n", __FUNCTION__, __LINE__, __FILE__);
      }
      else
      {
        // TODO: think about failure scenarios
        // FIXME
        if (update_tally_map(sender_addr, prop, - new_mdex.getAmountForSale(), BALANCE)) // subtract from what's available
        {
          // TODO: think about failure scenarios
          // FIXME
          update_tally_map(sender_addr, prop, new_mdex.getAmountForSale(), METADEX_RESERVE); // put in reserve
        }

        // price check
        PriceCheck("Insert", neworder_buyersprice, new_mdex.effectivePrice());

        if (msc_debug_metadex1) file_log("==== INSERTED: %s= %s\n", neworder_buyersprice.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed), new_mdex.ToString());
      }

      if (!p_prices) p_prices = &temp_prices;

      (*p_prices)[neworder_buyersprice] = *p_indexes;

      metadex[prop] = *p_prices;
    } // Must Insert

  rc = 0;

  if (msc_debug_metadex3) MetaDEx_debug_print();

  return rc;
}

int mastercore::MetaDEx_CANCEL_AT_PRICE(const uint256 txid, unsigned int block, const string &sender_addr, unsigned int prop, uint64_t amount, unsigned int property_desired, uint64_t amount_desired)
{
int rc = METADEX_ERROR -20;
CMPMetaDEx mdex(sender_addr, 0, prop, amount, property_desired, amount_desired, 0, 0, CMPTransaction::CANCEL_AT_PRICE);
md_PricesMap *prices = get_Prices(prop);
const CMPMetaDEx *p_mdex = NULL;

  if (msc_debug_metadex1) file_log("%s():%s\n", __FUNCTION__, mdex.ToString());

  if (msc_debug_metadex2) MetaDEx_debug_print();

  if (!prices)
  {
    file_log("%s() NOTHING FOUND for %s\n", __FUNCTION__, mdex.ToString());
    return rc -1;
  }

  // within the desired property map (given one property) iterate over the items
  for (md_PricesMap::iterator my_it = prices->begin(); my_it != prices->end(); ++my_it)
  {
  XDOUBLE sellers_price = (my_it->first);

    if (mdex.effectivePrice() != sellers_price) continue;

    md_Set *indexes = &(my_it->second);

    for (md_Set::iterator iitt = indexes->begin(); iitt != indexes->end();)
    { // for iitt
      p_mdex = &(*iitt);

      if (msc_debug_metadex3) file_log("%s(): %s\n", __FUNCTION__, p_mdex->ToString());

      if ((p_mdex->getDesProperty() != property_desired) || (p_mdex->getAddr() != sender_addr))
      {
        ++iitt;
        continue;
      }

      rc = 0;
      file_log("%s(): REMOVING %s\n", __FUNCTION__, p_mdex->ToString());

      // move from reserve to main
      update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), - p_mdex->getAmountForSale(), METADEX_RESERVE);
      update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), p_mdex->getAmountForSale(), BALANCE);

      // record the cancellation
      bool bValid = true;
      p_txlistdb->recordMetaDExCancelTX(txid, p_mdex->getHash(), bValid, block, p_mdex->getProperty(), p_mdex->getAmountForSale());

      indexes->erase(iitt++);
    }
  }

  if (msc_debug_metadex2) MetaDEx_debug_print();

  return rc;
}

int mastercore::MetaDEx_CANCEL_ALL_FOR_PAIR(const uint256 txid, unsigned int block, const string &sender_addr, unsigned int prop, unsigned int property_desired)
{
int rc = METADEX_ERROR -30;
md_PricesMap *prices = get_Prices(prop);
const CMPMetaDEx *p_mdex = NULL;

  file_log("%s(%d,%d)\n", __FUNCTION__, prop, property_desired);

  if (msc_debug_metadex3) MetaDEx_debug_print();

  if (!prices)
  {
    file_log("%s() NOTHING FOUND\n", __FUNCTION__);
    return rc -1;
  }

  // within the desired property map (given one property) iterate over the items
  for (md_PricesMap::iterator my_it = prices->begin(); my_it != prices->end(); ++my_it)
  {
  md_Set *indexes = &(my_it->second);

    for (md_Set::iterator iitt = indexes->begin(); iitt != indexes->end();)
    { // for iitt
      p_mdex = &(*iitt);

      if (msc_debug_metadex3) file_log("%s(): %s\n", __FUNCTION__, p_mdex->ToString());

      if ((p_mdex->getDesProperty() != property_desired) || (p_mdex->getAddr() != sender_addr))
      {
        ++iitt;
        continue;
      }

      rc = 0;
      file_log("%s(): REMOVING %s\n", __FUNCTION__, p_mdex->ToString());

      // move from reserve to main
      update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), - p_mdex->getAmountForSale(), METADEX_RESERVE);
      update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), p_mdex->getAmountForSale(), BALANCE);

      // record the cancellation
      bool bValid = true;
      p_txlistdb->recordMetaDExCancelTX(txid, p_mdex->getHash(), bValid, block, p_mdex->getProperty(), p_mdex->getAmountForSale());

      indexes->erase(iitt++);
    }
  }

  if (msc_debug_metadex3) MetaDEx_debug_print();

  return rc;
}

// scan the orderbook and remove everything for an address
int mastercore::MetaDEx_CANCEL_EVERYTHING(const uint256 txid, unsigned int block, const string &sender_addr)
{
int rc = METADEX_ERROR -40;

  file_log("%s()\n", __FUNCTION__);

  if (msc_debug_metadex2) MetaDEx_debug_print();

  file_log("<<<<<<\n");

  for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it)
  {
    unsigned int prop = my_it->first;

    file_log(" ## property: %u\n", prop);
    md_PricesMap & prices = my_it->second;

    for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it)
    {
      XDOUBLE price = (it->first);
      md_Set & indexes = (it->second);

      file_log("  # Price Level: %s\n", price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed));

      for (md_Set::iterator it = indexes.begin(); it != indexes.end();)
      {
        file_log("%s= %s\n", price.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed) , it->ToString());

        if ((it->getAddr() != sender_addr))
        {
          ++it;
          continue;
        }

        rc = 0;
        file_log("%s(): REMOVING %s\n", __FUNCTION__, it->ToString());

        // move from reserve to balance
        update_tally_map(it->getAddr(), it->getProperty(), - it->getAmountForSale(), METADEX_RESERVE);
        update_tally_map(it->getAddr(), it->getProperty(), it->getAmountForSale(), BALANCE);

        // record the cancellation
        bool bValid = true;
        p_txlistdb->recordMetaDExCancelTX(txid, it->getHash(), bValid, block, it->getProperty(), it->getAmountForSale());

        indexes.erase(it++);
      }
    }
  }
  file_log(">>>>>>\n");

  if (msc_debug_metadex2) MetaDEx_debug_print();

  return rc;
}

bool MetaDEx_compare::operator()(const CMPMetaDEx &lhs, const CMPMetaDEx &rhs) const
{
  if (lhs.getBlock() == rhs.getBlock()) return lhs.getIdx() < rhs.getIdx();
  else return lhs.getBlock() < rhs.getBlock();
}

void CMPMetaDEx::saveOffer(ofstream &file, SHA256_CTX *shaCtx) const
{
    string lineOut = (boost::format("%s,%d,%d,%d,%d,%d,%d,%d,%s,%d")
      % addr
      % block
      % amount_forsale
      % property
      % amount_desired
      % desired_property
      % (unsigned int) subaction
      % idx
      % txid.ToString()
      % still_left_forsale
      ).str();

    // add the line to the hash
    SHA256_Update(shaCtx, lineOut.c_str(), lineOut.length());

    // write the line
    file << lineOut << endl;
}


/*
 * Copyright (c) 2015, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/** \file
 * \brief State for corpus generator.
  */

#include "config.h"

#include "ng_corpus_properties.h"
#include "ue2common.h"

#include <boost/random/uniform_int_distribution.hpp>

// default constructor
CorpusProperties::CorpusProperties()
    : matchness(100), unmatchness(0), randomness(0), prefixRange(0, 0),
      suffixRange(0, 0), cycleMin(1), cycleMax(1),
      corpusLimit(DEFAULT_CORPUS_GENERATOR_LIMIT), editDistance(0),
      alphabetSize(~0) {
    // empty
}

bool CorpusProperties::setPercentages(unsigned int match, unsigned int unmatch,
                                      unsigned int random) {
    if (match + unmatch + random != 100) {
        // Do not update probabilities
        return false;
    }
    matchness = match;
    unmatchness = unmatch;
    randomness = random;
    return true;
}

void CorpusProperties::seed(unsigned val) {
    rngSeed = val;
    randomGen.seed(val);
}

unsigned CorpusProperties::getSeed() const {
    return rngSeed;
}

unsigned CorpusProperties::rand(unsigned n, unsigned m) {
    boost::random::uniform_int_distribution<> dist(n, m);
    return dist(randomGen);
}

// not const because it stores state for the random number generator
CorpusProperties::RollResult CorpusProperties::throwDice() {
    if (matchness == 100) {
        return ROLLED_MATCH;
    }
    if (unmatchness == 100) {
        return ROLLED_UNMATCH;
    }
    if (randomness == 100) {
        return ROLLED_RANDOM;
    }

    // This assumes a uniform distribution.  Perhaps factor some 'depth' param
    // and whether this 'depth' should increase or decrease the likelihood of
    // unmatch or random rolls.
    unsigned int outcome = rand(0, 99);
    if (outcome < matchness) {
        return ROLLED_MATCH;
    }
    if (outcome < matchness + unmatchness) {
        return ROLLED_UNMATCH;
    }

    return ROLLED_RANDOM;
}


// Copyright 2015-2020 Tier IV, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <quaternion_operation/quaternion_operation.h>

#include <boost/algorithm/clamp.hpp>
#include <iostream>
#include <memory>
#include <string>
#include <traffic_simulator/behavior/pedestrian/follow_lane_action.hpp>
#include <vector>

namespace entity_behavior
{
namespace pedestrian
{
FollowLaneAction::FollowLaneAction(const std::string & name, const BT::NodeConfiguration & config)
: entity_behavior::PedestrianActionNode(name, config)
{
}

void FollowLaneAction::getBlackBoardValues() { PedestrianActionNode::getBlackBoardValues(); }

BT::NodeStatus FollowLaneAction::tick()
{
  getBlackBoardValues();
  if (request != "none" && request != "follow_lane") {
    return BT::NodeStatus::FAILURE;
  }
  if (!entity_status.lanelet_pose_valid) {
    setOutput("updated_status", stopAtEndOfRoad());
    return BT::NodeStatus::RUNNING;
  }
  auto following_lanelets =
    hdmap_utils->getFollowingLanelets(entity_status.lanelet_pose.lanelet_id);
  if (!target_speed) {
    target_speed = hdmap_utils->getSpeedLimit(following_lanelets);
  }
  geometry_msgs::msg::Accel accel_new;
  accel_new = entity_status.action_status.accel;

  double target_accel =
    (target_speed.get() - entity_status.action_status.twist.linear.x) / step_time;
  if (entity_status.action_status.twist.linear.x > target_speed.get()) {
    target_accel = boost::algorithm::clamp(target_accel, -5, 0);
    /*　target_accel = boost::algorithm::clamp(target_accel,
      -1*vehicle_param_ptr->performance.max_deceleration, vehicle_param_ptr->performance.max_acceleration);
      */
  } else {
    target_accel = boost::algorithm::clamp(target_accel, 0, 3);
    /* target_accel = boost::algorithm::clamp(target_accel,
      -1*vehicle_param_ptr->performance.max_deceleration, vehicle_param_ptr->performance.max_acceleration);*/
  }
  accel_new.linear.x = target_accel;
  geometry_msgs::msg::Twist twist_new;
  twist_new.linear.x = boost::algorithm::clamp(
    entity_status.action_status.twist.linear.x + accel_new.linear.x * step_time, 0, 5.0);
  twist_new.linear.y = 0.0;
  twist_new.linear.z = 0.0;
  twist_new.angular.x = 0.0;
  twist_new.angular.y = 0.0;
  twist_new.angular.z = 0.0;

  double new_s =
    entity_status.lanelet_pose.s +
    (twist_new.linear.x + entity_status.action_status.twist.linear.x) / 2.0 * step_time;
  geometry_msgs::msg::Vector3 rpy = entity_status.lanelet_pose.rpy;

  openscenario_msgs::msg::EntityStatus entity_status_updated;
  entity_status_updated.time = current_time + step_time;
  entity_status_updated.lanelet_pose.lanelet_id = entity_status.lanelet_pose.lanelet_id;
  entity_status_updated.lanelet_pose.s = new_s;
  entity_status_updated.lanelet_pose.offset = entity_status.lanelet_pose.offset;
  entity_status_updated.lanelet_pose.rpy = rpy;
  entity_status_updated.action_status.twist = twist_new;
  entity_status_updated.action_status.accel = accel_new;
  entity_status_updated.pose = hdmap_utils->toMapPose(entity_status.lanelet_pose).pose;
  setOutput("updated_status", entity_status_updated);
  return BT::NodeStatus::RUNNING;
}
}  // namespace pedestrian
}  // namespace entity_behavior


/* Copyright (c) 2012 Cheese and Bacon Games, LLC */
/* This file is licensed under the MIT License. */
/* See the file docs/LICENSE.txt for the full license text. */

#include "cipher.h"
#include "pixels.h"

#include <boost/crc.hpp>

using namespace std;

const vector<unsigned char> Cipher::characters=Cipher::generate_characters();

vector<unsigned char> Cipher::generate_characters(){
    vector<unsigned char> temp_characters;

    for(unsigned char i=' ';i<='~';i++){
        temp_characters.push_back(i);
    }

    temp_characters.push_back('\n');

    return temp_characters;
}

int Cipher::get_max_character_index(){
    return characters.size()-1;
}

int Cipher::get_character_index(unsigned char input){
    for(int i=0;i<characters.size();i++){
        if(input==characters[i]){
            return i;
        }
    }

    return 0;
}

unsigned char Cipher::get_character(int input){
    if(input<0 || input>get_max_character_index()){
        input=0;
    }

    return characters[input];
}

vector<RNG> Cipher::generate_rngs(){
    vector<RNG> rngs;

    for(size_t i=0;i<seeds.size();i++){
        rngs.push_back(RNG(seeds[i]));
    }

    return rngs;
}

unsigned char Cipher::encipher_character(unsigned char character,vector<RNG>& rngs){
    int character_index=get_character_index(character);

    for(size_t i=0;i<rngs.size();i++){
        character_index+=(int)rngs[i].random_range(0,(uint32_t)get_max_character_index());

        while(character_index>get_max_character_index()){
            character_index-=get_max_character_index()+1;
        }
    }

    return get_character(character_index);
}

unsigned char Cipher::decipher_character(unsigned char character,vector<RNG>& rngs){
    int character_index=get_character_index(character);

    for(size_t i=rngs.size()-1;;i--){
        character_index-=(int)rngs[i].random_range(0,(uint32_t)get_max_character_index());

        while(character_index<0){
            character_index+=get_max_character_index()+1;
        }

        if(i==0){
            break;
        }
    }

    return get_character(character_index);
}

void Cipher::cipher(string& input,bool enciphering){
    if(seeds.size()>0){
        vector<RNG> rngs=generate_rngs();

        for(size_t i=0;i<input.length();i++){
            if(enciphering){
                input[i]=encipher_character(input[i],rngs);
            }
            else{
                input[i]=decipher_character(input[i],rngs);
            }
        }
    }
}

string Cipher::cipher_copy(string input,bool enciphering){
    cipher(input,enciphering);

    return input;
}

void Cipher::encipher_pixel(SDL_Surface* surface,int x,int y,vector<RNG>& rngs){
    Color color=surface_get_pixel(surface,x,y);
    int red=color.get_red_short();
    int green=color.get_green_short();
    int blue=color.get_blue_short();
    int alpha=color.get_alpha_short();

    for(size_t i=0;i<rngs.size();i++){
        red+=(int)rngs[i].random_range(0,255);
        green+=(int)rngs[i].random_range(0,255);
        blue+=(int)rngs[i].random_range(0,255);
        alpha+=(int)rngs[i].random_range(0,255);

        while(red>255){
            red-=256;
        }
        while(green>255){
            green-=256;
        }
        while(blue>255){
            blue-=256;
        }
        while(alpha>255){
            alpha-=256;
        }
    }

    color.set_rgb(red,green,blue,alpha);

    surface_put_pixel(surface,x,y,color);
}

void Cipher::decipher_pixel(SDL_Surface* surface,int x,int y,vector<RNG>& rngs){
    Color color=surface_get_pixel(surface,x,y);
    int red=color.get_red_short();
    int green=color.get_green_short();
    int blue=color.get_blue_short();
    int alpha=color.get_alpha_short();

    for(size_t i=rngs.size()-1;;i--){
        red-=(int)rngs[i].random_range(0,255);
        green-=(int)rngs[i].random_range(0,255);
        blue-=(int)rngs[i].random_range(0,255);
        alpha-=(int)rngs[i].random_range(0,255);

        while(red<0){
            red+=256;
        }
        while(green<0){
            green+=256;
        }
        while(blue<0){
            blue+=256;
        }
        while(alpha<0){
            alpha+=256;
        }

        if(i==0){
            break;
        }
    }

    color.set_rgb(red,green,blue,alpha);

    surface_put_pixel(surface,x,y,color);
}

void Cipher::cipher(SDL_Surface* input,bool enciphering){
    if(seeds.size()>0){
        vector<RNG> rngs=generate_rngs();

        for(int x=0;x<input->w;x++){
            for(int y=0;y<input->h;y++){
                if(enciphering){
                    encipher_pixel(input,x,y,rngs);
                }
                else{
                    decipher_pixel(input,x,y,rngs);
                }
            }
        }
    }
}

uint8_t Cipher::encipher_sound_byte(uint8_t sound_byte,vector<RNG>& rngs){
    int input=sound_byte;

    for(size_t i=0;i<rngs.size();i++){
        input+=(int)rngs[i].random_range(0,255);

        while(input>255){
            input-=256;
        }
    }

    return (uint8_t)input;
}

uint8_t Cipher::decipher_sound_byte(uint8_t sound_byte,vector<RNG>& rngs){
    int input=sound_byte;

    for(size_t i=rngs.size()-1;;i--){
        input-=(int)rngs[i].random_range(0,255);

        while(input<0){
            input+=256;
        }

        if(i==0){
            break;
        }
    }

    return (uint8_t)input;
}

void Cipher::cipher(Mix_Chunk* input,bool enciphering){
    if(seeds.size()>0){
        vector<RNG> rngs=generate_rngs();

        for(size_t i=0;i<input->alen;i++){
            if(enciphering){
                input->abuf[i]=encipher_sound_byte(input->abuf[i],rngs);
            }
            else{
                input->abuf[i]=decipher_sound_byte(input->abuf[i],rngs);
            }
        }
    }
}

Cipher::Cipher(const vector<string>& seed_strings){
    set_seeds(seed_strings);
}

void Cipher::set_seeds(const vector<string>& seed_strings){
    seeds.clear();

    for(size_t i=0;i<seed_strings.size();i++){
        if(seed_strings[i].length()>0){
            boost::crc_32_type result;
            result.process_bytes(seed_strings[i].data(),seed_strings[i].length());

            seeds.push_back((uint32_t)result.checksum());
        }
    }
}

void Cipher::encipher(string& input){
    cipher(input,true);
}

void Cipher::decipher(string& input){
    cipher(input,false);
}

string Cipher::encipher_copy(const string& input){
    return cipher_copy(input,true);
}

string Cipher::decipher_copy(const string& input){
    return cipher_copy(input,false);
}

void Cipher::encipher(SDL_Surface* input){
    cipher(input,true);
}

void Cipher::decipher(SDL_Surface* input){
    cipher(input,false);
}

void Cipher::encipher(Mix_Chunk* input){
    cipher(input,true);
}

void Cipher::decipher(Mix_Chunk* input){
    cipher(input,false);
}


#ifndef BackgroundHandler_cc
#define BackgroundHandler_cc

#include "MuonAnalysis/MomentumScaleCalibration/interface/BackgroundHandler.h"
#include <algorithm>
#include <TF1.h>
#include <iostream>
#include <boost/foreach.hpp>

typedef reco::Particle::LorentzVector lorentzVector;

BackgroundHandler::BackgroundHandler( const std::vector<int> & identifiers,
                                      const std::vector<double> & leftWindowBorders,
                                      const std::vector<double> & rightWindowBorders,
                                      const double * ResMass,
                                      const double * massWindowHalfWidth )
{
  // : leftWindowFactors_(leftWindowFactors),
  // rightWindowFactors_(rightWindowFactors)

  // Define the correspondence between regions and halfWidth to use
  // Defines also the function type to use (but they are checked to be consistent over a region)
  regToResHW_[0] = 0; // Region 0 use the one from Z
  regToResHW_[1] = 3; // Region 1 use the one from Upsilon1S
  regToResHW_[2] = 5; // Region 2 use the one from J/Psi

  // Define the correspondence between resonances and regions
  resToReg_[0] = 0; // Z
  resToReg_[1] = 1; // Upsilon3S
  resToReg_[2] = 1; // Upsilon2S
  resToReg_[3] = 1; // Upsilon1S
  resToReg_[4] = 2; // Psi2S
  resToReg_[5] = 2; // J/Psi

  // Throws cms::Exception("Configuration") in case the parameters are not what is expected
  consistencyCheck(identifiers, leftWindowBorders, rightWindowBorders);

  // Build the resonance windows
  for( unsigned int i=0; i<6; ++i ) {
    double mass = ResMass[i];
    double lowerLimit = mass - massWindowHalfWidth[i];
    double upperLimit = mass + massWindowHalfWidth[i];
    resonanceWindow_.push_back( MassWindow( mass, lowerLimit, upperLimit,
                                            std::vector<unsigned int>(1,i),
                                            backgroundFunctionService(identifiers[resToReg_[i]],
                                                                      lowerLimit,
                                                                      upperLimit) ) );
  }

  // Build the background windows
  // ----------------------------
  // Compute the mass center of each region
  double resMassForRegion[3];
  resMassForRegion[0] = ResMass[0];
  resMassForRegion[1] = (ResMass[1]+ResMass[2]+ResMass[3])/3;
  resMassForRegion[2] = (ResMass[4]+ResMass[5])/2;

  // Define which resonance is in which background window
  std::vector<std::vector<unsigned int> > indexes;
  indexes.push_back(std::vector<unsigned int>(1,0));
  indexes.push_back(std::vector<unsigned int>());
  for( int i=1; i<=3; ++i ) { indexes[1].push_back(i); }
  indexes.push_back(std::vector<unsigned int>());
  for( int i=4; i<=5; ++i ) { indexes[2].push_back(i); }

  unsigned int i=0;
  typedef std::vector<unsigned int> indexVec;
  BOOST_FOREACH(const indexVec & index, indexes) {
    //     double lowerLimit = resMassForRegion[i] - massWindowHalfWidth[regToResHW_[i]]*leftWindowFactors[i];
    //     double upperLimit = resMassForRegion[i] + massWindowHalfWidth[regToResHW_[i]]*rightWindowFactors[i];
    //     backgroundWindow_.push_back( MassWindow( resMassForRegion[i], lowerLimit, upperLimit, index,
    //                                              backgroundFunctionService(identifiers[i], lowerLimit, upperLimit ) ) );
    backgroundWindow_.push_back( MassWindow( resMassForRegion[i], leftWindowBorders[i], rightWindowBorders[i], index,
                                             backgroundFunctionService(identifiers[i], leftWindowBorders[i], rightWindowBorders[i] ) ) );
    ++i;
  }
  // Initialize the parNums to be used in the shifts of parval
  initializeParNums();
}

BackgroundHandler::~BackgroundHandler()
{
}

void BackgroundHandler::initializeParNums()
{
  // Initialize the parNums to be used in the shifts of parval
  parNumsRegions_[0] = 0;
  for( unsigned int i=1; i<backgroundWindow_.size() ; ++i ) {
    parNumsRegions_[i] = parNumsRegions_[i-1] + backgroundWindow_[i-1].backgroundFunction()->parNum();
  }
  parNumsResonances_[0] = parNumsRegions_[2]+backgroundWindow_[2].backgroundFunction()->parNum();
  for( unsigned int i=1; i<resonanceWindow_.size() ; ++i ) {
    parNumsResonances_[i] = parNumsResonances_[i-1] + resonanceWindow_[i-1].backgroundFunction()->parNum();
  }
}

void BackgroundHandler::setParameters(double* Start, double* Step, double* Mini, double* Maxi, int* ind, TString* parname, const std::vector<double> & parBgr, const std::vector<int> & parBgrOrder, const int muonType)
{
  std::vector<double>::const_iterator parBgrIt = parBgr.begin();
  std::vector<int>::const_iterator parBgrOrderIt = parBgrOrder.begin();
  // Set the parameters for the regions only if this is not a rescaling
  for( int iReg = 0; iReg < 3; ++iReg ) {
    int shift = parNumsRegions_[iReg];
    backgroundWindow_[iReg].backgroundFunction()->setParameters( &(Start[shift]), &(Step[shift]), &(Mini[shift]),
        &(Maxi[shift]), &(ind[shift]), &(parname[shift]),
        parBgrIt+shift, parBgrOrderIt+shift, muonType );
  }
  for( int iRes = 0; iRes < 6; ++iRes ) {
    // parNumsResonances is already shifted for the regions parameters
    int shift = parNumsResonances_[iRes];
    resonanceWindow_[iRes].backgroundFunction()->setParameters( &(Start[shift]), &(Step[shift]), &(Mini[shift]),
        &(Maxi[shift]), &(ind[shift]), &(parname[shift]),
        parBgrIt+shift, parBgrOrderIt+shift, muonType );
  }
}

bool BackgroundHandler::unlockParameter(const std::vector<int> & resfind, const unsigned int ipar)
{
  // parNumsRegions_ are shifted: [1] contains the number of parameters for 0 and so on.
  if( ipar < unsigned(parNumsRegions_[1]) && resfind[0] > 0 ) {
    return true;
  }
  if( ipar >= unsigned(parNumsRegions_[1]) && ipar < unsigned(parNumsRegions_[2]) && ( resfind[1] > 0 || resfind[2] > 0 || resfind[3] > 0 ) ) {
    return true;
  }
  // The first of parNumsResonances_ has the sum of parNums of the regions.
  if( ipar >= unsigned(parNumsRegions_[2]) && ipar < unsigned(parNumsResonances_[0]) && ( resfind[4] > 0 || resfind[5] > 0 ) ) {
    return true;
  }
  return false;
}

// std::pair<double, double> BackgroundHandler::windowFactors( const bool doBackgroundFit, const int ires )
// {
//   if( doBackgroundFit ) {
//     // Fitting the background: use the regions
//     return std::make_pair(leftWindowFactors_[resToReg_[ires]], rightWindowFactors_[resToReg_[ires]]);
//   }
//   else {
//     // Not fitting the background: use the resonances
//     return std::make_pair(1.,1.);
//   }
// }

std::pair<double, double> BackgroundHandler::windowBorders( const bool doBackgroundFit, const int ires )
{
  if( doBackgroundFit ) {
    // Fitting the background: use the regions
    return std::make_pair(backgroundWindow_[resToReg_[ires]].lowerBound(), backgroundWindow_[resToReg_[ires]].upperBound());
  }
  else {
    // Not fitting the background: use the resonances
    // return std::make_pair(1.,1.);
    return std::make_pair(resonanceWindow_[ires].lowerBound(), resonanceWindow_[ires].upperBound());
  }
}

double BackgroundHandler::resMass( const bool doBackgroundFit, const int ires )
{
  if( doBackgroundFit ) {
    // Fitting the background: use the regions
    return backgroundWindow_[resToReg_[ires]].mass();
  }
  else {
    // Not fitting the background: use the resonances
    return resonanceWindow_[ires].mass();
  }
}

void BackgroundHandler::rescale( std::vector<double> & parBgr, const double * ResMass, const double * massWindowHalfWidth,
                                 const std::vector<std::pair<reco::Particle::LorentzVector,reco::Particle::LorentzVector> > & muonPairs,
                                 const double & weight )
{
  countEventsInAllWindows(muonPairs, weight);

  // Loop on all regions and on all the resonances of each region and compute the background fraction
  // for each resonance window.
  unsigned int iRegion = 0;
  BOOST_FOREACH(MassWindow & backgroundWindow, backgroundWindow_)
  {
    // Iterator pointing to the first parameter of this background function in the full set of parameters
    std::vector<double>::const_iterator parBgrIt = (parBgr.begin()+parNumsRegions_[iRegion]);
    TF1 * backgroundFunctionForIntegral = backgroundWindow.backgroundFunction()->functionForIntegral(parBgrIt);
    // WARNING: this expects the background fraction parameter to be parBgr[0] for all the background functions.
    double kOld = *parBgrIt;
    double Nbw = backgroundWindow.events();
    double Ibw = backgroundFunctionForIntegral->Integral(backgroundWindow.lowerBound(),
                                                         backgroundWindow.upperBound());

    // index is the index of the resonance in the background window
    BOOST_FOREACH( unsigned int index, *(backgroundWindow.indexes()) )
    {
      // First set all parameters of the resonance window background function to those of the corresponding region
      for( int iPar = 0; iPar < resonanceWindow_[index].backgroundFunction()->parNum(); ++iPar ) {
        parBgr[parNumsResonances_[index]+iPar] = parBgr[parNumsRegions_[resToReg_[index]]+iPar];
      }
      // Estimated fraction of events in the resonance window
      double Irw = backgroundFunctionForIntegral->Integral(resonanceWindow_[index].lowerBound(),
                                                           resonanceWindow_[index].upperBound());
      double Nrw = resonanceWindow_[index].events();

      // Ibw is 1 (to avoid effects from approximation errors we set it to 1 and do not include it in the computation).
      if( Nrw != 0 ) parBgr[parNumsResonances_[index]] = kOld*Nbw/Nrw*Irw;
      else parBgr[parNumsResonances_[index]] = 0.;

      // Protect against fluctuations of number of events which could cause the fraction to go above 1.
      if( parBgr[parNumsResonances_[index]] > 1. ) parBgr[parNumsResonances_[index]] = 1.;

      double kNew = parBgr[parNumsResonances_[index]];
      std::cout << "For resonance = " << index << std::endl;
      std::cout << "backgroundWindow.lowerBound = " << backgroundWindow.lowerBound() << std::endl;
      std::cout << "backgroundWindow.upperBound = " << backgroundWindow.upperBound() << std::endl;
      std::cout << "parNumsResonances_["<<index<<"] = " << parNumsResonances_[index] << std::endl;
      std::cout << "Nbw = " << Nbw << ", Ibw = " << Ibw << std::endl;
      std::cout << "Nrw = " << Nrw << ", Irw = " << Irw << std::endl;
      std::cout << "k = " << kOld << ", k' = " << parBgr[parNumsResonances_[index]] << std::endl;
      std::cout << "background fraction in background window = Nbw*k = " << Nbw*kOld << std::endl;
      std::cout << "background fraction in resonance window = Nrw*k' = " << Nrw*kNew << std::endl;
    }
    ++iRegion;
    delete backgroundFunctionForIntegral;
  }
}

std::pair<double, double> BackgroundHandler::backgroundFunction( const bool doBackgroundFit,
								 const double * parval, const int resTotNum, const int ires,
								 const bool * resConsidered, const double * ResMass, const double ResHalfWidth[],
								 const int MuonType, const double & mass,
								 const double & eta1, const double & eta2 )
{
  if( doBackgroundFit ) {
    // Return the values for the region
    int iReg = resToReg_[ires];
    // return std::make_pair( parval[parNumsRegions_[iReg]] * backgroundWindow_[iReg].backgroundFunction()->fracVsEta(&(parval[parNumsRegions_[iReg]]), resEta),
    return std::make_pair( parval[parNumsRegions_[iReg]] * backgroundWindow_[iReg].backgroundFunction()->fracVsEta(&(parval[parNumsRegions_[iReg]]), eta1, eta2 ),
    			   (*(backgroundWindow_[iReg].backgroundFunction()))( &(parval[parNumsRegions_[iReg]]), mass, eta1, eta2 ) );
    // return std::make_pair( backgroundWindow_[iReg].backgroundFunction()->fracVsEta(&(parval[parNumsRegions_[iReg]]), eta1, eta2),
    // 			   (*(backgroundWindow_[iReg].backgroundFunction()))( &(parval[parNumsRegions_[iReg]]), mass, eta1, eta2 ) );
  }
  // Return the values for the resonance
  // return std::make_pair( parval[parNumsResonances_[ires]] * resonanceWindow_[ires].backgroundFunction()->fracVsEta(&(parval[parNumsResonances_[ires]]), resEta),
  // 			 (*(resonanceWindow_[ires].backgroundFunction()))( &(parval[parNumsResonances_[ires]]), mass, resEta ) );
  return std::make_pair( parval[parNumsResonances_[ires]] * resonanceWindow_[ires].backgroundFunction()->fracVsEta(&(parval[parNumsResonances_[ires]]), eta1, eta2),
			 (*(resonanceWindow_[ires].backgroundFunction()))( &(parval[parNumsResonances_[ires]]), mass, eta1, eta2 ) );
}

void BackgroundHandler::countEventsInAllWindows(const std::vector<std::pair<reco::Particle::LorentzVector,reco::Particle::LorentzVector> > & muonPairs,
                                                const double & weight)
{
  // First reset all the counters
  BOOST_FOREACH(MassWindow & resonanceWindow, resonanceWindow_) {
    resonanceWindow.resetCounter();
  }
  // Count events in background windows
  BOOST_FOREACH(MassWindow & backgroundWindow, backgroundWindow_) {
    backgroundWindow.resetCounter();
  }

  // Now count the events in each window
  std::pair<lorentzVector,lorentzVector> muonPair;
  BOOST_FOREACH(muonPair, muonPairs) {
    // Count events in resonance windows
    BOOST_FOREACH(MassWindow & resonanceWindow, resonanceWindow_) {
      resonanceWindow.count((muonPair.first + muonPair.second).mass(), weight);
    }
    // Count events in background windows
    BOOST_FOREACH(MassWindow & backgroundWindow, backgroundWindow_) {
      backgroundWindow.count((muonPair.first + muonPair.second).mass(), weight);
    }
  }
}

void BackgroundHandler::consistencyCheck(const std::vector<int> & identifiers,
                                         const std::vector<double> & leftWindowBorders,
                                         const std::vector<double> & rightWindowBorders) const noexcept(false)
{
  if( leftWindowBorders.size() != rightWindowBorders.size() ) {
    throw cms::Exception("Configuration") << "BackgroundHandler::BackgroundHandler: leftWindowBorders.size() = " << leftWindowBorders.size()
                                          << " != rightWindowBorders.size() = " << rightWindowBorders.size() << std::endl;
  }
  if( leftWindowBorders.size() != 3 ) {
    throw cms::Exception("Configuration") << "BackgroundHandler::BackgroundHandler: leftWindowBorders.size() = rightWindowBorders.size() = "
                                          << leftWindowBorders.size() << " != 3" << std::endl;
  }
  if( identifiers.size() != 3 ) {
    throw cms::Exception("Configuration") << "BackgroundHandler::BackgroundHandler: identifiers must match the number of regions = 3" << std::endl;
  }
}

#endif // BackgroundHandler_cc


#include "../include/init.hpp"

#include <iostream>
#include <cmath>
#include <opencv2/opencv.hpp>
#include <boost/version.hpp>
#include "../include/yuriface.hpp"
#include "../include/transform.hpp"
#include "../include/geometry.hpp"
#include "../include/face_part.hpp"

int main(int argc, char **argv) {

	std::cout << "Start Yuri Face" << std::endl;

	YuriFace *yuriface = LoadYFML(argv[1]);
	cv::Mat img = yuriface->getImage();

	Face *face_obj = NULL;
	yuriface2face(yuriface, face_obj);

	/*
	std::cout << "dots" << std::endl;

	for (Dot *dot : *yuriface->getObjects()->at(0)->getDotsVector()) {
		std::cout << dot->getX() << ":" << dot->getY() << std::endl;
		cv::circle(img, cv::Point(dot->getX(), dot->getY()), 5, cv::Scalar(0, 0, 0), 1, 1);
	}
	std::cout << "line" << std::endl;
	for (Dot *dot : *yuriface->getObjects()->at(0)->getDotsVector()) {
		std::cout << dot->getX() << ":" << dot->getY() << " ";
		for (Dot *dot2 : *dot->getConnectedDotsVector()) {
			std::cout << dot2->getX() << ":" << dot2->getY() << std::endl;
			cv::line(img, cv::Point(dot->getX(), dot->getY()), cv::Point(dot2->getX(), dot2->getY()), cv::Scalar(0, 0, 0));
		}
	}
	std::cout << "name : " << yuriface->getObjects()->at(0)->getName() << std::endl;

	*/

	cv::Point2f dst_points[4];

	cv::Rect rect = BoostBox2CvRect(CreateEnvelope(CreateRing(yuriface->getObjects()->at(0))));
	cv::Mat result = CutImage(img, rect);
	result = RotationTransform(result, 45);

	cv::Mat face = YuriInpainting(img, rect);

	dst_points[0] = cv::Point2f(0.0f, 0.0f);
	dst_points[1] = cv::Point2f(result.cols * 0.2, result.rows * 0.8);
	dst_points[2] = cv::Point2f(result.cols, result.rows);
	dst_points[3] = cv::Point2f(result.cols * 0.8, result.rows * 0.2);

	result = PerspectiveTransform(result, dst_points, cv::Size(result.cols, result.rows));

	result = RotationTransform(result, -45);

	int i;
	result = SharpenImage(result, &i);

	cv::namedWindow("Yuri Face");
	//result = YuriFacePasteImage(face, result, rect.x * 0.90, rect.y * 0.85).clone();
	result = face_obj->getMouth()->OpenMouth(face_obj->getFaceBase(), 6).clone();
	cv::Mat level3 = face_obj->getMouth()->OpenMouth(face_obj->getFaceBase(), 4).clone();

	while (true) {
		ShowAndWait(img, "Yuri Face", 180);
		ShowAndWait(level3, "Yuri Face", 180);
		ShowAndWait(result, "Yuri Face", 180);
	}

}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_BLOCK_DECRYPT_HPP
#define CRYPTO3_BLOCK_DECRYPT_HPP

#include <boost/crypto3/detail/type_traits.hpp>

#include <boost/crypto3/block/algorithm/block.hpp>

#include <boost/crypto3/block/cipher_value.hpp>
#include <boost/crypto3/block/cipher_state.hpp>
#include <boost/crypto3/block/cipher_key.hpp>

#include <boost/crypto3/block/detail/cipher_modes.hpp>

namespace boost {
    namespace crypto3 {
        namespace block {
            template<typename BlockCipher>
            using decryption_policy = typename block::modes::isomorphic<BlockCipher, nop_padding>::decryption_policy;
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam KeyIterator
         * @tparam OutputIterator
         *
         * @param first
         * @param last
         * @param key_first
         * @param key_last
         * @param out
         *
         * @return
         */
        template<typename BlockCipher, typename InputIterator, typename KeyInputIterator, typename OutputIterator,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        OutputIterator decrypt(InputIterator first, InputIterator last, KeyInputIterator key_first,
                               KeyInputIterator key_last, OutputIterator out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::itr_cipher_impl<StreamDecrypterImpl, OutputIterator> DecrypterImpl;

            return DecrypterImpl(first, last, std::move(out),
                                 CipherAccumulator(DecryptionMode(
                                     BlockCipher(block::cipher_key<BlockCipher>(key_first, key_last).key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam KeySinglePassRange
         * @tparam OutputIterator
         *
         * @param first
         * @param last
         * @param key
         * @param out
         *
         * @return
         */
        template<typename BlockCipher, typename InputIterator, typename KeySinglePassRange, typename OutputIterator,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        OutputIterator decrypt(InputIterator first, InputIterator last, const KeySinglePassRange &key,
                               OutputIterator out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::itr_cipher_impl<StreamDecrypterImpl, OutputIterator> DecrypterImpl;

            return DecrypterImpl(
                first, last, std::move(out),
                CipherAccumulator(DecryptionMode(BlockCipher(block::cipher_key<BlockCipher>(key).key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam OutputIterator
         *
         * @param first
         * @param last
         * @param key
         * @param out
         *
         * @return
         */
        template<typename BlockCipher, typename InputIterator, typename OutputIterator,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        OutputIterator decrypt(InputIterator first, InputIterator last, const block::cipher_key<BlockCipher> &key,
                               OutputIterator out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::itr_cipher_impl<StreamDecrypterImpl, OutputIterator> DecrypterImpl;

            return DecrypterImpl(first, last, std::move(out), CipherAccumulator(DecryptionMode(BlockCipher(key.key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam OutputAccumulator
         *
         * @param first
         * @param last
         * @param acc
         *
         * @return
         */
        template<typename BlockCipher, typename InputIterator,
                 typename OutputAccumulator = typename block::accumulator_set<typename block::modes::isomorphic<
                     BlockCipher, block::nop_padding>::template bind<block::decryption_policy<BlockCipher>>::type>,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        typename std::enable_if<boost::accumulators::detail::is_accumulator_set<OutputAccumulator>::value,
                                OutputAccumulator>::type &
            decrypt(InputIterator first, InputIterator last, OutputAccumulator &acc) {

            typedef block::detail::ref_cipher_impl<OutputAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(first, last, std::forward<OutputAccumulator>(acc));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam OutputAccumulator
         *
         * @param r
         * @param acc
         *
         * @return
         */

        template<typename BlockCipher, typename SinglePassRange,
                 typename OutputAccumulator = typename block::accumulator_set<
                     typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                         typename block::modes::isomorphic<BlockCipher, block::nop_padding>::decryption_policy>::type>,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        typename std::enable_if<boost::accumulators::detail::is_accumulator_set<OutputAccumulator>::value,
                                OutputAccumulator>::type &
            decrypt(const SinglePassRange &r, OutputAccumulator &acc) {

            typedef block::detail::ref_cipher_impl<OutputAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(r, acc);
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam KeyIterator
         * @tparam CipherAccumulator
         *
         * @param first
         * @param last
         * @param key_first
         * @param key_last
         *
         * @return
         */
        template<typename BlockCipher, typename InputIterator, typename KeyInputIterator,
                 typename CipherAccumulator = typename block::accumulator_set<typename block::modes::isomorphic<
                     BlockCipher, block::nop_padding>::template bind<block::decryption_policy<BlockCipher>>::type>,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        block::detail::range_cipher_impl<block::detail::value_cipher_impl<CipherAccumulator>>
            decrypt(InputIterator first, InputIterator last, KeyInputIterator key_first, KeyInputIterator key_last) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(first, last,
                                 CipherAccumulator(DecryptionMode(
                                     BlockCipher(block::cipher_key<BlockCipher>(key_first, key_last).key))));
        }

        /*!
         * @brief
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam KeySinglePassRange
         * @tparam CipherAccumulator
         *
         * @param first
         * @param last
         * @param key
         *
         * @return
         */
        template<typename BlockCipher, typename InputIterator, typename KeySinglePassRange,
                 typename CipherAccumulator = typename block::accumulator_set<typename block::modes::isomorphic<
                     BlockCipher, block::nop_padding>::template bind<block::decryption_policy<BlockCipher>>::type>,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        block::detail::range_cipher_impl<block::detail::value_cipher_impl<CipherAccumulator>>
            decrypt(InputIterator first, InputIterator last, const KeySinglePassRange &key) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(
                first, last, CipherAccumulator(DecryptionMode(BlockCipher(block::cipher_key<BlockCipher>(key).key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam InputIterator
         * @tparam CipherAccumulator
         * @param first
         * @param last
         * @param key
         * @return
         */
        template<typename BlockCipher, typename InputIterator,
                 typename CipherAccumulator = typename block::accumulator_set<typename block::modes::isomorphic<
                     BlockCipher, block::nop_padding>::template bind<block::decryption_policy<BlockCipher>>::type>,
                 typename = typename std::enable_if<detail::is_iterator<InputIterator>::value>::type>
        block::detail::range_cipher_impl<block::detail::value_cipher_impl<CipherAccumulator>>
            decrypt(InputIterator first, InputIterator last, const block::cipher_key<BlockCipher> &key) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(first, last, CipherAccumulator(DecryptionMode(BlockCipher(key.key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam KeyRange
         * @tparam OutputIterator
         *
         * @param rng
         * @param key
         * @param out
         *
         * @return
         */
        template<typename BlockCipher, typename SinglePassRange, typename KeySinglePassRange, typename OutputIterator,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        OutputIterator decrypt(const SinglePassRange &rng, const KeySinglePassRange &key, OutputIterator out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::itr_cipher_impl<StreamDecrypterImpl, OutputIterator> DecrypterImpl;

            return DecrypterImpl(
                rng, std::move(out),
                CipherAccumulator(DecryptionMode(BlockCipher(block::cipher_key<BlockCipher>(key).key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam OutputIterator
         *
         * @param rng
         * @param key
         * @param out
         * @return
         */
        template<typename BlockCipher, typename SinglePassRange, typename OutputIterator,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        OutputIterator decrypt(const SinglePassRange &rng, const block::cipher_key<BlockCipher> &key,
                               OutputIterator out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::itr_cipher_impl<StreamDecrypterImpl, OutputIterator> DecrypterImpl;

            return DecrypterImpl(rng, std::move(out), CipherAccumulator(DecryptionMode(BlockCipher(key.key))));
        }

        /*!
         * @brief
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam KeySinglePassRange
         * @tparam OutputRange
         *
         * @param rng
         * @param key
         * @param out
         *
         * @return
         */
        template<typename BlockCipher, typename SinglePassRange, typename KeySinglePassRange, typename OutputRange,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        OutputRange &decrypt(const SinglePassRange &rng, const KeySinglePassRange &key, OutputRange &out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(
                rng, std::move(out),
                CipherAccumulator(DecryptionMode(BlockCipher(block::cipher_key<BlockCipher>(key).key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam OutputRange
         * @param rng
         * @param key
         * @param out
         * @return
         */
        template<typename BlockCipher, typename SinglePassRange, typename OutputRange,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        OutputRange &decrypt(const SinglePassRange &rng, const block::cipher_key<BlockCipher> &key, OutputRange &out) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;
            typedef typename block::accumulator_set<DecryptionMode> CipherAccumulator;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(rng, std::move(out), CipherAccumulator(DecryptionMode(BlockCipher(key.key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam KeyRange
         * @tparam CipherAccumulator
         *
         * @param r
         * @param key
         *
         * @return
         */
        template<typename BlockCipher, typename SinglePassRange, typename KeySinglePassRange,
                 typename CipherAccumulator = typename block::accumulator_set<typename block::modes::isomorphic<
                     BlockCipher, block::nop_padding>::template bind<block::decryption_policy<BlockCipher>>::type>,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        block::detail::range_cipher_impl<block::detail::value_cipher_impl<CipherAccumulator>>
            decrypt(const SinglePassRange &r, const KeySinglePassRange &key) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(
                r, CipherAccumulator(DecryptionMode(BlockCipher(block::cipher_key<BlockCipher>(key).key))));
        }

        /*!
         * @brief
         *
         * @ingroup block_algorithms
         *
         * @tparam BlockCipher
         * @tparam SinglePassRange
         * @tparam CipherAccumulator
         * @param r
         * @param key
         * @return
         */
        template<typename BlockCipher, typename SinglePassRange,
                 typename CipherAccumulator = typename block::accumulator_set<typename block::modes::isomorphic<
                     BlockCipher, block::nop_padding>::template bind<block::decryption_policy<BlockCipher>>::type>,
                 typename = typename std::enable_if<detail::is_range<SinglePassRange>::value>::type>
        block::detail::range_cipher_impl<block::detail::value_cipher_impl<CipherAccumulator>>
            decrypt(const SinglePassRange &r, const block::cipher_key<BlockCipher> &key) {

            typedef typename block::modes::isomorphic<BlockCipher, block::nop_padding>::template bind<
                block::decryption_policy<BlockCipher>>::type DecryptionMode;

            typedef block::detail::value_cipher_impl<CipherAccumulator> StreamDecrypterImpl;
            typedef block::detail::range_cipher_impl<StreamDecrypterImpl> DecrypterImpl;

            return DecrypterImpl(r, CipherAccumulator(DecryptionMode(BlockCipher(key.key))));
        }
    }    // namespace crypto3
}    // namespace boost

#endif    // include guard