#include <eosio/chain/exceptions.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/resource_limits_private.hpp>
#include <eosio/chain/transaction_metadata.hpp>
#include <eosio/chain/transaction.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <eosio/chain/database_utils.hpp>
#include <algorithm>

namespace eosio { namespace chain { namespace resource_limits {

using resource_index_set = index_set<
   resource_limits_index,
   resource_usage_index,
   resource_limits_state_index,
   resource_limits_config_index
>;

static_assert( config::rate_limiting_precision > 0, "config::rate_limiting_precision must be positive" );

static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_usage, const elastic_limit_parameters& params) {
   uint64_t result = current_limit;
   if (average_usage > params.target ) {
      result = result * params.contract_rate;
   } else {
      result = result * params.expand_rate;
   }
   return std::min(std::max(result, params.max), params.max * params.max_multiplier);
}

void elastic_limit_parameters::validate()const {
   // At the very least ensure parameters are not set to values that will cause divide by zero errors later on.
   // Stricter checks for sensible values can be added later.
   EOS_ASSERT( periods > 0, resource_limit_exception, "elastic limit parameter 'periods' cannot be zero" );
   EOS_ASSERT( contract_rate.denominator > 0, resource_limit_exception, "elastic limit parameter 'contract_rate' is not a well-defined ratio" );
   EOS_ASSERT( expand_rate.denominator > 0, resource_limit_exception, "elastic limit parameter 'expand_rate' is not a well-defined ratio" );
}

void resource_limits_state_object::update_virtual_cpu_limit( const resource_limits_config_object& cfg ) {
   //idump((average_block_cpu_usage.average()));
   virtual_cpu_limit = update_elastic_limit(virtual_cpu_limit, average_block_cpu_usage.average(), cfg.cpu_limit_parameters);
   //idump((virtual_cpu_limit));
}

void resource_limits_state_object::update_virtual_net_limit( const resource_limits_config_object& cfg ) {
   virtual_net_limit = update_elastic_limit(virtual_net_limit, average_block_net_usage.average(), cfg.net_limit_parameters);
}

void resource_limits_manager::add_indices() {
   resource_index_set::add_indices(_db);
}

void resource_limits_manager::initialize_database() {
   const auto& config = _db.create<resource_limits_config_object>([](resource_limits_config_object& config){
      // see default settings in the declaration
   });

   _db.create<resource_limits_state_object>([&config](resource_limits_state_object& state){
      // see default settings in the declaration

      // start the chain off in a way that it is "congested" aka slow-start
      state.virtual_cpu_limit = config.cpu_limit_parameters.max;
      state.virtual_net_limit = config.net_limit_parameters.max;
   });
}

void resource_limits_manager::add_to_snapshot( const snapshot_writer_ptr& snapshot ) const {
   resource_index_set::walk_indices([this, &snapshot]( auto utils ){
      snapshot->write_section<typename decltype(utils)::index_t::value_type>([this]( auto& section ){
         decltype(utils)::walk(_db, [this, &section]( const auto &row ) {
            section.add_row(row, _db);
         });
      });
   });
}

void resource_limits_manager::read_from_snapshot( const snapshot_reader_ptr& snapshot ) {
   resource_index_set::walk_indices([this, &snapshot]( auto utils ){
      snapshot->read_section<typename decltype(utils)::index_t::value_type>([this]( auto& section ) {
         bool more = !section.empty();
         while(more) {
            decltype(utils)::create(_db, [this, &section, &more]( auto &row ) {
               more = section.read_row(row, _db);
            });
         }
      });
   });
}

void resource_limits_manager::initialize_account(const account_name& account) {
   _db.create<resource_limits_object>([&]( resource_limits_object& bl ) {
      bl.owner = account;
   });

   _db.create<resource_usage_object>([&]( resource_usage_object& bu ) {
      bu.owner = account;
   });
}

void resource_limits_manager::set_block_parameters(const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ) {
   cpu_limit_parameters.validate();
   net_limit_parameters.validate();
   const auto& config = _db.get<resource_limits_config_object>();
   _db.modify(config, [&](resource_limits_config_object& c){
      c.cpu_limit_parameters = cpu_limit_parameters;
      c.net_limit_parameters = net_limit_parameters;
   });
}

void resource_limits_manager::update_account_usage(const flat_set<account_name>& accounts, uint32_t time_slot ) {
   const auto& config = _db.get<resource_limits_config_object>();
   for( const auto& a : accounts ) {
      const auto& usage = _db.get<resource_usage_object,by_owner>( a );
      _db.modify( usage, [&]( auto& bu ){
          bu.net_usage.add( 0, time_slot, config.account_net_usage_average_window );
          bu.cpu_usage.add( 0, time_slot, config.account_cpu_usage_average_window );
      });
   }
}

void resource_limits_manager::add_transaction_usage(const flat_set<account_name>& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t time_slot ) {
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& config = _db.get<resource_limits_config_object>();

   for( const auto& a : accounts ) {

      const auto& usage = _db.get<resource_usage_object,by_owner>( a );
      int64_t unused;
      int64_t net_weight;
      int64_t cpu_weight;
      get_account_limits( a, unused, net_weight, cpu_weight );

      _db.modify( usage, [&]( auto& bu ){
          bu.net_usage.add( net_usage, time_slot, config.account_net_usage_average_window );
          bu.cpu_usage.add( cpu_usage, time_slot, config.account_cpu_usage_average_window );
      });

      if( cpu_weight >= 0 && state.total_cpu_weight > 0 ) {
         uint128_t window_size = config.account_cpu_usage_average_window;
         auto virtual_network_capacity_in_window = (uint128_t)state.virtual_cpu_limit * window_size;
         auto cpu_used_in_window                 = ((uint128_t)usage.cpu_usage.value_ex * window_size) / (uint128_t)config::rate_limiting_precision;

         uint128_t user_weight     = (uint128_t)cpu_weight;
         uint128_t all_user_weight = state.total_cpu_weight;

         auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;

         EOS_ASSERT( cpu_used_in_window <= max_user_use_in_window,
                     tx_cpu_usage_exceeded,
                     "authorizing account '${n}' has insufficient cpu resources for this transaction",
                     ("n", name(a))
                     ("cpu_used_in_window",cpu_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );
      }

      if( net_weight >= 0 && state.total_net_weight > 0) {

         uint128_t window_size = config.account_net_usage_average_window;
         auto virtual_network_capacity_in_window = (uint128_t)state.virtual_net_limit * window_size;
         auto net_used_in_window                 = ((uint128_t)usage.net_usage.value_ex * window_size) / (uint128_t)config::rate_limiting_precision;

         uint128_t user_weight     = (uint128_t)net_weight;
         uint128_t all_user_weight = state.total_net_weight;

         auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;

         EOS_ASSERT( net_used_in_window <= max_user_use_in_window,
                     tx_net_usage_exceeded,
                     "authorizing account '${n}' has insufficient net resources for this transaction",
                     ("n", name(a))
                     ("net_used_in_window",net_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );

      }
   }

   // account for this transaction in the block and do not exceed those limits either
   _db.modify(state, [&](resource_limits_state_object& rls){
      rls.pending_cpu_usage += cpu_usage;
      rls.pending_net_usage += net_usage;
   });

   EOS_ASSERT( state.pending_cpu_usage <= config.cpu_limit_parameters.max, block_resource_exhausted, "Block has insufficient cpu resources" );
   EOS_ASSERT( state.pending_net_usage <= config.net_limit_parameters.max, block_resource_exhausted, "Block has insufficient net resources" );
}

void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) {
   if (ram_delta == 0) {
      return;
   }

   const auto& usage  = _db.get<resource_usage_object,by_owner>( account );

   EOS_ASSERT( ram_delta <= 0 || UINT64_MAX - usage.ram_usage >= (uint64_t)ram_delta, transaction_exception,
              "Ram usage delta would overflow UINT64_MAX");
   EOS_ASSERT(ram_delta >= 0 || usage.ram_usage >= (uint64_t)(-ram_delta), transaction_exception,
              "Ram usage delta would underflow UINT64_MAX");

   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
   });
}

void resource_limits_manager::verify_account_ram_usage( const account_name account )const {
   int64_t ram_bytes; int64_t net_weight; int64_t cpu_weight;
   get_account_limits( account, ram_bytes, net_weight, cpu_weight );
   const auto& usage  = _db.get<resource_usage_object,by_owner>( account );

   if( ram_bytes >= 0 ) {
      EOS_ASSERT( usage.ram_usage <= ram_bytes, ram_usage_exceeded,
                  "account ${account} has insufficient ram; needs ${needs} bytes has ${available} bytes",
                  ("account", account)("needs",usage.ram_usage)("available",ram_bytes)              );
   }
}

int64_t resource_limits_manager::get_account_ram_usage( const account_name& name )const {
   return _db.get<resource_usage_object,by_owner>( name ).ram_usage;
}

bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
   //const auto& usage = _db.get<resource_usage_object,by_owner>( account );
   /*
    * Since we need to delay these until the next resource limiting boundary, these are created in a "pending"
    * state or adjusted in an existing "pending" state.  The chain controller will collapse "pending" state into
    * the actual state at the next appropriate boundary.
    */
   auto find_or_create_pending_limits = [&]() -> const resource_limits_object& {
      const auto* pending_limits = _db.find<resource_limits_object, by_owner>( boost::make_tuple(true, account) );
      if (pending_limits == nullptr) {
         const auto& limits = _db.get<resource_limits_object, by_owner>( boost::make_tuple(false, account));
         return _db.create<resource_limits_object>([&](resource_limits_object& pending_limits){
            pending_limits.owner = limits.owner;
            pending_limits.ram_bytes = limits.ram_bytes;
            pending_limits.net_weight = limits.net_weight;
            pending_limits.cpu_weight = limits.cpu_weight;
            pending_limits.pending = true;
         });
      } else {
         return *pending_limits;
      }
   };

   // update the users weights directly
   auto& limits = find_or_create_pending_limits();

   bool decreased_limit = false;

   if( ram_bytes >= 0 ) {

      decreased_limit = ( (limits.ram_bytes < 0) || (ram_bytes < limits.ram_bytes) );

      /*
      if( limits.ram_bytes < 0 ) {
         EOS_ASSERT(ram_bytes >= usage.ram_usage, wasm_execution_error, "converting unlimited account would result in overcommitment [commit=${c}, desired limit=${l}]", ("c", usage.ram_usage)("l", ram_bytes));
      } else {
         EOS_ASSERT(ram_bytes >= usage.ram_usage, wasm_execution_error, "attempting to release committed ram resources [commit=${c}, desired limit=${l}]", ("c", usage.ram_usage)("l", ram_bytes));
      }
      */
   }

   _db.modify( limits, [&]( resource_limits_object& pending_limits ){
      pending_limits.ram_bytes = ram_bytes;
      pending_limits.net_weight = net_weight;
      pending_limits.cpu_weight = cpu_weight;
   });

   return decreased_limit;
}

void resource_limits_manager::get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight ) const {
   const auto* pending_buo = _db.find<resource_limits_object,by_owner>( boost::make_tuple(true, account) );
   if (pending_buo) {
      ram_bytes  = pending_buo->ram_bytes;
      net_weight = pending_buo->net_weight;
      cpu_weight = pending_buo->cpu_weight;
   } else {
      const auto& buo = _db.get<resource_limits_object,by_owner>( boost::make_tuple( false, account ) );
      ram_bytes  = buo.ram_bytes;
      net_weight = buo.net_weight;
      cpu_weight = buo.cpu_weight;
   }
}

void resource_limits_manager::process_account_limit_updates() {
   auto& multi_index = _db.get_mutable_index<resource_limits_index>();
   auto& by_owner_index = multi_index.indices().get<by_owner>();

   // convenience local lambda to reduce clutter
   auto update_state_and_value = [](uint64_t &total, int64_t &value, int64_t pending_value, const char* debug_which) -> void {
      if (value > 0) {
         EOS_ASSERT(total >= value, rate_limiting_state_inconsistent, "underflow when reverting old value to ${which}", ("which", debug_which));
         total -= value;
      }

      if (pending_value > 0) {
         EOS_ASSERT(UINT64_MAX - total >= pending_value, rate_limiting_state_inconsistent, "overflow when applying new value to ${which}", ("which", debug_which));
         total += pending_value;
      }

      value = pending_value;
   };

   const auto& state = _db.get<resource_limits_state_object>();
   _db.modify(state, [&](resource_limits_state_object& rso){
      while(!by_owner_index.empty()) {
         const auto& itr = by_owner_index.lower_bound(boost::make_tuple(true));
         if (itr == by_owner_index.end() || itr->pending!= true) {
            break;
         }

         const auto& actual_entry = _db.get<resource_limits_object, by_owner>(boost::make_tuple(false, itr->owner));
         _db.modify(actual_entry, [&](resource_limits_object& rlo){
            update_state_and_value(rso.total_ram_bytes,  rlo.ram_bytes,  itr->ram_bytes, "ram_bytes");
            update_state_and_value(rso.total_cpu_weight, rlo.cpu_weight, itr->cpu_weight, "cpu_weight");
            update_state_and_value(rso.total_net_weight, rlo.net_weight, itr->net_weight, "net_weight");
         });

         multi_index.remove(*itr);
      }
   });
}

void resource_limits_manager::process_block_usage(uint32_t block_num) {
   const auto& s = _db.get<resource_limits_state_object>();
   const auto& config = _db.get<resource_limits_config_object>();
   _db.modify(s, [&](resource_limits_state_object& state){
      // apply pending usage, update virtual limits and reset the pending

      state.average_block_cpu_usage.add(state.pending_cpu_usage, block_num, config.cpu_limit_parameters.periods);
      state.update_virtual_cpu_limit(config);
      state.pending_cpu_usage = 0;

      state.average_block_net_usage.add(state.pending_net_usage, block_num, config.net_limit_parameters.periods);
      state.update_virtual_net_limit(config);
      state.pending_net_usage = 0;

   });

}

uint64_t resource_limits_manager::get_virtual_block_cpu_limit() const {
   const auto& state = _db.get<resource_limits_state_object>();
   return state.virtual_cpu_limit;
}

uint64_t resource_limits_manager::get_virtual_block_net_limit() const {
   const auto& state = _db.get<resource_limits_state_object>();
   return state.virtual_net_limit;
}

uint64_t resource_limits_manager::get_block_cpu_limit() const {
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& config = _db.get<resource_limits_config_object>();
   return config.cpu_limit_parameters.max - state.pending_cpu_usage;
}

uint64_t resource_limits_manager::get_block_net_limit() const {
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& config = _db.get<resource_limits_config_object>();
   return config.net_limit_parameters.max - state.pending_net_usage;
}

int64_t resource_limits_manager::get_account_cpu_limit( const account_name& name, bool elastic ) const {
   auto arl = get_account_cpu_limit_ex(name, elastic);
   return arl.available;
}

account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( const account_name& name, bool elastic) const {

   const auto& state = _db.get<resource_limits_state_object>();
   const auto& usage = _db.get<resource_usage_object, by_owner>(name);
   const auto& config = _db.get<resource_limits_config_object>();

   int64_t cpu_weight, x, y;
   get_account_limits( name, x, y, cpu_weight );

   if( cpu_weight < 0 || state.total_cpu_weight == 0 ) {
      return { -1, -1, -1 };
   }

   account_resource_limit arl;

   uint128_t window_size = config.account_cpu_usage_average_window;

   uint128_t virtual_cpu_capacity_in_window = (uint128_t)(elastic ? state.virtual_cpu_limit : config.cpu_limit_parameters.max) * window_size;
   uint128_t user_weight     = (uint128_t)cpu_weight;
   uint128_t all_user_weight = (uint128_t)state.total_cpu_weight;

   auto max_user_use_in_window = (virtual_cpu_capacity_in_window * user_weight) / all_user_weight;
   auto cpu_used_in_window  = impl::integer_divide_ceil((uint128_t)usage.cpu_usage.value_ex * window_size, (uint128_t)config::rate_limiting_precision);

   if( max_user_use_in_window <= cpu_used_in_window )
      arl.available = 0;
   else
      arl.available = impl::downgrade_cast<int64_t>(int64_t(max_user_use_in_window - cpu_used_in_window));

   arl.used = impl::downgrade_cast<int64_t>(int64_t(cpu_used_in_window));
   arl.max = impl::downgrade_cast<int64_t>(int64_t(max_user_use_in_window));
   return arl;
}

int64_t resource_limits_manager::get_account_net_limit( const account_name& name, bool elastic) const {
   auto arl = get_account_net_limit_ex(name, elastic);
   return arl.available;
}

account_resource_limit resource_limits_manager::get_account_net_limit_ex( const account_name& name, bool elastic) const {
   const auto& config = _db.get<resource_limits_config_object>();
   const auto& state  = _db.get<resource_limits_state_object>();
   const auto& usage  = _db.get<resource_usage_object, by_owner>(name);

   int64_t net_weight, x, y;
   get_account_limits( name, x, net_weight, y );

   if( net_weight < 0 || state.total_net_weight == 0) {
      return { -1, -1, -1 };
   }

   account_resource_limit arl;

   uint128_t window_size = config.account_net_usage_average_window;

   uint128_t virtual_network_capacity_in_window = (uint128_t)(elastic ? state.virtual_net_limit : config.net_limit_parameters.max) * window_size;
   uint128_t user_weight     = (uint128_t)net_weight;
   uint128_t all_user_weight = (uint128_t)state.total_net_weight;

   auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;
   auto net_used_in_window  = impl::integer_divide_ceil((uint128_t)usage.net_usage.value_ex * window_size, (uint128_t)config::rate_limiting_precision);

   if( max_user_use_in_window <= net_used_in_window )
      arl.available = 0;
   else
      arl.available = impl::downgrade_cast<int64_t>(int64_t(max_user_use_in_window - net_used_in_window));

   arl.used = impl::downgrade_cast<int64_t>(int64_t(net_used_in_window));
   arl.max = impl::downgrade_cast<int64_t>(int64_t(max_user_use_in_window));
   return arl;
}

} } } /// eosio::chain::resource_limits


////////////////////////////////////////////////////////////////////////////
//                    Header file for this                                    //
////////////////////////////////////////////////////////////////////////////////
#include "HLTriggerOffline/Egamma/interface/EmDQMReco.h"

////////////////////////////////////////////////////////////////////////////////
//                    Collaborating Class Header                              //
////////////////////////////////////////////////////////////////////////////////
#include "FWCore/Framework/interface/Frameworkfwd.h"
#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/Framework/interface/MakerMacros.h"
#include "DataFormats/HLTReco/interface/TriggerEventWithRefs.h"
#include "DataFormats/RecoCandidate/interface/RecoEcalCandidate.h"
#include "DataFormats/EgammaCandidates/interface/Electron.h"
//#include "DataFormats/EgammaCandidates/interface/PhotonFwd.h"
//#include "DataFormats/EgammaCandidates/interface/Photon.h"
#include "DataFormats/L1Trigger/interface/L1EmParticle.h"
#include "DataFormats/L1Trigger/interface/L1EmParticleFwd.h"
//#include "SimDataFormats/HepMCProduct/interface/HepMCProduct.h"
#include "SimDataFormats/GeneratorProducts/interface/HepMCProduct.h"
#include "FWCore/MessageLogger/interface/MessageLogger.h"
#include "DataFormats/Common/interface/AssociationMap.h"

#include "DataFormats/Common/interface/Handle.h"
#include "DataFormats/Common/interface/RefToBase.h"
#include "FWCore/ServiceRegistry/interface/Service.h"
//#include "PhysicsTools/UtilAlgos/interface/TFileService.h"
#include "FWCore/Utilities/interface/Exception.h"
#include "DataFormats/HLTReco/interface/TriggerTypeDefs.h"
#include "DataFormats/Common/interface/TriggerResults.h"
#include "DataFormats/HLTReco/interface/TriggerObject.h"
#include "DataFormats/HLTReco/interface/TriggerEvent.h"
#include <boost/format.hpp>
////////////////////////////////////////////////////////////////////////////////
//                           Root include files                               //
////////////////////////////////////////////////////////////////////////////////
#include "TFile.h"
#include "TDirectory.h"
#include "TH1F.h"
#include <iostream>
#include <string>
#include <Math/VectorUtil.h>
using namespace ROOT::Math::VectorUtil ;

//----------------------------------------------------------------------
// class EmDQMReco::FourVectorMonitorElements
//----------------------------------------------------------------------
EmDQMReco::FourVectorMonitorElements::FourVectorMonitorElements(EmDQMReco *_parent,
    DQMStore::IBooker &iBooker,
    const std::string &histogramNameTemplate,
    const std::string &histogramTitleTemplate
  ) :
  parent(_parent)
{
  // introducing variables for better code readability later on
  std::string histName;
  std::string histTitle;

  // et
  histName = boost::str(boost::format(histogramNameTemplate) % "et");
  histTitle = boost::str(boost::format(histogramTitleTemplate) % "E_{T}");
  etMonitorElement =  iBooker.book1D(histName.c_str(),
                                   histTitle.c_str(),
                                   parent->plotBins,
                                   parent->plotPtMin,
                                   parent->plotPtMax);

  // eta
  histName = boost::str(boost::format(histogramNameTemplate) % "eta");
  histTitle= boost::str(boost::format(histogramTitleTemplate) % "#eta");
  etaMonitorElement = iBooker.book1D(histName.c_str(),
                                  histTitle.c_str(),
                                  parent->plotBins,
                                  - parent->plotEtaMax,
                                    parent->plotEtaMax);

  // phi
  histName = boost::str(boost::format(histogramNameTemplate) % "phi");
  histTitle= boost::str(boost::format(histogramTitleTemplate) % "#phi");
  phiMonitorElement = iBooker.book1D(histName.c_str(),
                                  histTitle.c_str(),
                                  parent->plotBins,
                                  - parent->plotPhiMax,
                                    parent->plotPhiMax);
}

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

void
EmDQMReco::FourVectorMonitorElements::fill(const math::XYZTLorentzVector &momentum)
{
  etMonitorElement->Fill(momentum.Et());
  etaMonitorElement->Fill(momentum.eta() );
  phiMonitorElement->Fill(momentum.phi() );
}

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

////////////////////////////////////////////////////////////////////////////////
//                             Constructor                                    //
////////////////////////////////////////////////////////////////////////////////
EmDQMReco::EmDQMReco(const edm::ParameterSet& pset)
{
  ////////////////////////////////////////////////////////////
  //          Read from configuration file                  //
  ////////////////////////////////////////////////////////////
  dirname_="HLT/HLTEgammaValidation/"+pset.getParameter<std::string>("@module_label");

  // parameters for generator study
  reqNum    = pset.getParameter<unsigned int>("reqNum");
  pdgGen    = pset.getParameter<int>("pdgGen");
  recoEtaAcc = pset.getParameter<double>("genEtaAcc");
  recoEtAcc  = pset.getParameter<double>("genEtAcc");
  // plotting parameters (untracked because they don't affect the physics)
  plotPtMin  = pset.getUntrackedParameter<double>("PtMin",0.);
  plotPtMax  = pset.getUntrackedParameter<double>("PtMax",1000.);
  plotEtaMax = pset.getUntrackedParameter<double>("EtaMax", 2.7);
  plotPhiMax = pset.getUntrackedParameter<double>("PhiMax", 3.15);
  plotBins   = pset.getUntrackedParameter<unsigned int>("Nbins",50);
  useHumanReadableHistTitles = pset.getUntrackedParameter<bool>("useHumanReadableHistTitles", false);

  triggerNameRecoMonPath = pset.getUntrackedParameter<std::string>("triggerNameRecoMonPath","HLT_MinBias");
  processNameRecoMonPath = pset.getUntrackedParameter<std::string>("processNameRecoMonPath","HLT");

  recoElectronsInput = consumes<reco::GsfElectronCollection>(pset.getUntrackedParameter<edm::InputTag>("recoElectrons",edm::InputTag("gsfElectrons")));
  recoObjectsEBT = consumes<std::vector<reco::SuperCluster>>(edm::InputTag("correctedHybridSuperClusters"));
  recoObjectsEET = consumes<std::vector<reco::SuperCluster>>(edm::InputTag("correctedMulti5x5SuperClustersWithPreshower"));
  hltResultsT    = consumes<edm::TriggerResults>(edm::InputTag("TriggerResults","",processNameRecoMonPath));
  triggerObjT    = consumes<trigger::TriggerEventWithRefs>(edm::InputTag("hltTriggerSummaryRAW"));

  // preselction cuts
  // recocutCollection_= pset.getParameter<edm::InputTag>("cutcollection");
  recocut_          = pset.getParameter<int>("cutnum");

  // prescale = 10;
  eventnum = 0;

  // just init
  isHltConfigInitialized_ = false;

  ////////////////////////////////////////////////////////////
  //         Read in the Vector of Parameter Sets.          //
  //           Information for each filter-step             //
  ////////////////////////////////////////////////////////////
  std::vector<edm::ParameterSet> filters =
       pset.getParameter<std::vector<edm::ParameterSet> >("filters");

  int i = 0;
  for(std::vector<edm::ParameterSet>::iterator filterconf = filters.begin() ; filterconf != filters.end() ; filterconf++)
  {

    theHLTCollectionLabels.push_back(filterconf->getParameter<edm::InputTag>("HLTCollectionLabels"));
    theHLTOutputTypes.push_back(filterconf->getParameter<int>("theHLTOutputTypes"));
    // Grab the human-readable name, if it is not specified, use the Collection Label
    theHLTCollectionHumanNames.push_back(filterconf->getUntrackedParameter<std::string>("HLTCollectionHumanName",theHLTCollectionLabels[i].label()));

    std::vector<double> bounds = filterconf->getParameter<std::vector<double> >("PlotBounds");
    // If the size of plot "bounds" vector != 2, abort
    assert(bounds.size() == 2);
    plotBounds.push_back(std::pair<double,double>(bounds[0],bounds[1]));
    isoNames.push_back(filterconf->getParameter<std::vector<edm::InputTag> >("IsoCollections"));

    for (unsigned int i=0; i<isoNames.back().size(); i++) {
      switch(theHLTOutputTypes.back())  {
      case trigger::TriggerL1NoIsoEG:
	histoFillerL1NonIso->isoNameTokens_.push_back(consumes<edm::AssociationMap<edm::OneToValue<l1extra::L1EmParticleCollection , float>>>(isoNames.back()[i]));
	break;
      case trigger::TriggerL1IsoEG: // Isolated Level 1
	histoFillerL1Iso->isoNameTokens_.push_back(consumes<edm::AssociationMap<edm::OneToValue<l1extra::L1EmParticleCollection , float>>>(isoNames.back()[i]));
	break;
      case trigger::TriggerPhoton: // Photon
	histoFillerPho->isoNameTokens_.push_back(consumes<edm::AssociationMap<edm::OneToValue<reco::RecoEcalCandidateCollection , float>>>(isoNames.back()[i]));
	break;
      case trigger::TriggerElectron: // Electron
	histoFillerEle->isoNameTokens_.push_back(consumes<edm::AssociationMap<edm::OneToValue<reco::ElectronCollection , float>>>(isoNames.back()[i]));
	break;
      case trigger::TriggerCluster: // TriggerCluster
	histoFillerClu->isoNameTokens_.push_back(consumes<edm::AssociationMap<edm::OneToValue<reco::RecoEcalCandidateCollection , float>>>(isoNames.back()[i]));
	break;
      default:
	throw(cms::Exception("Release Validation Error") << "HLT output type not implemented: theHLTOutputTypes[n]" );
      }
    }

    // If the size of the isoNames vector is not greater than zero, abort
    assert(isoNames.back().size()>0);
    if (isoNames.back().at(0).label()=="none") {
      plotiso.push_back(false);
    } else {
      plotiso.push_back(true);
    }
    i++;
  } // END of loop over parameter sets

  // Record number of HLTCollectionLabels
  numOfHLTCollectionLabels = theHLTCollectionLabels.size();

}

///
///
///
void EmDQMReco::dqmBeginRun(const edm::Run& iRun, const edm::EventSetup& iSetup ) {

  bool isHltConfigChanged = false; // change of cfg at run boundaries?
  isHltConfigInitialized_ = hltConfig_.init( iRun, iSetup, "HLT", isHltConfigChanged );

}

////////////////////////////////////////////////////////////////////////////////
//       book DQM histograms                                                  //
////////////////////////////////////////////////////////////////////////////////
void
EmDQMReco::bookHistograms(DQMStore::IBooker &iBooker, edm::Run const &iRun, edm::EventSetup const &iSetup)
{
  //edm::Service<TFileService> fs;
  iBooker.setCurrentFolder(dirname_);

  ////////////////////////////////////////////////////////////
  //  Set up Histogram of Effiency vs Step.                 //
  //   theHLTCollectionLabels is a vector of InputTags      //
  //    from the configuration file.                        //
  ////////////////////////////////////////////////////////////

  std::string histName="total_eff";
  std::string histTitle = "total events passing";
  // This plot will have bins equal to 2+(number of
  //        HLTCollectionLabels in the config file)
  totalreco = iBooker.book1D(histName.c_str(),histTitle.c_str(),numOfHLTCollectionLabels+2,0,numOfHLTCollectionLabels+2);
  totalreco->setBinLabel(numOfHLTCollectionLabels+1,"Total");
  totalreco->setBinLabel(numOfHLTCollectionLabels+2,"Reco");
  for (unsigned int u=0; u<numOfHLTCollectionLabels; u++){totalreco->setBinLabel(u+1,theHLTCollectionLabels[u].label().c_str());}

  histName="total_eff_RECO_matched";
  histTitle="total events passing (Reco matched)";
  totalmatchreco = iBooker.book1D(histName.c_str(),histTitle.c_str(),numOfHLTCollectionLabels+2,0,numOfHLTCollectionLabels+2);
  totalmatchreco->setBinLabel(numOfHLTCollectionLabels+1,"Total");
  totalmatchreco->setBinLabel(numOfHLTCollectionLabels+2,"Reco");
  for (unsigned int u=0; u<numOfHLTCollectionLabels; u++){totalmatchreco->setBinLabel(u+1,theHLTCollectionLabels[u].label().c_str());}

  // MonitorElement* tmphisto;
  MonitorElement* tmpiso;

  ////////////////////////////////////////////////////////////
  // Set up generator-level histograms                      //
  ////////////////////////////////////////////////////////////
  std::string pdgIdString;
  switch(pdgGen) {
  case 11:
    pdgIdString="Electron";break;
  case 22:
    pdgIdString="Photon";break;
  default:
    pdgIdString="Particle";
  }

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

  // reco
  // (note that reset(..) must be used to set the value of the scoped_ptr...)
  histReco.reset(
      new FourVectorMonitorElements(this, iBooker,
          "reco_%s",             // pattern for histogram name
          "%s of " + pdgIdString + "s"
      ));

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

  // monpath
  histRecoMonpath.reset(
       new FourVectorMonitorElements(this, iBooker,
           "reco_%s_monpath",   // pattern for histogram name
           "%s of " + pdgIdString + "s monpath"
       )
  );

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

  // TODO: WHAT ARE THESE HISTOGRAMS FOR ? THEY SEEM NEVER REFERENCED ANYWHERE IN THIS FILE...
  // final X monpath
  histMonpath.reset(
       new FourVectorMonitorElements(this, iBooker,
           "final_%s_monpath",   // pattern for histogram name
           "Final %s Monpath"
       )
  );

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

  ////////////////////////////////////////////////////////////
  //  Set up histograms of HLT objects                      //
  ////////////////////////////////////////////////////////////

  // Determine what strings to use for histogram titles
  std::vector<std::string> HltHistTitle;
  if ( theHLTCollectionHumanNames.size() == numOfHLTCollectionLabels && useHumanReadableHistTitles ) {
    HltHistTitle = theHLTCollectionHumanNames;
  } else {
    for (unsigned int i =0; i < numOfHLTCollectionLabels; i++) {
      HltHistTitle.push_back(theHLTCollectionLabels[i].label());
    }
  }

  for(unsigned int i = 0; i< numOfHLTCollectionLabels ; i++){
    //--------------------
    // distributions of HLT objects passing filter i
    //--------------------

//    // Et
//    histName = theHLTCollectionLabels[i].label()+"et_all";
//    histTitle = HltHistTitle[i]+" Et (ALL)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax);
//    ethist.push_back(tmphisto);
//
//    // Eta
//    histName = theHLTCollectionLabels[i].label()+"eta_all";
//    histTitle = HltHistTitle[i]+" #eta (ALL)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax);
//    etahist.push_back(tmphisto);
//
//    // phi
//    histName = theHLTCollectionLabels[i].label()+"phi_all";
//    histTitle = HltHistTitle[i]+" #phi (ALL)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax);
//    phiHist.push_back(tmphisto);

    standardHist.push_back(new FourVectorMonitorElements(this, iBooker,
							 theHLTCollectionLabels[i].label()+"%s_all", // histogram name
							 HltHistTitle[i]+" %s (ALL)"                 // histogram title
							 ));

    //--------------------
    // distributions of reco object matching HLT object passing filter i
    //--------------------

    // Et
//    histName = theHLTCollectionLabels[i].label()+"et_RECO_matched";
//    histTitle = HltHistTitle[i]+" Et (RECO matched)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax);
//    ethistmatchreco.push_back(tmphisto);

//    // Eta
//    histName = theHLTCollectionLabels[i].label()+"eta_RECO_matched";
//    histTitle = HltHistTitle[i]+" #eta (RECO matched)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax);
//    etahistmatchreco.push_back(tmphisto);
//
//    // phi
//    histName = theHLTCollectionLabels[i].label()+"phi_RECO_matched";
//    histTitle = HltHistTitle[i]+" #phi (RECO matched)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax);
//    phiHistMatchReco.push_back(tmphisto);
    histMatchReco.push_back(new FourVectorMonitorElements(this, iBooker,
        theHLTCollectionLabels[i].label()+"%s_RECO_matched", // histogram name
        HltHistTitle[i]+" %s (RECO matched)"                 // histogram title
        ));

    //--------------------
    // distributions of reco object matching HLT object passing filter i
    //--------------------

//    // Et
//    histName = theHLTCollectionLabels[i].label()+"et_RECO_matched_monpath";
//    histTitle = HltHistTitle[i]+" Et (RECO matched, monpath)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax);
//    ethistmatchrecomonpath.push_back(tmphisto);
//
//    // Eta
//    histName = theHLTCollectionLabels[i].label()+"eta_RECO_matched_monpath";
//    histTitle = HltHistTitle[i]+" #eta (RECO matched, monpath)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax);
//    etahistmatchrecomonpath.push_back(tmphisto);
//
//    // phi
//    histName = theHLTCollectionLabels[i].label()+"phi_RECO_matched_monpath";
//    histTitle = HltHistTitle[i]+" #phi (RECO matched, monpath)";
//    tmphisto =  iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax);
//    phiHistMatchRecoMonPath.push_back(tmphisto);

    histMatchRecoMonPath.push_back(new FourVectorMonitorElements(this, iBooker,
        theHLTCollectionLabels[i].label()+"%s_RECO_matched_monpath", // histogram name
        HltHistTitle[i]+" %s (RECO matched, monpath)"                // histogram title
        ));
    //--------------------
    // distributions of HLT object that is closest delta-R match to sorted reco particle(s)
    //--------------------

    // Et
//    histName  = theHLTCollectionLabels[i].label()+"et_reco";
//    histTitle = HltHistTitle[i]+" Et (reco)";
//    tmphisto  = iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax);
//    histEtOfHltObjMatchToReco.push_back(tmphisto);
//
//    // eta
//    histName  = theHLTCollectionLabels[i].label()+"eta_reco";
//    histTitle = HltHistTitle[i]+" eta (reco)";
//    tmphisto  = iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax);
//    histEtaOfHltObjMatchToReco.push_back(tmphisto);
//
//    // phi
//    histName  = theHLTCollectionLabels[i].label()+"phi_reco";
//    histTitle = HltHistTitle[i]+" phi (reco)";
//    tmphisto  = iBooker.book1D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax);
//    histPhiOfHltObjMatchToReco.push_back(tmphisto);

    histHltObjMatchToReco.push_back(new FourVectorMonitorElements(this, iBooker,
        theHLTCollectionLabels[i].label()+"%s_reco",   // histogram name
        HltHistTitle[i]+" %s (reco)"                  // histogram title
        ));

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

    if (!plotiso[i]) {
      tmpiso = NULL;
      etahistiso.push_back(tmpiso);
      ethistiso.push_back(tmpiso);
      phiHistIso.push_back(tmpiso);

      etahistisomatchreco.push_back(tmpiso);
      ethistisomatchreco.push_back(tmpiso);
      phiHistIsoMatchReco.push_back(tmpiso);

      histEtaIsoOfHltObjMatchToReco.push_back(tmpiso);
      histEtIsoOfHltObjMatchToReco.push_back(tmpiso);
      histPhiIsoOfHltObjMatchToReco.push_back(tmpiso);

    } else {

      //--------------------
      // 2D plot: Isolation values vs X for all objects
      //--------------------

      // X = eta
      histName  = theHLTCollectionLabels[i].label()+"eta_isolation_all";
      histTitle = HltHistTitle[i]+" isolation vs #eta (all)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      etahistiso.push_back(tmpiso);

      // X = et
      histName  = theHLTCollectionLabels[i].label()+"et_isolation_all";
      histTitle = HltHistTitle[i]+" isolation vs Et (all)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      ethistiso.push_back(tmpiso);

      // X = phi
      histName  = theHLTCollectionLabels[i].label()+"phi_isolation_all";
      histTitle = HltHistTitle[i]+" isolation vs #phi (all)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      phiHistIso.push_back(tmpiso);

      //--------------------
      // 2D plot: Isolation values vs X for reco matched objects
      //--------------------

      // X = eta
      histName  = theHLTCollectionLabels[i].label()+"eta_isolation_RECO_matched";
      histTitle = HltHistTitle[i]+" isolation vs #eta (reco matched)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      etahistisomatchreco.push_back(tmpiso);

      // X = et
      histName  = theHLTCollectionLabels[i].label()+"et_isolation_RECO_matched";
      histTitle = HltHistTitle[i]+" isolation vs Et (reco matched)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      ethistisomatchreco.push_back(tmpiso);

      // X = eta
      histName  = theHLTCollectionLabels[i].label()+"phi_isolation_RECO_matched";
      histTitle = HltHistTitle[i]+" isolation vs #phi (reco matched)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      phiHistIsoMatchReco.push_back(tmpiso);

      //--------------------
      // 2D plot: Isolation values vs X for HLT object that
      // is closest delta-R match to sorted reco particle(s)
      //--------------------

      // X = eta
      histName  = theHLTCollectionLabels[i].label()+"eta_isolation_reco";
      histTitle = HltHistTitle[i]+" isolation vs #eta (reco)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,-plotEtaMax,plotEtaMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      histEtaIsoOfHltObjMatchToReco.push_back(tmpiso);

      // X = et
      histName  = theHLTCollectionLabels[i].label()+"et_isolation_reco";
      histTitle = HltHistTitle[i]+" isolation vs Et (reco)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,plotPtMin,plotPtMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      histEtIsoOfHltObjMatchToReco.push_back(tmpiso);

      // X = phi
      histName  = theHLTCollectionLabels[i].label()+"phi_isolation_reco";
      histTitle = HltHistTitle[i]+" isolation vs #phi (reco)";
      tmpiso    = iBooker.book2D(histName.c_str(),histTitle.c_str(),plotBins,-plotPhiMax,plotPhiMax,plotBins,plotBounds[i].first,plotBounds[i].second);
      histPhiIsoOfHltObjMatchToReco.push_back(tmpiso);
      //--------------------

    } // END of HLT histograms
  }
}

////////////////////////////////////////////////////////////////////////////////
//                                Destructor                                  //
////////////////////////////////////////////////////////////////////////////////
EmDQMReco::~EmDQMReco(){
}

////////////////////////////////////////////////////////////////////////////////
//                     method called to for each event                        //
////////////////////////////////////////////////////////////////////////////////
void
EmDQMReco::analyze(const edm::Event & event , const edm::EventSetup& setup)
{

  // protect from hlt config failure
  if( !isHltConfigInitialized_ ) return;

  eventnum++;
  bool plotMonpath = false;
  bool plotReco = true;

  edm::Handle<edm::View<reco::Candidate> > recoObjects;
  edm::Handle<std::vector<reco::SuperCluster> > recoObjectsEB;
  edm::Handle<std::vector<reco::SuperCluster> > recoObjectsEE;

  if (pdgGen == 11) {

    event.getByToken(recoElectronsInput, recoObjects);

    if (recoObjects->size() < (unsigned int)recocut_) {
      // edm::LogWarning("EmDQMReco") << "Less than "<< recocut_ <<" Reco particles with pdgId=" << pdgGen << ".  Only " << cutRecoCounter->size() << " particles.";
      return;
    }
  } else if (pdgGen == 22) {

    event.getByToken(recoObjectsEBT, recoObjectsEB);
    event.getByToken(recoObjectsEET, recoObjectsEE);

    if (recoObjectsEB->size() + recoObjectsEE->size() < (unsigned int)recocut_) {
      // edm::LogWarning("EmDQMReco") << "Less than "<< recocut_ <<" Reco particles with pdgId=" << pdgGen << ".  Only " << cutRecoCounter.size() << " particles.";
      return;
    }
  }

  edm::Handle<edm::TriggerResults> HLTR;
  event.getByToken(hltResultsT, HLTR);

  ///
  /// NOTE:
  /// hltConfigProvider initialization has been moved to beginRun()
  ///

  /* if (theHLTCollectionHumanNames[0] == "hltL1sRelaxedSingleEgammaEt8"){
    triggerIndex = hltConfig.triggerIndex("HLT_L1SingleEG8");
  } else if (theHLTCollectionHumanNames[0] == "hltL1sRelaxedSingleEgammaEt5") {
    triggerIndex = hltConfig.triggerIndex("HLT_L1SingleEG5");
  } else if (theHLTCollectionHumanNames[0] == "hltL1sRelaxedDoubleEgammaEt5") {
    triggerIndex = hltConfig.triggerIndex("HLT_L1DoubleEG5");
  } else {
    triggerIndex = hltConfig.triggerIndex("");
    } */

  unsigned int triggerIndex;
  triggerIndex = hltConfig_.triggerIndex(triggerNameRecoMonPath);

  //triggerIndex must be less than the size of HLTR or you get a CMSException
  bool isFired = false;
  if (triggerIndex < HLTR->size()){
    isFired = HLTR->accept(triggerIndex);
  }

  // fill L1 and HLT info
  // get objects possed by each filter
  edm::Handle<trigger::TriggerEventWithRefs> triggerObj;
  event.getByToken(triggerObjT, triggerObj);

  if(!triggerObj.isValid()) {
    edm::LogWarning("EmDQMReco") << "RAW-type HLT results not found, skipping event";
    return;
  }

  ////////////////////////////////////////////////////////////
  //  Fill the bin labeled "Total"                          //
  //   This will be the number of events looked at.         //
  ////////////////////////////////////////////////////////////
  totalreco->Fill(numOfHLTCollectionLabels+0.5);
  totalmatchreco->Fill(numOfHLTCollectionLabels+.5);

  ////////////////////////////////////////////////////////////
  //  Fill the bin labeled "Total"                          //
  //   This will be the number of events looked at.         //
  ////////////////////////////////////////////////////////////
  //total->Fill(numOfHLTCollectionLabels+0.5);
  //totalmatch->Fill(numOfHLTCollectionLabels+0.5);

  ////////////////////////////////////////////////////////////
  //               Fill reconstruction info                      //
  ////////////////////////////////////////////////////////////
  // the recocut_ highest Et generator objects of the preselected type are our matches

  std::vector<reco::Particle> sortedReco;
  if (plotReco == true) {
    if (pdgGen == 11) {
      for(edm::View<reco::Candidate>::const_iterator recopart = recoObjects->begin(); recopart != recoObjects->end();recopart++){
        reco::Particle tmpcand(  recopart->charge(), recopart->p4(), recopart->vertex(),recopart->pdgId(),recopart->status() );
        sortedReco.push_back(tmpcand);
      }
    }
    else if (pdgGen == 22) {
      for(std::vector<reco::SuperCluster>::const_iterator recopart2 = recoObjectsEB->begin(); recopart2 != recoObjectsEB->end();recopart2++){
        float en = recopart2->energy();
        float er = sqrt(pow(recopart2->x(),2) + pow(recopart2->y(),2) + pow(recopart2->z(),2) );
        float px = recopart2->energy()*recopart2->x()/er;
        float py = recopart2->energy()*recopart2->y()/er;
        float pz = recopart2->energy()*recopart2->z()/er;
        reco::Candidate::LorentzVector thisLV(px,py,pz,en);
        reco::Particle tmpcand(  0, thisLV, math::XYZPoint(0.,0.,0.), 22, 1 );
        sortedReco.push_back(tmpcand);
      }
      for(std::vector<reco::SuperCluster>::const_iterator recopart2 = recoObjectsEE->begin(); recopart2 != recoObjectsEE->end();recopart2++){
        float en = recopart2->energy();
        float er = sqrt(pow(recopart2->x(),2) + pow(recopart2->y(),2) + pow(recopart2->z(),2) );
        float px = recopart2->energy()*recopart2->x()/er;
        float py = recopart2->energy()*recopart2->y()/er;
        float pz = recopart2->energy()*recopart2->z()/er;
        reco::Candidate::LorentzVector thisLV(px,py,pz,en);
        reco::Particle tmpcand(  0, thisLV, math::XYZPoint(0.,0.,0.), 22, 1 );
        sortedReco.push_back(tmpcand);
      }
    }

    std::sort(sortedReco.begin(),sortedReco.end(),pTComparator_ );

    // Now the collection of gen particles is sorted by pt.
    // So, remove all particles from the collection so that we
    // only have the top "1 thru recocut_" particles in it

    sortedReco.erase(sortedReco.begin()+recocut_,sortedReco.end());

    for (unsigned int i = 0 ; i < recocut_ ; i++ ) {
        //validity has been implicitily checked by the cut on recocut_ above
        histReco->fill(sortedReco[i].p4());

//      etreco ->Fill( sortedReco[i].et()  );
//      etareco->Fill( sortedReco[i].eta() );
//      phiReco->Fill( sortedReco[i].phi() );

      if (isFired) {
        histRecoMonpath->fill(sortedReco[i].p4());
        plotMonpath = true;
      }

    } // END of loop over Reconstructed particles

    if (recocut_ >= reqNum) totalreco->Fill(numOfHLTCollectionLabels+1.5); // this isn't really needed anymore keep for backward comp.
    if (recocut_ >= reqNum) totalmatchreco->Fill(numOfHLTCollectionLabels+1.5); // this isn't really needed anymore keep for backward comp.

  }

   ////////////////////////////////////////////////////////////
  //            Loop over filter modules                    //
  ////////////////////////////////////////////////////////////
  for(unsigned int n=0; n < numOfHLTCollectionLabels ; n++) {
    // These numbers are from the Parameter Set, such as:
    //   theHLTOutputTypes = cms.uint32(100)
    switch(theHLTOutputTypes[n])
    {
      case trigger::TriggerL1NoIsoEG: // Non-isolated Level 1
        histoFillerL1NonIso->fillHistos(triggerObj,event,n, sortedReco, plotReco, plotMonpath);
	break;
      case trigger::TriggerL1IsoEG: // Isolated Level 1
        histoFillerL1Iso->fillHistos(triggerObj,event,n, sortedReco, plotReco, plotMonpath);
	break;
      case trigger::TriggerPhoton: // Photon
        histoFillerPho->fillHistos(triggerObj,event,n, sortedReco, plotReco, plotMonpath);
	break;
      case trigger::TriggerElectron: // Electron
        histoFillerEle->fillHistos(triggerObj,event,n, sortedReco, plotReco, plotMonpath);
	break;
      case trigger::TriggerCluster: // TriggerCluster
        histoFillerClu->fillHistos(triggerObj,event,n, sortedReco, plotReco, plotMonpath);
	break;
      default:
        throw(cms::Exception("Release Validation Error") << "HLT output type not implemented: theHLTOutputTypes[n]" );
    }
    } // END of loop over filter modules
}

////////////////////////////////////////////////////////////////////////////////
// fillHistos                                                                 //
//   Called by analyze method.                                                //
////////////////////////////////////////////////////////////////////////////////
template <class T> void HistoFillerReco<T>::fillHistos(edm::Handle<trigger::TriggerEventWithRefs>& triggerObj,const edm::Event& iEvent ,unsigned int n, std::vector<reco::Particle>& sortedReco, bool plotReco, bool plotMonpath)
{
  std::vector<edm::Ref<T> > recoecalcands;
  if ( ( triggerObj->filterIndex(dqm->theHLTCollectionLabels[n])>=triggerObj->size() )){ // only process if available
    return;
  }

  ////////////////////////////////////////////////////////////
  //      Retrieve saved filter objects                     //
  ////////////////////////////////////////////////////////////
  triggerObj->getObjects(triggerObj->filterIndex(dqm->theHLTCollectionLabels[n]),dqm->theHLTOutputTypes[n],recoecalcands);
  //Danger: special case, L1 non-isolated
  // needs to be merged with L1 iso
  if (dqm->theHLTOutputTypes[n] == trigger::TriggerL1NoIsoEG){
    std::vector<edm::Ref<T> > isocands;
    triggerObj->getObjects(triggerObj->filterIndex(dqm->theHLTCollectionLabels[n]),trigger::TriggerL1IsoEG,isocands);
    if (isocands.size()>0)
      {
        for (unsigned int i=0; i < isocands.size(); i++)
          recoecalcands.push_back(isocands[i]);
      }
  } // END of if theHLTOutputTypes == 82

  if (recoecalcands.size() < 1){ // stop if no object passed the previous filter
    return;
  }

  if (recoecalcands.size() >= dqm->reqNum )
    dqm->totalreco->Fill(n+0.5);

  ///////////////////////////////////////////////////
  // check for validity                            //
  // prevents crash in CMSSW_3_1_0_pre6            //
  ///////////////////////////////////////////////////
  for (unsigned int j=0; j<recoecalcands.size(); j++){
    if(!( recoecalcands.at(j).isAvailable())){
      edm::LogError("EmDQMReco") << "Event content inconsistent: TriggerEventWithRefs contains invalid Refs" << std::endl << "invalid refs for: " << dqm->theHLTCollectionLabels[n].label();
      return;
    }
  }

  ////////////////////////////////////////////////////////////
  //  Loop over all HLT objects in this filter step, and    //
  //  fill histograms.                                      //
  ////////////////////////////////////////////////////////////
  //  bool foundAllMatches = false;
  //  unsigned int numOfHLTobjectsMatched = 0;
  for (unsigned int i=0; i<recoecalcands.size(); i++) {

    dqm->standardHist[n].fill(recoecalcands[i]->p4());

    ////////////////////////////////////////////////////////////
    //  Plot isolation variables (show the not-yet-cut        //
    //  isolation, i.e. associated to next filter)            //
    ////////////////////////////////////////////////////////////
    if ( n+1 < dqm->numOfHLTCollectionLabels ) { // can't plot beyond last
      if (dqm->plotiso[n+1]) {
        for (unsigned int j =  0 ; j < isoNameTokens_.size() ;j++  ){
          edm::Handle<edm::AssociationMap<edm::OneToValue< T , float > > > depMap;
          iEvent.getByToken(isoNameTokens_.at(j),depMap);
          if (depMap.isValid()){ //Map may not exist if only one candidate passes a double filter
            typename edm::AssociationMap<edm::OneToValue< T , float > >::const_iterator mapi = depMap->find(recoecalcands[i]);
            if (mapi!=depMap->end()){  // found candidate in isolation map!
              dqm->etahistiso[n+1]->Fill(recoecalcands[i]->eta(),mapi->val);
              dqm->ethistiso[n+1]->Fill(recoecalcands[i]->et()  ,mapi->val);
              dqm->phiHistIso[n+1]->Fill(recoecalcands[i]->phi(),mapi->val);
      }
    }
  }
      }
    } // END of if n+1 < then the number of hlt collections
  }

  ////////////////////////////////////////////////////////////
  // Loop over the Reconstructed Particles, and find the        //
  // closest HLT object match.                              //
  ////////////////////////////////////////////////////////////
  if (plotReco == true) {
    for (unsigned int i=0; i < dqm->recocut_; i++) {
      math::XYZVector currentRecoParticleMomentum = sortedReco[i].momentum();

      // float closestRecoDeltaR = 0.5;
      float closestRecoDeltaR = 1000.;
      int closestRecoEcalCandIndex = -1;
      for (unsigned int j=0; j<recoecalcands.size(); j++) {
        float deltaR = DeltaR(recoecalcands[j]->momentum(),currentRecoParticleMomentum);

        if (deltaR < closestRecoDeltaR) {
          closestRecoDeltaR = deltaR;
          closestRecoEcalCandIndex = j;
        }
    }

      // If an HLT object was found within some delta-R
      // of this reco particle, store it in a histogram
      if ( closestRecoEcalCandIndex >= 0 ) {
//        histEtOfHltObjMatchToReco[n] ->Fill( recoecalcands[closestRecoEcalCandIndex]->et()  );
//        histEtaOfHltObjMatchToReco[n]->Fill( recoecalcands[closestRecoEcalCandIndex]->eta() );
//        histPhiOfHltObjMatchToReco[n]->Fill( recoecalcands[closestRecoEcalCandIndex]->phi() );

          dqm->histHltObjMatchToReco[n].fill(recoecalcands[closestRecoEcalCandIndex]->p4());

        // Also store isolation info
        if (n+1 < dqm->numOfHLTCollectionLabels){ // can't plot beyond last
          if (dqm->plotiso[n+1] ){  // only plot if requested in config
            for (unsigned int j =  0 ; j < isoNameTokens_.size() ;j++  ){
              edm::Handle<edm::AssociationMap<edm::OneToValue< T , float > > > depMap;
              iEvent.getByToken(isoNameTokens_.at(j),depMap);
              if (depMap.isValid()){ //Map may not exist if only one candidate passes a double filter
                typename edm::AssociationMap<edm::OneToValue< T , float > >::const_iterator mapi = depMap->find(recoecalcands[closestRecoEcalCandIndex]);
                if (mapi!=depMap->end()) {  // found candidate in isolation map!
                  dqm->histEtaIsoOfHltObjMatchToReco[n+1]->Fill( recoecalcands[closestRecoEcalCandIndex]->eta(),mapi->val);
                  dqm->histEtIsoOfHltObjMatchToReco[n+1] ->Fill( recoecalcands[closestRecoEcalCandIndex]->et(), mapi->val);
                  dqm->histPhiIsoOfHltObjMatchToReco[n+1] ->Fill( recoecalcands[closestRecoEcalCandIndex]->phi(), mapi->val);
                }
              }
            }
          }
        }
      } // END of if closestEcalCandIndex >= 0
    }

    ////////////////////////////////////////////////////////////
    //        Fill reco matched objects into histograms         //
    ////////////////////////////////////////////////////////////
    unsigned int mtachedRecoParts = 0;
    float minrecodist=0.3;
    if(n==0) minrecodist=0.5; //low L1-resolution => allow wider matching
    for(unsigned int i =0; i < dqm->recocut_; i++){
      //match generator candidate
      bool matchThis= false;
      math::XYZVector candDir=sortedReco[i].momentum();
      unsigned int closest = 0;
      double closestDr = 1000.;
      for(unsigned int trigOb = 0 ; trigOb < recoecalcands.size(); trigOb++){
        double dr = DeltaR(recoecalcands[trigOb]->momentum(),candDir);
        if (dr < closestDr) {
          closestDr = dr;
          closest = trigOb;
        }
        if (closestDr > minrecodist) { // it's not really a "match" if it's that far away
          closest = -1;
        } else {
          mtachedRecoParts++;
          matchThis = true;
        }
      }
      if ( !matchThis ) continue; // only plot matched candidates
      // fill coordinates of mc particle matching trigger object
//      ethistmatchreco[n] ->Fill( sortedReco[i].et()  );
//      etahistmatchreco[n]->Fill( sortedReco[i].eta() );
//      phiHistMatchReco[n]->Fill( sortedReco[i].phi() );
      dqm->histMatchReco[n].fill(sortedReco[i].p4());

      if (plotMonpath) {
//        ethistmatchrecomonpath[n]->Fill( sortedReco[i].et() );
//        etahistmatchrecomonpath[n]->Fill( sortedReco[i].eta() );
//        phiHistMatchRecoMonPath[n]->Fill( sortedReco[i].phi() );
          dqm->histMatchRecoMonPath[n].fill(sortedReco[i].p4());

      }
      ////////////////////////////////////////////////////////////
      //  Plot isolation variables (show the not-yet-cut        //
      //  isolation, i.e. associated to next filter)            //
      ////////////////////////////////////////////////////////////
      if (n+1 < dqm->numOfHLTCollectionLabels){ // can't plot beyond last
        if (dqm->plotiso[n+1] ){  // only plot if requested in config
          for (unsigned int j =  0 ; j < isoNameTokens_.size() ;j++  ){
            edm::Handle<edm::AssociationMap<edm::OneToValue< T , float > > > depMapReco;
            iEvent.getByToken(isoNameTokens_.at(j),depMapReco);
            if (depMapReco.isValid()){ //Map may not exist if only one candidate passes a double filter
              typename edm::AssociationMap<edm::OneToValue< T , float > >::const_iterator mapi = depMapReco->find(recoecalcands[closest]);
              if (mapi!=depMapReco->end()){  // found candidate in isolation map!
                dqm->etahistisomatchreco[n+1]->Fill(sortedReco[i].eta(),mapi->val);
                dqm->ethistisomatchreco[n+1]->Fill(sortedReco[i].et(),mapi->val);
                dqm->phiHistIsoMatchReco[n+1]->Fill(sortedReco[i].eta(),mapi->val);
              }
            }
          }
        }
      } // END of if n+1 < then the number of hlt collections
    }
    // fill total reco matched efficiency
    if (mtachedRecoParts >= dqm->reqNum )
     dqm-> totalmatchreco->Fill(n+0.5);
  }

}

////////////////////////////////////////////////////////////////////////////////
//      method called once each job just after ending the event loop          //
////////////////////////////////////////////////////////////////////////////////
void EmDQMReco::endJob(){

}

DEFINE_FWK_MODULE(EmDQMReco);


#include "EOSPixels.hpp"
#include <utility>
#include <vector>
#include <string>

#include <cmath>
#include <eosiolib/action.hpp>
#include <eosiolib/asset.hpp>

#include <eosiolib/time.hpp>
#include <eosiolib/asset.hpp>
#include <eosiolib/contract.hpp>
#include <eosiolib/types.hpp>
#include <eosiolib/transaction.hpp>
#include <eosiolib/crypto.h>
#include <boost/algorithm/string.hpp>

/*
#include <stdlib.h>
#define SHA256_ROTL(a,b) (((a>>(32-b))&(0x7fffffff>>(31-b)))|(a<<b))
#define SHA256_SR(a,b) ((a>>b)&(0x7fffffff>>(b-1)))
#define SHA256_Ch(x,y,z) ((x&y)^((~x)&z))
#define SHA256_Maj(x,y,z) ((x&y)^(x&z)^(y&z))
#define SHA256_E0(x) (SHA256_ROTL(x,30)^SHA256_ROTL(x,19)^SHA256_ROTL(x,10))
#define SHA256_E1(x) (SHA256_ROTL(x,26)^SHA256_ROTL(x,21)^SHA256_ROTL(x,7))
#define SHA256_O0(x) (SHA256_ROTL(x,25)^SHA256_ROTL(x,14)^SHA256_SR(x,3))
#define SHA256_O1(x) (SHA256_ROTL(x,15)^SHA256_ROTL(x,13)^SHA256_SR(x,10))

#include "memo.hpp"
#include "types.hpp"

using namespace eosio;
using namespace std;

extern char* StrSHA256(const char* str, long long length, char* sha256){

    //计算字符串SHA-256
    //参数说明：
    //str         字符串指针
    //length      字符串长度
   // sha256         用于保存SHA-256的字符串指针
    //返回值为参数sha256

    char *pp, *ppend;
    long l, i, W[64], T1, T2, A, B, C, D, E, F, G, H, H0, H1, H2, H3, H4, H5, H6, H7;
    H0 = 0x6a09e667, H1 = 0xbb67ae85, H2 = 0x3c6ef372, H3 = 0xa54ff53a;
    H4 = 0x510e527f, H5 = 0x9b05688c, H6 = 0x1f83d9ab, H7 = 0x5be0cd19;
    long K[64] = {
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
    };
    l = length + ((length % 64 > 56) ? (128 - length % 64) : (64 - length % 64));
    if (!(pp = (char*)malloc((unsigned long)l))) return 0;
    for (i = 0; i < length; pp[i + 3 - 2 * (i % 4)] = str[i], i++);
    for (pp[i + 3 - 2 * (i % 4)] = 128, i++; i < l; pp[i + 3 - 2 * (i % 4)] = 0, i++);
    *((long*)(pp + l - 4)) = length << 3;
    *((long*)(pp + l - 8)) = length >> 29;
    for (ppend = pp + l; pp < ppend; pp += 64){
        for (i = 0; i < 16; W[i] = ((long*)pp)[i], i++);
        for (i = 16; i < 64; W[i] = (SHA256_O1(W[i - 2]) + W[i - 7] + SHA256_O0(W[i - 15]) + W[i - 16]), i++);
        A = H0, B = H1, C = H2, D = H3, E = H4, F = H5, G = H6, H = H7;
        for (i = 0; i < 64; i++){
            T1 = H + SHA256_E1(E) + SHA256_Ch(E, F, G) + K[i] + W[i];
            T2 = SHA256_E0(A) + SHA256_Maj(A, B, C);
            H = G, G = F, F = E, E = D + T1, D = C, C = B, B = A, A = T1 + T2;
        }
        H0 += A, H1 += B, H2 += C, H3 += D, H4 += E, H5 += F, H6 += G, H7 += H;
    }
    free(pp - l);
    sprintf(sha256, "%08X%08X%08X%08X%08X%08X%08X%08X", H0, H1, H2, H3, H4, H5, H6, H7);
    return sha256;
}
*/

template <uint64_t A, typename B, typename... C>
void clear_table(multi_index<A, B, C...> *table, uint16_t limit) {
  auto it = table->begin();
  uint16_t count = 0;
  while (it != table->end() && count < limit) {
    it = table->erase(it);
    count++;
  }
}

void eospixels::clearpixels(uint16_t count, uint16_t nonce) {
  require_auth(TEAM_ACCOUNT);

  auto itr = canvases.begin();
  eosio_assert(itr != canvases.end(), "no canvas exists");

  pixel_store pixels(_self, itr->id);
  clear_table(&pixels, count);
}

void eospixels::clearaccts(uint16_t count, uint16_t nonce) {
  require_auth(TEAM_ACCOUNT);

  clear_table(&accounts, count);
}

void eospixels::clearcanvs(uint16_t count, uint16_t nonce) {
  require_auth(TEAM_ACCOUNT);

  clear_table(&canvases, count);
}

void eospixels::resetquota() {
  require_auth(TEAM_ACCOUNT);

  auto guardItr = guards.begin();
  if (guardItr == guards.end()) {
    guards.emplace(_self, [&](guard &grd) {
      grd.id = 0;
      grd.quota = WITHDRAW_QUOTA;
    });
  } else {
    guards.modify(guardItr, 0, [&](guard &grd) { grd.quota = WITHDRAW_QUOTA; });
  }
}

// FIXME change allPixels to a reference?
void eospixels::drawPixel(pixel_store &allPixels,
                          const st_pixelOrder &pixelOrder,
                          st_transferContext &ctx) {
  auto loc = pixelOrder.location();

  auto pixelRowItr = allPixels.find(loc.row);

  // TODO extract this into its own method
  // Emplace & initialize empty row if it doesn't already exist
  bool hasRow = pixelRowItr != allPixels.end();
  if (!hasRow) {
    pixelRowItr = allPixels.emplace(_self, [&](pixel_row &pixelRow) {
      pixelRow.row = loc.row;
      pixelRow.initialize_empty_pixels();
    });
  }

  auto pixels = pixelRowItr->pixels;
  auto pixel = pixels[loc.col];

  auto result = ctx.purchase(pixel, pixelOrder);
  if (result.isSkipped) {
    return;
  }

  allPixels.modify(pixelRowItr, 0, [&](pixel_row &pixelRow) {
    pixelRow.pixels[loc.col] = {pixelOrder.color, pixel.nextPriceCounter(),
                                ctx.purchaser};
  });

  if (!result.isFirstBuyer) {
    deposit(pixel.owner, result.ownerEarningScaled);
  }
}

bool eospixels::isValidReferrer(account_name name) {
  auto it = accounts.find(name);

  if (it == accounts.end()) {
    return false;
  }

  // referrer must have painted at least one pixel
  return it->pixelsDrawn > 0;
}

void eospixels::onTransfer(const currency::transfer &transfer) {
  if (transfer.to != _self) return;
  // eosio_assert(transfer.to == _self, "yes!!");

  auto quantity = asset(1000, EOS_SYMBOL); // 1000 = 0.1 EOS

  auto accountItr = accounts.find(transfer.from);
  eosio_assert(accountItr != accounts.end(),
               "account not registered to the game");

        auto s = read_transaction(nullptr, 0);
        char *tx = (char *)malloc(s);
        read_transaction(tx, s);
        checksum256 tx_hash;
        //string tx_hash ="";
        sha256(tx, s, &tx_hash);

  action(permission_level{_self, N(active)}, N(eosio.token), N(transfer),
         std::make_tuple(_self, transfer.from, quantity,
                         std::string("test")))
      .send();

   auto player = *accountItr;
   /*
  accounts.modify(accountItr, 0, [&](account &acct) {
    acct.betCount  += player.betCount++;

  });
  */
  //string_stream ss;
  //save_to(ss, transfer);

      /*

  auto canvasItr = canvases.begin();
  eosio_assert(canvasItr != canvases.end(), "game not started");
  auto canvas = *canvasItr;
  eosio_assert(!canvas.isEnded(), "game ended");

  auto from = transfer.from;
  auto accountItr = accounts.find(from);
  eosio_assert(accountItr != accounts.end(),
               "account not registered to the game");

  pixel_store allPixels(_self, canvas.id);

  auto memo = TransferMemo();
  memo.parse(transfer.memo);

  auto ctx = st_transferContext();
  ctx.amountLeft = transfer.quantity.amount;
  ctx.purchaser = transfer.from;
  ctx.referrer = memo.referrer;

  // Remove referrer if it is invalid
  if (ctx.referrer != 0 &&
      (ctx.referrer == from || !isValidReferrer(ctx.referrer))) {
    ctx.referrer = 0;
  }

  // Every pixel has a "fee". For IPO the fee is the whole pixel price. For
  // takeover, the fee is a percentage of the price increase.

  for (auto &pixelOrder : memo.pixelOrders) {
    drawPixel(allPixels, pixelOrder, ctx);
  }

  size_t paintSuccessPercent =
      ctx.paintedPixelCount * 100 / memo.pixelOrders.size();
  eosio_assert(paintSuccessPercent >= 80, "Too many pixels did not paint.");

  if (ctx.amountLeft > 0) {
    // Refund user with whatever is left over
    deposit(from, ctx.amountLeftScaled());
  }

  ctx.updateFeesDistribution();

  canvases.modify(canvasItr, 0, [&](auto &cv) {
    cv.lastPaintedAt = now();
    cv.lastPainter = from;

    ctx.updateCanvas(cv);
  });

  accounts.modify(accountItr, 0,
                  [&](account &acct) { ctx.updatePurchaserAccount(acct); });

  if (ctx.hasReferrer()) {
    deposit(ctx.referrer, ctx.referralEarningScaled);
  }
  */
}

void eospixels::end() {
  // anyone can create new canvas
  auto itr = canvases.begin();
  eosio_assert(itr != canvases.end(), "no canvas exists");

  auto c = *itr;
  eosio_assert(c.isEnded(), "canvas still has time left");

  // reclaim memory
  canvases.erase(itr);

  // create new canvas
  canvases.emplace(_self, [&](canvas &newCanvas) {
    newCanvas.id = c.id + 1;
    newCanvas.lastPaintedAt = now();
    newCanvas.duration = CANVAS_DURATION;
  });
}

void eospixels::refreshLastPaintedAt() {
  auto itr = canvases.begin();
  eosio_assert(itr != canvases.end(), "no canvas exists");

  canvases.modify(itr, 0,
                  [&](canvas &newCanvas) { newCanvas.lastPaintedAt = now(); });
}

void eospixels::refresh() {
  require_auth(TEAM_ACCOUNT);

  refreshLastPaintedAt();
}

void eospixels::changedur(time duration) {
  require_auth(TEAM_ACCOUNT);

  auto itr = canvases.begin();
  eosio_assert(itr != canvases.end(), "no canvas exists");

  canvases.modify(itr, 0,
                  [&](canvas &newCanvas) { newCanvas.duration = duration; });
}

void eospixels::createacct(const account_name account) {
  require_auth(account);

  auto itr = accounts.find(account);
  eosio_assert(itr == accounts.end(), "account already exist");

  accounts.emplace(account, [&](auto &acct) { acct.owner = account; });
}

void eospixels::init() {
  require_auth(_self);
  // make sure table records is empty
  eosio_assert(canvases.begin() == canvases.end(), "already initialized");

  canvases.emplace(_self, [&](canvas &newCanvas) {
    newCanvas.id = 0;
    newCanvas.lastPaintedAt = now();
    newCanvas.duration = CANVAS_DURATION;
  });
}

void eospixels::withdraw(const account_name to) {
  require_auth(to);

  auto canvasItr = canvases.begin();
  eosio_assert(canvasItr != canvases.end(), "no canvas exists");

  auto canvas = *canvasItr;
  eosio_assert(canvas.pixelsDrawn >= WITHDRAW_PIXELS_THRESHOLD,
               "canvas still in game initialization");

  auto acctItr = accounts.find(to);
  eosio_assert(acctItr != accounts.end(), "unknown account");

  auto guardItr = guards.begin();
  eosio_assert(guardItr != guards.end(), "no withdraw guard exists");

  auto player = *acctItr;
  auto grd = *guardItr;

  uint64_t withdrawAmount = calculateWithdrawalAndUpdate(canvas, player, grd);

  guards.modify(guardItr, 0, [&](guard &g) { g.quota = grd.quota; });

  accounts.modify(acctItr, 0, [&](account &acct) {
    acct.balanceScaled = player.balanceScaled;
    acct.maskScaled = player.maskScaled;
  });

  auto quantity = asset(withdrawAmount, EOS_SYMBOL);
  action(permission_level{_self, N(active)}, N(eosio.token), N(transfer),
         std::make_tuple(_self, to, quantity,
                         std::string("Withdraw from EOS Pixels")))
      .send();
}

void eospixels::deposit(const account_name user,
                        const uint128_t quantityScaled) {
  eosio_assert(quantityScaled > 0, "must deposit positive quantity");

  auto itr = accounts.find(user);

  accounts.modify(itr, 0,
                  [&](auto &acct) { acct.balanceScaled += quantityScaled; });
}

void eospixels::apply(account_name contract, action_name act) {
  if (contract == N(eosio.token) && act == N(transfer)) {
    // React to transfer notification.
    // DANGER: All methods MUST check whethe token symbol is acceptable.

    auto transfer = unpack_action_data<currency::transfer>();
    eosio_assert(transfer.quantity.symbol == EOS_SYMBOL,
                 "must pay with EOS token");

    onTransfer(transfer);
    return;
  }

  if (contract != _self) return;

  // needed for EOSIO_API macro
  auto &thiscontract = *this;
  switch (act) {
    // first argument is name of CPP class, not contract
    EOSIO_API(eospixels, (init)(refresh)(changedur)(end)(createacct)(withdraw)(
                             clearpixels)(clearaccts)(clearcanvs)(resetquota))
  };
}

extern "C" {
[[noreturn]] void apply(uint64_t receiver, uint64_t code, uint64_t action) {
  eospixels pixels(receiver);
  pixels.apply(code, action);
  eosio_exit(0);
}
}


/*
 * This file was quickly hacked together to provide simple graphics output and
 * button controls for simulating a TinyScreen. It's really bad quality but does
 * its job: Providing an environment with visual output for compiling code that
 * runs on the TinyScreen+ in a similar way.
 */

#ifdef SDL2LIB
#include <SDL.h>
#else
#include <GLFW/glfw3.h>
#include <GL/gl.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <TinyScreen.h>
#include <SPI.h>
#include <Wire.h>
#include <stdio.h>
#include <sys/time.h>

#ifdef PNGSAVE
#define png_infopp_NULL (png_infopp)NULL
#define int_p_NULL (int*)NULL
#include <boost/gil/gil_all.hpp>
#include <boost/gil/extension/io/png_dynamic_io.hpp>
#endif

#define TINYSCREEN_WIDTH 96
#define TINYSCREEN_HEIGHT 64

#define SCREEN_TEXTURE_SIZE 128
#define SCREEN_UMAX (96.0f / (float)SCREEN_TEXTURE_SIZE)
#define SCREEN_VMAX (64.0f / (float)SCREEN_TEXTURE_SIZE)

#define SCREEN_Y 64
#define SCREEN_X 96

#define KEYBIT_UP 0x01
#define KEYBIT_DOWN 0x02
#define KEYBIT_LEFT 0x04
#define KEYBIT_RIGHT 0x08
#define KEYBIT_BUTTON1 0x10
#define KEYBIT_BUTTON2 0x20

#define TinyArcadePinX 42
#define TinyArcadePinY 1
#define TinyArcadePin1 45
#define TinyArcadePin2 44

SerialX Serial;
TwoWire Wire;
#ifdef SDL2LIB
unsigned int controls = 0;
#endif

void delay(int msec)
{
    struct timespec val;
    struct timespec rem;
    val.tv_sec = msec / 1000;
    val.tv_nsec = msec % 1000 * 1000000L;
    rem.tv_sec = 0;
    rem.tv_nsec = 0;
    nanosleep(&val, &rem);
}

#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>

typedef boost::minstd_rand base_generator_type;
#include <ctime>

static base_generator_type generator(42u);

void randomSeed(int seed)
{
 generator.seed(static_cast<unsigned int>(std::time(0)));
}

int random(int min, int max)
{
 boost::uniform_int<> ran_dist(min, max - 1);
 boost::variate_generator<base_generator_type&, boost::uniform_int<> > ran(generator, ran_dist);
 return ran();
/*    int x = random();
    x = x % (max - min);
    return x + min;*/
}

typedef struct {
    unsigned char screenData[SCREEN_TEXTURE_SIZE*SCREEN_TEXTURE_SIZE * 3];
    unsigned char rawFrameBuffer[TINYSCREEN_WIDTH * TINYSCREEN_HEIGHT * 2];
    unsigned char x,y;
    bool is16bit;
    bool isRecordingTSV;
#ifdef SDL2LIB
    SDL_Window *window;
    SDL_Renderer *mainRenderer;
    SDL_Texture *mainTexture;
    SDL_Surface *mainScreen;
    SDL_Rect *rect;
    int mult;
#else
    GLuint screenTexture;
    GLFWwindow* window;
#endif
    FILE *tsvFP;
} Emulator;

Emulator emulator;

static void writeFrameBufferToTSV() {
    if (emulator.isRecordingTSV && emulator.tsvFP) {
        printf("Writing framebuffer to tsv\n");
        fwrite(emulator.rawFrameBuffer,1,sizeof(emulator.rawFrameBuffer),emulator.tsvFP);
    }
}

static void updateScreen() {
#ifdef SDL2LIB
    SDL_UpdateTexture(emulator.mainTexture, emulator.rect, emulator.mainScreen->pixels, emulator.mainScreen->pitch);
    SDL_RenderClear(emulator.mainRenderer);
    SDL_RenderCopy(emulator.mainRenderer, emulator.mainTexture, NULL, NULL);
    SDL_RenderPresent(emulator.mainRenderer);
#else
    glBindTexture(GL_TEXTURE_2D, emulator.screenTexture);
    glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, SCREEN_TEXTURE_SIZE, SCREEN_TEXTURE_SIZE, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, emulator.screenData);
#endif
}

void TwoWire::requestFrom(byte address, int quantity)
{
#ifdef SDL2LIB
    where = 0;
    int RXY = 511;
    data[0] = RXY >> 2;
    data[1] = RXY >> 2;
    int LX = ((controls & KEYBIT_RIGHT) ? -511 : 0) + ((controls & KEYBIT_LEFT) ? 511 : 0)+511;
    data[2] = LX >> 2;
    int LY = ((controls & KEYBIT_UP) ? 511 : 0) + ((controls & KEYBIT_DOWN) ? -511 : 0)+511;
    data[3] = LY >> 2;
    data[4] = ((((((LX & 3) << 2) | (LY & 3)) << 2) | (RXY & 3)) << 2) | (RXY & 3);
    data[5] = ((controls & KEYBIT_BUTTON1) ? 0 : 4) | ((controls & KEYBIT_BUTTON2) ? 0 : 8);
#else
    GLFWwindow* window = emulator.window;
    where = 0;
    int RXY = 511;
    data[0] = RXY >> 2;
    data[1] = RXY >> 2;
    int LX = (glfwGetKey(window, GLFW_KEY_RIGHT) ? -511 : 0) + (glfwGetKey(window, GLFW_KEY_LEFT) ? 511 : 0)+511;
    data[2] = LX >> 2;
    int LY = (glfwGetKey(window, GLFW_KEY_UP) ? 511 : 0) + (glfwGetKey(window, GLFW_KEY_DOWN) ? -511 : 0)+511;
    data[3] = LY >> 2;
    data[4] = ((((((LX & 3) << 2) | (LY & 3)) << 2) | (RXY & 3)) << 2) | (RXY & 3);
    data[5] = (glfwGetKey(window, GLFW_KEY_G) ? 0 : 4) | (glfwGetKey(window, GLFW_KEY_H) ? 0 : 8);
#endif
}

int digitalRead(int pin) {
#ifndef SDL2LIB
    GLFWwindow* window = emulator.window;
    switch (pin) {
        case 4: case TinyArcadePin1: return (glfwGetKey(window, GLFW_KEY_G) ? 0 : 1);
        case 5: case TinyArcadePin2: return (glfwGetKey(window, GLFW_KEY_H) ? 0 : 1);
        default: return 0;
    }
#endif
    return 0;
}

int analogWrite(int pin, int val) {
 return 0;
}

int analogRead(int pin) {
 #ifndef SDL2LIB
    GLFWwindow* window = emulator.window;
    switch (pin) {
        case 2: case TinyArcadePinX: return (glfwGetKey(window, GLFW_KEY_RIGHT) ? -511 : 0) + (glfwGetKey(window, GLFW_KEY_LEFT) ? 511 : 0)+511;
        case 3: case TinyArcadePinY: return (glfwGetKey(window, GLFW_KEY_UP) ? -511 : 0) + (glfwGetKey(window, GLFW_KEY_DOWN) ? 511 : 0)+511;
        default: return 0;
    }
#endif
    return 0;
}

void setup();

void loop();

#ifndef SDL2LIB
static void drawCircle(float x, float y, float radius, int div) {
    glBegin(GL_TRIANGLE_FAN);
    glVertex2f(x, y);
    for (int i=0;i<=div;i+=1) {
        float ang = (float)i/(float)div * 3.141593f * 2.0f;
        float px = sinf(ang) * radius;
        float py = cosf(ang) * radius;
        glVertex2f(px+x,py+y);
    }
    glEnd();
}

static void drawG(float x, float y, float size) {
    glBegin(GL_QUADS);
    glVertex2f(x, y + size);
    glVertex2f(x + size, y + size);
    glVertex2f(x + size, y + size - size/8);
    glVertex2f(x, y + size - size/8);
    glVertex2f(x + size - size/8, y + size);
    glVertex2f(x + size, y + size);
    glVertex2f(x + size, y + size - size/4);
    glVertex2f(x + size - size/8, y + size - size/4);
    glVertex2f(x, y);
    glVertex2f(x, y + size);
    glVertex2f(x + size/8, y + size);
    glVertex2f(x + size/8, y);
    glVertex2f(x, y);
    glVertex2f(x + size, y);
    glVertex2f(x + size, y + size/8);
    glVertex2f(x, y + size/8);
    glVertex2f(x + size, y);
    glVertex2f(x + size, y + size/2 + size/16);
    glVertex2f(x + size - size/8, y + size/2 + size/16);
    glVertex2f(x + size - size/8, y);
    glVertex2f(x + size/2, y + size/2 + size/16);
    glVertex2f(x + size/2, y + size/2 - size/16);
    glVertex2f(x + size, y + size/2 - size/16);
    glVertex2f(x + size, y + size/2 + size/16);
    glEnd();
}

static void drawH(float x, float y, float size) {
    glBegin(GL_QUADS);
    glVertex2f(x, y);
    glVertex2f(x, y + size);
    glVertex2f(x + size/8, y + size);
    glVertex2f(x + size/8, y);
    glVertex2f(x + size, y);
    glVertex2f(x + size, y + size);
    glVertex2f(x + size - size/8, y + size);
    glVertex2f(x + size - size/8, y);
    glVertex2f(x + size/8, y + size/2 + size/16);
    glVertex2f(x + size - size/8, y + size/2 + size/16);
    glVertex2f(x + size - size/8, y + size/2 - size/16);
    glVertex2f(x + size/8, y + size/2 - size/16);
    glEnd();
}
#endif

void TinyScreen::startData(void) {
#ifdef SDL2LIB
    SDL_LockSurface(emulator.mainScreen);
#endif
}
void TinyScreen::startCommand(void) {}
void TinyScreen::endTransfer(void) {
#ifdef SDL2LIB
    SDL_UnlockSurface(emulator.mainScreen);
#endif
    updateScreen();
#ifndef SDL2LIB
    GLFWwindow* window = emulator.window;
    float ratio;
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    ratio = width / (float) height;
    glViewport(0, 0, width, height);
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(0.25f,0.25f,0.5f,0.0f);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
   // glRotatef((float) glfwGetTime() * 50.f, 0.f, 0.f, 1.f);
    /*glBegin(GL_TRIANGLES);
    glColor3f(1.f, 0.f, 0.f);
    glVertex3f(-0.6f, -0.4f, 0.f);
    glColor3f(0.f, 1.f, 0.f);
    glVertex3f(0.6f, -0.4f, 0.f);
    glColor3f(0.f, 0.f, 1.f);
    glVertex3f(0.f, 0.6f, 0.f);
    glEnd();*/
    glDisable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, emulator.screenTexture);
    float scale = ratio > 1.3f ? 2.25f : (2.25f/1.3f) * ratio;

    glScalef(scale,scale,scale);
    glTranslatef(-0.48f, -0.12f,0);

    glColor3f(.0f,.0f,0.f);
    drawCircle(.15f,-.25f,.18f,16);
    glColor3f(.8f,.8f,0.8f);
    float stickX = -(float)analogRead(2) / 1023.f + .5f;
    float stickY = -(float)analogRead(3) / 1023.f + .5f;
    drawCircle(.15f + stickX*.15f,-.25f + stickY * .15f,.10f,12);

    glColor3f(.0f,.0f,0.f);
    drawCircle(.6f,-.325f,.075f,12);
    drawCircle(.8f,-.225f,.075f,12);
    glColor3f(.8f,.2f,0.f);
    float buttonY = !digitalRead(4) ? -.32f : -.3f;
    drawCircle(.6f,buttonY,.075f,12);
    glColor3f(.0f,.0f,0.f);
    drawG(.6f - .0375f, buttonY - .0375f, .075f);
    glColor3f(.8f,.2f,0.f);
    buttonY = !digitalRead(5) ? -.22f : -.2f;
    drawCircle(.8f,buttonY,.075f,12);
    glColor3f(.0f,.0f,0.f);
    drawH(.8f - .0375f, buttonY - .0375f, .075f);
    glColor3f(.8f,.2f,0.f);
    int buttons = getButtons();
    glBegin(GL_QUADS);
    // bottom left
    float buttonX = buttons & 1 ? -.05f : -.075f;
    glVertex2f(buttonX, .2f);
    glVertex2f(buttonX, .1f);
    glVertex2f(.1f, .1f);
    glVertex2f(.1f, .2f);
    glEnd();

    buttonX = (buttons & 2) ? -.05f : -.075f;
    glColor3f(.8f,.2f,0.f);
    glBegin(GL_QUADS);
    glVertex2f(buttonX, .54f);
    glVertex2f(buttonX, .44f);
    glVertex2f(.1f, .44f);
    glVertex2f(.1f, .54f);
    glEnd();
    glColor3f(.8f,.2f,0.f);
    buttonX = (buttons & 4) ? 1.01f : 1.035f;
    glBegin(GL_QUADS);
    glVertex2f(.5f, .54f);
    glVertex2f(.5f, .44f);
    glVertex2f(buttonX, .44f);
    glVertex2f(buttonX, .54f);
    glEnd();
    buttonX = (buttons & 8) ? 1.01f : 1.035f;
    glBegin(GL_QUADS);
    glVertex2f(.5f, .2f);
    glVertex2f(.5f, .1f);
    glVertex2f(buttonX, .1f);
    glVertex2f(buttonX, .2f);
    glEnd();

    float margin = .02f;
    glColor3f(0,0,0);
    glBegin(GL_QUADS);
    glVertex3f(-margin, -margin, 0);
    glVertex3f(-margin, 0.64f+margin, 0);
    glVertex3f(0.96f+margin, 0.64f+margin, 0);
    glVertex3f(0.96f+margin, -margin, 0);
    glEnd();

    glEnable(GL_TEXTURE_2D);
    glColor3f(1,1,1);
    glBegin(GL_QUADS);
    glTexCoord2f(0, SCREEN_VMAX); glVertex3f(0, 0, 0);
    glTexCoord2f(0, 0); glVertex3f(0, 0.64f, 0);
    glTexCoord2f(SCREEN_UMAX, 0); glVertex3f(0.96f, 0.64f, 0);
    glTexCoord2f(SCREEN_UMAX, SCREEN_VMAX); glVertex3f(0.96f, 0, 0);
    glEnd();

    if (emulator.isRecordingTSV) {
        writeFrameBufferToTSV();
    }

    glfwSwapBuffers(window);
    glfwPollEvents();

    if (glfwWindowShouldClose(window)) {
        if (emulator.tsvFP) {
            fclose(emulator.tsvFP);
            emulator.tsvFP = 0;
        }
        glfwDestroyWindow(window);
        glfwTerminate();
        exit(EXIT_SUCCESS);
    }
#endif
}
void TinyScreen::begin(void) {}
void TinyScreen::begin(uint8_t) {}
void TinyScreen::on(void) {}
void TinyScreen::off(void) {}
void TinyScreen::setFlip(uint8_t) {}
void TinyScreen::setMirror(uint8_t) {}
void TinyScreen::setBitDepth(uint8_t is16bit) {
    emulator.is16bit = (is16bit & TSBitDepth16) ? true : false;
}
void TinyScreen::setBrightness(uint8_t) {}
void TinyScreen::setWindowTitle(const char *title)
{
#ifndef SDL2LIB
    glfwSetWindowTitle(emulator.window, title);
#endif
}
//void TinyScreen::writeRemap(void) {}
//accelerated drawing commands
void TinyScreen::drawPixel(uint8_t, uint8_t, uint16_t) {}
void TinyScreen::drawLine(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) {}
void TinyScreen::drawLine(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) {}
void TinyScreen::drawRect(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) {}
void TinyScreen::drawRect(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) {}
void TinyScreen::clearWindow(uint8_t, uint8_t, uint8_t, uint8_t) {}
//basic graphics commands
void TinyScreen::writePixel(uint16_t) {
}
void TinyScreen::writeBuffer(uint8_t *rgb, int num) {
    if (num > TINYSCREEN_WIDTH * (emulator.is16bit ? 2 : 1)) {
        printf("line too long: %d\n",num);
    }
    //uint16_t *rgb565_16 = (uint16_t*)&rgb565[0];
    int idx = emulator.x + emulator.y * SCREEN_TEXTURE_SIZE;
    int bufferIdx = (emulator.x + emulator.y * TINYSCREEN_WIDTH) * 2;
#ifdef SDL2LIB
    Uint8 *pixels = (Uint8*)emulator.mainScreen->pixels;
#endif
    uint8_t *rgb565 = rgb;
    for (int i=0;i<num; i+=1) {
        uint8_t r,g,b;
        if (emulator.is16bit) {
            uint16_t word = 0;
            word = rgb565[i]<<8 | rgb565[i+1];
            r = word & 31;
            g = (word >> 5) & 63;
            b = word >> 11;

            r = (r << 3 | r >> 2);
            g = (g << 2 | g >> 4);
            b = (b << 3 | b >> 2);
            emulator.rawFrameBuffer[bufferIdx++ % sizeof(emulator.rawFrameBuffer)] = rgb565[i];
            emulator.rawFrameBuffer[bufferIdx++ % sizeof(emulator.rawFrameBuffer)] = rgb565[i+1];
            i+=1;
        } else {
            uint8_t rgb233 = rgb[i];
            r = rgb233 & 3;
            r = (r << 6) | (r << 4) | (r << 2) | r;
            g = (rgb233 >> 2) & 7;
            g = g << 5 | g << 2 | g >> 1;
            b = rgb233 >> 5 & 7;
            b = b << 5 | b << 2 | b >> 1;
            uint16_t word = 0;
            word |= (((uint16_t)r) & 0x00F8) >> 3;
            word |= (((uint16_t)g) & 0x00FC) << 2;
            word |= (((uint16_t)b) & 0x00F8) << 8;
            emulator.rawFrameBuffer[bufferIdx++ % sizeof(emulator.rawFrameBuffer)] = word >> 8;
            emulator.rawFrameBuffer[bufferIdx++ % sizeof(emulator.rawFrameBuffer)] = word & 0x00FF;
        }
        // my gif screencsat program doesn't like 00ff00
        if (r == 0 && g >= 250 && b == 0) g = 250;
        emulator.screenData[idx*3+0] = r;
        emulator.screenData[idx*3+1] = g;
        emulator.screenData[idx*3+2] = b;
#ifdef SDL2LIB
        int startIdx = ((emulator.x + ((i / (emulator.is16bit ? 2 : 1)) % TINYSCREEN_WIDTH) + emulator.y * TINYSCREEN_WIDTH * emulator.mult) * emulator.mult) * 4;
        for (int yMod = 0; yMod < emulator.mult; yMod++)
        {
         for (int xMod = 0; xMod < emulator.mult; xMod++)
         {
          pixels[startIdx + xMod * 4] = b;
          pixels[startIdx + xMod * 4 + 1] = g;
          pixels[startIdx + xMod * 4 + 2] = r;
          pixels[startIdx + xMod * 4 + 3] = 255;
         }
         startIdx += TINYSCREEN_WIDTH * emulator.mult * 4;
        }
#endif
        if (idx % SCREEN_TEXTURE_SIZE == TINYSCREEN_WIDTH-1) {
            idx = emulator.x + (++emulator.y) * SCREEN_TEXTURE_SIZE;//SCREEN_TEXTURE_SIZE - TINYSCREEN_WIDTH + 1;
        } else {
            idx += 1;
        }
    }
    //emulator.y+=1;
}
/*void TinyScreen::setX(uint8_t, uint8_t);
void TinyScreen::setY(uint8_t, uint8_t);*/
void TinyScreen::goTo(uint8_t x, uint8_t y) {
    emulator.x = x;
    emulator.y = y;
}

//I2C GPIO related
uint8_t TinyScreen::getButtons(void) {
#ifdef SDL2LIB
    return 0;
#else
    GLFWwindow* window = emulator.window;
    int tr = (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS);
    int br = (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS);
    int tl = (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS);
    int bl = (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS);
    return tl << 1 | bl | tr << 2 | br << 3;
#endif
}
/*void TinyScreen::writeGPIO(uint8_t, uint8_t);*/
//font
void TinyScreen::setFont(const FONT_INFO&) {}
void TinyScreen::setCursor(uint8_t, uint8_t) {}
void TinyScreen::fontColor(uint8_t, uint8_t) {}
size_t TinyScreen::write(uint8_t) { return 0; }

static void init() {

    for (int x=0;x<TINYSCREEN_WIDTH;x+=1) {
        for (int y=0;y<TINYSCREEN_HEIGHT;y+=1) {
            int idx = (x + y * SCREEN_TEXTURE_SIZE) * 3;
            emulator.screenData[idx] = x * 255 / TINYSCREEN_WIDTH;
            emulator.screenData[idx + 1] = y * 255 / TINYSCREEN_HEIGHT;
        }
    }

#ifndef SDL2LIB
    glGenTextures(1, &emulator.screenTexture);
#endif
    updateScreen();
#ifndef SDL2LIB
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
#endif
}

unsigned long millis() {
    struct timeval ret;
    gettimeofday(&ret, NULL);
    return ret.tv_sec * 1000 + ret.tv_usec / 1000;
}

static void error_callback(int /*error*/, const char* description)
{
    fputs(description, stderr);
}

#ifndef SDL2LIB
static void key_callback(GLFWwindow* window, int key, int /*scancode*/, int action, int /*mods*/)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key == GLFW_KEY_R && action == GLFW_RELEASE) {
        emulator.isRecordingTSV = !emulator.isRecordingTSV;
        if (emulator.isRecordingTSV) {
            char filename[128];
            sprintf(filename,"rec-%s.tsv","test");
            printf("Starting recording to %s\n", filename);
            emulator.tsvFP = fopen(filename, "wb");
        } else {
            printf("Recording finished\n");
            if (emulator.tsvFP) {
                fclose(emulator.tsvFP);
                emulator.tsvFP = 0;
            }
        }
    }
#ifdef PNGSAVE
    if (key == GLFW_KEY_P && action == GLFW_RELEASE) {
        boost::gil::rgb8_image_t img(TINYSCREEN_WIDTH, TINYSCREEN_HEIGHT);
        for (int y = 0; y < TINYSCREEN_HEIGHT; y++)
        {
            for (int x = 0; x < TINYSCREEN_WIDTH; x++)
            {
                int indx = (x + y * SCREEN_TEXTURE_SIZE) * 3;
                boost::gil::rgb8_pixel_t p(emulator.screenData[indx], emulator.screenData[indx + 1], emulator.screenData[indx + 2]);
                *(view(img).at(x, y)) = p;
            }
        }
        boost::gil::png_write_view("screenshot.png", const_view(img));
    }
#endif
}
#endif

int main(int argc, char *argv[])
{
    bool full = false;
    bool softRender = false;
    int opt;
    static struct option long_options[] =
    {
        {"multiplier", 1, 0, 'u'},
        {0, 0, 0, 0}
    };
#ifdef SDL2LIB
    int screenX, screenY;
    emulator.mult = 4;
    emulator.rect = NULL;
    screenX = SCREEN_X * emulator.mult;
    screenY = SCREEN_Y * emulator.mult;
#endif
    while ((opt = getopt_long(argc,argv,"pu:fw", long_options, NULL)) != -1)
    {
        switch (opt)
        {
#ifdef SDL2LIB
            case 'p':
                emulator.rect = new SDL_Rect;
                emulator.rect->x = 48;
                emulator.rect->y = 8;
                emulator.rect->w = 384;
                emulator.rect->h = 256;
                emulator.mult = 4;
                softRender = true;
                screenX = 480;
                screenY = 272;
                full = true;
                break;
            case 'u':
                if (optarg)
                {
                    emulator.mult = atol(optarg);
                    screenX = SCREEN_X * emulator.mult;
                    screenY = SCREEN_Y * emulator.mult;
                }
                break;
#endif
            case 'f':
                full = true;
                break;
            case 'w':
                softRender = true;
                break;
            default:
                break;
        }
    }
#ifdef SDL2LIB
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) < 0)
    {
        printf("Failed - SDL_Init\n");
        exit(0);
    }
    SDL_Window* window;
    window = SDL_CreateWindow("TinyScreen Simulator",
                              SDL_WINDOWPOS_UNDEFINED,
                              SDL_WINDOWPOS_UNDEFINED,
                              screenX,
                              screenY,
                              (full ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0));
    emulator.window = window;
    if (window == NULL)
    {
        printf("Failed - SDL_CreateWindow\n");
        exit(0);
    }

    emulator.mainRenderer = SDL_CreateRenderer(emulator.window, -1, (softRender ? SDL_RENDERER_SOFTWARE : 0));
    if (emulator.mainRenderer == NULL)
    {
        printf("Failed - SDL_CreateRenderer\n");
        exit(0);
    }
    emulator.mainTexture = SDL_CreateTexture(emulator.mainRenderer,
                                SDL_PIXELFORMAT_ARGB8888,
                                SDL_TEXTUREACCESS_STREAMING,
                                screenX,
                                screenY);
    if (emulator.mainTexture == NULL)
    {
        printf("Failed - SDL_CreateTexture\n");
        exit(0);
    }
    emulator.mainScreen = SDL_CreateRGBSurface(0, SCREEN_X * emulator.mult, SCREEN_Y * emulator.mult, 32,
                                           0x00FF0000,
                                           0x0000FF00,
                                           0x000000FF,
                                           0xFF000000);
    if (emulator.mainScreen == NULL)
    {
        printf("Failed - SDL_CreateRGBSurface\n");
        exit(0);
    }
#else
    GLFWwindow* window;
    glfwSetErrorCallback(error_callback);
    if (!glfwInit())
        exit(EXIT_FAILURE);
    window = glfwCreateWindow(420, 420, "TinyScreen Simulator", NULL, NULL);
    emulator.window = window;
    if (!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);
    glfwSetKeyCallback(window, key_callback);
#endif
    init();
    setup();
    int frame = 0;
#ifdef SDL2LIB
    while (true)
#else
    while (!glfwWindowShouldClose(window))
#endif
    {
        loop();
        frame++;
#ifdef SDL2LIB
        SDL_Event sdlevent;
        while (SDL_PollEvent(&sdlevent))
        {
            if (sdlevent.type == SDL_QUIT)
            {
                SDL_Quit();
                exit(EXIT_SUCCESS);
            }
            else if (sdlevent.type == SDL_KEYDOWN)
            {
                if ((sdlevent.key.keysym.sym == SDLK_UP) || (sdlevent.key.keysym.sym == SDLK_KP_8))
                {
                    controls |= KEYBIT_UP;
                }
                else if ((sdlevent.key.keysym.sym == SDLK_LEFT) || (sdlevent.key.keysym.sym == SDLK_KP_4))
                {
                    controls |= KEYBIT_LEFT;
                }
                else if ((sdlevent.key.keysym.sym == SDLK_DOWN) || (sdlevent.key.keysym.sym == SDLK_KP_2))
                {
                    controls |= KEYBIT_DOWN;
                }
                else if ((sdlevent.key.keysym.sym == SDLK_RIGHT) || (sdlevent.key.keysym.sym == SDLK_KP_6))
                {
                    controls |= KEYBIT_RIGHT;
                }
                else if (sdlevent.key.keysym.sym == SDLK_g)
                {
                    controls |= KEYBIT_BUTTON1;
                }
                else if (sdlevent.key.keysym.sym == SDLK_h)
                {
                    controls |= KEYBIT_BUTTON2;
                }
                else if (sdlevent.key.keysym.sym == SDLK_ESCAPE)
                {
                    SDL_Quit();
                    exit(EXIT_SUCCESS);
                }
            }
            else if (sdlevent.type == SDL_KEYUP)
            {
                if ((sdlevent.key.keysym.sym == SDLK_UP) || (sdlevent.key.keysym.sym == SDLK_KP_8))
                {
                    controls &= ~KEYBIT_UP;
                }
                else if ((sdlevent.key.keysym.sym == SDLK_LEFT) || (sdlevent.key.keysym.sym == SDLK_KP_4))
                {
                    controls &= ~KEYBIT_LEFT;
                }
                else if ((sdlevent.key.keysym.sym == SDLK_DOWN) || (sdlevent.key.keysym.sym == SDLK_KP_2))
                {
                    controls &= ~KEYBIT_DOWN;
                }
                else if ((sdlevent.key.keysym.sym == SDLK_RIGHT) || (sdlevent.key.keysym.sym == SDLK_KP_6))
                {
                    controls &= ~KEYBIT_RIGHT;
                }
                else if (sdlevent.key.keysym.sym == SDLK_g)
                {
                    controls &= ~KEYBIT_BUTTON1;
                }
                else if (sdlevent.key.keysym.sym == SDLK_h)
                {
                    controls &= ~KEYBIT_BUTTON2;
                }
            }
        }
#endif
/*        if ((emulator.isRecordingTSV) && ((emulator.is16bit) || (frame % 2))) {
            writeFrameBufferToTSV();
        }*/
    }
    if (emulator.tsvFP) {
        fclose(emulator.tsvFP);
        emulator.tsvFP = 0;
    }
#ifdef SDL2LIB
    SDL_Quit();
#else
    glfwDestroyWindow(window);
    glfwTerminate();
#endif
    exit(EXIT_SUCCESS);
}