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

#include <iostream>
#include <fstream>
#include <libgen.h>
#include <cmath>
#include <boost/program_options.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/scoped_ptr.hpp>

#include <libplump/libplump.h>
#include <libplump/switching_restaurant.h>

using namespace std;
using namespace gatsby::libplump;
namespace po = boost::program_options;
namespace fs = boost::filesystem;

static unsigned int num_types = 256;

/**
 * Predict probabilities on test file.
 */
d_vec predict(po::variables_map& vm, HPYPModel& m, int start_pos, seq_type seq) {
  d_vec predictive;
  for(int i = start_pos; i < (int)seq.size(); ++i) {
    if (vm.count("sum")) {
        d_vec dist = m.predictiveDistribution(start_pos, i);
        if (!closeTo(sum(dist), 1)) {
          std::cout << "probs don't sum to one, but " << sum(dist) << ", probs: " << iterableToString(dist) << std::endl;
        }
    }
    switch (vm["fragment"].as<int>()) {
      case 1:
        predictive.push_back(m.predict(start_pos, i, seq[i]));
        break;
      case 2:
        predictive.push_back(m.predictWithFragmentation(start_pos, i, seq[i]));
        break;
      case 3:
        predictive.push_back(m.predictBelow(start_pos, i, seq[i]));
        break;
    }
  }
  return predictive;
}

IParameters* getParameters(po::variables_map& vm) {
  switch(vm["parameters"].as<int>()) {
    case 0:
      cerr << "getParameters(): Using SimpleParameters" << endl;
      return new SimpleParameters(vm["disc"].as<d_vec>(), vm["alpha"].as<double>());
    case 1:
      cerr << "getParameters(): Using GradientParameters" << endl;
      return new GradientParameters(vm["disc"].as<d_vec>(), vm["alpha"].as<double>());
  }
}

IAddRemoveRestaurant* getRestaurant(po::variables_map& vm) {
  switch(vm["restaurant"].as<int>()) {
    case 0:
      cerr << "getRestaurant(): Using KneserNeyRestaurant" << endl;
      return new KneserNeyRestaurant();
    case 1:
      cerr << "getRestaurant(): Using SimpleFullRestaurant" << endl;
      return new SimpleFullRestaurant();
    case 2:
      cerr << "getRestaurant(): Using HistogramRestaurant" << endl;
      return new HistogramRestaurant();
    case 3:
      cerr << "getRestaurant(): Using ReinstantiatingCompactRestaurant" << endl;
      return new ReinstantiatingCompactRestaurant();
    case 4:
      cerr << "getRestaurant(): Using StirlingCompactRestaurant" << endl;
      return new StirlingCompactRestaurant();
    case 5:
      cerr << "getRestaurant(): "
           << "Using SwitchingRestaurant(SimpleFullRestaurant, 10)"
           << endl;
      return new SwitchingRestaurant(new SimpleFullRestaurant(), 10);
    case 6:
      cerr << "getRestaurant(): Using PowerLawRestaurant" << endl;
      return new PowerLawRestaurant();
    case 7:
      cerr << "getRestaurant(): Using FractionalRestaurant" << endl;
      return new FractionalRestaurant();
    case 8:
      cerr << "getRestaurant(): Using ExpectedTablesCompactRestaurant" << endl;
      return new ExpectedTablesCompactRestaurant();
    case 9:
      cerr << "getRestaurant(): Using LocallyOptimalRestaurant" << endl;
      return new LocallyOptimalRestaurant();
  }
  cout << "Unknown restaurant type (--restaurant)!";
  exit(1);
}

INodeManager* getNodeManager(po::variables_map& vm,
    const IPayloadFactory& payloadFactory) {
  return new SimpleNodeManager(payloadFactory);
}

void pushFileToSeq(po::variables_map& vm, std::string filename, seq_type& seq) {
  if (vm.count("read-int32")) {
    pushFileToVec<int>(filename, seq, vm["head"].as<int>());
  } else {
    pushFileToVec<unsigned char>(filename, seq, vm["head"].as<int>());
  }
}

void runSampler(po::variables_map& vm, HPYPModel& model, int train_length) {
  if (vm["sampler"].as<int>() == 1) {
    model.runGibbsSampler(false);
  }
  if (vm["sampler"].as<int>() == 2) {
    model.runGibbsSampler(true);
  }
  if (vm["sampler"].as<int>() == 3) {
    model.removeAddSweep(1, train_length);
  }
}

double score_file(po::variables_map& vm) {
  string filename = vm["input-file"].as<string>();
  fs::path input_path(filename);
  if (!fs::exists(input_path)) {
    cerr << "Input file not found!" << endl;
    exit(1);
  }

  seq_type seq;
  pushFileToSeq(vm, filename, seq);
  cout << "Sequence length: " << seq.size() << endl;
  cout << "Number of types:  " << num_types << endl;
  cout << seq[0] << endl;

  boost::scoped_ptr<IParameters> parameters(getParameters(vm));
  boost::scoped_ptr<IAddRemoveRestaurant> restaurant(getRestaurant(vm));
  boost::scoped_ptr<INodeManager> nodeManager(
      getNodeManager(vm, restaurant->getFactory()));

  HPYPModel model(seq, *nodeManager, *restaurant, *parameters, num_types);

  d_vec losses;
  if (vm.count("load-serialized-nodes")) {
    Serializer nodeSerializer(vm["load-serialized-nodes"].as<string>());
    nodeSerializer.loadNodesAndPayloads(*nodeManager, restaurant->getFactory());
  } else {
    int lag = vm["train-lag"].as<int>();
    if (lag == 0) {
      losses = model.computeLosses(0, seq.size());
    } else {
      losses = model.computeLossesWithDeletion(0, seq.size(), lag);
    }
  }

  if (vm.count("debug")) {
    model.checkConsistency();
  }

  cout << "Training loss: " << mean(losses) << endl;

  if (vm.count("print-tree")) {
    cout << model.toString() << endl;
  }

  if (vm.count("dump-losses")) {
    iterableToCSVFile(losses,"train-losses");
  }

  if (vm.count("test-file")) {
    d_vec current_sample_losses;
    int start_pos = seq.size();
    pushFileToSeq(vm, vm["test-file"].as<string>(), seq);
    cout << "Test sequence length: " << seq.size()-start_pos << endl;
    int mode = vm["mode"].as<int>();
    ostringstream dump_fn;
    dump_fn << "losses_" << fs::basename(input_path); // <<  "_"  << m.parameters.alpha;
    dump_fn << "_" << vm["restaurant"].as<int>()  << "_" << vm["mode"].as<int>() << "_" << vm["fragment"].as<int>() << "_" << vm["prefix"].as<string>() << ".csv";
    ofstream losses_f(dump_fn.str().c_str());
    current_sample_losses.push_back(prob2loss<double>(predict(vm, model, start_pos, seq)));
    losses_f << current_sample_losses.back() << ", ";
    cout << "loss: " << current_sample_losses.back() << endl;
    if (vm.count("burn-in")) {
      for (int i = 0; i < vm["burn-in"].as<int>(); ++i) {
        cout << "Burn-in iteration: " << i << endl;
        if (vm.count("joint")) {
          double joint = model.computeLogJoint();
          cout << "log-joint: " << joint << endl;
          cerr << joint << ", " << current_sample_losses.back() << endl;
        }
        runSampler(vm, model, start_pos);
        if (vm.count("debug")) {
          if (model.checkConsistency()) {
            cout << "Model consistent." << endl;
          } else {
            cout << "Consitency check failed." << endl;
          }
        }
        current_sample_losses.push_back(prob2loss<double>(predict(vm, model, start_pos, seq)));
        losses_f << current_sample_losses.back() << ", ";
        cout << "loss: " << current_sample_losses.back() << endl;
        losses_f.flush();
        if (vm.count("debug") && vm.count("print-tree")) {
          cout << model.toString() << endl;
        }
      }
      if (vm.count("dump-losses")) {
        iterableToCSVFile(predict(vm, model, start_pos, seq), "test-probs_0");
      }
      d_vec_vec sample_predictions;
      for (int i = 0; i < vm["samples"].as<int>(); ++i) {
        cout << "sampling iteration iteration: " << i << endl;
        runSampler(vm, model, start_pos);
        sample_predictions.push_back(predict(vm, model, start_pos, seq));
        cout << "loss (this sample): " << prob2loss<double>(sample_predictions.back()) << endl;
        cout << "loss (avg): " << prob2loss<double>(average(sample_predictions)) << endl;
        if (vm.count("dump-losses")) {
          ostringstream dump_test_loss_fn;
          dump_test_loss_fn << "test-probs_" << i + 1;
          iterableToCSVFile(sample_predictions.back(), dump_test_loss_fn.str());
        }
        if (vm.count("joint")) {
          double joint = model.computeLogJoint();
          cerr << joint << ", "
               << prob2loss<double>(sample_predictions.back()) << ", "
               << prob2loss<double>(average(sample_predictions)) << endl;
        }
      }
    }

    d_vec online_losses;
    int lag = vm["lag"].as<int>();
    if (lag == 0) {
      online_losses = model.computeLosses(start_pos, seq.size());
    } else {
      online_losses = model.computeLossesWithDeletion(start_pos, seq.size(), lag);
    }
    if (vm.count("dump-losses")) {
      iterableToCSVFile(online_losses,"online-test-losses");
    }
    cout << "loss (online): " << mean(online_losses) << endl;
  }

  if (vm.count("save-serialized-nodes")) {
    Serializer nodeSerializer(vm["save-serialized-nodes"].as<string>());
    nodeSerializer.saveNodesAndPayloads(*nodeManager, restaurant->getFactory());
  }
  cout << "Discounts: ";
  for (int i=0; i < vm["disc"].as<d_vec>().size(); ++i) {
    cout << parameters->getDiscount(i) << ", ";
  }
  cout << endl;
  cout << "Concentration: " << parameters->getConcentration(1., -1, 0) << endl;

  return mean(losses);
}

int main(int argc, char* argv[]) {
  const double sm_disc[] = {0.05, 0.7, 0.8, 0.82, 0.84, 0.88, 0.91, 0.92, 0.93, 0.94, 0.95};
  //const double sm_disc[] = {.62, .69, .74, .80, .95};
  d_vec default_discounts;
  default_discounts.assign(sm_disc,&sm_disc[11]);
  // Declare the supported options.
  po::options_description generic("Generic options");
  po::options_description dumping("Dumping options");
  po::options_description hidden("Hidden options");
  generic.add_options()
    ("help", "produce help message")
    ("debug,D", "Print debugging output")
    ("joint,J", "Compute joint distribution")
    ("sum,s", "Check that probabilities sum to one")
    ("print-tree", "Print the context tree to the screen")
    ("fragment", po::value<int>()->default_value(1), "1: nofrag; 2: frag; 3:below")
    ("read-int32", "Read input data as 32 bit integers")
    ("test-file", po::value<string>(), "Test file")
    ("save-serialized-nodes", po::value<string>(), "File to contain serialized nodes")
    ("load-serialized-nodes", po::value<string>(), "File to contain serialized nodes")
    ("head",po::value<int>()->default_value(0), "If given, cuts input to this number of symbols")
    ("mode", po::value<int>()->default_value(1), "1: particle filter, 2: no fragment, 3: fragment")
    ("sampler", po::value<int>()->default_value(1), "1: add/remove, 2: direct gibbs; 3: remove-add")
    ("restaurant", po::value<int>()->default_value(1),
     "0:KN, 1: SimpleFull, 2: Histogram, 3: ReinstantiatingCompact, 4: StirlingCompact, 5: Switching, 6: PowerLaw, 7: Fractional")
    ("parameters", po::value<int>()->default_value(0),
     "0:Simple, 1: Gradient")
    ("burn-in",po::value<int>()->default_value(0), "Number of Gibbs iterations for burn in")
    ("samples,s",po::value<int>()->default_value(1), "Number of samples used for prediction")
    ("lag,l",po::value<int>()->default_value(0), "Lag for deleted prediction (0=off)")
    ("train-lag",po::value<int>()->default_value(0), "Lag for deleted training (0=off)")
    ("num-types", po::value<int>()->default_value(256), "Number of types")
    ("alpha,a", po::value<double>()->default_value(5), "Concentration parameter")
    ("disc,d", po::value<d_vec>()->default_value(default_discounts,"..."), "Discount parameter(s)")
    ;

  dumping.add_options()
    ("dump-losses", "Dump per-symbol losses into a file named losses")
    ("prefix",po::value<std::string>()->default_value(""), "Prefix for output files")
    ;

  hidden.add_options()
    ("input-file", po::value<string>(), "input file")
    ;

  po::options_description cmdline_options;
  cmdline_options.add(generic).add(hidden).add(dumping);

  po::options_description visible_options;
  visible_options.add(generic).add(dumping);

  po::positional_options_description p;
  p.add("input-file", 1);

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).
      options(cmdline_options).positional(p).run(), vm);
  po::notify(vm);

  string usage = "Usage: score_file [OPTIONS]... FILENAME\n";

  if (vm.count("help") || !vm.count("input-file")) {
    cout << usage << visible_options << "\n";
    exit(1);
  }

  num_types = vm["num-types"].as<int>();

  init_rng();

  if (vm.count("input-file")) {
    double score;
    score = score_file(vm);
    cout << "log-loss: " << score << endl;
    //exit(0); // force exit to avoid expensive cleanup
  }
  free_rng();
}


#include <iostream>
#include <string>
#include <ncurses.h>
#include <vector>
#include <fstream>
#include <sstream>
#include <cmath>
#include <algorithm>
#include <iterator>
#include <boost/variant.hpp>
#include "visitors.h"
#include "player.h"
#include "enemy.h"
#include "color.h"
#include "map.h"
#include "equipmenttype.h"
#include "weaponInstance.h"

extern bool checkIfAttackHit(double acc, double eva);

Player::Player(std::string p_class)
{
    for (int j = 0; j < 5; j++) {
        Armor defArm;
        equipped_armor.push_back(defArm);
    }
    this->vision = 6;
    this->primary_weapon = new Sword(1);
    this->secondary_weapon = new Dagger(1);

    this->inventory.food_capacity = 5;
    this->inventory.weapon_capacity = 3;
    this->inventory.armor_capacity = 3;
    this->inventory.food_count = 0;
    this->inventory.armor_count = 0;
    this->inventory.weapon_count = 0;

    this->race = p_class;
    std::string element;
    std::ifstream infile("playerAttributes.csv");
    while(infile.good()) {
        getline(infile, element, ',');
        if (element == p_class) {
            getline(infile, element, ',');
            this->accuracy = std::atof(element.c_str());
            getline(infile, element, ',');
            this->speed = std::atoi(element.c_str());
            getline(infile, element, ',');
            this->base_damage = std::atoi(element.c_str());
            getline(infile, element, ',');
            this->allowed_moves = std::atoi(element.c_str());
            getline(infile, element, ',');
            this->base_evasion = std::atof(element.c_str());
            getline(infile, element, ',');
            this->base_protection = std::atof(element.c_str());
            getline(infile, element, ',');
            this->base_total_health = std::atoi(element.c_str());
            getline(infile, element, ',');
            this->level_up_multiplier_health = std::atof(element.c_str());
            getline(infile, element, ',');
            this->level_up_multiplier_damage = std::atof(element.c_str());
            getline(infile, element, '\n');
            this->crit_chance = std::atof(element.c_str());
            infile.close();
            break;
        }
        else {
            getline(infile, element);
        }
    }

    //placeholder. For debugging only
    this->base_total_health = 5000;

    //These attributes are constant for all player types
    this->current_total_health = this->base_total_health;
    this->current_health = this->base_total_health;
    this->used_moves = 0;
    this->level_up_multiplier_xp = 1.25;
    this->current_xp_cap = 150;
    this->level = 1;
    this->level_points = 0;
    this->current_xp = 0;
    this->passive_health_regen_counter = 0;
    this->passive_health_regen_trigger = 50;
    this->passive_health_regen_amount = 0;
    this->inventory_index = 0;
    this->max_inventory_index = 2;
    this->souls_cap = 25;
    this->souls = 0;
    this->souls_multiplier = 1.15;
    this->current_evasion = this->base_evasion;
    this->current_protection = this->base_protection;
    this->in_combat = false;
    this->is_stunned = false;
    this->bleed_damage = 0;
    this->bleed_rounds = 0;
}

int getLootIndex(std::vector<Enemy::Loot> *loot, long loot_id)
{
    for (size_t i = 0; i < loot->size(); i++) {
        if (loot->at(i).l_id == loot_id) {
            return i;
        }
    }
    return -1;
}

/*
*foodEvents()
*   Goes through the list of the food items that the player has eaten,
*   and for each item checks if the item should trigger health gain (and if so, gives health to player),
*   increments the given counters, and deletes the food item if it should expire.
*/
void Player::foodEvents()
{
    bool deleted;
    std::vector<Food>::iterator food_iter;
    for (food_iter = this->active_food.begin(); food_iter != this->active_food.end();) {
        deleted = false;
        food_iter->trigger_count++;
        this->current_health += food_iter->health_gain_per_trigger;
        if (food_iter->trigger_count == food_iter->total_triggers) {
            food_iter = this->active_food.erase(food_iter);
            deleted = true;
        }
        if (!deleted) {
            food_iter++;
        }
    }
}

/*
*eatFood(Food, WINDOW*)
*   Gets passed a food item, adds it to the player's active_food vector,
*   and gives initial health boost to player.
*/
void Player::eatFood(Food *food_item, WINDOW *player_status_window)
{
    this->active_food.push_back(*food_item);
    this->current_health += food_item->initial_health_gain;
    if (this->current_health > this->current_total_health) {
        this->current_health = this->current_total_health;
    }
    this->printStatus(player_status_window);
    this->inventory.food_count--;
    return;
}

void Player::setPrimaryWeapon(Weapon weapon)
{
    *this->primary_weapon = weapon;
}

void Player::setSecondaryWeapon(Weapon weapon)
{
    *this->secondary_weapon = weapon;
}

void Player::setBleedDamage(int damage, int rounds)
{
    this->bleed_damage += damage;
    this->bleed_rounds += rounds;
}

void Player::takeDamageOverTime(WINDOW *alert_window)
{
    if (this->bleed_rounds > 0) {
        wattron(alert_window, Color::RedBlack);
        wprintw(alert_window, "You bleed for %d damage.\n", this->bleed_damage);
        wattroff(alert_window, Color::RedBlack);
        wrefresh(alert_window);
        this->bleed_rounds--;
        this->current_health -= this->bleed_damage;
    }
    else {
        this->bleed_damage = 0;
    }
}

/*
*printInventory(WINDOW*, int, WINDOW*)
*   Prints player's inventory in a categorical manner according to equipment.
*   Simply goes down the corresponding list (Weapon, Food, Armor, etc), prints a counter
*   and the item's name and description
*/
void Player::printInventory(WINDOW *inv_window, int index, WINDOW *item_description_window)
{
    wclear(inv_window);
    wclear(item_description_window);
    int counter = 0;
    std::vector<boost::variant<Weapon, Food, Armor>> equipment;
    std::vector<Equipment>::iterator iter;
    boost::variant<Weapon, Food, Armor> current_equipped;

    wattron(inv_window, A_BOLD);

    /*
    *There's a bit of code repetition here that I'd like to reduce later
    */
    switch(this->inventory_index) {
        case static_cast<int>(EquipmentType::Weapon):
            equipment.insert(equipment.end(), this->inventory.weapons.begin(), this->inventory.weapons.end());
            wprintw(inv_window, "WEAPONS [%d/%d] ->\n", this->inventory.weapon_count, this->inventory.weapon_capacity);
            wattrset(inv_window, A_NORMAL);
            if (equipment.size() == 0) {
                wprintw(inv_window, "<EMPTY>");
                wrefresh(inv_window);
                return;
            }
            if (boost::get<Weapon>(equipment.at(index)).is_primary) {
                current_equipped = *(this->primary_weapon);
            }
            else {
                current_equipped = *(this->secondary_weapon);
            }
            break;
        case static_cast<int>(EquipmentType::Food):
            equipment.insert(equipment.end(), this->inventory.food.begin(), this->inventory.food.end());
            wprintw(inv_window, "<- FOOD [%d/%d]\n", this->inventory.food_count, this->inventory.food_capacity);
            wattrset(inv_window, A_NORMAL);
            if (equipment.size() == 0) {
                wprintw(inv_window, "<EMPTY>");
                wrefresh(inv_window);
                return;
            }
            current_equipped = *(new Food());
            break;
        case static_cast<int>(EquipmentType::Armor):
            equipment.insert(equipment.end(), this->inventory.armor.begin(), this->inventory.armor.end());
            wprintw(inv_window, "<- ARMOR [%d/%d] ->\n", this->inventory.armor_count, this->inventory.armor_capacity);
            wattrset(inv_window, A_NORMAL);
            if (equipment.size() == 0) {
                wprintw(inv_window, "<EMPTY>");
                wrefresh(inv_window);
                return;
            }
            if (this->inventory.armor.size() > 0) {
                current_equipped = equipped_armor.at(static_cast<int>(this->inventory.armor.at(index).armor_type));
            }
            break;
        default:
            return;
    }

    for (size_t i = 0; i < equipment.size(); i++) {
        if (counter == index) {
            wattron(inv_window, A_STANDOUT);
            boost::apply_visitor(Visitors::compare_to(current_equipped, item_description_window), equipment.at(i));
        }
        boost::apply_visitor(Visitors::output_list_name(counter, inv_window), equipment.at(i));
        if (counter == index) {
            wattroff(inv_window, A_STANDOUT);
        }
        counter++;
    }

    wrefresh(inv_window);
}

/*
*findValidLootID(std::vector<Enemy::Loot>)
*   Returns a valid (unused at time of creation) loot id for use when the player drops
*   an item and it is added to the loot table.
*/
int findValidLootID(std::vector<Enemy::Loot> loot)
{
    std::vector<int> used_ids;
    int new_id = 0;
    for (size_t i = 0; i < loot.size(); i++) {
        used_ids.push_back(loot.at(i).l_id);
    }
    while(true) {
        if (!(std::find(used_ids.begin(), used_ids.end(), new_id) != used_ids.end())) {
            return new_id;
        }
        new_id++;
    }
}

/*
*manageInventory(WINDOW*, WINDOW*, WINDOW*, std::vector<Enemy::Loot>*)
*   Main inventory management system of player.
*   Player can switch between windows for each type of equipment (Armor, Weapon, Food, ...) using left and right arrow keys
*   <ENTER>: allows player to equip Weapon/Armor or eat Food
*   <d>: allows player to drop an item, will be put into world as a standard loot object
*   <KEY_UP>/<KEY_DOWN>: Allows player to navigate menus
*/
void Player::manageInventory(WINDOW *inv_window, WINDOW *player_status_window, WINDOW *alert_win, WINDOW *item_description_window, std::vector<Enemy::Loot> *loot)
{
    int ch;
    int index = 0;
    Weapon wtemp;
    Armor atemp;
    Enemy::Loot loot_obj;
    bool valid_drop = false;
    std::vector<Weapon> *weapon_vect;
    //WINDOW *item_description_window = newwin(30, 30, 22, 60);
    while (true) {
        //temp = NULL;
        printInventory(inv_window, index, item_description_window);
        ch = getch();
        switch(ch) {
            //player movement
            case KEY_UP:
                if (index > 0) {
                    index--;
                }
                break;
            case KEY_DOWN:
                if (this->inventory_index == static_cast<int>(EquipmentType::Weapon) && index < this->inventory.weapons.size() - 1) {
                    index++;
                }
                else if (this->inventory_index == static_cast<int>(EquipmentType::Food) && index < this->inventory.food.size() - 1) {
                    index++;
                }
                else if (this->inventory_index == static_cast<int>(EquipmentType::Armor) && index < this->inventory.armor.size() - 1) {
                    index++;
                }
                break;
            case KEY_RIGHT:
                if (this->inventory_index < this->max_inventory_index) {
                    this->inventory_index++;
                    index = 0;
                    wclear(item_description_window);
                    wrefresh(item_description_window);
                }
                break;
            case KEY_LEFT:
                if (this->inventory_index > 0) {
                    this->inventory_index--;
                    index = 0;
                    wclear(item_description_window);
                    wrefresh(item_description_window);
                }
                break;
            case KEY_ENTER: case '\n':
                if (this->inventory_index == static_cast<int>(EquipmentType::Weapon) && this->inventory.weapons.size() > 0) {
                    weapon_vect = &this->inventory.weapons;
                    wtemp = weapon_vect->at(index);

                    if (wtemp.type == "P") {
                        weapon_vect->at(index) = *this->primary_weapon;
                        this->setPrimaryWeapon(wtemp);
                    }
                    else {
                        weapon_vect->at(index) = *this->secondary_weapon;
                        this->setSecondaryWeapon(wtemp);
                    }

                    wprintw(alert_win, "Equipped %s\n", (wtemp.name).c_str());
                    wrefresh(alert_win);
                    index = 0;
                }
                else if (this->inventory_index == static_cast<int>(EquipmentType::Food) && this->inventory.food.size() > 0) {
                    this->eatFood(&this->inventory.food.at(index), player_status_window);
                    this->inventory.food.erase(this->inventory.food.begin() + index);
                    index = 0;
                }
                else if (this->inventory_index == static_cast<int>(EquipmentType::Armor) && this->inventory.armor.size() > 0) {
                    atemp = this->inventory.armor.at(index);
                    this->inventory.armor.erase(this->inventory.armor.begin() + index);
                    if (equipped_armor.at(static_cast<int>(atemp.armor_type)).armor_type != ArmorType::Default) {
                        this->inventory.armor.push_back(equipped_armor.at(static_cast<int>(atemp.armor_type)));
                    }
                    else {
                        this->inventory.armor_count--;
                    }
                    equipped_armor.at(static_cast<int>(atemp.armor_type)) = atemp;
                    wprintw(alert_win, "Equipped %s\n", (atemp.name).c_str());
                    wrefresh(alert_win);
                    wclear(item_description_window);
                    wrefresh(item_description_window);
                    index = 0;
                }
                break;
            case 'd':
                loot_obj.food.clear();
                loot_obj.weapons.clear();
                loot_obj.armor.clear();

                /*
                TODO: reduce repetition here with a boost static visitor
                */
                if (this->inventory_index == static_cast<int>(EquipmentType::Weapon) && this->inventory.weapons.size() > 0) {
                    loot_obj.weapons.push_back(this->inventory.weapons.at(index));
                    this->inventory.weapons.erase(this->inventory.weapons.begin() + index);
                    this->inventory.weapon_count--;
                    valid_drop = true;
                }
                else if (this->inventory_index == static_cast<int>(EquipmentType::Food) && this->inventory.food.size() > 0) {
                    loot_obj.food.push_back(this->inventory.food.at(index));
                    this->inventory.food.erase(this->inventory.food.begin() + index);
                    this->inventory.food_count--;
                    valid_drop = true;
                }
                else if (this->inventory_index == static_cast<int>(EquipmentType::Armor) && this->inventory.armor.size() > 0) {
                    loot_obj.armor.push_back(this->inventory.armor.at(index));
                    this->inventory.armor.erase(this->inventory.armor.begin() + index);
                    this->inventory.armor_count--;
                    valid_drop = true;
                }

                if (valid_drop) {
                    loot_obj.l_id = findValidLootID(*loot);
                    loot_obj.row = this->row;
                    loot_obj.col = this->col;
                    loot_obj.dropped_by = this->race;
                    loot_obj.despawn_counter = 0;
                    loot->push_back(loot_obj);
                    index = 0;
                    valid_drop = false;
                }
                break;
            case 'e': case 'l': case 'a':
                wclear(inv_window);
                wclear(item_description_window);
                wrefresh(inv_window);
                wrefresh(item_description_window);
                return;
            default:
                break;
        }
    }
    wclear(item_description_window);
    wrefresh(item_description_window);
}

void Player::printEquipped(WINDOW *inv_window, WINDOW *item_description_window, int index)
{
    wclear(inv_window);
    wclear(item_description_window);
    std::string armor_names[] = {"[BOOT]", "[LEGS]", "[CHST]", "[GLVS]", "[HELM]"};
    wattron(inv_window, A_BOLD);
    wprintw(inv_window, "EQUIPPED ARMOR\n");
    wattroff(inv_window, A_BOLD);

    for (size_t i = 0; i < this->equipped_armor.size(); i++) {
        if (i == index) {
            wattron(inv_window, A_STANDOUT);
            this->equipped_armor.at(i).printDescription(item_description_window);
        }
        wprintw(inv_window, " %s %s\n", armor_names[i].c_str(), this->equipped_armor.at(i).name.c_str());
        wattroff(inv_window, A_STANDOUT);
    }
    double armor_score = getArmorScore();
    wprintw(inv_window, " TOTAL PROT: %.0f%%\n", armor_score * 100);

    wattron(inv_window, A_BOLD);
    wprintw(inv_window, "\nEQUIPPED WEAPONS\n");
    wattroff(inv_window, A_BOLD);

    if (index == 5) {
        wattron(inv_window, A_STANDOUT);
        wprintw(inv_window, " [PRIM] %s\n", (this->primary_weapon->name).c_str());
        this->primary_weapon->printDescription(item_description_window);
        wattroff(inv_window, A_STANDOUT);
    }
    else {
        wprintw(inv_window, " [PRIM] %s\n", (this->primary_weapon->name).c_str());
    }

    if (index == 6) {
        wattron(inv_window, A_STANDOUT);
        wprintw(inv_window, " [SCND] %s\n", (this->secondary_weapon->name).c_str());
        this->secondary_weapon->printDescription(item_description_window);
        wattroff(inv_window, A_STANDOUT);
    }
    else {
        wprintw(inv_window, " [SCND] %s\n", (this->secondary_weapon->name).c_str());
    }

    wrefresh(inv_window);
    wrefresh(item_description_window);
}

void Player::unequipArmor(WINDOW *alert_win, int index)
{
    if (this->equipped_armor.at(index).name != "<NONE>") {
        if ((this->inventory.armor_count < this->inventory.armor_capacity)) {
            Armor defaultArmor;
            wprintw(alert_win, "Unequipped %s.\n", this->equipped_armor.at(index).name.c_str());
            wrefresh(alert_win);
            this->inventory.armor.push_back(this->equipped_armor.at(index));
            this->equipped_armor.at(index) = defaultArmor;
            this->inventory.armor_count++;
        }
        else {
            wprintw(alert_win, "No room in inventory for %s.\n", this->equipped_armor.at(index).name.c_str());
            wrefresh(alert_win);
        }
    }
}

/*
*manageArmor(WINDOW*, WINDOW*, WINDOW*)
*When 'a' is pressed in the main game loop, currently equipped armor is displayed using this function.
*The player can navigate the equipped armor and unequip it and check statistics.
*/
void Player::manageArmor(WINDOW *inv_window, WINDOW *item_description_window, WINDOW *alert_win)
{
    int ch;
    int index = 0;
    int n_weapons = 2;

    while (true) {
        printEquipped(inv_window, item_description_window, index);
        ch = getch();
        switch(ch) {
            case KEY_UP:
                if (index > 0) {
                    index--;
                }
                break;
            case KEY_DOWN:
                if (index < (this->equipped_armor.size() - 1) + n_weapons) {
                    index++;
                }
                break;
            case KEY_ENTER: case '\n':
                if (!(index > (this->equipped_armor.size() - 1))) {
                    this->unequipArmor(alert_win, index);
                }
                break;
            case 'a': case 'e':
                wclear(inv_window);
                wclear(item_description_window);
                wrefresh(inv_window);
                wrefresh(item_description_window);
                return;
            default:
                break;
        }
    }
}

void Player::gainSouls(int souls_to_gain, WINDOW *alert_win)
{
    this->souls += souls_to_gain;
    if (this->souls > souls_cap) {
        this->souls = souls_cap;
    }
    wattrset(alert_win, A_NORMAL);
    wattron(alert_win, Color::GreenBlack);
    wprintw(alert_win, "Acquired %d souls.\n", souls_to_gain);
    wrefresh(alert_win);
    wattroff(alert_win, Color::GreenBlack);
}

bool Player::pickupLoot(Weapon weapon_item)
{
    if (this->inventory.weapon_count < this->inventory.weapon_capacity) {
        this->inventory.weapons.push_back(weapon_item);
        this->inventory.weapon_count++;
        return true;
    }
    return false;
}

bool Player::pickupLoot(Food food_item)
{
    if (this->inventory.food_count < this->inventory.food_capacity) {
        this->inventory.food.push_back(food_item);
        this->inventory.food_count++;
        return true;
    }
    return false;
}

bool Player::pickupLoot(Armor armor_item)
{
    if (this->inventory.armor_count < this->inventory.armor_capacity) {
        this->inventory.armor.push_back(armor_item);
        this->inventory.armor_count++;
        return true;
    }
    return false;
}

double Player::getArmorScore()
{
    double score = 0;
    for (Armor a : equipped_armor) {
        score += a.protection;
    }
    return score;
}

void Player::levelUp(WINDOW *alert_win)
{
    this->current_xp -= this->current_xp_cap;
    this->level++;
    this->current_xp_cap *= this->level_up_multiplier_xp;

    wattroff(alert_win, Color::RedBlack);
    wattron(alert_win, Color::CyanBlack);
    wprintw(alert_win, "Level up! %d -> %d\n", this->level - 1, this->level);
    wrefresh(alert_win);
    wattroff(alert_win, Color::CyanBlack);
    wattron(alert_win, Color::RedBlack);

    this->base_total_health = std::ceil(this->base_total_health * this->level_up_multiplier_health);
    this->base_damage = std::ceil(this->base_damage * this->level_up_multiplier_damage);
    this->current_total_health = this->base_total_health;
    this->level_points++;
}

void Player::gainExp(int xp, WINDOW *alert_win)
{
    wattrset(alert_win, A_NORMAL);
    wattron(alert_win, Color::GreenBlack);
    wprintw(alert_win, "You gained %d XP.\n", xp);
    wattroff(alert_win, Color::GreenBlack);
    this->current_xp += xp;
    if (this->current_xp >= this->current_xp_cap) {
        this->levelUp(alert_win);
    }
}

void Player::setPosition(int row, int col)
{
    this->row = row;
    this->col = col;
}

void Player::setInCombatAbsolute(bool toggle)
{
    this->in_combat = toggle;
}

void Player::setInCombatCheck(std::vector<Enemy> enemies)
{
    for (Enemy e : enemies){
        if (e.isInCombat()) {
            this->in_combat = true;
            return;
        }
    }
    this->in_combat = false;
}

bool Player::isInCombat()
{
    return this->in_combat;
}

//Will print things like player gold count, health, status, and
//directional indicators to next obelisk
void Player::printStatus(WINDOW *player_window)
{
    wmove(player_window, 0, 0);
    wclrtoeol(player_window);
    double ratio = (double)this->current_health / (double)this->current_total_health;

    if (ratio > 0.75) {
        wattron(player_window, Color::GreenBlack);
    }
    else if (ratio > 0.30) {
        wattron(player_window, Color::YellowBlack);
    }
    else {
        wattron(player_window, Color::RedBlack);
    }

    wprintw(player_window, "Health: ");
    //If health is below a certain threshold display it in red
    wprintw(player_window, "%d/%d\n", this->current_health, this->current_total_health);
    wattrset(player_window, A_NORMAL);

    wprintw(player_window, "Level: %d        XP: %d/%d\n", this->level, this->current_xp, this->current_xp_cap);
    if (this->souls == this->souls_cap) {
        wattron(player_window, Color::YellowBlack);
    }
    wprintw(player_window, "SOULS: %d/%d", this->souls, this->souls_cap);
    wattroff(player_window, Color::YellowBlack);
    wrefresh(player_window);
}

int Player::computeAttackPower(Weapon weapon, WINDOW *alert_window) {
    Attack w_attack = weapon.attack;
    int damage = (weapon.damage + this->base_damage) * w_attack.damage_mod;
    int damage_range = weapon.damage_range;
    double crit_chance = (weapon.crit_chance + this->crit_chance) * w_attack.crit_chance_mod;
    double crit_roll = ((double) rand() / RAND_MAX);

    if (damage_range != 0) {
        damage += (rand() % damage_range);
    }
    if (this->souls == this->souls_cap) {
        damage *= this->souls_multiplier;
    }
    if (crit_chance >= crit_roll) {
        damage *= 2;
        wprintw(alert_window, "CRITICAL HIT\n");
        wrefresh(alert_window);
    }
    return (int)damage;
}

/*
*Attacks enemy. Returns true if enemy is killed, false otherwise
*
*/
bool Player::attack(Enemy *enemy, bool usePrimary, WINDOW *alert_window)
{
    Weapon player_weapon;
    if (usePrimary) {
        player_weapon = *(this->primary_weapon);
    }
    else {
        player_weapon = *(this->secondary_weapon);
    }
    this->takeDamageOverTime(alert_window);
    if (this->current_health <= 0 || this->is_stunned) {
        if (this->is_stunned) {
            this->is_stunned = false;
        }
        return false;
    }

    bool attack_hit = checkIfAttackHit((player_weapon.accuracy + this->accuracy) * player_weapon.attack.accuracy_mod, enemy->current_evasion);

    wattron(alert_window, Color::MagentaBlack);
    if (attack_hit) {
        int dmg = this->computeAttackPower(player_weapon, alert_window) * (1 - enemy->current_protection);
        double bleed_roll = ((double) rand() / RAND_MAX);
        double stun_roll = ((double) rand() / RAND_MAX);
        //Roll for bleed
        if (bleed_roll <= (player_weapon.bleed_chance * player_weapon.attack.bleed_chance_mod) - enemy->bleed_resist) {
            wprintw(alert_window, "%s is bleeding.\n", (enemy->name).c_str());
            enemy->setBleedDamage(player_weapon.attack.bleed_damage, player_weapon.attack.bleed_rounds);
        }
        //Roll for stun
        if (stun_roll <= (player_weapon.stun_chance * player_weapon.attack.stun_chance_mod)) {
            wattroff(alert_window, Color::MagentaBlack);
            wattron(alert_window, Color::YellowBlack);
            wprintw(alert_window, "%s is stunned.\n", (enemy->name).c_str());
            wattroff(alert_window, Color::YellowBlack);
            wattron(alert_window, Color::MagentaBlack);
            enemy->is_stunned = true;
        }
        enemy->current_health -= dmg;
        wprintw(alert_window, "You hit %s for %d damage.\n", (enemy->name).c_str(), dmg);
        wrefresh(alert_window);
        wattroff(alert_window, Color::MagentaBlack);
    }
    else {
        wprintw(alert_window, "You miss %s.\n", (enemy->name).c_str());
        wrefresh(alert_window);
        wattroff(alert_window, Color::MagentaBlack);
    }
    return enemy->current_health <= 0;
}

//Only passively regenerate on active moves (no skips)
//This promotes active gameplay
//Also only do if health is not capped
void Player::passiveHealthRegeneration(WINDOW *player_window)
{
    if (this->current_health < this->current_total_health) {
        this->passive_health_regen_counter++;
        if (this->passive_health_regen_counter == this->passive_health_regen_trigger) {
            this->passive_health_regen_counter = 0;
            this->current_health += this->passive_health_regen_amount;
        }
        this->printStatus(player_window);
    }
}

//Returns true if the movement was valid, false otherwise
bool Player::moveSpace(int direction, Map map, WINDOW *player_window, std::vector<Enemy> enemies)
{
    int row_mod = 0;
    int col_mod = 0;
    switch (direction) {
        case KEY_UP:
            row_mod = -1;
            break;
        case KEY_DOWN:
            row_mod = 1;
            break;
        case KEY_LEFT:
            col_mod = -1;
            break;
        case KEY_RIGHT:
            col_mod = 1;
            break;
        default:
            break;
    }

    if (map.isValidMove(this->row + row_mod, this->col + col_mod, enemies)) {
        this->row += row_mod;
        this->col += col_mod;
        if (this->passive_health_regen_amount > 0) {
            passiveHealthRegeneration(player_window);
        }
        return true;
    }
    return false;

}


#include "kernel.h"
#include "structure.h"
#include "y_grad.h"
#include "sparse_gp.h"
#include "b2.h"
#include "b2_simple.h"
#include "b2_norm.h"
#include "b3.h"
#include "two_body.h"
#include "three_body.h"
#include "three_body_wide.h"
#include "four_body.h"
#include "squared_exponential.h"
#include "normalized_dot_product.h"
#include "norm_dot_icm.h"

#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include <Eigen/Dense>
#include <vector>

namespace py = pybind11;

PYBIND11_MODULE(_C_flare, m) {
  // Structure
  py::class_<Structure>(m, "Structure")
      .def(py::init<const Eigen::MatrixXd &, const std::vector<int> &,
                    const Eigen::MatrixXd &>())
      .def(py::init<const Eigen::MatrixXd &, const std::vector<int> &,
                    const Eigen::MatrixXd &, double,
                    std::vector<Descriptor *>>())
      .def_readwrite("noa", &Structure::noa)
      .def_readwrite("cell", &Structure::cell)
      .def_readwrite("species", &Structure::species)
      .def_readwrite("positions", &Structure::positions)
      .def_readwrite("cell_transpose", &Structure::cell_transpose)
      .def_readwrite("wrapped_positions", &Structure::wrapped_positions)
      .def_readwrite("volume", &Structure::volume)
      .def_readwrite("energy", &Structure::energy)
      .def_readwrite("forces", &Structure::forces)
      .def_readwrite("stresses", &Structure::stresses)
      .def_readwrite("mean_efs", &Structure::mean_efs)
      .def_readwrite("variance_efs", &Structure::variance_efs)
      .def_readwrite("local_uncertainties", &Structure::local_uncertainties)
      .def_readonly("descriptors", &Structure::descriptors)
      .def_readonly("descriptor_calculators",
                    &Structure::descriptor_calculators)
      .def("compute_descriptors", &Structure::compute_descriptors)
      .def("wrap_positions", &Structure::wrap_positions)
      .def_static("to_json", &Structure::to_json)
      .def_static("from_json", &Structure::from_json);

  // Descriptor values
  py::class_<DescriptorValues>(m, "DescriptorValues")
      .def(py::init<>())
      .def_readwrite("n_descriptors", &DescriptorValues::n_descriptors)
      .def_readwrite("n_types", &DescriptorValues::n_types)
      .def_readwrite("n_atoms", &DescriptorValues::n_atoms)
      .def_readwrite("volume", &DescriptorValues::volume)
      .def_readwrite("descriptors", &DescriptorValues::descriptors)
      .def_readwrite("descriptor_force_dervs",
                     &DescriptorValues::descriptor_force_dervs)
      .def_readwrite("neighbor_coordinates",
                     &DescriptorValues::neighbor_coordinates)
      .def_readwrite("descriptor_norms", &DescriptorValues::descriptor_norms)
      .def_readwrite("descriptor_force_dots",
                     &DescriptorValues::descriptor_force_dots)
      .def_readwrite("cutoff_values", &DescriptorValues::cutoff_values)
      .def_readwrite("cutoff_dervs", &DescriptorValues::cutoff_dervs)
      .def_readwrite("neighbor_counts", &DescriptorValues::neighbor_counts)
      .def_readwrite("cumulative_neighbor_counts",
                     &DescriptorValues::cumulative_neighbor_counts)
      .def_readwrite("atom_indices", &DescriptorValues::atom_indices)
      .def_readwrite("neighbor_indices", &DescriptorValues::neighbor_indices)
      .def_readwrite("n_clusters_by_type",
                     &DescriptorValues::n_clusters_by_type)
      .def_readwrite("n_neighbors_by_type",
                     &DescriptorValues::n_neighbors_by_type);

  py::class_<ClusterDescriptor>(m, "ClusterDescriptor")
      .def_readonly("descriptors", &ClusterDescriptor::descriptors);

  // Descriptor calculators
  py::class_<Descriptor>(m, "Descriptor")
      .def("compute_struc", &Descriptor::compute_struc);

  py::class_<TwoBody, Descriptor>(m, "TwoBody")
      .def(py::init<double, int, const std::string &,
                    const std::vector<double> &>());

  py::class_<ThreeBody, Descriptor>(m, "ThreeBody")
      .def(py::init<double, int, const std::string &,
                    const std::vector<double> &>());

  py::class_<ThreeBodyWide, Descriptor>(m, "ThreeBodyWide")
      .def(py::init<double, int, const std::string &,
                    const std::vector<double> &>());

  py::class_<FourBody, Descriptor>(m, "FourBody")
      .def(py::init<double, int, const std::string &,
                    const std::vector<double> &>());

  py::class_<B2, Descriptor>(m, "B2")
      .def(py::init<const std::string &, const std::string &,
                    const std::vector<double> &, const std::vector<double> &,
                    const std::vector<int> &>())
      .def(py::init<const std::string &, const std::string &,
                    const std::vector<double> &, const std::vector<double> &,
                    const std::vector<int> &,
                    const Eigen::MatrixXd &>())
      .def_readonly("radial_basis", &B2::radial_basis)
      .def_readonly("cutoff_function", &B2::cutoff_function)
      .def_readonly("radial_hyps", &B2::radial_hyps)
      .def_readonly("cutoff_hyps", &B2::cutoff_hyps)
      .def_readonly("cutoffs", &B2::cutoffs)
      .def_readonly("descriptor_settings", &B2::descriptor_settings);

  py::class_<B2_Simple, Descriptor>(m, "B2_Simple")
      .def(py::init<const std::string &, const std::string &,
                    const std::vector<double> &, const std::vector<double> &,
                    const std::vector<int> &>());

  py::class_<B2_Norm, Descriptor>(m, "B2_Norm")
      .def(py::init<const std::string &, const std::string &,
                    const std::vector<double> &, const std::vector<double> &,
                    const std::vector<int> &>());

  py::class_<B3, Descriptor>(m, "B3")
      .def(py::init<const std::string &, const std::string &,
                    const std::vector<double> &, const std::vector<double> &,
                    const std::vector<int> &>());

  // Kernel functions
  py::class_<Kernel>(m, "Kernel");

  py::class_<NormalizedDotProduct, Kernel>(m, "NormalizedDotProduct")
      .def(py::init<double, double>())
      .def_readonly("sigma", &NormalizedDotProduct::sigma)
      .def_readonly("power", &NormalizedDotProduct::power)
      .def_readonly("kernel_hyperparameters",
                    &NormalizedDotProduct::kernel_hyperparameters)
      .def("envs_envs", &NormalizedDotProduct::envs_envs)
      .def("envs_struc", &NormalizedDotProduct::envs_struc)
      .def("struc_struc", &NormalizedDotProduct::struc_struc);

  py::class_<NormalizedDotProduct_ICM, Kernel>(m, "NormalizedDotProduct_ICM")
      .def(py::init<double, double, Eigen::MatrixXd>());

  py::class_<SquaredExponential, Kernel>(m, "SquaredExponential")
      .def(py::init<double, double>());

  // Sparse GP DTC
  py::class_<SparseGP>(m, "SparseGP")
      .def(py::init<>())
      .def(py::init<std::vector<Kernel *>, double, double, double>())
      .def("set_hyperparameters", &SparseGP::set_hyperparameters)
      .def("predict_mean", &SparseGP::predict_mean)
      .def("predict_SOR", &SparseGP::predict_SOR)
      .def("predict_DTC", &SparseGP::predict_DTC)
      .def("predict_local_uncertainties",
           &SparseGP::predict_local_uncertainties)
      .def("add_all_environments", &SparseGP::add_all_environments)
      .def("add_specific_environments", &SparseGP::add_specific_environments)
      .def("add_random_environments", &SparseGP::add_random_environments)
      .def("add_uncertain_environments",
           &SparseGP::add_uncertain_environments)
      .def("add_training_structure", &SparseGP::add_training_structure)
      .def("update_matrices_QR", &SparseGP::update_matrices_QR)
      .def("compute_likelihood", &SparseGP::compute_likelihood)
      .def("compute_likelihood_stable", &SparseGP::compute_likelihood_stable)
      .def("compute_likelihood_gradient",
           &SparseGP::compute_likelihood_gradient)
      .def("write_mapping_coefficients", &SparseGP::write_mapping_coefficients)
      .def_readonly("varmap_coeffs", &SparseGP::varmap_coeffs) // for debugging and unit test
      .def("compute_cluster_uncertainties", &SparseGP::compute_cluster_uncertainties) // for debugging and unit test
      .def("write_varmap_coefficients", &SparseGP::write_varmap_coefficients)
      .def_readwrite("Kuu_jitter", &SparseGP::Kuu_jitter)
      .def_readonly("complexity_penalty", &SparseGP::complexity_penalty)
      .def_readonly("data_fit", &SparseGP::data_fit)
      .def_readonly("constant_term", &SparseGP::constant_term)
      .def_readwrite("log_marginal_likelihood",
                     &SparseGP::log_marginal_likelihood)
      .def_readwrite("likelihood_gradient", &SparseGP::likelihood_gradient)
      .def_readonly("kernels", &SparseGP::kernels)
      .def_readonly("hyperparameters", &SparseGP::hyperparameters)
      .def_readonly("training_structures", &SparseGP::training_structures)
      .def_readonly("sparse_indices", &SparseGP::sparse_indices)
      .def_readonly("sparse_descriptors", &SparseGP::sparse_descriptors)
      .def_readonly("n_energy_labels", &SparseGP::n_energy_labels)
      .def_readonly("n_force_labels", &SparseGP::n_force_labels)
      .def_readonly("n_stress_labels", &SparseGP::n_stress_labels)
      .def_readonly("force_noise", &SparseGP::force_noise)
      .def_readonly("energy_noise", &SparseGP::energy_noise)
      .def_readonly("stress_noise", &SparseGP::stress_noise)
      .def_readonly("noise_vector", &SparseGP::noise_vector)
      .def_readonly("Kuu", &SparseGP::Kuu)
      .def_readonly("Kuu_kernels", &SparseGP::Kuu_kernels)
      .def_readonly("Kuf", &SparseGP::Kuf)
      .def_readonly("Kuf_kernels", &SparseGP::Kuf_kernels)
      .def_readonly("alpha", &SparseGP::alpha)
      .def_readonly("Kuu_inverse", &SparseGP::Kuu_inverse)
      .def_readonly("Sigma", &SparseGP::Sigma)
      .def_readonly("n_sparse", &SparseGP::n_sparse)
      .def_readonly("n_labels", &SparseGP::n_labels)
      .def_readonly("y", &SparseGP::y)
      .def_static("to_json", &SparseGP::to_json)
      .def_static("from_json", &SparseGP::from_json);
}


#include <iostream>
#include <fstream>
#include <ctime>

#include <boost/program_options.hpp>

#include <harp.hpp>

#include "specex_message.h"
#include "specex_fits.h"
#include "specex_psf.h"
#include "specex_psf_io.h"
#include "specex_gauss_hermite_psf.h"
#include "specex_image_data.h"
#include "specex_serialization.h"
#include "specex_model_image.h"

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

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

  string input_psf_xml_filename="";
  string input_psf_fits_filename="";
  string output_image_fits_filename="";
  string output_psf_fits_filename="";

  specex_set_verbose(true);

  popts::options_description desc ( "Allowed Options" );
  desc.add_options()
    ( "help,h", "display usage information" )
    ( "in_xml", popts::value<string>( & input_psf_xml_filename ), "input PSF xml file name" )
    ( "in_fits", popts::value<string>( & input_psf_fits_filename ), "input PSF fits file name" )
    ( "out_fits_image", popts::value<string>( & output_image_fits_filename ), "output image fits file name" )
    ( "out_fits_psf", popts::value<string>( & output_psf_fits_filename ), "output psf fits file name" )
    ;

  popts::variables_map vm;
  try {
    popts::store(popts::command_line_parser( argc, argv ).options(desc).run(), vm);
    popts::notify(vm);

    if ( ( argc < 2 ) || vm.count( "help" ) || ( ! vm.count( "in_xml" ) && ( ! vm.count( "in_fits" ) ) )  ||  ( ! vm.count( "out_fits_image") && ( ! vm.count( "out_fits_psf" ) ) ) ) {
      cerr << endl;
      cerr << desc << endl;
      return EXIT_FAILURE;
    }
  }catch(std::exception) {
    cerr << "error in arguments" << endl;
    cerr << endl;
    cerr << desc << endl;
    return EXIT_FAILURE;
  }

  specex::PSF_p psf;

  if(vm.count( "in_fits" ))
    SPECEX_ERROR("reading fits not implemented yet");

  specex::read_psf_xml(psf,input_psf_xml_filename);

  /*
  if(psf->Name() !=  "GaussHermite2PSF") {
    cout << "code only works with GAUSSHERMITE2PSF" << endl;
    return 0;
  }
  */

  if(vm.count( "out_fits_psf" ))
  specex::write_psf_fits(psf,output_psf_fits_filename);

  if(0){
    // testing coordinate system
    double xc = 400;
    double yc = 2000;
    harp::vector_double P(psf->LocalNAllPar());
    P.clear();
    int i=floor(xc);
    //for(i=floor(xc)-3;i<=floor(xc)+3;i++)
    for(int j=floor(yc)-3;j<=floor(yc)+3;j++)
      cout << i << " " << j << " " << psf->PSFValueWithParamsXY(400,2000,i,j,P,NULL,NULL) << endl;

    return 0;
  }

  // now create a test image
  SPECEX_INFO("Generate an image with spots of flux=1 at each 50A starting at 3500A for each fiber addressed by PSF");

  SPECEX_INFO("create list of spots ...");

  vector<specex::Spot_p> spots;

  double wave_step = 50;

  for(std::map<int,specex::PSF_Params>::const_iterator bundle_it = psf->ParamsOfBundles.begin();
      bundle_it != psf->ParamsOfBundles.end(); ++bundle_it) {
    for(int fiber = bundle_it->second.fiber_min;
	fiber <= bundle_it->second.fiber_max; ++fiber) {

      const specex::Trace& trace = psf->GetTrace(fiber);
      double trace_wave_min = trace.X_vs_W.xmin;
      double trace_wave_max = trace.X_vs_W.xmax;
      double min_wave = 3600; //wave_step*int(max(3500.,trace_wave_min)/wave_step+1);

      SPECEX_INFO("bundle " << bundle_it->first << " fiber " << fiber << " wave range = " << trace_wave_min << " " << trace_wave_max);
      SPECEX_INFO("bundle " << bundle_it->first << " fiber " << fiber << " coefs = " << trace.X_vs_W.coeff);

      // loop on wavelength
      for(double wave = min_wave ; wave<= trace_wave_max; wave += wave_step) {

	specex::Spot_p spot(new specex::Spot());

	spot->xc = trace.X_vs_W.Value(wave);
	spot->yc = trace.Y_vs_W.Value(wave);
	spot->flux = 1;
	spot->wavelength = wave;
	spot->fiber = fiber;
	spot->fiber_bundle = bundle_it->first;
	spots.push_back(spot);
	SPECEX_INFO("fiber=" << spot->fiber << " wave=" << spot->wavelength << " x=" << spot->xc << " y=" << spot->yc);

	//break; // DEBUG, only one spot per fiber
      }

      //exit(12); // DEBUG
    }
  }

#ifdef CONTINUUM
  SPECEX_INFO("remove continuum");
  for(std::map<int,specex::PSF_Params>::iterator bundle_it = psf->ParamsOfBundles.begin();
      bundle_it != psf->ParamsOfBundles.end(); ++bundle_it) {
    bundle_it->second.ContinuumPol.coeff.clear();
  }
#endif

  if(0) {
    SPECEX_INFO("remove psf tail");
    for(std::map<int,specex::PSF_Params>::iterator bundle_it = psf->ParamsOfBundles.begin();
	bundle_it != psf->ParamsOfBundles.end(); ++bundle_it) {
      bundle_it->second.AllParPolXW[psf->ParamIndex("TAILAMP")]->coeff.clear();
    }
  }

   // create image
  specex::image_data img(psf->ccd_image_n_cols,psf->ccd_image_n_rows);

  specex::image_data weight(psf->ccd_image_n_cols,psf->ccd_image_n_rows);
  for(size_t i=0;i<weight.data.size();i++) weight.data(i)=1;

  bool only_on_spots = false;
  bool only_psf_core = false;
  bool only_positive = false;

  if(output_image_fits_filename!="") {
    SPECEX_INFO("computing  image ...");
    specex::parallelized_compute_model_image(img,weight,psf,spots,only_on_spots,only_psf_core,only_positive,0,0);
    //specex::compute_model_image(img,weight,psf,spots,only_on_spots,only_psf_core,only_positive,-1,-1,12);

    SPECEX_INFO("writing image ...");
    fitsfile * fp;
    harp::fits::create ( fp, output_image_fits_filename );
    harp::fits::img_append < double > ( fp, img.n_rows(), img.n_cols() );
    harp::fits::img_write ( fp, img.data, false );

    int status=0;
    char comment[800];
    fits_write_comment(fp,"generateds by specex_test_psf_specter_io",&status);
    sprintf(comment,"from %s",input_psf_xml_filename.c_str());
    fits_write_comment(fp,comment,&status);
    sprintf(comment,"spots generated every %fA, starting at %fA",wave_step,spots[0]->wavelength);
    fits_write_comment(fp,comment,&status);
    fits_write_comment(fp,"flux in each spot is 1 electron",&status);
    harp::fits::close ( fp );

  }else{
    SPECEX_INFO("no full image");
  }

  { // one spot
    vector<specex::Spot_p> one_spot;
    specex::Spot_p spot = spots[0];
    spot->wavelength = (double)((int)spot->wavelength);
    one_spot.push_back(spot);

    img    = specex::image_data(spot->xc+50,spot->yc+50);
    weight = specex::image_data(spot->xc+50,spot->yc+50);
    for(size_t i=0;i<weight.data.size();i++) weight.data(i)=1;

    char filename[1000];

    specex::PSF_Params& params_of_bundle = psf->ParamsOfBundles.find(spot->fiber_bundle)->second;

    /*
    if(psf->HasParam("GHNSIG")) {
      params_of_bundle.AllParPolXW[psf->ParamIndex("GHNSIG")]->coeff.clear();
      params_of_bundle.AllParPolXW[psf->ParamIndex("GHNSIG")]->coeff(0)=1000; // don't want to cut off core
      cout << "set GHNSIG to very large value" << endl;
    }
    */

    harp::vector_double psf_params = psf->AllLocalParamsFW(spot->fiber,spot->wavelength,spot->fiber_bundle);

    double scal2    = 0;
    if(psf->Name() ==  "GaussHermite2PSF") {
      scal2    = psf_params[psf->ParamIndex("GH2-0-0")];
      cout << "norm of first  GH PSF = " << (1-scal2) << endl;
      cout << "norm of second GH PSF = " <<  scal2 << endl;
    }

    vector<string> names = psf->DefaultParamNames();
    for(size_t k=0;k<psf_params.size(); k++) {
      cout << k << " " << names[k] << " " << psf_params(k) << endl;
    }

    img.data.clear();
    specex::parallelized_compute_model_image(img,weight,psf,one_spot,only_on_spots,only_psf_core,only_positive,50,50,spot->fiber_bundle);
    specex::image_data all_contributions_img = img;

    params_of_bundle.AllParPolXW[psf->ParamIndex("TAILAMP")]->coeff.clear();
    img.data.clear();
    specex::parallelized_compute_model_image(img,weight,psf,one_spot,only_on_spots,only_psf_core,only_positive,50,50,spot->fiber_bundle);
    specex::image_data without_tails_img = img;
    specex::image_data only_tails_img = all_contributions_img;
    only_tails_img.data -= without_tails_img.data;

    for(size_t p=0;p<params_of_bundle.AllParPolXW.size();p++) {
      if(names[p].find("GH2-")!=names[p].npos) {
	cout << "clearing " << names[p] << endl;
	params_of_bundle.AllParPolXW[p]->coeff.clear();
      }
    }

    sprintf(filename,"single_spot_fiber_%d_wave_%d.fits",spot->fiber,(int)spot->wavelength);
    specex::write_new_fits_image(filename,all_contributions_img);
    sprintf(filename,"single_spot_fiber_%d_wave_%d_without_tails.fits",spot->fiber,(int)spot->wavelength);
    specex::write_new_fits_image(filename,without_tails_img);

    //sprintf(filename,"single_spot_fiber_%d_wave_%d_only_tails.fits",spot->fiber,(int)spot->wavelength);
    //specex::write_new_fits_image(filename,only_tails_img);

    if(psf->Name() ==  "GaussHermite2PSF") {
      img.data.clear();
      specex::parallelized_compute_model_image(img,weight,psf,one_spot,only_on_spots,only_psf_core,only_positive,50,50,spot->fiber_bundle);
      specex::image_data only_core_gaussian_img = img;

      // need to compute naked gaussian to fix amplitude offset
      for(size_t p=0;p<params_of_bundle.AllParPolXW.size();p++) {
	if(names[p].find("GH-")!=names[p].npos) {
	  cout << "clearing " << names[p] << endl;
	  params_of_bundle.AllParPolXW[p]->coeff.clear();
	}
      }

      img.data.clear();
      specex::parallelized_compute_model_image(img,weight,psf,one_spot,only_on_spots,only_psf_core,only_positive,50,50,spot->fiber_bundle);
      specex::image_data only_core_naked_gaussian_img = img;
      only_core_gaussian_img.data -= scal2*only_core_naked_gaussian_img.data;

      specex::image_data only_second_gaussian_img = without_tails_img;
      only_second_gaussian_img.data -= only_core_gaussian_img.data;

      specex::image_data zero = all_contributions_img;
      zero.data -= only_core_gaussian_img.data;
      zero.data -= only_second_gaussian_img.data;
      zero.data -= only_tails_img.data;

      sprintf(filename,"single_spot_fiber_%d_wave_%d_only_core_gaussian.fits",spot->fiber,(int)spot->wavelength);
      specex::write_new_fits_image(filename,only_core_gaussian_img);
      sprintf(filename,"single_spot_fiber_%d_wave_%d_only_second_gaussian.fits",spot->fiber,(int)spot->wavelength);
      specex::write_new_fits_image(filename,only_second_gaussian_img);

      sprintf(filename,"zero.fits");
      specex::write_new_fits_image(filename,zero);

    }
  } // end of one spot

  return EXIT_SUCCESS;
}


#include <iostream>

#include <boost/generator_iterator.hpp>
#include <boost/random.hpp>

#include "tmod.h"

#include <chrono>
#include <cstdint>
#include <iostream>

uint64_t timeSinceEpochMillisec()
{
    using namespace std::chrono;
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch())
        .count();
}

int16_t tmodReadAdc(uint16_t hardwareAddress)
{
    // Hardware address can't be negative as it is uint16_t.
    if (hardwareAddress > tmodMaxAdcs())
        return TMOD_INVALID_VOLTAGE_MEASUREMENT;

    boost::mt19937 rng(timeSinceEpochMillisec());
    boost::uniform_int<> adcMeasurementRange(0, TMOD_MAX_ADC_VALUE);
    boost::variate_generator<boost::mt19937, boost::uniform_int<>>
        randomMeasurement(rng, adcMeasurementRange);
    return randomMeasurement();
}

uint16_t tmodMaxAdcs() { return TMOD_MAX_ADCS; }


#ifndef STAN_MATH_PRIM_MAT_PROB_CATEGORICAL_LOGIT_LOG_HPP
#define STAN_MATH_PRIM_MAT_PROB_CATEGORICAL_LOGIT_LOG_HPP

#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/prob/categorical_logit_lpmf.hpp>
#include <boost/math/tools/promotion.hpp>
#include <vector>

namespace stan {
namespace math {

/**
 * @deprecated use categorical_logit_lpmf
 */
template <bool propto, typename T_prob>
typename boost::math::tools::promote_args<T_prob>::type categorical_logit_log(
    int n, const Eigen::Matrix<T_prob, Eigen::Dynamic, 1>& beta) {
  return categorical_logit_lpmf<propto, T_prob>(n, beta);
}

/**
 * @deprecated use categorical_logit_lpmf
 */
template <typename T_prob>
inline typename boost::math::tools::promote_args<T_prob>::type
categorical_logit_log(int n,
                      const Eigen::Matrix<T_prob, Eigen::Dynamic, 1>& beta) {
  return categorical_logit_lpmf<T_prob>(n, beta);
}

/**
 * @deprecated use categorical_logit_lpmf
 */
template <bool propto, typename T_prob>
typename boost::math::tools::promote_args<T_prob>::type categorical_logit_log(
    const std::vector<int>& ns,
    const Eigen::Matrix<T_prob, Eigen::Dynamic, 1>& beta) {
  return categorical_logit_lpmf<propto, T_prob>(ns, beta);
}

/**
 * @deprecated use categorical_logit_lpmf
 */
template <typename T_prob>
inline typename boost::math::tools::promote_args<T_prob>::type
categorical_logit_log(const std::vector<int>& ns,
                      const Eigen::Matrix<T_prob, Eigen::Dynamic, 1>& beta) {
  return categorical_logit_lpmf<T_prob>(ns, beta);
}

}  // namespace math
}  // namespace stan
#endif


#include <cstring>

#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>

#include <epecur/types.hpp>
#include <epecur/track.hpp>
#include <epecur/StdHits.hpp>

#include "DriftCalibHook.hpp"

DriftCalibHook::DriftCalibHook(Geometry &g, int _drift_calib_cut)
    : StdHits(g, NULL)
    , drift_calib_cut(_drift_calib_cut)
{
	// Nothing
}

unsigned int	DriftCalibHook::generate_calibration_curve( chamber_id_t chamber_id )
{
	uint16_t	time = 0;
	unsigned int	overal_integral = 0, integral = 0;
	auto	&calib = calibration_curve[chamber_id];

	calib.resize(MAX_TIME_COUNTS);

	BOOST_FOREACH(auto counts, time_distributions[chamber_id])
	{
		overal_integral += counts;
	}

	BOOST_FOREACH(auto counts, time_distributions[chamber_id])
	{
		integral += counts;
		auto	value = integral / (float)overal_integral;
		calib[time] = value;
		time++;
	}

	return overal_integral;
}

void	DriftCalibHook::generate_calibration_curves()
{
	BOOST_FOREACH(auto gr_tup, geom.group_chambers)
	{
		group_id_t	group_id = gr_tup.first;
		device_type_t	device_type = geom.group_device_type[group_id];

		if (device_type != DEV_TYPE_DRIFT)
		{
			continue;
		}

		BOOST_FOREACH(auto axis_tup, gr_tup.second)
		{
			device_axis_t	axis = axis_tup.first;
			vector<chamber_id_t>	&chambers =
				geom.group_chambers[group_id][axis];

			BOOST_FOREACH(chamber_id_t chamber_id, chambers)
			{
				generate_calibration_curve(chamber_id);
			}
		}
	}
}

void	DriftCalibHook::handle_event_end()
{
	BOOST_FOREACH(auto gr_tup, geom.group_chambers)
	{
		group_id_t	group_id = gr_tup.first;

		if (geom.group_device_type[group_id] != DEV_TYPE_DRIFT)
		{
			continue;
		}

		BOOST_FOREACH(auto &axis_tup, gr_tup.second)
		{
			const vector<chamber_id_t>	&chambers = axis_tup.second;

			BOOST_FOREACH(chamber_id_t chamber_id, chambers)
			{
				vector<wire_pos_t> &wire_pos = last_event_drift_wire_pos[chamber_id];
				auto it = wire_pos.begin();

				BOOST_FOREACH(uint16_t time, last_event_drift_time[chamber_id])
				{
					auto	&calib = time_distributions[chamber_id];

					// Here we are removing hits from the adjacent cells
					bool fail = false;
					for(auto it2 = wire_pos.begin(); it2 != it; it2++)
					{
						if (fabs(*it - *it2) <= 2.001)
						{
							fail = true;
							break;
						}
					}
					if (fail)
						break;
					it++;

					if (calib.empty())
					{
						calib.resize(MAX_TIME_COUNTS);
					}

					if ((drift_calib_cut < 0) || (time < drift_calib_cut))
					{
						calib[time]++;
					}
				}
			}
		}
	}
}


// Copyright 2018-2019 Carnegie Mellon University.  See LICENSE file for terms.

#include <boost/graph/topological_sort.hpp>
#include <z3++.h>

#include <boost/range/algorithm/for_each.hpp>

// For the main pharos infastructure that tracks functions.
#include "descriptors.hpp"

// For IR
#include "ir.hpp"

#include "wp.hpp"

// For get_func_containing_addr
#include "path.hpp"

using namespace pharos;
using namespace pharos::ir;

namespace {
  using Rose::BinaryAnalysis::SmtSolver;

  // LET crap.
  auto constexpr OP_LET = Rose::BinaryAnalysis::SymbolicExpr::Operator::OP_LET;

  // map from variables to their bound expressions
  using letmap = std::map<Register, IRExprPtr>;

  using InteriorPtr = SymbolicExpr::InteriorPtr;

  // We *must* use pass by value here
  IRExprPtr expand_lets (IRExprPtr e, letmap lm) {
    Register v;
    InteriorPtr i;

    if ((v = e->isLeafNode ())) {
      if ((v->isVariable () || v->isMemory ()) && lm.count (v)) {
        // It's a reference to v that has been bound
        return lm[v];
      } else {
        // It's some other leaf node that we don't have to change
        return e;
      }
    } else {
      i = e->isInteriorNode ();
      assert (i);
      auto children = i->children ();
      if (i->getOperator () == OP_LET) {
        // This is a let expression!
        IRExprPtr v_, ve, vin;
        std::tie(v_, ve, vin) = std::make_tuple(children.at (0), children.at (1), children.at (2));
        v = v_->isLeafNode ();
        // First expand ve
        ve = expand_lets (ve, lm);
        // Now add v -> ve to lm
        lm[v] = ve;
        // And recurse on vin
        return expand_lets (vin, lm);
      } else {
        // A non-let interior expression.  Recurse on each child and rebuild the expression
        std::vector <IRExprPtr> new_children;

        std::transform (children.begin (), children.end (),
                        std::back_inserter (new_children),
                        [lm] (IRExprPtr childe) {
                          return expand_lets (childe, lm);
                        });
        return SymbolicExpr::Interior::create (i->nBits (),
                                               i->getOperator (),
                                               new_children);
      }
    }
  }

  IRExprPtr makeLet (Register a, IRExprPtr b, IRExprPtr c) {
    return SymbolicExpr::Interior::create (c->nBits (), OP_LET, a, b, c);
  }

}

namespace pharos {
  IRExprPtr expand_lets (const IRExprPtr &e) {
    letmap l;
    return ::expand_lets (e, l);
  }
}

// Better names for variables during Z3 translation.
// Very dangerous to enable because it changes all Pharos programs!
// Please do not commit (only for debugging).
#if 0
namespace Rose {
  namespace BinaryAnalysis {
void
Z3Solver::ctxVariableDeclarations(const VariableSet &vars) {
  BOOST_FOREACH (const SymbolicExpr::LeafPtr &var, vars.values()) {
    ASSERT_not_null(var);
    ASSERT_require(var->isVariable() || var->isMemory());
    if (ctxVarDecls_.exists(var)) {
      // already emitted a declaration for this variable
    } else if (var->isScalar()) {
      z3::sort range = ctx_->bv_sort(var->nBits());
      std::stringstream ss;
      ss << *var;
      z3::func_decl decl = z3::function(ss.str().c_str(), 0, NULL, range);
      ctxVarDecls_.insert(var, decl);
    } else {
      ASSERT_require(var->domainWidth() > 0);
      z3::sort addr = ctx_->bv_sort(var->domainWidth());
      z3::sort value = ctx_->bv_sort(var->nBits());
      z3::sort range = ctx_->array_sort(addr, value);
      std::stringstream ss;
      ss << *var;
      z3::func_decl decl = z3::function(ss.str().c_str(), 0, NULL, range);
      ctxVarDecls_.insert(var, decl);
    }
  }
}
}
}
#endif

namespace {
  IRExprPtr wp_stmt(const Stmt& s, const IRExprPtr& post, const Register& mem) {
    struct StmtVisitor : public boost::static_visitor<IRExprPtr> {
      const IRExprPtr& post;
      const Register& mem;
      StmtVisitor(const IRExprPtr& post_, const Register& mem_)
        : post(post_), mem(mem_) {}

      IRExprPtr operator()(const RegWriteStmt &rs) {
        // Substitute instances of var (rs.first) with exp (rs.second) in post
        return makeLet (rs.first, rs.second, post);
        //return post->substitute(rs.first, rs.second);
      }
      IRExprPtr operator()(const MemWriteStmt &ms) {
        // Update memory
        IRExprPtr newmem = SymbolicExpr::makeWrite (mem, ms.first, ms.second);
        return makeLet (mem, newmem, post);
        //return post->substitute(mem, newmem);
      }
      IRExprPtr operator()(UNUSED const SpecialStmt &ss) {
        // Return false? It's not clear what the best thing to do here is.
        return SymbolicExpr::makeBoolean (false);
      }
      IRExprPtr operator()(UNUSED const CallStmt &cs) {
        // Return false? It's not clear what the best thing to do here is.
        return SymbolicExpr::makeBoolean (false);
      }
      IRExprPtr operator()(UNUSED const InsnStmt &is) {
        // Instruction statements do not effect weakest preconditions
        return post;
      }
    };

    StmtVisitor vis(post, mem);
    return boost::apply_visitor(vis, s);
  }

  IRExprPtr wp_stmts(const Stmts& stmts, const IRExprPtr& post, const Register& mem) {
    return std::accumulate(stmts.rbegin(), stmts.rend(), post,
                           [&mem](const IRExprPtr& e,
                                  const Stmt &stmt)
                           { return wp_stmt(stmt, e, mem); });
  }

  // When we remove edges from the CFG, we are left with some vertices that are partially
  // specified.  If we do not do anything, the unspecified executions are 'true' in the WP
  // formula which is not what we want.  This function adds edges for those executions to the
  // error node.
  IR add_error_edges (const IR& ir) {
    IRCFG cfg = ir.get_cfg ();
    auto edgecond_map = boost::get(boost::edge_name_t(), cfg);

    auto error = ir.get_error ();

    BGL_FORALL_VERTICES (v, cfg, IRCFG) {
      if (boost::out_degree (v, cfg) == 1) {
        IRCFGEdge e = *boost::begin (boost::out_edges (v, cfg));
        auto cond = edgecond_map [e];
        if (cond) {
          auto d = boost::target (e, cfg);
          if (d == error) {
            // Conditional edge to error.  Make it unconditional.
            edgecond_map [e] = EdgeCond ();
          } else {
            // Otherwise add a new edge to error.
            IRCFGEdge new_edge;
            bool succ;
            std::tie (new_edge, succ) = boost::add_edge (v, error, cfg);
            assert (succ);
            auto t = SymbolicExpr::makeInvert (*cond);
            edgecond_map [new_edge] = EdgeCond (t);
          }
        }
      }

    }
    return IR (ir, cfg);
  }
}

namespace pharos {

  IRExprPtr wp_cfg(const IR& ir_, const IRExprPtr &post) {
    std::vector<IRCFGVertex> rtopo;

    IR ir = add_error_edges (split_edges (filter_backedges (ir_)));

    const IRCFG& cfg = ir.get_cfg();
    auto ir_map = boost::get(boost::vertex_ir_t(), cfg);
    //auto name_map = boost::get(boost::vertex_name_t(), cfg);
    auto edgecond_map = boost::get(boost::edge_name_t(), cfg);

    topological_sort (cfg,
                      std::back_inserter (rtopo));

    // This maps a BB to its WP
    std::map<IRCFGVertex, IRExprPtr> bbwp;

    for (auto v : rtopo) {
      IRExprPtr bbpost;
      if (boost::out_degree (v, cfg) == 0) {
        // This is the exit
        bbpost = post;
      } else {
        auto it = boost::out_edges (v, cfg);
        bbpost = std::accumulate (it.first,
                                  it.second,
                                  SymbolicExpr::makeBoolean (true),
                                  [&] (IRExprPtr exp, const IRCFGEdge &e) {
                                    auto sv = boost::target (e, cfg);
                                    assert (bbwp.count (sv) == 1);
                                    return SymbolicExpr::makeAnd (exp, bbwp[sv]);
                                  });
      }

      IRExprPtr wp = wp_stmts (*ir_map[v], bbpost, ir.get_mem ());

      // Because we called split_edges, there is at most one incoming edge that has a condition.
      // If there is one, we treat it like an assume statement, so e => q
      auto incoming_edges = boost::in_edges (v, cfg);
      auto edgeit = std::find_if (incoming_edges.first,
                                  incoming_edges.second,
                                  [&] (IRCFGEdge e) {
                                    return (edgecond_map[e]);
                                  });
      if (edgeit != incoming_edges.second) {
        wp = SymbolicExpr::makeIte (*edgecond_map[*edgeit], wp, SymbolicExpr::makeBoolean (true));
        //std::cout << "added edge condition " << **edgecond_map[*edgeit] << std::endl;
      }

      bbwp[v] = wp;

      // PharosZ3Solver solver;
      // solver.insert (expand_lets (bbwp[v]));
      // solver.z3Update ();
      // std::cout << "WP " << name_map[v] << " " << (solver.check() == Rose::BinaryAnalysis::SmtSolver::Satisfiable::SAT_YES) << " " << solver.z3Assertions ().at (0) << std::endl;
      // if (solver.check () == Rose::BinaryAnalysis::SmtSolver::Satisfiable::SAT_YES) {
      //        std::cout << solver.z3Solver ()->get_model () << std::endl;
      // }
    }

    const IRCFGVertex &entry = ir.get_entry ();

    return bbwp[entry];
  }

  std::pair<IR,IRExprPtr> add_reached_postcondition (const IR& ir, const std::set<rose_addr_t> targets, boost::optional<Register> hit_var_) {
    IRCFG cfg = ir.get_cfg ();

    Register hit_var;

    // Create the variable if necessary
    if (hit_var_) {
      hit_var = *hit_var_;
    } else {
      hit_var = SymbolicExpr::makeVariable (1, "hit_target")->isLeafNode ();
    }

    std::set<rose_addr_t> targetbbs;

    // Find the basic blocks that the target addresses are in
    std::transform (targets.begin (),
                    targets.end (),
                    std::inserter (targetbbs, targetbbs.end ()),
                    [&] (rose_addr_t addr) {
                      // Because we are using an instruction level CFG, this is a noop for now.
                      return addr;
                    });

    // Check for NULL

    auto bb_map = boost::get (boost::vertex_name_t (), cfg);
    auto ir_map = boost::get (boost::vertex_ir_t (), cfg);

    // Initialize the variable to false in the entry
    auto entry = ir.get_entry ();
    auto irstmts = ir_map[entry];
    auto newstmt = RegWriteStmt (hit_var, SymbolicExpr::makeBoolean (false));
    irstmts->insert (irstmts->begin (), newstmt);

    // Loop over each BB.  If the BB matches one of the targets, adjust
    // the IR to insert writes at the proper places.

    // Note that we do NOT care if we successfully reach the exit.
    // Therefore we add a write before the first matching target and
    // remove any outgoing edges which effectively makes the node an
    // exit.

    BGL_FORALL_VERTICES(v, cfg, IRCFG) {
      auto insn_addr = bb_map[v].get_insn_addr ();
      if (insn_addr && targetbbs.count (*insn_addr)) {
        auto stmts = ir_map[v];
        auto firstaddrstmt = std::find_if (stmts->begin (),
                                           stmts->end (),
                                           [&] (Stmt s) {
                                             auto addr = addrFromStmt (s);
                                             return addr && targets.count (*addr) == 1;
                                           });
        assert (firstaddrstmt != stmts->end ());

        stmts->erase (firstaddrstmt+1, stmts->end ());
        newstmt = RegWriteStmt (hit_var, SymbolicExpr::makeBoolean (true));
        stmts->push_back (newstmt);

        // Remove all outgoing edges
        boost::clear_out_edges (v, cfg);
      } else if (boost::out_degree (v, cfg) == 0) {
        // This is just an optimization to make WP simplify a little bit better
        auto stmts = ir_map[v];
        newstmt = RegWriteStmt (hit_var, SymbolicExpr::makeBoolean (false));
        stmts->push_back (newstmt);
      }
    }

    return std::make_pair (IR (ir, cfg), hit_var);
  }

}

namespace {
  struct HelperVisitor : public boost::static_visitor<boost::optional<Stmt>> {
    const DescriptorSet &ds;
    const std::set<ImportCall> &funcs;
    const IR &ir;
    int &n;
    bool ignore = false;
    HelperVisitor (const DescriptorSet &ds_, IR &ir_, const std::set<ImportCall> &funcs_, int &n_) : ds(ds_), funcs(funcs_), ir(ir_), n(n_) {}
    boost::optional<Stmt> operator () (const InsnStmt &is) {
      ignore = false;
      return (Stmt) is;
    }
    boost::optional<Stmt> operator () (const CallStmt &cs) {
      if (const ImportCall *ec = boost::get<ImportCall> (&(std::get<1> (cs)))) {
        // Is it one of the targeted functions?
        auto i = std::find (funcs.begin (), funcs.end (), *ec);
        if (i != funcs.end ()) {
          int nbits = ds.get_arch_bits ();
          Register eax = ir.get_reg(ds.get_arch_reg("eax"));
	  std::stringstream vname;
	  vname << ec->first << "!" << ec->second << "@" << addr_str (std::get<2> (cs)->get_address ())
		<< ":" << n;
          IRExprPtr nv = SymbolicExpr::makeVariable (nbits, vname.str ());
          ignore = true;
          n++;
          return (Stmt) (RegWriteStmt (eax, nv));
        }
      }

      return (Stmt) cs;
    }
    template <typename T>
    boost::optional<Stmt> operator () (T const &x) const {
      if (ignore)
        return boost::none;
      else
        return (Stmt) x;
    }
  };
}

namespace pharos {
  IR rewrite_imported_calls (const DescriptorSet& ds, IR &ir, const std::set<ImportCall> funcs) {
    int n = 0;

    IRCFG cfg = ir.get_cfg ();
    auto ir_map = boost::get(boost::vertex_ir_t(), cfg);

    BGL_FORALL_VERTICES (v, cfg, IRCFG) {
      auto stmts = *ir_map [v];
      StmtsPtr newstmts (new Stmts ());
      HelperVisitor vis (ds, ir, funcs, n);
      // Visit each stmt backwards.  If we see a call to a matching
      // callsite, we ignore any stmt until we see the InsnStmt which
      // marks the beginning of that instruction.  This is used to
      // ignore stack adjustments among other things.
      std::for_each (stmts.rbegin (),
                     stmts.rend (),
                     [&] (Stmt &os) {
                       if (boost::optional<Stmt> ns = boost::apply_visitor (vis, os)) {
                         newstmts->push_back (*ns);
                       }
                      });
      // Put the new statements in the right order.
      std::reverse (newstmts->begin (), newstmts->end ());
      ir_map [v] = newstmts;
    }

    GINFO << "Rewrote " << n << " imported calls." << LEND;

    return IR(ir, cfg);
  }
}

/* Local Variables:   */
/* mode: c++          */
/* fill-column:    95 */
/* comment-column: 0  */
/* End:               */


// Copyright (c) 2020 Julian Bernhard, Klemens Esterle, Patrick Hart and
// Tobias Kessler
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#ifndef MODULES_MODELS_DYNAMIC_DYNAMIC_MODEL_HPP_
#define MODULES_MODELS_DYNAMIC_DYNAMIC_MODEL_HPP_

#include <Eigen/Core>
#include <boost/variant.hpp>
#include <queue>
#include <memory>
#include <utility>

#include "modules/commons/base_type.hpp"

namespace modules {
namespace models {
namespace dynamic {

typedef enum StateDefinition : int {
  TIME_POSITION = 0,  // unit is seconds
  X_POSITION = 1,  // unit is meter
  Y_POSITION = 2,  // unit is meter
  THETA_POSITION = 3,  // unit is rad
  VEL_POSITION = 4,  // unit is meter/second
  MIN_STATE_SIZE = 5,
  Z_POSITION = 6  // only placeholder, not used at the moment
} StateDefinition;

using State = Eigen::Matrix<float, Eigen::Dynamic, 1>;
using Input = Eigen::Matrix<float, Eigen::Dynamic, 1>;

using Trajectory = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>;

class DynamicModel : public commons::BaseType {
 public:
  explicit DynamicModel(modules::commons::ParamsPtr params) :
    BaseType(params), input_size_(0) {}

  virtual ~DynamicModel() {}

  virtual State StateSpaceModel(const State &x, const Input &u) const = 0;

  virtual std::shared_ptr<DynamicModel> Clone() const = 0;

  int input_size_;
};

typedef std::shared_ptr<DynamicModel> DynamicModelPtr;

}  // namespace dynamic
}  // namespace models
}  // namespace modules

#endif  // MODULES_MODELS_DYNAMIC_DYNAMIC_MODEL_HPP_


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

#ifndef CRYPTO3_HASH_SHA1_HPP
#define CRYPTO3_HASH_SHA1_HPP

#include <boost/crypto3/hash/detail/sha1/sha1_policy.hpp>
#include <boost/crypto3/hash/detail/state_adder.hpp>
#include <boost/crypto3/hash/detail/davies_meyer_compressor.hpp>
#include <boost/crypto3/hash/detail/merkle_damgard_construction.hpp>
#include <boost/crypto3/hash/detail/block_stream_processor.hpp>
#include <boost/crypto3/hash/detail/merkle_damgard_padding.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            /*!
             * @brief SHA1. Widely adopted NSA designed hashes function. Starting
             * to show significant signs of weakness, and collisions can now be
             * generated. Avoid in new designs.
             * @ingroup hashes
             */
            class sha1 {
                typedef detail::sha1_policy policy_type;
                typedef typename policy_type::block_cipher_type block_cipher_type;

            public:
                constexpr static const std::size_t word_bits = policy_type::word_bits;
                typedef typename policy_type::word_type word_type;

                constexpr static const std::size_t block_bits = policy_type::block_bits;
                constexpr static const std::size_t block_words = policy_type::block_words;
                typedef typename policy_type::block_type block_type;

                constexpr static const std::size_t digest_bits = policy_type::digest_bits;
                typedef typename policy_type::digest_type digest_type;

                struct construction {
                    struct params_type {
                        typedef typename policy_type::digest_endian digest_endian;

                        constexpr static const std::size_t length_bits = policy_type::length_bits;
                        constexpr static const std::size_t digest_bits = policy_type::digest_bits;
                    };

                    typedef merkle_damgard_construction<params_type, typename policy_type::iv_generator,
                                                        davies_meyer_compressor<block_cipher_type, detail::state_adder>,
                                                        detail::merkle_damgard_padding<policy_type>>
                        type;
                };

                template<typename StateAccumulator, std::size_t ValueBits>
                struct stream_processor {
                    struct params_type {
                        typedef typename policy_type::digest_endian digest_endian;

                        constexpr static const std::size_t value_bits = ValueBits;
                    };

                    typedef block_stream_processor<construction, StateAccumulator, params_type> type;
                };
            };
        }    // namespace hashes
    }        // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_HASH_SHA1_HPP


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

#include <smsg/smessage.h>

#include <test/setup_common.h>
#include <net.h>
#ifdef ENABLE_WALLET
#include <wallet/wallet.h>
#endif
#include <xxhash/xxhash.h>

#include <boost/test/unit_test.hpp>

struct SmsgTestingSetup : public TestingSetup {
    SmsgTestingSetup() : TestingSetup(CBaseChainParams::MAIN, true) {}
};

BOOST_FIXTURE_TEST_SUITE(smsg_tests, SmsgTestingSetup)

const std::string sTestMessage = "A short test message 0123456789 !@#$%^&*()_+-=";

BOOST_AUTO_TEST_CASE(smsg_test_xxhash)
{
    XXH32_state_t *state = XXH32_createState();
    XXH32_reset(state, 1);
    XXH32_update(state, sTestMessage.data(), sTestMessage.length());
    uint32_t hash_new = XXH32_digest(state);
    BOOST_CHECK(hash_new == 474992878);
    XXH32_freeState(state);
}

BOOST_AUTO_TEST_CASE(smsg_test_ckeyId_inits_null)
{
    CKeyID k;
    BOOST_CHECK(k.IsNull());
}

#ifdef ENABLE_WALLET

void CheckValid(smsg::SecureMessage &smsg, CKeyID &kFrom, CKeyID &kTo, bool expect_pass)
{
    int rv = 0;
    BOOST_CHECK(0 == smsgModule.Encrypt(smsg, kFrom, kTo, sTestMessage));
    BOOST_CHECK(0 == smsgModule.SetHash((uint8_t*)&smsg, smsg.pPayload, smsg.nPayload));
    if (expect_pass) {
        BOOST_CHECK_MESSAGE(0 == (rv = smsgModule.Validate((uint8_t*)&smsg, smsg.pPayload, smsg.nPayload)), "Validate failed " << rv);
    } else {
        BOOST_CHECK_MESSAGE(0 != (rv = smsgModule.Validate((uint8_t*)&smsg, smsg.pPayload, smsg.nPayload)), "Validate passed " << rv);
    }

    // Reset
    delete[] smsg.pPayload;
    smsg.pPayload = nullptr;
    smsg.nPayload = 0;
}

BOOST_AUTO_TEST_CASE(smsg_test)
{
    SeedInsecureRand();

    int rv = 0;
    const int nKeys = 12;
    auto chain = interfaces::MakeChain();
    std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
    std::vector<CKey> keyOwn(nKeys);
    for (int i = 0; i < nKeys; i++) {
        InsecureNewKey(keyOwn[i], true);
        LOCK(wallet->cs_wallet);
        wallet->AddKey(keyOwn[i]);
    }

    std::vector<CKey> keyRemote(nKeys);
    for (int i = 0; i < nKeys; i++) {
        InsecureNewKey(keyRemote[i], true);
        LOCK(wallet->cs_wallet);
        wallet->AddKey(keyRemote[i]); // need pubkey
    }

    std::vector<std::shared_ptr<CWallet>> temp_vpwallets;
    BOOST_CHECK(true == smsgModule.Start(wallet, temp_vpwallets, false));

    smsg::SecureMessage smsg;
    smsg.m_ttl = 1;
    CKeyID kFrom = keyOwn[0].GetPubKey().GetID();
    CKeyID kTo = keyRemote[0].GetPubKey().GetID();
    CheckValid(smsg, kFrom, kTo, false);
    smsg.m_ttl = smsg::SMSG_MAX_FREE_TTL + 1;
    CheckValid(smsg, kFrom, kTo, false);
    smsg.m_ttl = smsg::SMSG_MAX_FREE_TTL;
    CheckValid(smsg, kFrom, kTo, true);
    smsg.m_ttl = smsg::SMSG_MIN_TTL;
    CheckValid(smsg, kFrom, kTo, true);

    CKeyID idNull;
    for (int i = 0; i < nKeys; i++) {
        smsg::SecureMessage smsg;
        smsg::MessageData msg;
        smsg.m_ttl = 1 * smsg::SMSG_SECONDS_IN_DAY;
        CKeyID kFrom = keyOwn[i].GetPubKey().GetID();
        CKeyID kTo = keyRemote[i].GetPubKey().GetID();
        CKeyID kFail = keyRemote[(i+1) % nKeys].GetPubKey().GetID();
        std::string sAddrFrom = EncodeDestination(PKHash(kFrom));
        std::string sAddrTo = EncodeDestination(PKHash(kTo));
        std::string sAddrFail = EncodeDestination(PKHash(kFail));

        bool fSendAnonymous = rand() % 3 == 0;

        BOOST_CHECK_MESSAGE(0 == (rv = smsgModule.Encrypt(smsg, fSendAnonymous ? idNull : kFrom, kTo, sTestMessage)), "SecureMsgEncrypt " << rv);

        BOOST_CHECK_MESSAGE(0 == (rv = smsgModule.SetHash((uint8_t*)&smsg, smsg.pPayload, smsg.nPayload)), "SecureMsgSetHash " << rv);

        BOOST_CHECK_MESSAGE(0 == (rv = smsgModule.Validate((uint8_t*)&smsg, smsg.pPayload, smsg.nPayload)), "SecureMsgValidate " << rv);

        BOOST_CHECK_MESSAGE(0 == (rv = smsgModule.Decrypt(false, kTo, smsg, msg)), "SecureMsgDecrypt " << rv);

        BOOST_CHECK(msg.vchMessage.size()-1 == sTestMessage.size()
            && 0 == memcmp(&msg.vchMessage[0], sTestMessage.data(), msg.vchMessage.size()-1));

        rv = smsgModule.Decrypt(false, kFail, smsg, msg);
        BOOST_CHECK_MESSAGE(smsg::SMSG_MAC_MISMATCH == rv, "SecureMsgDecrypt " << smsg::GetString(rv));
    }

    smsgModule.Shutdown();
}
#endif

BOOST_AUTO_TEST_SUITE_END()


// copyright (c) 2009-2014 the moorecoin core developers
// distributed under the mit software license, see the accompanying
// file copying or http://www.opensource.org/licenses/mit-license.php.

#include "checkpoints.h"

#include "chainparams.h"
#include "main.h"
#include "uint256.h"

#include <stdint.h>

#include <boost/foreach.hpp>

namespace checkpoints {

    /**
     * how many times slower we expect checking transactions after the last
     * checkpoint to be (from checking signatures, which is skipped up to the
     * last checkpoint). this number is a compromise, as it can't be accurate
     * for every system. when reindexing from a fast disk with a slow cpu, it
     * can be up to 20, while when downloading from a slow network with a
     * fast multicore cpu, it won't be much higher than 1.
     */
    static const double sigcheck_verification_factor = 5.0;

    //! guess how far we are in the verification process at the given block index
    double guessverificationprogress(const ccheckpointdata& data, cblockindex *pindex, bool fsigchecks) {
        if (pindex==null)
            return 0.0;

        int64_t nnow = time(null);

        double fsigcheckverificationfactor = fsigchecks ? sigcheck_verification_factor : 1.0;
        double fworkbefore = 0.0; // amount of work done before pindex
        double fworkafter = 0.0;  // amount of work left after pindex (estimated)
        // work is defined as: 1.0 per transaction before the last checkpoint, and
        // fsigcheckverificationfactor per transaction after.

        if (pindex->nchaintx <= data.ntransactionslastcheckpoint) {
            double ncheapbefore = pindex->nchaintx;
            double ncheapafter = data.ntransactionslastcheckpoint - pindex->nchaintx;
            double nexpensiveafter = (nnow - data.ntimelastcheckpoint)/86400.0*data.ftransactionsperday;
            fworkbefore = ncheapbefore;
            fworkafter = ncheapafter + nexpensiveafter*fsigcheckverificationfactor;
        } else {
            double ncheapbefore = data.ntransactionslastcheckpoint;
            double nexpensivebefore = pindex->nchaintx - data.ntransactionslastcheckpoint;
            double nexpensiveafter = (nnow - pindex->getblocktime())/86400.0*data.ftransactionsperday;
            fworkbefore = ncheapbefore + nexpensivebefore*fsigcheckverificationfactor;
            fworkafter = nexpensiveafter*fsigcheckverificationfactor;
        }

        return fworkbefore / (fworkbefore + fworkafter);
    }

    int gettotalblocksestimate(const ccheckpointdata& data)
    {
        const mapcheckpoints& checkpoints = data.mapcheckpoints;

        if (checkpoints.empty())
            return 0;

        return checkpoints.rbegin()->first;
    }

    cblockindex* getlastcheckpoint(const ccheckpointdata& data)
    {
        const mapcheckpoints& checkpoints = data.mapcheckpoints;

        boost_reverse_foreach(const mapcheckpoints::value_type& i, checkpoints)
        {
            const uint256& hash = i.second;
            blockmap::const_iterator t = mapblockindex.find(hash);
            if (t != mapblockindex.end())
                return t->second;
        }
        return null;
    }

} // namespace checkpoints