#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <math.h>
#include <vector>
#include <numeric>
#include <strings.h>
#include <assert.h>

#include <dirent.h>

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/adapted/c_array.hpp>

#include "mail.h"

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)

typedef boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > Polygon;

using namespace std;

/*=======================================================================
STATIC EVALUATION PARAMETERS
=======================================================================*/

// holds the number of test images on the server
const int32_t N_TESTIMAGES = 7518;

// easy, moderate and hard evaluation level
enum DIFFICULTY{EASY=0, MODERATE=1, HARD=2};

// evaluation metrics: image, ground or 3D
enum METRIC{IMAGE=0, GROUND=1, BOX3D=2};

// evaluation parameter
const int32_t MIN_HEIGHT[3]     = {40, 25, 25};     // minimum height for evaluated groundtruth/detections
const int32_t MAX_OCCLUSION[3]  = {0, 1, 2};        // maximum occlusion level of the groundtruth used for evaluation
const double  MAX_TRUNCATION[3] = {0.15, 0.3, 0.5}; // maximum truncation level of the groundtruth used for evaluation

// evaluated object classes
enum CLASSES{CAR=0, PEDESTRIAN=1, CYCLIST=2};
const int NUM_CLASS = 3;

// parameters varying per class
vector<string> CLASS_NAMES;
// the minimum overlap required for 2D evaluation on the image/ground plane and 3D evaluation
const double MIN_OVERLAP[3][3] = {{0.7, 0.5, 0.5}, {0.5, 0.25, 0.25}, {0.5, 0.25, 0.25}};

// no. of recall steps that should be evaluated (discretized)
const double N_SAMPLE_PTS = 41;

// initialize class names
void initGlobals () {
  CLASS_NAMES.push_back("car");
  CLASS_NAMES.push_back("pedestrian");
  CLASS_NAMES.push_back("cyclist");
}

/*=======================================================================
DATA TYPES FOR EVALUATION
=======================================================================*/

// holding data needed for precision-recall and precision-aos
struct tPrData {
  vector<double> v;           // detection score for computing score thresholds
  double         similarity;  // orientation similarity
  int32_t        tp;          // true positives
  int32_t        fp;          // false positives
  int32_t        fn;          // false negatives
  tPrData () :
    similarity(0), tp(0), fp(0), fn(0) {}
};

// holding bounding boxes for ground truth and detections
struct tBox {
  string  type;     // object type as car, pedestrian or cyclist,...
  double   x1;      // left corner
  double   y1;      // top corner
  double   x2;      // right corner
  double   y2;      // bottom corner
  double   alpha;   // image orientation
  tBox (string type, double x1,double y1,double x2,double y2,double alpha) :
    type(type),x1(x1),y1(y1),x2(x2),y2(y2),alpha(alpha) {}
};

// holding ground truth data
struct tGroundtruth {
  tBox    box;        // object type, box, orientation
  double  truncation; // truncation 0..1
  int32_t occlusion;  // occlusion 0,1,2 (non, partly, fully)
  double ry;
  double  t1, t2, t3;
  double h, w, l;
  tGroundtruth () :
    box(tBox("invalild",-1,-1,-1,-1,-10)),truncation(-1),occlusion(-1) {}
  tGroundtruth (tBox box,double truncation,int32_t occlusion) :
    box(box),truncation(truncation),occlusion(occlusion) {}
  tGroundtruth (string type,double x1,double y1,double x2,double y2,double alpha,double truncation,int32_t occlusion) :
    box(tBox(type,x1,y1,x2,y2,alpha)),truncation(truncation),occlusion(occlusion) {}
};

// holding detection data
struct tDetection {
  tBox    box;    // object type, box, orientation
  double  thresh; // detection score
  double  ry;
  double  t1, t2, t3;
  double  h, w, l;
  tDetection ():
    box(tBox("invalid",-1,-1,-1,-1,-10)),thresh(-1000) {}
  tDetection (tBox box,double thresh) :
    box(box),thresh(thresh) {}
  tDetection (string type,double x1,double y1,double x2,double y2,double alpha,double thresh) :
    box(tBox(type,x1,y1,x2,y2,alpha)),thresh(thresh) {}
};

/*=======================================================================
FUNCTIONS TO LOAD DETECTION AND GROUND TRUTH DATA ONCE, SAVE RESULTS
=======================================================================*/
vector<int32_t> indices;

vector<tDetection> loadDetections(string file_name, bool &compute_aos,
        vector<bool> &eval_image, vector<bool> &eval_ground,
        vector<bool> &eval_3d, bool &success) {

  // holds all detections (ignored detections are indicated by an index vector
  vector<tDetection> detections;
  FILE *fp = fopen(file_name.c_str(),"r");
  if (!fp) {
    success = false;
    return detections;
  }
  while (!feof(fp)) {
    tDetection d;
    double trash;
    char str[255];
    if (fscanf(fp, "%s %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
                   str, &trash, &trash, &d.box.alpha, &d.box.x1, &d.box.y1,
                   &d.box.x2, &d.box.y2, &d.h, &d.w, &d.l, &d.t1, &d.t2, &d.t3,
                   &d.ry, &d.thresh)==16) {

        // d.thresh = 1;
      d.box.type = str;
      detections.push_back(d);

      // orientation=-10 is invalid, AOS is not evaluated if at least one orientation is invalid
      if(d.box.alpha == -10)
        compute_aos = false;

      // a class is only evaluated if it is detected at least once
      for (int c = 0; c < NUM_CLASS; c++) {
        if (!strcasecmp(d.box.type.c_str(), CLASS_NAMES[c].c_str())) {
          if (!eval_image[c] && d.box.x1 >= 0)
            eval_image[c] = true;
          if (!eval_ground[c] && d.t1 != -1000)
            eval_ground[c] = true;
          if (!eval_3d[c] && d.t2 != -1000)
            eval_3d[c] = true;
          break;
        }
      }
    }
  }
  fclose(fp);
  success = true;
  return detections;
}

vector<tGroundtruth> loadGroundtruth(string file_name,bool &success) {

  // holds all ground truth (ignored ground truth is indicated by an index vector
  vector<tGroundtruth> groundtruth;
  FILE *fp = fopen(file_name.c_str(),"r");
  if (!fp) {
    success = false;
    return groundtruth;
  }
  while (!feof(fp)) {
    tGroundtruth g;
    char str[255];
    if (fscanf(fp, "%s %lf %d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
                   str, &g.truncation, &g.occlusion, &g.box.alpha,
                   &g.box.x1,   &g.box.y1,     &g.box.x2,    &g.box.y2,
                   &g.h,      &g.w,        &g.l,       &g.t1,
                   &g.t2,      &g.t3,        &g.ry )==15) {
      g.box.type = str;
      groundtruth.push_back(g);
    }
  }
  fclose(fp);
  success = true;
  return groundtruth;
}

void saveStats (const vector<double> &precision, const vector<double> &aos, FILE *fp_det, FILE *fp_ori) {

  // save precision to file
  if(precision.empty())
    return;
  for (int32_t i=0; i<precision.size(); i++)
    fprintf(fp_det,"%f ",precision[i]);
  fprintf(fp_det,"\n");

  // save orientation similarity, only if there were no invalid orientation entries in submission (alpha=-10)
  if(aos.empty())
    return;
  for (int32_t i=0; i<aos.size(); i++)
    fprintf(fp_ori,"%f ",aos[i]);
  fprintf(fp_ori,"\n");
}

/*=======================================================================
EVALUATION HELPER FUNCTIONS
=======================================================================*/

// criterion defines whether the overlap is computed with respect to both areas (ground truth and detection)
// or with respect to box a or b (detection and "dontcare" areas)
inline double imageBoxOverlap(tBox a, tBox b, int32_t criterion=-1){

  // overlap is invalid in the beginning
  double o = -1;

  // get overlapping area
  double x1 = max(a.x1, b.x1);
  double y1 = max(a.y1, b.y1);
  double x2 = min(a.x2, b.x2);
  double y2 = min(a.y2, b.y2);

  // compute width and height of overlapping area
  double w = x2-x1;
  double h = y2-y1;

  // set invalid entries to 0 overlap
  if(w<=0 || h<=0)
    return 0;

  // get overlapping areas
  double inter = w*h;
  double a_area = (a.x2-a.x1) * (a.y2-a.y1);
  double b_area = (b.x2-b.x1) * (b.y2-b.y1);

  // intersection over union overlap depending on users choice
  if(criterion==-1)     // union
    o = inter / (a_area+b_area-inter);
  else if(criterion==0) // bbox_a
    o = inter / a_area;
  else if(criterion==1) // bbox_b
    o = inter / b_area;

  // overlap
  return o;
}

inline double imageBoxOverlap(tDetection a, tGroundtruth b, int32_t criterion=-1){
  return imageBoxOverlap(a.box, b.box, criterion);
}

// compute polygon of an oriented bounding box
template <typename T>
Polygon toPolygon(const T& g) {
    using namespace boost::numeric::ublas;
    using namespace boost::geometry;
    matrix<double> mref(2, 2);
    mref(0, 0) = cos(g.ry); mref(0, 1) = sin(g.ry);
    mref(1, 0) = -sin(g.ry); mref(1, 1) = cos(g.ry);

    static int count = 0;
    matrix<double> corners(2, 4);
    double data[] = {g.l / 2, g.l / 2, -g.l / 2, -g.l / 2,
                     g.w / 2, -g.w / 2, -g.w / 2, g.w / 2};
    std::copy(data, data + 8, corners.data().begin());
    matrix<double> gc = prod(mref, corners);
    for (int i = 0; i < 4; ++i) {
        gc(0, i) += g.t1;
        gc(1, i) += g.t3;
    }

    double points[][2] = {{gc(0, 0), gc(1, 0)},{gc(0, 1), gc(1, 1)},{gc(0, 2), gc(1, 2)},{gc(0, 3), gc(1, 3)},{gc(0, 0), gc(1, 0)}};
    Polygon poly;
    append(poly, points);
    return poly;
}

// measure overlap between bird's eye view bounding boxes, parametrized by (ry, l, w, tx, tz)
inline double groundBoxOverlap(tDetection d, tGroundtruth g, int32_t criterion = -1) {
    using namespace boost::geometry;
    Polygon gp = toPolygon(g);
    Polygon dp = toPolygon(d);

    std::vector<Polygon> in, un;
    intersection(gp, dp, in);
    union_(gp, dp, un);

    double inter_area = in.empty() ? 0 : area(in.front());
    double union_area = area(un.front());
    double o;
    if(criterion==-1)     // union
        o = inter_area / union_area;
    else if(criterion==0) // bbox_a
        o = inter_area / area(dp);
    else if(criterion==1) // bbox_b
        o = inter_area / area(gp);

    return o;
}

// measure overlap between 3D bounding boxes, parametrized by (ry, h, w, l, tx, ty, tz)
inline double box3DOverlap(tDetection d, tGroundtruth g, int32_t criterion = -1) {
    using namespace boost::geometry;
    Polygon gp = toPolygon(g);
    Polygon dp = toPolygon(d);

    std::vector<Polygon> in, un;
    intersection(gp, dp, in);
    union_(gp, dp, un);

    double ymax = min(d.t2, g.t2);
    double ymin = max(d.t2 - d.h, g.t2 - g.h);

    double inter_area = in.empty() ? 0 : area(in.front());
    double inter_vol = inter_area * max(0.0, ymax - ymin);

    double det_vol = d.h * d.l * d.w;
    double gt_vol = g.h * g.l * g.w;

    double o;
    if(criterion==-1)     // union
        o = inter_vol / (det_vol + gt_vol - inter_vol);
    else if(criterion==0) // bbox_a
        o = inter_vol / det_vol;
    else if(criterion==1) // bbox_b
        o = inter_vol / gt_vol;

    return o;
}

vector<double> getThresholds(vector<double> &v, double n_groundtruth){

  // holds scores needed to compute N_SAMPLE_PTS recall values
  vector<double> t;

  // sort scores in descending order
  // (highest score is assumed to give best/most confident detections)
  sort(v.begin(), v.end(), greater<double>());

  // get scores for linearly spaced recall
  double current_recall = 0;
  for(int32_t i=0; i<v.size(); i++){

    // check if right-hand-side recall with respect to current recall is close than left-hand-side one
    // in this case, skip the current detection score
    double l_recall, r_recall, recall;
    l_recall = (double)(i+1)/n_groundtruth;
    if(i<(v.size()-1))
      r_recall = (double)(i+2)/n_groundtruth;
    else
      r_recall = l_recall;

    if( (r_recall-current_recall) < (current_recall-l_recall) && i<(v.size()-1))
      continue;

    // left recall is the best approximation, so use this and goto next recall step for approximation
    recall = l_recall;

    // the next recall step was reached
    t.push_back(v[i]);
    current_recall += 1.0/(N_SAMPLE_PTS-1.0);
  }
  return t;
}

void cleanData(CLASSES current_class, const vector<tGroundtruth> &gt, const vector<tDetection> &det, vector<int32_t> &ignored_gt, vector<tGroundtruth> &dc, vector<int32_t> &ignored_det, int32_t &n_gt, DIFFICULTY difficulty){

  // extract ground truth bounding boxes for current evaluation class
  for(int32_t i=0;i<gt.size(); i++){

    // only bounding boxes with a minimum height are used for evaluation
    double height = gt[i].box.y2 - gt[i].box.y1;

    // neighboring classes are ignored ("van" for "car" and "person_sitting" for "pedestrian")
    // (lower/upper cases are ignored)
    int32_t valid_class;

    // all classes without a neighboring class
    if(!strcasecmp(gt[i].box.type.c_str(), CLASS_NAMES[current_class].c_str()))
      valid_class = 1;

    // classes with a neighboring class
    else if(!strcasecmp(CLASS_NAMES[current_class].c_str(), "Pedestrian") && !strcasecmp("Person_sitting", gt[i].box.type.c_str()))
      valid_class = 0;
    else if(!strcasecmp(CLASS_NAMES[current_class].c_str(), "Car") && !strcasecmp("Van", gt[i].box.type.c_str()))
      valid_class = 0;

    // classes not used for evaluation
    else
      valid_class = -1;

    // ground truth is ignored, if occlusion, truncation exceeds the difficulty or ground truth is too small
    // (doesn't count as FN nor TP, although detections may be assigned)
    bool ignore = false;
    if(gt[i].occlusion>MAX_OCCLUSION[difficulty] || gt[i].truncation>MAX_TRUNCATION[difficulty] || height<MIN_HEIGHT[difficulty])
      ignore = true;

    // set ignored vector for ground truth
    // current class and not ignored (total no. of ground truth is detected for recall denominator)
    if(valid_class==1 && !ignore){
      ignored_gt.push_back(0);
      n_gt++;
    }

    // neighboring class, or current class but ignored
    else if(valid_class==0 || (ignore && valid_class==1))
      ignored_gt.push_back(1);

    // all other classes which are FN in the evaluation
    else
      ignored_gt.push_back(-1);
  }

  // extract dontcare areas
  for(int32_t i=0;i<gt.size(); i++)
    if(!strcasecmp("DontCare", gt[i].box.type.c_str()))
      dc.push_back(gt[i]);

  // extract detections bounding boxes of the current class
  for(int32_t i=0;i<det.size(); i++){

    // neighboring classes are not evaluated
    int32_t valid_class;
    if(!strcasecmp(det[i].box.type.c_str(), CLASS_NAMES[current_class].c_str()))
      valid_class = 1;
    else
      valid_class = -1;

    int32_t height = fabs(det[i].box.y1 - det[i].box.y2);
    // set ignored vector for detections
    if(height<MIN_HEIGHT[difficulty])
      ignored_det.push_back(1);
    else if(valid_class==1)
      ignored_det.push_back(0);
    else
      ignored_det.push_back(-1);
  }
}

tPrData computeStatistics(CLASSES current_class, const vector<tGroundtruth> &gt,
        const vector<tDetection> &det, const vector<tGroundtruth> &dc,
        const vector<int32_t> &ignored_gt, const vector<int32_t>  &ignored_det,
        bool compute_fp, double (*boxoverlap)(tDetection, tGroundtruth, int32_t),
        METRIC metric, bool compute_aos=false, double thresh=0, bool debug=false){

  tPrData stat = tPrData();
  const double NO_DETECTION = -10000000;
  vector<double> delta;            // holds angular difference for TPs (needed for AOS evaluation)
  vector<bool> assigned_detection; // holds wether a detection was assigned to a valid or ignored ground truth
  assigned_detection.assign(det.size(), false);
  vector<bool> ignored_threshold;
  ignored_threshold.assign(det.size(), false); // holds detections with a threshold lower than thresh if FP are computed

  // detections with a low score are ignored for computing precision (needs FP)
  if(compute_fp)
    for(int32_t i=0; i<det.size(); i++)
      if(det[i].thresh<thresh)
        ignored_threshold[i] = true;

  // evaluate all ground truth boxes
  for(int32_t i=0; i<gt.size(); i++){

    // this ground truth is not of the current or a neighboring class and therefore ignored
    if(ignored_gt[i]==-1)
      continue;

    /*=======================================================================
    find candidates (overlap with ground truth > 0.5) (logical len(det))
    =======================================================================*/
    int32_t det_idx          = -1;
    double valid_detection = NO_DETECTION;
    double max_overlap     = 0;

    // search for a possible detection
    bool assigned_ignored_det = false;
    for(int32_t j=0; j<det.size(); j++){

      // detections not of the current class, already assigned or with a low threshold are ignored
      if(ignored_det[j]==-1)
        continue;
      if(assigned_detection[j])
        continue;
      if(ignored_threshold[j])
        continue;

      // find the maximum score for the candidates and get idx of respective detection
      double overlap = boxoverlap(det[j], gt[i], -1);

      // for computing recall thresholds, the candidate with highest score is considered
      if(!compute_fp && overlap>MIN_OVERLAP[metric][current_class] && det[j].thresh>valid_detection){
        det_idx         = j;
        valid_detection = det[j].thresh;
      }

      // for computing pr curve values, the candidate with the greatest overlap is considered
      // if the greatest overlap is an ignored detection (min_height), the overlapping detection is used
      else if(compute_fp && overlap>MIN_OVERLAP[metric][current_class] && (overlap>max_overlap || assigned_ignored_det) && ignored_det[j]==0){
        max_overlap     = overlap;
        det_idx         = j;
        valid_detection = 1;
        assigned_ignored_det = false;
      }
      else if(compute_fp && overlap>MIN_OVERLAP[metric][current_class] && valid_detection==NO_DETECTION && ignored_det[j]==1){
        det_idx              = j;
        valid_detection      = 1;
        assigned_ignored_det = true;
      }
    }

    /*=======================================================================
    compute TP, FP and FN
    =======================================================================*/

    // nothing was assigned to this valid ground truth
    if(valid_detection==NO_DETECTION && ignored_gt[i]==0) {
      stat.fn++;
    }

    // only evaluate valid ground truth <=> detection assignments (considering difficulty level)
    else if(valid_detection!=NO_DETECTION && (ignored_gt[i]==1 || ignored_det[det_idx]==1))
      assigned_detection[det_idx] = true;

    // found a valid true positive
    else if(valid_detection!=NO_DETECTION){

      // write highest score to threshold vector
      stat.tp++;
      stat.v.push_back(det[det_idx].thresh);

      // compute angular difference of detection and ground truth if valid detection orientation was provided
      if(compute_aos)
        delta.push_back(gt[i].box.alpha - det[det_idx].box.alpha);

      // clean up
      assigned_detection[det_idx] = true;
    }
  }

  // if FP are requested, consider stuff area
  if(compute_fp){

    // count fp
    for(int32_t i=0; i<det.size(); i++){

      // count false positives if required (height smaller than required is ignored (ignored_det==1)
      if(!(assigned_detection[i] || ignored_det[i]==-1 || ignored_det[i]==1 || ignored_threshold[i]))
        stat.fp++;
    }

    // do not consider detections overlapping with stuff area
    int32_t nstuff = 0;
    for(int32_t i=0; i<dc.size(); i++){
      for(int32_t j=0; j<det.size(); j++){

        // detections not of the current class, already assigned, with a low threshold or a low minimum height are ignored
        if(assigned_detection[j])
          continue;
        if(ignored_det[j]==-1 || ignored_det[j]==1)
          continue;
        if(ignored_threshold[j])
          continue;

        // compute overlap and assign to stuff area, if overlap exceeds class specific value
        double overlap = boxoverlap(det[j], dc[i], 0);
        if(overlap>MIN_OVERLAP[metric][current_class]){
          assigned_detection[j] = true;
          nstuff++;
        }
      }
    }

    // FP = no. of all not to ground truth assigned detections - detections assigned to stuff areas
    stat.fp -= nstuff;

    // if all orientation values are valid, the AOS is computed
    if(compute_aos){
      vector<double> tmp;

      // FP have a similarity of 0, for all TP compute AOS
      tmp.assign(stat.fp, 0);
      for(int32_t i=0; i<delta.size(); i++)
        tmp.push_back((1.0+cos(delta[i]))/2.0);

      // be sure, that all orientation deltas are computed
      assert(tmp.size()==stat.fp+stat.tp);
      assert(delta.size()==stat.tp);

      // get the mean orientation similarity for this image
      if(stat.tp>0 || stat.fp>0)
        stat.similarity = accumulate(tmp.begin(), tmp.end(), 0.0);

      // there was neither a FP nor a TP, so the similarity is ignored in the evaluation
      else
        stat.similarity = -1;
    }
  }
  return stat;
}

/*=======================================================================
EVALUATE CLASS-WISE
=======================================================================*/

bool eval_class (FILE *fp_det, FILE *fp_ori, CLASSES current_class,
        const vector< vector<tGroundtruth> > &groundtruth,
        const vector< vector<tDetection> > &detections, bool compute_aos,
        double (*boxoverlap)(tDetection, tGroundtruth, int32_t),
        vector<double> &precision, vector<double> &aos,
        DIFFICULTY difficulty, METRIC metric) {
    assert(groundtruth.size() == detections.size());

  // init
  int32_t n_gt=0;                                     // total no. of gt (denominator of recall)
  vector<double> v, thresholds;                       // detection scores, evaluated for recall discretization
  vector< vector<int32_t> > ignored_gt, ignored_det;  // index of ignored gt detection for current class/difficulty
  vector< vector<tGroundtruth> > dontcare;            // index of dontcare areas, included in ground truth

  // for all test images do
  for (int32_t i=0; i<groundtruth.size(); i++){

    // holds ignored ground truth, ignored detections and dontcare areas for current frame
    vector<int32_t> i_gt, i_det;
    vector<tGroundtruth> dc;

    // only evaluate objects of current class and ignore occluded, truncated objects
    cleanData(current_class, groundtruth[i], detections[i], i_gt, dc, i_det, n_gt, difficulty);
    ignored_gt.push_back(i_gt);
    ignored_det.push_back(i_det);
    dontcare.push_back(dc);

    // compute statistics to get recall values
    tPrData pr_tmp = tPrData();
    pr_tmp = computeStatistics(current_class, groundtruth[i], detections[i], dc, i_gt, i_det, false, boxoverlap, metric);

    // add detection scores to vector over all images
    for(int32_t j=0; j<pr_tmp.v.size(); j++)
      v.push_back(pr_tmp.v[j]);
  }

  // get scores that must be evaluated for recall discretization
  thresholds = getThresholds(v, n_gt);

  // compute TP,FP,FN for relevant scores
  vector<tPrData> pr;
  pr.assign(thresholds.size(),tPrData());
  for (int32_t i=0; i<groundtruth.size(); i++){

    // for all scores/recall thresholds do:
    for(int32_t t=0; t<thresholds.size(); t++){
      tPrData tmp = tPrData();
      tmp = computeStatistics(current_class, groundtruth[i], detections[i], dontcare[i],
                              ignored_gt[i], ignored_det[i], true, boxoverlap, metric,
                              compute_aos, thresholds[t], t==38);

      // add no. of TP, FP, FN, AOS for current frame to total evaluation for current threshold
      pr[t].tp += tmp.tp;
      pr[t].fp += tmp.fp;
      pr[t].fn += tmp.fn;
      if(tmp.similarity!=-1)
        pr[t].similarity += tmp.similarity;
    }
  }

  // compute recall, precision and AOS
  vector<double> recall;
  precision.assign(N_SAMPLE_PTS, 0);
  if(compute_aos)
    aos.assign(N_SAMPLE_PTS, 0);
  double r=0;
  for (int32_t i=0; i<thresholds.size(); i++){
    r = pr[i].tp/(double)(pr[i].tp + pr[i].fn);
    recall.push_back(r);
    precision[i] = pr[i].tp/(double)(pr[i].tp + pr[i].fp);
    if(compute_aos)
      aos[i] = pr[i].similarity/(double)(pr[i].tp + pr[i].fp);
  }

  // filter precision and AOS using max_{i..end}(precision)
  for (int32_t i=0; i<thresholds.size(); i++){
    precision[i] = *max_element(precision.begin()+i, precision.end());
    if(compute_aos)
      aos[i] = *max_element(aos.begin()+i, aos.end());
  }

  // save statisics and finish with success
  saveStats(precision, aos, fp_det, fp_ori);
    return true;
}

void saveAndPlotPlots(string dir_name,string file_name,string obj_type,vector<double> vals[],bool is_aos){

  char command[1024];

  // save plot data to file
  FILE *fp = fopen((dir_name + "/" + file_name + ".txt").c_str(),"w");
  printf("save %s\n", (dir_name + "/" + file_name + ".txt").c_str());
  for (int32_t i=0; i<(int)N_SAMPLE_PTS; i++)
    fprintf(fp,"%f %f %f %f\n",(double)i/(N_SAMPLE_PTS-1.0),vals[0][i],vals[1][i],vals[2][i]);
  fclose(fp);

  // create png + eps
  for (int32_t j=0; j<2; j++) {

    // open file
    FILE *fp = fopen((dir_name + "/" + file_name + ".gp").c_str(),"w");

    // save gnuplot instructions
    if (j==0) {
      fprintf(fp,"set term png size 450,315 font \"Helvetica\" 11\n");
      fprintf(fp,"set output \"%s.png\"\n",file_name.c_str());
    } else {
      fprintf(fp,"set term postscript eps enhanced color font \"Helvetica\" 20\n");
      fprintf(fp,"set output \"%s.eps\"\n",file_name.c_str());
    }

    // set labels and ranges
    fprintf(fp,"set size ratio 0.7\n");
    fprintf(fp,"set xrange [0:1]\n");
    fprintf(fp,"set yrange [0:1]\n");
    fprintf(fp,"set xlabel \"Recall\"\n");
    if (!is_aos) fprintf(fp,"set ylabel \"Precision\"\n");
    else         fprintf(fp,"set ylabel \"Orientation Similarity\"\n");
    obj_type[0] = toupper(obj_type[0]);
    fprintf(fp,"set title \"%s\"\n",obj_type.c_str());

    // line width
    int32_t   lw = 5;
    if (j==0) lw = 3;

    // plot error curve
    fprintf(fp,"plot ");
    fprintf(fp,"\"%s.txt\" using 1:2 title 'Easy' with lines ls 1 lw %d,",file_name.c_str(),lw);
    fprintf(fp,"\"%s.txt\" using 1:3 title 'Moderate' with lines ls 2 lw %d,",file_name.c_str(),lw);
    fprintf(fp,"\"%s.txt\" using 1:4 title 'Hard' with lines ls 3 lw %d",file_name.c_str(),lw);

    // close file
    fclose(fp);

    // run gnuplot => create png + eps
    sprintf(command,"cd %s; gnuplot %s",dir_name.c_str(),(file_name + ".gp").c_str());
    system(command);
  }

  // create pdf and crop
  sprintf(command,"cd %s; ps2pdf %s.eps %s_large.pdf",dir_name.c_str(),file_name.c_str(),file_name.c_str());
  system(command);
  sprintf(command,"cd %s; pdfcrop %s_large.pdf %s.pdf",dir_name.c_str(),file_name.c_str(),file_name.c_str());
  system(command);
  sprintf(command,"cd %s; rm %s_large.pdf",dir_name.c_str(),file_name.c_str());
  system(command);
}

bool eval(string result_sha,Mail* mail){

  // set some global parameters
  initGlobals();

  // ground truth and result directories
  string gt_dir         = "data/object/label_2";
  string result_dir     = "results/" + result_sha;
  string plot_dir       = result_dir + "/plot";

  // create output directories
  system(("mkdir " + plot_dir).c_str());

  // hold detections and ground truth in memory
  vector< vector<tGroundtruth> > groundtruth;
  vector< vector<tDetection> >   detections;

  // holds wether orientation similarity shall be computed (might be set to false while loading detections)
  // and which labels where provided by this submission
  bool compute_aos=true;
  vector<bool> eval_image(NUM_CLASS, false);
  vector<bool> eval_ground(NUM_CLASS, false);
  vector<bool> eval_3d(NUM_CLASS, false);

  // for all images read groundtruth and detections
  mail->msg("Loading detections...");
  for (int32_t i=0; i<N_TESTIMAGES; i++) {

    // file name
    char file_name[256];
    sprintf(file_name,"%06d.txt",indices.at(i));

    // read ground truth and result poses
    bool gt_success,det_success;
    vector<tGroundtruth> gt   = loadGroundtruth(gt_dir + "/" + file_name,gt_success);
    vector<tDetection>   det  = loadDetections(result_dir + "/data/" + file_name,
            compute_aos, eval_image, eval_ground, eval_3d, det_success);
    groundtruth.push_back(gt);
    detections.push_back(det);

    // check for errors
    if (!gt_success) {
      mail->msg("ERROR: Couldn't read: %s of ground truth. Please write me an email!", file_name);
      return false;
    }
    if (!det_success) {
      mail->msg("ERROR: Couldn't read: %s", file_name);
      return false;
    }
  }
  mail->msg("  done.");

  // holds pointers for result files
  FILE *fp_det=0, *fp_ori=0;

  // eval image 2D bounding boxes
  for (int c = 0; c < NUM_CLASS; c++) {
    CLASSES cls = (CLASSES)c;
    if (eval_image[c]) {
      fp_det = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection.txt").c_str(), "w");
      if(compute_aos)
        fp_ori = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_orientation.txt").c_str(),"w");
      vector<double> precision[3], aos[3];
      if(   !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap, precision[0], aos[0], EASY, IMAGE)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap, precision[1], aos[1], MODERATE, IMAGE)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap, precision[2], aos[2], HARD, IMAGE)) {
        mail->msg("%s evaluation failed.", CLASS_NAMES[c].c_str());
        return false;
      }
      fclose(fp_det);
      saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection", CLASS_NAMES[c], precision, 0);
      if(compute_aos){
        saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_orientation", CLASS_NAMES[c], aos, 1);
        fclose(fp_ori);
      }
    }
  }

  // don't evaluate AOS for birdview boxes and 3D boxes
  compute_aos = false;

  // eval bird's eye view bounding boxes
  for (int c = 0; c < NUM_CLASS; c++) {
    CLASSES cls = (CLASSES)c;
    if (eval_ground[c]) {
      fp_det = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection_ground.txt").c_str(), "w");
      vector<double> precision[3], aos[3];
      if(   !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap, precision[0], aos[0], EASY, GROUND)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap, precision[1], aos[1], MODERATE, GROUND)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap, precision[2], aos[2], HARD, GROUND)) {
        mail->msg("%s evaluation failed.", CLASS_NAMES[c].c_str());
        return false;
      }
      fclose(fp_det);
      saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection_ground", CLASS_NAMES[c], precision, 0);
    }
  }

  // eval 3D bounding boxes
  for (int c = 0; c < NUM_CLASS; c++) {
    CLASSES cls = (CLASSES)c;
    if (eval_3d[c]) {
      fp_det = fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection_3d.txt").c_str(), "w");
      vector<double> precision[3], aos[3];
      if(   !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap, precision[0], aos[0], EASY, BOX3D)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap, precision[1], aos[1], MODERATE, BOX3D)
         || !eval_class(fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap, precision[2], aos[2], HARD, BOX3D)) {
        mail->msg("%s evaluation failed.", CLASS_NAMES[c].c_str());
        return false;
      }
      fclose(fp_det);
      saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection_3d", CLASS_NAMES[c], precision, 0);
    }
  }

  // success
  return true;
}

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

  // we need 2 or 4 arguments!
  if (argc!=2 && argc!=4) {
    cout << "Usage: ./eval_detection result_sha [user_sha email]" << endl;
    return 1;
  }

  // read arguments
  string result_sha = argv[1];

  // init notification mail
  Mail *mail;
  if (argc==4) mail = new Mail(argv[3]);
  else         mail = new Mail();
  mail->msg("Thank you for participating in our evaluation!");

  // run evaluation
  if (eval(result_sha,mail)) {
    mail->msg("Your evaluation results are available at:");
    mail->msg("http://www.cvlibs.net/datasets/kitti/user_submit_check_login.php?benchmark=object&user=%s&result=%s",argv[2], result_sha.c_str());
  } else {
    system(("rm -r results/" + result_sha).c_str());
    mail->msg("An error occured while processing your results.");
    mail->msg("Please make sure that the data in your zip archive has the right format!");
  }

  // send mail and exit
  delete mail;

  return 0;
}


#ifndef __wrapper_MSSMNoFV_onshell_def_gm2calc_1_2_0_hpp__
#define __wrapper_MSSMNoFV_onshell_def_gm2calc_1_2_0_hpp__

#include <Eigen/Core>
#include "wrapper_MSSMNoFV_onshell_mass_eigenstates_decl.hpp"

#include "identification.hpp"

namespace CAT_3(BACKENDNAME,_,SAFE_VERSION)
{

   namespace gm2calc
   {

      // Member functions:
      inline void MSSMNoFV_onshell::set_verbose_output(bool flag)
      {
         get_BEptr()->set_verbose_output(flag);
      }

      inline bool MSSMNoFV_onshell::do_verbose_output() const
      {
         return get_BEptr()->do_verbose_output();
      }

      inline void MSSMNoFV_onshell::set_alpha_MZ(double arg_1)
      {
         get_BEptr()->set_alpha_MZ(arg_1);
      }

      inline void MSSMNoFV_onshell::set_alpha_thompson(double arg_1)
      {
         get_BEptr()->set_alpha_thompson(arg_1);
      }

      inline void MSSMNoFV_onshell::set_Ae(const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& A)
      {
         get_BEptr()->set_Ae(A);
      }

      inline void MSSMNoFV_onshell::set_Au(const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& A)
      {
         get_BEptr()->set_Au(A);
      }

      inline void MSSMNoFV_onshell::set_Ad(const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& A)
      {
         get_BEptr()->set_Ad(A);
      }

      inline void MSSMNoFV_onshell::set_Ae(unsigned int i, unsigned int k, double a)
      {
         get_BEptr()->set_Ae(i, k, a);
      }

      inline void MSSMNoFV_onshell::set_Au(unsigned int i, unsigned int k, double a)
      {
         get_BEptr()->set_Au(i, k, a);
      }

      inline void MSSMNoFV_onshell::set_Ad(unsigned int i, unsigned int k, double a)
      {
         get_BEptr()->set_Ad(i, k, a);
      }

      inline void MSSMNoFV_onshell::set_MA0(double m)
      {
         get_BEptr()->set_MA0(m);
      }

      inline void MSSMNoFV_onshell::set_TB(double arg_1)
      {
         get_BEptr()->set_TB(arg_1);
      }

      inline double MSSMNoFV_onshell::get_EL() const
      {
         return get_BEptr()->get_EL();
      }

      inline double MSSMNoFV_onshell::get_EL0() const
      {
         return get_BEptr()->get_EL0();
      }

      inline double MSSMNoFV_onshell::get_gY() const
      {
         return get_BEptr()->get_gY();
      }

      inline double MSSMNoFV_onshell::get_MUDIM() const
      {
         return get_BEptr()->get_MUDIM();
      }

      inline double MSSMNoFV_onshell::get_TB() const
      {
         return get_BEptr()->get_TB();
      }

      inline double MSSMNoFV_onshell::get_vev() const
      {
         return get_BEptr()->get_vev();
      }

      inline const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& MSSMNoFV_onshell::get_Ae() const
      {
         return get_BEptr()->get_Ae();
      }

      inline const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& MSSMNoFV_onshell::get_Au() const
      {
         return get_BEptr()->get_Au();
      }

      inline const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& MSSMNoFV_onshell::get_Ad() const
      {
         return get_BEptr()->get_Ad();
      }

      inline double MSSMNoFV_onshell::get_Ae(unsigned int i, unsigned int k) const
      {
         return get_BEptr()->get_Ae(i, k);
      }

      inline double MSSMNoFV_onshell::get_Au(unsigned int i, unsigned int k) const
      {
         return get_BEptr()->get_Au(i, k);
      }

      inline double MSSMNoFV_onshell::get_Ad(unsigned int i, unsigned int k) const
      {
         return get_BEptr()->get_Ad(i, k);
      }

      inline double MSSMNoFV_onshell::get_MW() const
      {
         return get_BEptr()->get_MW();
      }

      inline double MSSMNoFV_onshell::get_MZ() const
      {
         return get_BEptr()->get_MZ();
      }

      inline double MSSMNoFV_onshell::get_ME() const
      {
         return get_BEptr()->get_ME();
      }

      inline double MSSMNoFV_onshell::get_MM() const
      {
         return get_BEptr()->get_MM();
      }

      inline double MSSMNoFV_onshell::get_ML() const
      {
         return get_BEptr()->get_ML();
      }

      inline double MSSMNoFV_onshell::get_MU() const
      {
         return get_BEptr()->get_MU();
      }

      inline double MSSMNoFV_onshell::get_MC() const
      {
         return get_BEptr()->get_MC();
      }

      inline double MSSMNoFV_onshell::get_MT() const
      {
         return get_BEptr()->get_MT();
      }

      inline double MSSMNoFV_onshell::get_MD() const
      {
         return get_BEptr()->get_MD();
      }

      inline double MSSMNoFV_onshell::get_MS() const
      {
         return get_BEptr()->get_MS();
      }

      inline double MSSMNoFV_onshell::get_MBMB() const
      {
         return get_BEptr()->get_MBMB();
      }

      inline double MSSMNoFV_onshell::get_MB() const
      {
         return get_BEptr()->get_MB();
      }

      inline double MSSMNoFV_onshell::get_MA0() const
      {
         return get_BEptr()->get_MA0();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USe() const
      {
         return get_BEptr()->get_USe();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USm() const
      {
         return get_BEptr()->get_USm();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_UStau() const
      {
         return get_BEptr()->get_UStau();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USu() const
      {
         return get_BEptr()->get_USu();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USd() const
      {
         return get_BEptr()->get_USd();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USc() const
      {
         return get_BEptr()->get_USc();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USs() const
      {
         return get_BEptr()->get_USs();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USb() const
      {
         return get_BEptr()->get_USb();
      }

      inline const ::Eigen::Matrix<double, 2, 2, 0, 2, 2>& MSSMNoFV_onshell::get_USt() const
      {
         return get_BEptr()->get_USt();
      }

      inline void MSSMNoFV_onshell::convert_to_onshell(double precision, unsigned int max_iterations)
      {
         get_BEptr()->convert_to_onshell(precision, max_iterations);
      }

      inline void MSSMNoFV_onshell::convert_to_onshell(double precision)
      {
         get_BEptr()->convert_to_onshell__BOSS(precision);
      }

      inline void MSSMNoFV_onshell::convert_to_onshell()
      {
         get_BEptr()->convert_to_onshell__BOSS();
      }

      inline void MSSMNoFV_onshell::calculate_masses()
      {
         get_BEptr()->calculate_masses();
      }

      inline void MSSMNoFV_onshell::check_problems() const
      {
         get_BEptr()->check_problems();
      }

      inline void MSSMNoFV_onshell::convert_yukawa_couplings_treelevel()
      {
         get_BEptr()->convert_yukawa_couplings_treelevel();
      }

      // Wrappers for original constructors:
      inline MSSMNoFV_onshell::MSSMNoFV_onshell() :
         MSSMNoFV_onshell_mass_eigenstates(__factory0())
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      inline MSSMNoFV_onshell::MSSMNoFV_onshell(const gm2calc::MSSMNoFV_onshell_mass_eigenstates& arg_1) :
         MSSMNoFV_onshell_mass_eigenstates(__factory1(arg_1))
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      // Special pointer-based constructor:
      inline MSSMNoFV_onshell::MSSMNoFV_onshell(Abstract_MSSMNoFV_onshell* in) :
         MSSMNoFV_onshell_mass_eigenstates(in)
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      // Copy constructor:
      inline MSSMNoFV_onshell::MSSMNoFV_onshell(const MSSMNoFV_onshell& in) :
         MSSMNoFV_onshell_mass_eigenstates(in.get_BEptr()->pointer_copy__BOSS())
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      // Assignment operator:
      inline MSSMNoFV_onshell& MSSMNoFV_onshell::operator=(const MSSMNoFV_onshell& in)
      {
         if (this != &in)
         {
            get_BEptr()->pointer_assign__BOSS(in.get_BEptr());
         }
         return *this;
      }

      // Destructor:
      inline MSSMNoFV_onshell::~MSSMNoFV_onshell()
      {
         if (get_BEptr() != 0)
         {
            get_BEptr()->set_delete_wrapper(false);
            if (can_delete_BEptr())
            {
               delete BEptr;
               BEptr = 0;
            }
         }
         set_delete_BEptr(false);
      }

      // Returns correctly casted pointer to Abstract class:
      inline Abstract_MSSMNoFV_onshell* gm2calc::MSSMNoFV_onshell::get_BEptr() const
      {
         return dynamic_cast<Abstract_MSSMNoFV_onshell*>(BEptr);
      }
   }

}

#include "gambit/Backends/backend_undefs.hpp"

#endif /* __wrapper_MSSMNoFV_onshell_def_gm2calc_1_2_0_hpp__ */


/***************************************************************************
 * Copyright 1998-2018 by authors (see AUTHORS.txt)                        *
 *                                                                         *
 *   This file is part of LuxCoreRender.                                   *
 *                                                                         *
 * 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 <boost/format.hpp>

#include "slg/engines/bidirvmcpu/bidirvmcpu.h"

using namespace std;
using namespace luxrays;
using namespace slg;

void HashGrid::Build(vector<vector<PathVertexVM> > &pathsVertices, const float radius) {
	// Reset statistic counters
	//mergeHitsV2V = 0;
	//mergeHitsV2S = 0;
	//mergeHitsS2S = 0;

	radius2 = radius * radius;

	// Build the vertices bounding box
	vertexCount = 0;
	vertexBBox = BBox();
	for (u_int i = 0; i < pathsVertices.size(); ++i) {
		vertexCount += pathsVertices[i].size();

		for (u_int j = 0; j < pathsVertices[i].size(); ++j)
			vertexBBox = Union(vertexBBox, pathsVertices[i][j].bsdf.hitPoint.p);
	}

	if (vertexCount <= 0)
		return;

	vertexBBox.Expand(radius + DEFAULT_EPSILON_STATIC);

	// Calculate the size of the grid cell
	const float cellSize = radius * 2.f;
	invCellSize = 1.f / cellSize;

	gridSize = vertexCount;
	cellEnds.resize(gridSize);
	fill(cellEnds.begin(), cellEnds.end(), 0);
	lightVertices.resize(gridSize, NULL);

	for (u_int i = 0, k = 0; i < pathsVertices.size(); ++i) {
		for (u_int j = 0; j < pathsVertices[i].size(); ++j, ++k) {
			const PathVertexVM *vertex = &pathsVertices[i][j];

			cellEnds[Hash(vertex->bsdf.hitPoint.p)]++;
		}
	}

	int sum = 0;
	for (u_int i = 0; i < cellEnds.size(); ++i) {
		int temp = cellEnds[i];
		cellEnds[i] = sum;
		sum += temp;
	}

	for (u_int i = 0; i < pathsVertices.size(); ++i) {
		for (u_int j = 0; j < pathsVertices[i].size(); ++j) {
			const PathVertexVM *vertex = &pathsVertices[i][j];

			const int targetIdx = cellEnds[Hash(vertex->bsdf.hitPoint.p)]++;
            lightVertices[targetIdx] = vertex;
		}
	}
}

void HashGrid::Process(const BiDirVMCPURenderThread *thread,
		const PathVertexVM &eyeVertex, Spectrum *radiance) const {
	if ((vertexCount <= 0) ||
			!vertexBBox.Inside(eyeVertex.bsdf.hitPoint.p))
		return;

	const Vector distMin = eyeVertex.bsdf.hitPoint.p - vertexBBox.pMin;
	const Vector cellPoint = invCellSize * distMin;
	const Vector coordFloor(floorf(cellPoint.x), floorf(cellPoint.y), floorf(cellPoint.z));

	const int px = int(coordFloor.x);
	const int py = int(coordFloor.y);
	const int pz = int(coordFloor.z);

	const Vector fractCoord = cellPoint - coordFloor;

	const int pxo = px + ((fractCoord.x < .5f) ? -1 : +1);
	const int pyo = py + ((fractCoord.y < .5f) ? -1 : +1);
	const int pzo = pz + ((fractCoord.z < .5f) ? -1 : +1);

	int i0, i1;
	HashRange(Hash(px, py, pz), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(px, py, pzo), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(px, pyo, pz), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(px, pyo, pzo), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(pxo, py, pz), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(pxo, py, pzo), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(pxo, pyo, pz), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
	HashRange(Hash(pxo, pyo, pzo), &i0, &i1);
	Process(thread, eyeVertex, i0, i1, radiance);
}

void HashGrid::Process(const BiDirVMCPURenderThread *thread,
		const PathVertexVM &eyeVertex, const int i0, const int i1,
		Spectrum *radiance) const {
	for (int i = i0; i < i1; ++i) {
		const PathVertexVM *lightVertex = lightVertices[i];
		Process(thread, eyeVertex, lightVertex, radiance);
	}
}

void HashGrid::Process(const BiDirVMCPURenderThread *thread,
		const PathVertexVM &eyeVertex, const PathVertexVM *lightVertex,
		Spectrum *radiance) const {
	const float distance2 = (lightVertex->bsdf.hitPoint.p - eyeVertex.bsdf.hitPoint.p).LengthSquared();

	if (distance2 <= radius2) {
		float eyeBsdfPdfW, eyeBsdfRevPdfW;
		BSDFEvent eyeEvent;
		// I need to remove the dotN term from the result (see below)
		Spectrum eyeBsdfEval = eyeVertex.bsdf.Evaluate(lightVertex->bsdf.hitPoint.fixedDir,
				&eyeEvent, &eyeBsdfPdfW, &eyeBsdfRevPdfW);
		if(eyeBsdfEval.Black())
			return;

		// Volume BSDF doesn't multiply BSDF::Evaluate() by dotN so I need
		// to remove the term only if it isn't a Volume
		if (!eyeVertex.bsdf.IsVolume())
			eyeBsdfEval /= AbsDot(lightVertex->bsdf.hitPoint.fixedDir, eyeVertex.bsdf.hitPoint.geometryN);

		BiDirVMCPURenderEngine *engine = (BiDirVMCPURenderEngine *)thread->renderEngine;
		if (eyeVertex.depth >= engine->rrDepth) {
			// Russian Roulette
			const float prob = RenderEngine::RussianRouletteProb(eyeBsdfEval, engine->rrImportanceCap);
			eyeBsdfPdfW *= prob;
			eyeBsdfRevPdfW *= prob; // Note: SmallVCM uses light prob here
		}

		// MIS weights
		const float weightLight = lightVertex->dVCM * thread->misVcWeightFactor +
			lightVertex->dVM * BiDirVMCPURenderThread::MIS(eyeBsdfPdfW);
		const float weightCamera = eyeVertex.dVCM * thread->misVcWeightFactor +
			eyeVertex.dVM * BiDirVMCPURenderThread::MIS(eyeBsdfRevPdfW);
		const float misWeight = 1.f / (weightLight + 1.f + weightCamera);

		radiance[lightVertex->lightID] += (thread->vmNormalization * misWeight) *
				eyeVertex.throughput * eyeBsdfEval * lightVertex->throughput;

		// Statistics
		/*if (eyeVertex.bsdf.IsVolume()) {
			if (lightVertex->bsdf.IsVolume())
				++mergeHitsV2V;
			else
				++mergeHitsV2S;
		} else {
			if (lightVertex->bsdf.IsVolume())
				++mergeHitsV2S;
			else
				++mergeHitsS2S;
		}*/
	}
}

/*void HashGrid::PrintStatistics() const {
	const double mergeTotal = mergeHitsV2V + mergeHitsV2S + mergeHitsS2S;

	if (mergeTotal == 0.f)
		cout << "Volume2Volume = 0  Volume2Surface = 0  Volume2Volume = 0\n";
	else
		cout << boost::format("Volume2Volume = %d (%.2f%%)  Volume2Surface = %d (%.2f%%)  Surface2Surface = %d (%.2f%%)") %
				mergeHitsV2V % (100.0 * mergeHitsV2V / mergeTotal) %
				mergeHitsV2S % (100.0 * mergeHitsV2S / mergeTotal) %
				mergeHitsS2S % (100.0 * mergeHitsS2S / mergeTotal) << "\n";
}*/


#ifndef CRC_HPP
#define CRC_HPP

/**
 * @Author Pawel Ptasznik
 *
 * @Copyright Terabee 2020
 *
 */

#include <boost/crc.hpp>

namespace terabee
{
namespace internal
{
namespace crc
{

inline uint8_t calcCrc8(const uint8_t* data, int numBytes)
{
  boost::crc_optimal<8, 0x7, 0, 0> crc;
  crc.process_bytes(data, numBytes);
  return crc.checksum();
}

inline uint32_t calcCrc32(const uint8_t* data, int numBytes)
{
  boost::crc_optimal<32, 0x4C11DB7, 0xFFFFFFFF, 0> crc;
  crc.process_bytes(data, numBytes);
  return crc.checksum();
}

}  // namespace crc
}  // namespace internal
}  // namespace terabee

#endif  // CRC_HPP


/*****************************************
 *** Model of a single LIF/MAT2 neuron ***
 *****************************************/

/*** Copyright 2017-2021 Jannik Luboeinski ***
 *** licensed under Apache-2.0 (http://www.apache.org/licenses/LICENSE-2.0) ***

 *** Uses the library boost/archive - ***
 *** Copyright 2002 Robert Ramey ***
 *** licensed under the Boost Software License v1.0 (http://www.boost.org/LICENSE_1_0.txt) ***

 *** Uses the library boost/serialization -  ***
 *** Copyright 2002 Robert Ramey, 2005 Matthias Troyer ***
 *** licensed under the Boost Software License v1.0 (http://www.boost.org/LICENSE_1_0.txt) ***/

#include <random>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include "Stimulus.cpp"

using namespace std;

/*** Neuron class ***
 * Represents one neuron */
class Neuron {

friend class boost::serialization::access;

private:

/*** Computational parameters ***/
double dt; // s, one timestep for numerical simulation

/*** State variables ***/
double V; // mV, the current membrane potential at the soma
#if DENDR_SPIKES == ON
double I_dendr; // nA, the current evoked by dendritic spikes
double I_dendr_A; // nA, first contribution to the current evoked by dendritic spikes
double I_dendr_B; // nA, second contribution to the current evoked by dendritic spikes
double I_dendr_C; // nA, third contribution to the current evoked by dendritic spikes
double dendr_inp_integral; // nC, the integral over the charge deposited in the dendrite in the last 2 ms (or conductivity
                           // in case of conductance-based synapses)
vector<double> dendr_inp_history; // nC, vector containing the PSC amplitudes of the last 2 ms
double dendr_int_window; // s, dendritic integration window
double refractory_dendr; // s, time span until refractory period for dendritic spikes is over
double t_ref_dendr; // s, absolute refractory period for dendritic spikes - has to be at least one timestep!
double dendr_spike_threshold; // nC, threshold that dendr_inp_integral has to cross for a dendritic spike to be evoked
double I_dendr_A_amp; // nC, amplitude of first current component of dendritic spikes
double I_dendr_B_amp; // nC, amplitude of second current component of dendritic spikes
double I_dendr_C_amp; // nC, amplitude of third current component of dendritic spikes
double tau_dendr_A; // s, decay time constant of first current component of dendritic spikes
double tau_dendr_B; // s, decay time constant of first current component of dendritic spikes
double tau_dendr_C; // s, decay time constant of first current component of dendritic spikes
#endif
#if NEURON_MODEL == MAT2
double ad_th; // mV, the adaptive voltage threshold
double exp1; // mV, the fast component of the adaptive voltage threshold
double exp2; // mV, the slow component of the adaptive voltage threshold
#endif
double p_P; // the protein amount for LTP in this neuron
double p_C; // the common protein amount for LTP and LTP in this neuron
double p_D; // the protein amount for LTD in this neuron
double I_stim; // nA, the externally applied stimulus current
double I_bg; // nA, the external background noise current (computed using Gaussian noise or an OU process with mean 0)
#if COND_BASED_SYN == ON
double I_int_exc; // nA, the synaptic input from excitatory network neurons affecting this neuron
double I_int_inh; // nA, the synaptic input from inhibitory network neurons affecting this neuron
double V_exc_syn_rev; // mV, the reversal potential for conductance-based, excitatory synapses
double V_inh_syn_rev; // mV, the reversal potential for conductance-based, inhibitory synapses
#endif
double I_int; // nA, the synaptic input from other network neurons affecting this neuron
double refractory; // s, time span until absolute refractory period is over
bool active; // specifies if neuron is currently spiking
int spike_count; // the total number of spikes that occurred since the last reset
vector<int> spike_history; // vector of all spike times (in units of timesteps) in the process since the last reset
int spike_history_reserve; // the maximum number of spikes
int inh_incoming; // number of incoming inhibitory connections in a network
int exc_incoming; // number of incoming excitatory connections in a network
int inh_outgoing; // number of outgoing connections to inhibitory neurons in a network
int exc_outgoing; // number of outgoing connections to excitatory neurons in a network
vector<int> outgoing; // vector of all outgoing connections to neurons in a network
Stimulus cst; // current stimulus for this neuron
minstd_rand0 rg; // default uniform generator for random numbers (seed is chosen in constructor)
normal_distribution<double> norm_dist; // normal distribution to obtain Gaussian white noise, constructed in Neuron class constructor
#ifdef TWO_NEURONS_ONE_SYNAPSE
bool poisson_neuron; // indicates if the neuron solely serves as Poisson spike generator
#endif

protected:

/*** Physical parameters ***/
double tau_mem; // s, the membrane time constant
double R_mem; // MΩ, resistance of the cell membrane
double V_rev; // mV, the reversal potential of the neuron
double V_reset; // mV, the reset potential of the neuron
double t_ref; // s, absolute refractory period - has to be at least one timestep!
#if NEURON_MODEL == LIF
double V_th; // mV, the threshold potential of the neuron
double V_spike; // mV, the height of an action potential
#elif NEURON_MODEL == MAT2
double ad_th_limit; // mV, the limit of the adaptive voltage threshold for time toward infinity
#endif
double tau_OU; // s, correlation time of the Ornstein-Uhlenbeck process
double sigma_WN; // nA s^1/2, standard deviation of the Gaussian white noise driving the OU process
#if SYNAPSE_MODEL == MONOEXP
double sigma_OU; // nA, standard deviation of the OU process
#endif
double I_0; // nA, the mean of the external synaptic inputs (i.e., of the OU process)
int type; // the type of this neuron (inhibitory/excitatory - TYPE_INH/TYPE_EXC)

/*** normalRandomNumber ***
 * Returns a random number drawn from a normal distribution with standard deviation 1 and mean 0 *
 * - return: the random number of type double (technically in units of sqrt(s)) */
inline double normalRandomNumber()
{
	double nd = norm_dist(rg);

	/* Check for mean and standard deviation *
	static int i = 0;
	static double mean =0.0;
	static double var =0.0;
	const int av_steps = 1000000;
	i++;
	mean += nd;
	var += pow2(nd); // compute variance based on an assumed mean of 0.0
	if (i == av_steps)
	{
		cout << endl << "mean: " << mean / double(av_steps) << endl;
		cout << "stddev: " << sqrt(var / double(av_steps)) << endl;
		mean = 0.0;
		var = 0.0;
		i=0;
	} */
	return nd;
}

public:

/*** saveNeuronParams ***
 * Saves all the neuron parameters to a given file */
void saveNeuronParams(ofstream *f) const
{
	*f << endl;
	*f << "Neuron parameters:" << endl;
	*f << "tau_mem = " << tau_mem << " s" << endl;
	*f << "R_mem = " << R_mem << " MΩ" << endl;
	*f << "V_rev = " << V_rev << " mV" << endl;
	*f << "t_ref = " << t_ref << " s" << endl;
#if NEURON_MODEL == LIF
	*f << "V_reset = " << V_reset << " mV" << endl;
	*f << "V_th = " << V_th << " mV" << endl;
	*f << "V_spike = " << V_spike << " mV" << endl;
#endif
#if COND_BASED_SYN == ON
	*f << "V_exc_syn_rev = " << V_exc_syn_rev << " mV" << endl;
	*f << "V_inh_syn_rev = " << V_inh_syn_rev << " mV" << endl;
#endif
	*f << "tau_OU = " << tau_OU << " s" << endl;
	*f << "sigma_WN = " << sigma_WN << " nA s^1/2" << endl;
	*f << "I_0 = " << I_0 << " nA" << endl;
#if COND_BASED_SYN == ON

#endif
#if DENDR_SPIKES == ON
	*f << "t_ref_dendr = " << t_ref_dendr << " s" << endl;
	*f << "dendr_spike_threshold = " << dendr_spike_threshold << " nC" << endl;
	*f << "dendr_int_window = " << dendr_int_window << " s" << endl;
	*f << "I_dendr_A_amp = " << I_dendr_A_amp << " nA" << endl;
	*f << "I_dendr_B_amp = " << I_dendr_B_amp << " nA" << endl;
	*f << "I_dendr_C_amp = " << I_dendr_C_amp << " nA" << endl;
	*f << "tau_dendr_A = " << tau_dendr_A << " s" << endl;
	*f << "tau_dendr_B = " << tau_dendr_B << " s" << endl;
	*f << "tau_dendr_C = " << tau_dendr_C << " s" << endl;
#endif
}

/*** serialize ***
 * Saves all state variables to a file using serialization from boost *
 * - ar: the archive stream *
 * - version: the archive version */
template<class Archive> void serialize(Archive &ar, const unsigned int version)
{
	ar & V;
#if DENDR_SPIKES == ON
	ar & I_dendr;
	ar & I_dendr_A;
	ar & I_dendr_B;
	ar & I_dendr_C;
	ar & dendr_inp_integral;
	ar & dendr_inp_history;
	ar & refractory_dendr;
#endif
#if NEURON_MODEL == MAT2
	ar & ad_th;
	ar & exp1;
	ar & exp2;
#endif
	ar & p_P;
	ar & p_C;
	ar & p_D;
	ar & I_stim;
	ar & I_bg;
#if COND_BASED_SYN == ON
	ar & I_int_exc;
	ar & I_int_inh;
	ar & V_exc_syn_rev;
	ar & V_inh_syn_rev;
#endif
	ar & I_int;
	ar & refractory;
	ar & active;
	ar & spike_count;
	ar & spike_history;
}

/*** getNumberIncoming ***
 * Returns the number of either inhibitory or excitatory incoming connections to this neuron *
 * from other neurons in a network *
 * - int type: the type of incoming connections (inh./exc.)
 * - return: the number of incoming connections */
int getNumberIncoming(int type) const
{
	if (type == TYPE_INH)
		return inh_incoming;
	else if (type == TYPE_EXC)
		return exc_incoming;
	else
		throw invalid_argument("Invalid neuron type.");
}

/*** getNumberOutgoing ***
 * Returns the number of connections outgoing from this neuron to other *
 * neurons (of a specific type) *
 * - int type [optional]: the type of the postsynaptic neuron (inh./exc.)
 * - return: the number of outgoing connections */
int getNumberOutgoing(int type) const
{
	if (type == TYPE_INH)
		return inh_outgoing;
	else if (type == TYPE_EXC)
		return exc_outgoing;
	else
		throw invalid_argument("Invalid neuron type.");
}
int getNumberOutgoing() const
{
	return outgoing.size();
}

/*** incNumberIncoming ***
 * Increases the number of incoming connections to this neuron (only to be used while *
 * a network is being built) *
 * - int type: the type of the presynaptic neuron (inh./exc.) */
void incNumberIncoming(int type)
{
	if (type == TYPE_INH)
		inh_incoming++;
	else if (type == TYPE_EXC)
		exc_incoming++;
}

/*** addOutgoingConnection ***
 * Adds an outgoing connection from this neuron to another one (only to be used while a network is being built) *
 * - index: the index of the neuron that receives the connection *
 * - int type: the type of the receiving neuron (inh./exc.) */
void addOutgoingConnection(int index, int type)
{
	outgoing.push_back(index);

	if (type == TYPE_INH)
		inh_outgoing++;
	else if (type == TYPE_EXC)
		exc_outgoing++;
}

/*** getOutgoingConnection ***
 * Returns the index of a neuron receiving an outgoing connection *
 * - arr_index: the index of the connection in the array *
 * - return: the index of the neuron that receives the connection */
int getOutgoingConnection(int arr_index) const
{
	return outgoing[arr_index];
}

/*** getVoltage ***
 * Returns the membrane potential of the neuron *
 * - return: the membrane potential in mV */
double getVoltage() const
{
	return V;
}

/*** getThreshold ***
 * Returns the value of the (possibly) dynamic membrane threshold of the neuron *
 * - return: the membrane threshold in mV */
double getThreshold() const
{
#if NEURON_MODEL == LIF
	return V_th;
#elif NEURON_MODEL == MAT2
	return ad_th;
#endif
}

/*** getCurrent ***
 * Returns total current affecting the neuron *
 * - return: the instantaneous current in nA */
double getCurrent() const
{
	return I_stim+I_bg+I_int;
}

/*** getStimulusCurrent ***
 * Returns current evoked by external stimulation *
 * - return: the instantaneous current stimulus in nA */
double getStimulusCurrent() const
{
	return I_stim;
}

/*** getBGCurrent ***
 * Returns current external background current accounting for external inputs *
 * - return: the instantaneous external background current in nA */
double getBGCurrent() const
{
	return I_bg;
}

/*** getConstCurrent ***
 * Returns the mean of the external current *
 * - return: the constant current in nA */
double getConstCurrent() const
{
	return I_0;
}

/*** getSigma ***
 * Returns the standard deviation of the white noise entering the external current *
 * - return: the standard deviation in nA s^(1/2) */
double getSigma() const
{
	return sigma_WN;
}

/*** getSynapticCurrent ***
 * Returns the internal synaptic current that arrived in the previous timestep *
 * - return: the synaptic current in nA */
double getSynapticCurrent() const
{
	return I_int;
}

/*** setSynapticCurrent ***
 * Sets the synaptic input current from other neurons within the network to this neuron *
 * - _I_int: the synaptic current in nA */
void setSynapticCurrent(const double _I_int)
{
	I_int = _I_int;
}

#if COND_BASED_SYN == ON
/*** getExcSynapticCurrent ***
 * Returns the internal excitatory synaptic conductance of the previous timestep *
 * - return: the excitatory synaptic conductance in nS */
double getExcSynapticCurrent() const
{
	return I_int_exc;
}

/*** getInhSynapticCurrent ***
 * Returns the internal inhibitory synaptic conductance of the previous timestep *
 * - return: the inhibitory synaptic conductance in nS */
double getInhSynapticCurrent() const
{
	return I_int_inh;
}

/*** setExcSynapticCurrent ***
 * Sets the synaptic input current from excitatory neurons within the network to this neuron *
 * - _I_int_exc: the synaptic current/conductance in nA/nS */
void setExcSynapticCurrent(const double _I_int_exc)
{
	I_int_exc = _I_int_exc;
}

/*** setInhSynapticCurrent ***
 * Sets the synaptic input current from inhibitory neurons within the network to this neuron *
 * - _I_int_inh: the synaptic current/conductance in nA/nS */
void setInhSynapticCurrent(const double _I_int_inh)
{
	I_int_inh = _I_int_inh;
}
#endif

/*** increaseExcSynapticCurrent ***
 * Adds a specified contribution to the excitatory synaptic input current/conductance *
 * - contr: the contribution to be added to the synaptic current/conductance in nA/nS */
void increaseExcSynapticCurrent(const double contr)
{
#if COND_BASED_SYN == ON
	I_int_exc += contr;
#else
	I_int += contr;
#endif
}

/*** increaseInhSynapticCurrent ***
 * Adds a specified contribution to the inhibitory synaptic input current/conductance *
 * - contr: the contribution to be added to the synaptic current/conductance in nA/nS */
void increaseInhSynapticCurrent(const double contr)
{
#if COND_BASED_SYN == ON
	I_int_inh += contr;
#else
	I_int -= contr;
#endif
}

#if DENDR_SPIKES == ON
/*** updateDendriteInput ***
 * Adds the the amplitude of a PSC current to the history and to the integral of the deposited charge *
 * - psc_amplitude: the PSC amplitde in nA/nS/nC */
void updateDendriteInput(const double psc_amplitude)
{
	dendr_inp_history[dendr_inp_history.size()-1] += psc_amplitude;
	dendr_inp_integral += psc_amplitude;
}

/*** getDendriticCurrent ***
 * Returns the current that dendritic spiking caused in the previous timestep *
 * - return: the synaptic current in nA */
double getDendriticCurrent() const
{
	return I_dendr;
}

/*** compDendriticCurrent ***
 * Computes the dendritic current at time t as in Jahnke et al., 2015 *
 * - t: the time counted from the beginning of a dendritic spike *
void compDendriticCurrent(double t)
{
    I_dendr = -55.*exp(-t/0.0002) + 64.*exp(-t/0.0003) - 9.*exp(-t/0.0007);
}*/
#endif

/*** getActivity ***
 * Returns true if the neuron is spiking in this instant of duration dt *
 * ATTENTION: can cause complications when used in a population for it does *
 * not include information about the exact spike time *
 * - return: whether neuron is firing or not */
bool getActivity() const
{
	if (active)
		return true;
	else
		return false;
}

/*** getSpikeTime ***
 * Returns the spike time for a given spike number (ATTENTION: argument n should not exceed the result of getSpikeHistorySize() -
 * this is not checked here for performance reasons) *
 * - int n: the number of the spike (in temporal order, starting with 1)
 * - return: the spike time in units of time bins for the n-th spike (or -1 if it does not exist) */
int getSpikeTime(int n) const
{
	return spike_history[n-1];
}

/*** spikeAt ***
 * Returns whether or not a spike has occurred at a given timestep, begins searching *
 * from latest spike *
 * - int t_step: the time bin at which the spike should have occurred
 * - return: true if a spike occurred, false if not */
bool spikeAt(int t_step) const
{
	for(int i=spike_history.size()-1; i>=0; i--)
	{
		if (spike_history[i] == t_step) // value that is looked for
			return true;
		else if (spike_history[i] < t_step) // already below value that is looked for
			return false;
	}
	return false;
}

/*** spikesInInterval ***
 * Counts the number of spikes in a given interval *
 * - int tb_start: the time bin at which the interval begins *
 * - int tb_end: one time bin after the interval ends *
 * - return: the number of spikes in the given interval */
int spikesInInterval(int tb_start, int tb_end) const
{
	int count = 0;

	for(int i=spike_history.size()-1; i>=0; i--)
	{
		if (spike_history[i] >= tb_start && spike_history[i] < tb_end)
			count++;
	}

	return count;
}

/*** hideSpikeTime ***
 * "Hides" the spike time for a given spike number, which means that *
 * it will be stored as a negative number, such that it will not anymore *
 * be taken into consideration for synaptic transmission *
 * (WAS ONLY NECESSARY FOR AN APPROXIMATION THAT IS NOT USED ANYMORE) *
 * - int n: the number of the spike (in temporal order, starting with 1)
 * - return: the spike time for the n-th spike (or -1.0 if it does not exist) */
void hideSpikeTime(int n)
{
	if (n <= spike_history.size() && n >= 1)
		spike_history[n-1] *= -1;
}

/*** removeSpikes ***
 * Removes a specified set of spikes from history, to save memory *
 * - int start: the number of the spike to start with (in temporal order, starting with 1)
 * - int end: the number of the spike to end with (in temporal order, starting with 1) */
void removeSpikes(int start, int end)
{
	spike_history.erase(spike_history.begin()+start-1,spike_history.begin()+end);
}

/*** getSpikeCount ***
 * Returns the number of spikes that have occurred since the last reset (including those that have been removed) *
 * - return: the number of spikes */
int getSpikeCount() const
{
	return spike_count;
}

/*** getSpikeHistorySize ***
 * Returns the current size of the spike history vector *
 * - return: the size of the spike history vector */
int getSpikeHistorySize() const
{
	return spike_history.size();
}

/*** processTimeStep ***
 * Processes one timestep (of duration delta_t) for the neuron *
 * - int tb_step: timestep at which to evaluate stimulus (< 0 before stimulus onset) *
 * - int tb_init: initial timestep for simple decay process (should be positive only in decaying state!) */
void processTimeStep(int tb_step, int tb_init)
{
	double delta_t; // duration of the timestep in seconds, either dt or tb_step-tb_init

	if (tb_init < 0)
		delta_t = dt;
	else
		delta_t = (tb_step - tb_init) * dt;

#if SYNAPSE_MODEL == DELTA
	I_bg = normalRandomNumber() * sqrt(1/delta_t) * sigma_WN + I_0;
#elif SYNAPSE_MODEL == MONOEXP
	I_bg = (I_bg-I_0) * exp(-delta_t/tau_OU) + normalRandomNumber() * sqrt(1. - exp(-2.*delta_t/tau_OU)) * sigma_OU + I_0; // compute external synaptic input in nA
#endif

#if COND_BASED_SYN == ON
	//I_int = (I_int_exc * V_exc_syn_rev + I_int_inh * V_inh_syn_rev - (I_int_exc + I_int_inh) * V) / 1000.; // divide by 1000 to get from nS*mV=pA to nA
	I_int = (I_int_exc * (V_exc_syn_rev - V) + I_int_inh * (V_inh_syn_rev - V)) / 1000.; // divide by 1000 to get from nS*mV=pA to nA
#endif

#ifdef TWO_NEURONS_ONE_SYNAPSE
	if (poisson_neuron == false)
	{
#endif

#if NEURON_MODEL == MAT2

		// MAT(2) neuron
		V = V * exp(-delta_t/tau_mem) + R_mem*(I_int + I_bg) * (1. - exp(-delta_t/tau_mem)); // compute mem. pot. in mV (analytical solution)

		exp1 = exp1 * exp(-delta_t/0.01); // fast threshold relaxation
		exp2 = exp2 * exp(-delta_t/0.2); // slow threshold relaxation

		if (active)
		{

			exp1 = exp1 + 0.015; // add new spike with full contribution alpha_1
			exp2 = exp2 + 0.003; // add new spike with full contribution alpha_2

			active = false;
		}

		ad_th = ad_th_limit + exp1 + exp2; // update adaptive threshold

#elif NEURON_MODEL == LIF

#if DENDR_SPIKES == ON

		// exponential decay of dendritic spikes
		I_dendr_A *= exp(- delta_t / tau_dendr_A);
		I_dendr_B *= exp(- delta_t / tau_dendr_B);
		I_dendr_C *= exp(- delta_t / tau_dendr_C);

		if (refractory_dendr > EPSILON) // if in refractory period for dendritic spikes
		{
			refractory_dendr -= delta_t;
		}
		else
		{
			if (dendr_inp_integral > dendr_spike_threshold) // threshold has been crossed
			{
				// dendrite spike contributions do not have to be added up because possible remaining contributions should have decayed to zero
				I_dendr_A = -55.; //I_dendr_A -= 55.;
				I_dendr_B = 64.; //I_dendr_B += 64.;
				I_dendr_C = -9.; //I_dendr_C -= 9.;

				refractory_dendr = t_ref_dendr;
			}
		}

		//compDendriticCurrent(tb_step*delta_t - I_dendr_A);
		I_dendr = I_dendr_A + I_dendr_B + I_dendr_C;

		dendr_inp_integral -= dendr_inp_history[0]; // remove oldest contributions from integral
		dendr_inp_history.erase(dendr_inp_history.begin()); // remove oldest contributions from history
		dendr_inp_history.push_back(0.); // add slot for new contributions

#endif

		// LIF
		//V += delta_t/tau_mem * (- V + V_rev + R_mem*(I_bg + I_int)); // compute mem. pot. in mV (Euler method)
		V = V * exp(-delta_t/tau_mem) + (V_rev + R_mem*(  I_bg
			                                        + I_int
#if DENDR_SPIKES == ON
		                                                + I_dendr
#endif
		                                                         )) * (1. - exp(-delta_t/tau_mem)); // compute mem. pot. in mV (analytical solution)

#endif

#ifdef TWO_NEURONS_ONE_SYNAPSE
	} // poisson_neuron == false

	else
	{
		active = false;
		V = V_reset;
	} // poisson_neuron == true
#endif
	if (cst.isSet() && tb_init < 0 && abs(I_stim = cst.get(tb_step)) > EPSILON) // stimulation; get stimulus current in nA
	{
#if STIM_TYPE == POISSON_STIMULATION
		V += R_mem * I_stim;
#else
		V += R_mem * I_stim * (1. - exp(-delta_t/tau_mem));
#endif

#ifdef TWO_NEURONS_ONE_SYNAPSE
	#if NEURON_MODEL == MAT2
		V = ad_th; // definite spiking (the magnitude of I_stim is not important as long as it is finite)
	#elif NEURON_MODEL == LIF
		V = V_th;
	#endif
#endif
	}

	if (refractory > EPSILON // if in refractory period
#ifdef TWO_NEURONS_ONE_SYNAPSE
	   && poisson_neuron == false
#endif
	   )
	{
#if NEURON_MODEL == LIF
		active = false;

		V = V_reset;
#endif
		refractory -= delta_t;
	}
	else
	{
#if NEURON_MODEL == LIF
		if (V >= V_th) // threshold crossing
		{
			V = V_reset;
#elif NEURON_MODEL == MAT2
		if (V >= ad_th) // threshold crossing
		{
#endif
			refractory = t_ref;
			if (tb_step >= 0) // count only those spikes that occur after the stimulus onset
				spike_history.push_back(tb_step);

			active = true;
			spike_count++;
		}
	}

}

/*** setProteinAmounts ***
 * Sets the protein amounts in the neuron *
 * - double _p_P: momentary protein amount for potentiation *
 * - double _p_C: momentary common protein amount *
 * - double _p_D: momentary protein amount for depression */
void setProteinAmounts(const double _p_P, const double _p_C, const double _p_D)
{
	p_P = _p_P;
	p_C = _p_C;
	p_D = _p_D;
}

/*** getPProteinAmount ***
 * Returns the protein amount for potentiation in the neuron *
 * - return: momentary protein amount */
double getPProteinAmount() const
{
	return p_P;
}

/*** getCProteinAmount ***
 * Returns the common protein amount in the neuron *
 * - return: momentary protein amount */
double getCProteinAmount() const
{
	return p_C;
}

/*** getDProteinAmount ***
 * Returns the protein amount for depression in the neuron *
 * - return: momentary protein amount */
double getDProteinAmount() const
{
	return p_D;
}

/*** setCurrentStimulus ***
 * Sets a current stimulus for the neuron *
 * - Stimulus& _cst: shape of one stimulus period */
void setCurrentStimulus(const Stimulus& _cst)
{
	cst = _cst;
}

/*** isStimulusSet ***
 * Returns true if a stimulus has been set *
 * - return: the value of isSet() in Stimulus class */
bool isStimulusSet() const
{
	return cst.isSet();
}

/*** multiplyCurrentStimulus ***
 * Multiplies the set current stimulus by a real number *
 * - double r: number to multiply */
void multiplyCurrentStimulus(double r)
{
	cst.multiplyBy(r);
}

/*** setConstCurrent ***
 * Sets the constant current (mean of Gaussian white noise or OU process) to a newly defined value *
 * - double _I_0: constant current in nA */
void setConstCurrent(double _I_0)
{
	I_0 = _I_0;
}

/*** setSigma ***
 * Sets the standard deviation of the external input current (i.e., Gaussian white noise or the OU process)*
 * - double _sigma: standard deviation in nA s^1/2 */
void setSigma(double _sigma)
{
	sigma_WN = _sigma;

#if SYNAPSE_MODEL == MONOEXP
	sigma_OU = sigma_WN / sqrt(2.*tau_OU); // sigma of the OU process; required unit for tau_OU is [s]
#endif
}

/*** setTauOU ***
 * Sets the time constant of the Ornstein-Uhlenbeck process *
 * (synaptic time constant of assumed input synapses) *
 * - double _tau_OU: the synaptic time constant in s*/
void setTauOU(double _tau_OU)
{
	tau_OU = _tau_OU;
}

/*** setType ***
 * Sets the type of this neuron (inhibitory/excitatory) *
 * - int _type: the neuron type */
void setType(int _type)
{
	type = _type;
}

/*** setSpikeHistoryMemory ***
 * Sets the RAM size that shall be reserved for the spike history *
 * - int storage_steps: the size of the storage timespan in timesteps *
 * - return: the reserved size of the spike history vector */
int setSpikeHistoryMemory(int storage_steps)
{
	spike_history_reserve = int(round(storage_steps*(dt/t_ref)));
}

/*** getType ***
 * Returns the type of this neuron (inhbitory/excitatory) *
 * - return: the neuron type */
int getType() const
{
	return type;
}

#ifdef TWO_NEURONS_ONE_SYNAPSE
/*** setPoisson ***
 * Allows for making this neuron a Poisson neuron (used to generate Poissonian spikes only, without own voltage dynamics) *
 * - bool _poisson_neuron: indicates if the neuron shall be Poissonian */
void setPoisson(bool _poisson_neuron)
{
	poisson_neuron = _poisson_neuron;
}
#endif

/*** resetConnections ***
 * Resets the connections *
 * - only_exc: if true, only counters for excitatory connections are reset */
void resetConnections(bool only_exc = false)
{
	exc_outgoing = 0;
	exc_incoming = 0;

	if (!only_exc)
	{
		inh_outgoing = 0;
		inh_incoming = 0;
	}

	vector<int>().swap(outgoing);
}

/*** reset ***
 * Resets neuron to initial state */
void reset()
{
	vector<int>().swap(spike_history); // additionally to clearing the vector, reallocates it
	spike_history.reserve(spike_history_reserve); // pre-allocates space for the maximum number of spikes

	V = V_rev;

#if DENDR_SPIKES == ON
	I_dendr = 0.;
	I_dendr_A = 0.;//-1000.;
	I_dendr_B = 0.;
	I_dendr_C = 0.;
	dendr_inp_history.assign(int(round(dendr_int_window/dt)), 0.);
	dendr_inp_integral = 0.;
	refractory_dendr = 0.;
#endif
#if NEURON_MODEL == MAT2
	ad_th = ad_th_limit;
	exp1 = 0.0;
	exp2 = 0.0;
#endif
	refractory = 0.0; // neuron ready to fire
	p_P = 0.0;
	p_C = 0.0;
	p_D = 0.0;
	I_stim = 0.0;
	I_bg = 0.0;
#if COND_BASED_SYN == ON
	I_int_exc = 0.0;
	I_int_inh = 0.0;
#endif
	I_int = 0.0;

	active = false;
	spike_count = 0;

	rg.seed(getClockSeed()); // set new seed by clock's epoch
	norm_dist.reset(); // reset the normal distribution for random numbers
}

/*** Constructor ***
 * Sets all parameters on experimentally determined values *
 * _dt: size of a timestep in seconds */
Neuron(const double _dt) :
	dt(_dt), rg(getClockSeed()), norm_dist(0.0,1.0)
{
	tau_mem = 0.010; // from Yamauchi et al., 2011

#if NEURON_MODEL == MAT2

	t_ref = 0.002; // from Kobayashi et al., 2009

#ifdef TWO_NEURONS_ONE_SYNAPSE_ALT
	R_mem = 0.01;
#elif defined TWO_NEURONS_ONE_SYNAPSE
	R_mem = 0.0001; // from Li et al., 2016 (mind that the quantity R here is a different one than there!)
#else
	R_mem = 0.01;
	//R_mem = 50.0; // from Kobayashi et al., 2009 / Yamauchi et al., 2009
#endif

	V_rev = 0.0;
	V_reset = 0.0; // estimated to model afterhypolarization in combination with t_ref (see also Dayan & Abbott, p. 4)
	ad_th_limit = 0.005; // from Li et al., 2016

#elif NEURON_MODEL == LIF

	t_ref = 0.002; // estimated to model afterhypolarization in combination with V_reset
	R_mem = 10.0; // from Dayan & Abbott, fig. 5.
	V_th = -55.0; // from Dayan & Abbott, p. 162
	V_spike = 35.0; // estimated from sketch in Bachelor's thesis
	V_rev = -65.0; // from Dayan & Abbott, fig. 5.
	V_reset = -70.0; // estimated to model afterhypolarization in combination with t_ref (see also Dayan & Abbott, p. 4)

#endif
	sigma_WN = 0.;
#if SYNAPSE_MODEL == MONOEXP
	sigma_OU = 0.;
#endif
	I_0 = 0.;
	tau_OU = 0.005; // estimated

#ifdef TWO_NEURONS_ONE_SYNAPSE
	poisson_neuron = false;
#endif

#if COND_BASED_SYN == ON
	V_exc_syn_rev = 0.; // Moritz' Master's thesis
	V_inh_syn_rev = -70.; // Moritz' Master's thesis
#endif
#if DENDR_SPIKES == ON
	dendr_spike_threshold = 6.5; // estimated (Jahnke et al., 2015: 8.65)
	dendr_int_window = 0.002;
	t_ref_dendr = 0.005;
	I_dendr_A_amp = -55.;
	I_dendr_B_amp = 64.;
	I_dendr_C_amp = -9.;
	tau_dendr_A = 0.0002;
	tau_dendr_B = 0.0003;
	tau_dendr_C = 0.0007;
#endif

	reset();
	resetConnections();
}

/*** Destructor ***
 * Frees the allocated memory */
~Neuron()
{

}

};


/*
 *            Copyright 2009-2020 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      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.
 *
 */

// Third party includes
#include <boost/format.hpp>

// VOTCA includes
#include <votca/tools/elements.h>
#include <votca/tools/tokenizer.h>

// Local VOTCA includes
#include "votca/xtp/atomcontainer.h"
#include "votca/xtp/classicalsegment.h"

namespace votca {
namespace xtp {

// MPS files have a weird format positions can be in bohr or angstroem,
// multipoles are in q*bohr^k, with k rank of multipole and polarisabilities are
// in angstroem^3
template <class T>
void ClassicalSegment<T>::LoadFromFile(std::string filename) {

  std::string line;
  std::ifstream intt;
  intt.open(filename);
  double unit_conversion = tools::conv::ang2bohr;

  Index readinmultipoles = 0;
  Index numberofmultipoles = 0;
  Vector9d multipoles = Vector9d::Zero();
  Index rank = 0;

  if (!intt.is_open()) {
    throw std::runtime_error("File:" + filename + " could not be opened");
  }
  while (intt.good()) {

    std::getline(intt, line);
    tools::Tokenizer toker(line, " \t");
    std::vector<std::string> split = toker.ToVector();

    if (!split.size() || split[0] == "!" || split[0].substr(0, 1) == "!") {
      continue;
    }

    // ! Interesting information here, e.g.
    // ! DCV2T opt
    // ! SP        RB3LYP          6-311+G(d,p)
    // Units bohr
    //
    // C          -4.2414603400   -3.8124751600    0.0017575736    Rank  2
    //  -0.3853409355
    //  -0.0002321905   0.2401559510   0.6602334308
    //  -0.7220625314   0.0004894995  -0.0003833545   0.4526409813  -0.50937399
    //  P 1.75

    // Units used
    if (split[0] == "Units") {
      std::string units = split[1];
      if (units != "bohr" && units != "angstrom") {
        throw std::runtime_error("Unit " + units + " in file " + filename +
                                 " not supported.");
      }
      if (units == "bohr") {
        unit_conversion = 1.0;
      }
    } else if (split.size() == 6) {
      // element,  position,  rank limit convert to bohr
      std::string name = split[0];
      Eigen::Vector3d pos;
      Index id = Index(this->_atomlist.size());
      pos[0] = boost::lexical_cast<double>(split[1]);
      pos[1] = boost::lexical_cast<double>(split[2]);
      pos[2] = boost::lexical_cast<double>(split[3]);
      rank = boost::lexical_cast<Index>(split[5]);
      numberofmultipoles = (rank + 1) * (rank + 1);
      multipoles = Vector9d::Zero();
      pos *= unit_conversion;
      this->_atomlist.push_back(T(id, name, pos));
    }
    // 'P', dipole polarizability
    else if (split[0] == "P") {
      Eigen::Matrix3d p1;
      if (split.size() == 7) {
        double pxx = boost::lexical_cast<double>(split[1]);
        double pxy = boost::lexical_cast<double>(split[2]);
        double pxz = boost::lexical_cast<double>(split[3]);
        double pyy = boost::lexical_cast<double>(split[4]);
        double pyz = boost::lexical_cast<double>(split[5]);
        double pzz = boost::lexical_cast<double>(split[6]);
        p1 << pxx, pxy, pxz, pxy, pyy, pyz, pxz, pyz, pzz;
      } else if (split.size() == 2) {
        double pxx = boost::lexical_cast<double>(split[1]);
        p1 = pxx * Eigen::Matrix3d::Identity();
      } else {
        throw std::runtime_error("Invalid line in " + filename + ": " + line);
      }
      double unit_conversion_3 = std::pow(tools::conv::ang2bohr, 3);
      p1 = p1 * unit_conversion_3;
      this->_atomlist.back().setpolarization(p1);
    }
    // Multipole lines
    else {
      // stay in bohr
      for (const std::string& entry : split) {
        double qXYZ = boost::lexical_cast<double>(entry);
        if (multipoles.size() < readinmultipoles) {
          throw std::runtime_error("ReadMpsFile: File" + filename +
                                   "is not properly formatted");
        }
        multipoles(readinmultipoles) = qXYZ;
        readinmultipoles++;
      }
      if (readinmultipoles == numberofmultipoles) {
        Eigen::Vector3d temp_dipoles = multipoles.segment<3>(1);
        // mps format for dipoles is z x y
        // we need x y z
        multipoles(1) = temp_dipoles(1);
        multipoles(2) = temp_dipoles(2);
        multipoles(3) = temp_dipoles(0);
        this->_atomlist.back().setMultipole(multipoles, rank);
        readinmultipoles = 0;
      }
    }
  }
  this->calcPos();
}

template <class T>
double ClassicalSegment<T>::CalcTotalQ() const {
  double Q = 0;
  for (const T& site : this->_atomlist) {
    Q += site.getCharge();
  }
  return Q;
}

template <class T>
Eigen::Vector3d ClassicalSegment<T>::CalcDipole() const {
  Eigen::Vector3d dipole = Eigen::Vector3d::Zero();

  Eigen::Vector3d CoM = this->getPos();
  for (const T& site : this->_atomlist) {
    dipole += (site.getPos() - CoM) * site.getCharge();
    dipole += site.getDipole();
  }
  return dipole;
}

template <class T>
void ClassicalSegment<T>::WriteMPS(std::string filename,
                                   std::string header) const {

  std::ofstream ofs;
  ofs.open(filename, std::ofstream::out);
  if (!ofs.is_open()) {
    throw std::runtime_error("Bad file handle: " + filename);
  }

  ofs << (boost::format("! GENERATED BY VOTCA::XTP::%1$s\n") % header);
  ofs << (boost::format("! N=%2$d Q[e]=%1$+1.7f\n") % CalcTotalQ() %
          this->size());
  ofs << boost::format("Units angstrom\n");

  for (const T& site : this->_atomlist) {
    ofs << site.WriteMpsLine("angstrom");
  }
  ofs.close();
}

template <class T>
std::string ClassicalSegment<T>::identify() const {
  return "";
}

template <>
std::string ClassicalSegment<PolarSite>::identify() const {
  return "PolarSegment";
}
template <>
std::string ClassicalSegment<StaticSite>::identify() const {
  return "StaticSegment";
}

template class ClassicalSegment<PolarSite>;
template class ClassicalSegment<StaticSite>;

}  // namespace xtp
}  // namespace votca