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

// This is a modified version of the Kitti "object development kit"
// available from http://www.cvlibs.net/datasets/kitti/eval_object.php

#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 <common/filesystem.hpp>
#include <common/types.hpp>

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cassert>

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <string>
#include <functional>

#include "kittiobjevalmodule.hpp"

#ifdef BOOST_GEOMETRY_REGISTER_C_ARRAY_CS
BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)
#endif

using autoware::common::types::bool8_t;
using autoware::common::types::char8_t;
using autoware::common::types::float64_t;
using std::size_t;

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

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

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

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

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

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

// parameters varying per class
std::vector<std::basic_string<char8_t>> CLASS_NAMES;
std::vector<std::basic_string<char8_t>> CLASS_NAMES_CAP;
// the minimum overlap required for 2D evaluation on the image/ground plane and 3D evaluation
constexpr float64_t MIN_OVERLAP[3][3] = {{0.7, 0.5, 0.5}, {0.7, 0.5, 0.5}, {0.7, 0.5, 0.5}};

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

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

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

// holding data needed for precision-recall and precision-aos
struct tPrData
{
  std::vector<float64_t> v;           // detection score for computing score thresholds
  float64_t 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
{
  std::basic_string<char8_t> type;      // object type as car, pedestrian or cyclist,...
  float64_t x1;        // left corner
  float64_t y1;        // top corner
  float64_t x2;        // right corner
  float64_t y2;        // bottom corner
  float64_t alpha;     // image orientation
  tBox(
    std::basic_string<char8_t> type, float64_t x1, float64_t y1, float64_t x2, float64_t y2,
    float64_t 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
  float64_t truncation;  // truncation 0..1
  int32_t occlusion;  // occlusion 0,1,2 (non, partly, fully)
  float64_t ry;
  float64_t t1, t2, t3;
  float64_t h, w, l;
  tGroundtruth()
  : box(tBox("invalild", -1, -1, -1, -1, -10)), truncation(-1), occlusion(-1) {}
  tGroundtruth(tBox box, float64_t truncation, int32_t occlusion)
  : box(box), truncation(truncation), occlusion(occlusion) {}
  tGroundtruth(
    std::basic_string<char8_t> type, float64_t x1, float64_t y1, float64_t x2,
    float64_t y2, float64_t alpha, float64_t 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
  float64_t thresh;  // detection score
  float64_t ry;
  float64_t t1, t2, t3;
  float64_t h, w, l;
  tDetection()
  : box(tBox("invalid", -1, -1, -1, -1, -10)), thresh(-1000) {}
  tDetection(tBox box, float64_t thresh)
  : box(box), thresh(thresh) {}
  tDetection(
    std::basic_string<char8_t> type, float64_t x1, float64_t y1, float64_t x2,
    float64_t y2, float64_t alpha, float64_t thresh)
  : box(tBox(type, x1, y1, x2, y2, alpha)), thresh(thresh) {}
};

/*=======================================================================
FUNCTIONS TO LOAD DETECTION AND GROUND TRUTH DATA ONCE, SAVE RESULTS
=======================================================================*/
std::vector<tDetection> loadDetections(
  std::basic_string<char8_t> file_name, bool8_t & compute_aos,
  std::vector<bool8_t> & eval_image, std::vector<bool8_t> & eval_ground,
  std::vector<bool8_t> & eval_3d, bool8_t & success)
{
  // holds all detections (ignored detections are indicated by an index vector
  std::vector<tDetection> detections;
  FILE * fp = fopen(file_name.c_str(), "r");
  if (!fp) {
    success = false;
    return detections;
  }
  while (!feof(fp)) {
    tDetection d;
    float64_t trash;
    char8_t 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.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 (size_t c = 0; c < NUM_CLASS; c++) {
        if (!strcasecmp(
            d.box.type.c_str(),
            CLASS_NAMES[c].c_str()) || !strcasecmp(d.box.type.c_str(), CLASS_NAMES_CAP[c].c_str()))
        {
          if (!eval_image[c] && d.box.x1 >= 0) {
            eval_image[c] = true;
          }
          if (!eval_ground[c] && d.t1 != -1000 && d.t3 != -1000 && d.w > 0 && d.l > 0) {
            eval_ground[c] = true;
          }
          if (!eval_3d[c] && d.t1 != -1000 && d.t2 != -1000 && d.t3 != -1000 && d.h > 0 &&
            d.w > 0 && d.l > 0)
          {
            eval_3d[c] = true;
          }
          break;
        }
      }
    }
  }

  fclose(fp);
  success = true;
  return detections;
}

std::vector<tGroundtruth> loadGroundtruth(std::basic_string<char8_t> file_name, bool8_t & success)
{
  // holds all ground truth (ignored ground truth is indicated by an index vector
  std::vector<tGroundtruth> groundtruth;
  FILE * fp = fopen(file_name.c_str(), "r");
  if (!fp) {
    success = false;
    return groundtruth;
  }
  while (!feof(fp)) {
    tGroundtruth g;
    char8_t 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 std::vector<float64_t> & precision, const std::vector<float64_t> & aos, FILE * fp_det,
  FILE * fp_ori)
{
  // save precision to file
  if (precision.empty()) {
    return;
  }
  for (uint32_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 (uint32_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 float64_t imageBoxOverlap(tBox a, tBox b, int32_t criterion = -1)
{
  // overlap is invalid in the beginning
  float64_t o = -1;

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

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

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

  // get overlapping areas
  float64_t inter = w * h;
  float64_t a_area = (a.x2 - a.x1) * (a.y2 - a.y1);
  float64_t 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 float64_t 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)
{
  boost::numeric::ublas::matrix<float64_t> 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);

  boost::numeric::ublas::matrix<float64_t> corners(2, 4);
  float64_t 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());
  boost::numeric::ublas::matrix<float64_t> gc = prod(mref, corners);
  for (size_t i = 0; i < 4; ++i) {
    gc(0, i) += g.t1;
    gc(1, i) += g.t3;
  }

  float64_t 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;
  boost::geometry::append(poly, points);
  return poly;
}

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

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

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

  return o;
}

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

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

  float64_t ymax = std::min(d.t2, g.t2);
  float64_t ymin = std::max(d.t2 - d.h, g.t2 - g.h);

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

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

  float64_t o = 0;
  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;
}

std::vector<float64_t> getThresholds(std::vector<float64_t> & v, float64_t n_groundtruth)
{
  // holds scores needed to compute N_SAMPLE_PTS recall values
  std::vector<float64_t> t;

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

  // get scores for linearly spaced recall
  float64_t current_recall = 0;
  for (uint32_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
    float64_t l_recall, r_recall /*, recall*/;
    l_recall = static_cast<float64_t>(i + 1) / n_groundtruth;
    if (i < (v.size() - 1)) {
      r_recall = static_cast<float64_t>(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 std::vector<tGroundtruth> & gt,
  const std::vector<tDetection> & det, std::vector<int32_t> & ignored_gt,
  std::vector<tGroundtruth> & dc, std::vector<int32_t> & ignored_det, int32_t & n_gt,
  DIFFICULTY difficulty)
{
  // extract ground truth bounding boxes for current evaluation class
  for (uint32_t i = 0; i < gt.size(); i++) {
    // only bounding boxes with a minimum height are used for evaluation
    float64_t 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;
    const char8_t * class_name = CLASS_NAMES[static_cast<size_t>(current_class)].c_str();
    const char8_t * box_type = gt[i].box.type.c_str();

    // all classes without a neighboring class
    if (!strcasecmp(box_type, class_name)) {
      valid_class = 1;
    } else if (!strcasecmp(class_name, "Pedestrian") && !strcasecmp("Person_sitting", box_type)) {
      // classes with a neighboring class
      valid_class = 0;
    } else if (!strcasecmp(class_name, "Car") && !strcasecmp("Van", box_type)) {
      valid_class = 0;
    } else {
      // classes not used for evaluation
      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)
    bool8_t ignore = false;
    if (gt[i].occlusion > MAX_OCCLUSION[static_cast<size_t>(difficulty)] ||
      gt[i].truncation > MAX_TRUNCATION[static_cast<size_t>(difficulty)] ||
      height <= MIN_HEIGHT[static_cast<size_t>(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++;
    } else if (valid_class == 0 || (ignore && valid_class == 1)) {
      // neighboring class, or current class but ignored
      ignored_gt.push_back(1);
    } else {
      // all other classes which are FN in the evaluation
      ignored_gt.push_back(-1);
    }
  }

  // extract dontcare areas
  for (uint32_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 (uint32_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[static_cast<size_t>(current_class)].c_str()))
    {
      valid_class = 1;
    } else {
      valid_class = -1;
    }

    auto height = fabs(det[i].box.y1 - det[i].box.y2);

    // set ignored vector for detections
    if (height < MIN_HEIGHT[static_cast<size_t>(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 std::vector<tGroundtruth> & gt,
  const std::vector<tDetection> & det, const std::vector<tGroundtruth> & dc,
  const std::vector<int32_t> & ignored_gt, const std::vector<int32_t> & ignored_det,
  bool8_t compute_fp, float64_t (* boxoverlap)(tDetection, tGroundtruth, int32_t),
  METRIC metric, bool8_t compute_aos = false, float64_t thresh = 0)
{
  tPrData stat = tPrData();
  const float64_t NO_DETECTION = -10000000;
  // holds angular difference for TPs (needed for AOS evaluation)
  std::vector<float64_t> delta;
  // holds wether a detection was assigned to a valid or ignored ground truth
  std::vector<bool8_t> assigned_detection;
  assigned_detection.assign(det.size(), false);
  std::vector<bool8_t> ignored_threshold;
  // holds detections with a threshold lower than thresh if FP are computed
  ignored_threshold.assign(det.size(), false);

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

  // evaluate all ground truth boxes
  for (uint32_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))
    =======================================================================*/
    uint32_t det_idx = 0;
    float64_t valid_detection = NO_DETECTION;
    float64_t max_overlap = 0;

    // search for a possible detection
    bool8_t assigned_ignored_det = false;
    for (uint32_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
      float64_t overlap = boxoverlap(det[j], gt[i], -1);

      // for computing recall thresholds, the candidate with highest score is considered
      if (!compute_fp &&
        overlap > MIN_OVERLAP[static_cast<size_t>(metric)][static_cast<size_t>(current_class)] &&
        det[j].thresh > valid_detection)
      {
        det_idx = j;
        valid_detection = det[j].thresh;
      } else if (compute_fp &&  // NOLINT
        overlap > MIN_OVERLAP[static_cast<size_t>(metric)][static_cast<size_t>(current_class)] &&
        (overlap > max_overlap || assigned_ignored_det) && ignored_det[j] == 0)
      {
        // 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
        max_overlap = overlap;
        det_idx = j;
        valid_detection = 1;
        assigned_ignored_det = false;
      } else if (compute_fp &&  // NOLINT
        overlap > MIN_OVERLAP[static_cast<size_t>(metric)][static_cast<size_t>(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++;
    } else if (valid_detection != NO_DETECTION &&  // NOLINT
      (ignored_gt[i] == 1 || ignored_det[det_idx] == 1))
    {
      // only evaluate valid ground truth <=> detection assignments (considering difficulty level)
      assigned_detection[det_idx] = true;
    } else if (valid_detection != NO_DETECTION) {
      // found a valid true positive
      // 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 (uint32_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 (uint32_t i = 0; i < dc.size(); i++) {
      for (uint32_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
        float64_t overlap = boxoverlap(det[j], dc[i], 0);
        auto metric_s = static_cast<size_t>(metric);
        auto current_class_s = static_cast<size_t>(current_class);
        if (overlap > MIN_OVERLAP[metric_s][current_class_s]) {
          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) {
      std::vector<float64_t> tmp;

      // FP have a similarity of 0, for all TP compute AOS
      tmp.assign(static_cast<size_t>(stat.fp), 0);
      for (uint32_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() == static_cast<size_t>(stat.fp + stat.tp));
      assert(delta.size() == static_cast<size_t>(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);
      } else {
        // there was neither a FP nor a TP, so the similarity is ignored in the evaluation
        stat.similarity = -1;
      }
    }
  }
  return stat;
}

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

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

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

  // for all test images do
  for (uint32_t i = 0; i < groundtruth.size(); i++) {
    // holds ignored ground truth, ignored detections and dontcare areas for current frame
    std::vector<int32_t> i_gt, i_det;
    std::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 (uint32_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
  std::vector<tPrData> pr;
  pr.assign(thresholds.size(), tPrData());
  for (uint32_t i = 0; i < groundtruth.size(); i++) {
    // for all scores/recall thresholds do:
    for (uint32_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]);

      // 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
  std::vector<float64_t> recall;
  precision.assign(N_SAMPLE_PTS, 0);
  if (compute_aos) {
    aos.assign(N_SAMPLE_PTS, 0);
  }
  float64_t r = 0;
  for (uint32_t i = 0; i < thresholds.size(); i++) {
    r = pr[i].tp / static_cast<float64_t>(pr[i].tp + pr[i].fn);
    recall.push_back(r);
    precision[i] = pr[i].tp / static_cast<float64_t>(pr[i].tp + pr[i].fp);
    if (compute_aos) {
      aos[i] = pr[i].similarity / static_cast<float64_t>(pr[i].tp + pr[i].fp);
    }
  }

  // filter precision and AOS using max_{i..end}(precision)
  for (uint32_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(
  std::basic_string<char8_t> dir_name, std::basic_string<char8_t> file_name,
  std::basic_string<char8_t> obj_type, std::vector<float64_t> vals[], bool8_t is_aos)
{
  // 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 (size_t i = 0; i < static_cast<size_t>(N_SAMPLE_PTS); i++) {
    fprintf(
      fp, "%f %f %f %f\n", static_cast<float64_t>(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++) {
    std::basic_string<char8_t> type = "_png";

    if (j != 0) {
      type = "_pdf";
    }
    // open file
    FILE * fp = fopen((dir_name + "/" + file_name + type + ".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] = static_cast<char8_t>(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);
  }
}

namespace kittisdk
{

extern "C"
int32_t eval(
  std::basic_string<char8_t> ground_truth_path, std::basic_string<char8_t> detection_path,
  std::basic_string<char8_t> output_path, bool8_t eval_2d_res, bool8_t eval_ground_red,
  bool8_t eval_3d_res, bool8_t print_stdout, bool8_t create_plot, int32_t n_testimages)
{
  // set some global parameters
  initGlobals();

  // ground truth and result directories
  std::basic_string<char8_t> result_dir = output_path;
  std::basic_string<char8_t> plot_dir = result_dir + "/plot";

  // Check folder existance
  if (!ghc::filesystem::is_directory(ghc::filesystem::path(ground_truth_path)) ||
    !ghc::filesystem::is_directory(ghc::filesystem::path(detection_path)) ||
    !ghc::filesystem::is_directory(ghc::filesystem::path(result_dir)) )
  {
    if (print_stdout) {
      printf("Folder structure error, please check the existance of: \n");
      printf("%s\n", ground_truth_path.c_str());
      printf("%s\n", detection_path.c_str());
      printf("%s\n", result_dir.c_str());
    }
    return 0;
  }

  if (create_plot && !ghc::filesystem::is_directory(ghc::filesystem::path(plot_dir))) {
    printf("Folder structure error, please check the existance of: \n");
    printf("%s\n", plot_dir.c_str());
  }

  // hold detections and ground truth in memory
  std::vector<std::vector<tGroundtruth>> groundtruth;
  std::vector<std::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
  bool8_t compute_aos = true;
  std::vector<bool8_t> eval_image(NUM_CLASS, false);
  std::vector<bool8_t> eval_ground(NUM_CLASS, false);
  std::vector<bool8_t> eval_3d(NUM_CLASS, false);

  // for all images read groundtruth and detections
  if (print_stdout) {
    printf("Loading detections...\n");
  }

  for (int32_t i = 0; i < n_testimages; i++) {
    // file name
    char8_t file_name[256];
    snprintf(file_name, sizeof(file_name), "%06d.txt", i);

    // read ground truth and result poses
    bool8_t gt_success, det_success;
    std::vector<tGroundtruth> gt = loadGroundtruth(ground_truth_path + "/" + file_name, gt_success);
    std::vector<tDetection> det = loadDetections(
      detection_path + "/" + 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) {
      if (print_stdout) {
        printf("ERROR: Couldn't read: %s of ground truth.\n", file_name);
      }
      return 0;
    }
    if (!det_success) {
      if (print_stdout) {
        printf("ERROR: Couldn't read: %s\n", file_name);
      }
      return 0;
    }
  }
  if (print_stdout) {
    printf("  done.\n");
  }

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

  if (eval_2d_res) {
    // eval image 2D bounding boxes
    for (size_t c = 0; c < static_cast<size_t>(NUM_CLASS); c++) {
      CLASSES cls = (CLASSES)c;

      if (eval_image[c]) {
        if (print_stdout) {
          printf("Starting 2D evaluation (%s) ...\n", CLASS_NAMES[c].c_str());
        }
        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");
        }
        std::vector<float64_t> precision[3], aos[3];
        if (!eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap,
            precision[0], aos[0], DIFFICULTY::EASY, METRIC::IMAGE) ||
          !eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap,
            precision[1], aos[1], DIFFICULTY::MODERATE, METRIC::IMAGE) ||
          !eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, imageBoxOverlap,
            precision[2], aos[2], DIFFICULTY::HARD, METRIC::IMAGE))
        {
          if (print_stdout) {
            printf("%s evaluation failed.\n", CLASS_NAMES[c].c_str());
          }
          return 0;
        }
        fclose(fp_det);
        if (create_plot) {
          saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_detection", CLASS_NAMES[c], precision, 0);
        }
        if (compute_aos) {
          if (create_plot) {
            saveAndPlotPlots(plot_dir, CLASS_NAMES[c] + "_orientation", CLASS_NAMES[c], aos, 1);
          }
          fclose(fp_ori);
        }
        if (print_stdout) {
          printf("  done.\n");
        }
      }
    }
  }

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

  if (eval_ground_red) {
    // eval bird's eye view bounding boxes
    for (size_t c = 0; c < static_cast<size_t>(NUM_CLASS); c++) {
      CLASSES cls = (CLASSES)c;

      if (eval_ground[c]) {
        if (print_stdout) {
          printf("Starting bird's eye evaluation (%s) ...\n", CLASS_NAMES[c].c_str());
        }
        fp_det = fopen(
          (result_dir + "/stats_" + CLASS_NAMES[c] + "_detection_ground.txt").c_str(), "w");
        std::vector<float64_t> precision[3], aos[3];
        if (!eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap,
            precision[0], aos[0], DIFFICULTY::EASY, METRIC::GROUND) ||
          !eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap,
            precision[1], aos[1], DIFFICULTY::MODERATE, METRIC::GROUND) ||
          !eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, groundBoxOverlap,
            precision[2], aos[2], DIFFICULTY::HARD, METRIC::GROUND))
        {
          if (print_stdout) {
            printf("%s evaluation failed.\n", CLASS_NAMES[c].c_str());
          }
          return 0;
        }
        fclose(fp_det);
        if (create_plot) {
          saveAndPlotPlots(
            plot_dir, CLASS_NAMES[c] + "_detection_ground", CLASS_NAMES[c],
            precision, 0);
        }
        if (print_stdout) {
          printf("  done.\n");
        }
      }
    }
  }

  if (eval_3d_res) {
    // eval 3D bounding boxes
    for (size_t c = 0; c < static_cast<size_t>(NUM_CLASS); c++) {
      CLASSES cls = (CLASSES)c;

      if (eval_3d[c]) {
        if (print_stdout) {
          printf("Starting 3D evaluation (%s) ...\n", CLASS_NAMES[c].c_str());
        }
        fp_det =
          fopen((result_dir + "/stats_" + CLASS_NAMES[c] + "_detection_3d.txt").c_str(), "w");
        std::vector<float64_t> precision[3], aos[3];
        if (!eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap,
            precision[0], aos[0], DIFFICULTY::EASY, METRIC::BOX3D) ||
          !eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap,
            precision[1], aos[1], DIFFICULTY::MODERATE, METRIC::BOX3D) ||
          !eval_class(
            fp_det, fp_ori, cls, groundtruth, detections, compute_aos, box3DOverlap,
            precision[2], aos[2], DIFFICULTY::HARD, METRIC::BOX3D))
        {
          if (print_stdout) {
            printf("%s evaluation failed.\n", CLASS_NAMES[c].c_str());
          }
          return 0;
        }
        fclose(fp_det);
        if (create_plot) {
          saveAndPlotPlots(
            plot_dir, CLASS_NAMES[c] + "_detection_3d", CLASS_NAMES[c], precision,
            0);
        }
        if (print_stdout) {
          printf("  done.\n");
        }
      }
    }
  }

  // success
  return 1;
}

}  // namespace kittisdk


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2015 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2015 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2015 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to 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 BOOST_GEOMETRY_ALGORITHMS_SIMPLIFY_HPP
#define BOOST_GEOMETRY_ALGORITHMS_SIMPLIFY_HPP

#include <cstddef>

#include <boost/core/ignore_unused.hpp>
#include <boost/range.hpp>

#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant/variant_fwd.hpp>

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/exterior_ring.hpp>
#include <boost/geometry/core/interior_rings.hpp>
#include <boost/geometry/core/mutable_range.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/geometries/concepts/check.hpp>
#include <boost/geometry/strategies/agnostic/simplify_douglas_peucker.hpp>
#include <boost/geometry/strategies/concepts/simplify_concept.hpp>
#include <boost/geometry/strategies/default_strategy.hpp>
#include <boost/geometry/strategies/distance.hpp>

#include <boost/geometry/algorithms/area.hpp>
#include <boost/geometry/algorithms/clear.hpp>
#include <boost/geometry/algorithms/convert.hpp>
#include <boost/geometry/algorithms/detail/equals/point_point.hpp>
#include <boost/geometry/algorithms/not_implemented.hpp>
#include <boost/geometry/algorithms/is_empty.hpp>
#include <boost/geometry/algorithms/perimeter.hpp>

#include <boost/geometry/algorithms/detail/distance/default_strategies.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace simplify
{

template <typename Range>
inline bool is_degenerate(Range const& range)
{
    return boost::size(range) == 2
        && detail::equals::equals_point_point(geometry::range::front(range),
                                              geometry::range::back(range));
}

struct simplify_range_insert
{
    template<typename Range, typename Strategy, typename OutputIterator, typename Distance>
    static inline void apply(Range const& range, OutputIterator out,
                             Distance const& max_distance, Strategy const& strategy)
    {
        boost::ignore_unused(strategy);

        if (is_degenerate(range))
        {
            std::copy(boost::begin(range), boost::begin(range) + 1, out);
        }
        else if (boost::size(range) <= 2 || max_distance < 0)
        {
            std::copy(boost::begin(range), boost::end(range), out);
        }
        else
        {
            strategy.apply(range, out, max_distance);
        }
    }
};

struct simplify_copy
{
    template <typename RangeIn, typename RangeOut, typename Strategy, typename Distance>
    static inline void apply(RangeIn const& range, RangeOut& out,
                             Distance const& , Strategy const& )
    {
        std::copy
            (
                boost::begin(range), boost::end(range),
                    geometry::range::back_inserter(out)
            );
    }
};

template <std::size_t MinimumToUseStrategy>
struct simplify_range
{
    template <typename RangeIn, typename RangeOut, typename Strategy, typename Distance>
    static inline void apply(RangeIn const& range, RangeOut& out,
                    Distance const& max_distance, Strategy const& strategy)
    {
        // For a RING:
        // Note that, especially if max_distance is too large,
        // the output ring might be self intersecting while the input ring is
        // not, although chances are low in normal polygons

        if (boost::size(range) <= MinimumToUseStrategy || max_distance < 0)
        {
            simplify_copy::apply(range, out, max_distance, strategy);
        }
        else
        {
            simplify_range_insert::apply
                (
                    range, geometry::range::back_inserter(out), max_distance, strategy
                );
        }

        // Verify the two remaining points are equal. If so, remove one of them.
        // This can cause the output being under the minimum size
        if (is_degenerate(out))
        {
            range::resize(out, 1);
        }
    }
};

struct simplify_ring
{
private :
    template <typename Area>
    static inline int area_sign(Area const& area)
    {
        return area > 0 ? 1 : area < 0 ? -1 : 0;
    }

    template <typename Strategy, typename Ring>
    static std::size_t get_opposite(std::size_t index, Ring const& ring)
    {
        typename Strategy::distance_strategy_type distance_strategy;

        // Verify if it is NOT the case that all points are less than the
        // simplifying distance. If so, output is empty.
        typename Strategy::distance_type max_distance(-1);

        typename geometry::point_type<Ring>::type point = range::at(ring, index);
        std::size_t i = 0;
        for (typename boost::range_iterator<Ring const>::type
                it = boost::begin(ring); it != boost::end(ring); ++it, ++i)
        {
            // This actually is point-segment distance but will result
            // in point-point distance
            typename Strategy::distance_type dist = distance_strategy.apply(*it, point, point);
            if (dist > max_distance)
            {
                max_distance = dist;
                index = i;
            }
        }
        return index;
    }

public :
    template <typename Ring, typename Strategy, typename Distance>
    static inline void apply(Ring const& ring, Ring& out,
                    Distance const& max_distance, Strategy const& strategy)
    {
        std::size_t const size = boost::size(ring);
        if (size == 0)
        {
            return;
        }

        int const input_sign = area_sign(geometry::area(ring));

        std::set<std::size_t> visited_indexes;

        // Rotate it into a copied vector
        // (vector, because source type might not support rotation)
        // (duplicate end point will be simplified away)
        typedef typename geometry::point_type<Ring>::type point_type;

        std::vector<point_type> rotated(size);

        // Closing point (but it will not start here)
        std::size_t index = 0;

        // Iterate (usually one iteration is enough)
        for (std::size_t iteration = 0; iteration < 4u; iteration++)
        {
            // Always take the opposite. Opposite guarantees that no point
            // "halfway" is chosen, creating an artefact (very narrow triangle)
            // Iteration 0: opposite to closing point (1/2, = on convex hull)
            //              (this will start simplification with that point
            //               and its opposite ~0)
            // Iteration 1: move a quarter on that ring, then opposite to 1/4
            //              (with its opposite 3/4)
            // Iteration 2: move an eight on that ring, then opposite (1/8)
            // Iteration 3: again move a quarter, then opposite (7/8)
            // So finally 8 "sides" of the ring have been examined (if it were
            // a semi-circle). Most probably, there are only 0 or 1 iterations.
            switch (iteration)
            {
                case 1 : index = (index + size / 4) % size; break;
                case 2 : index = (index + size / 8) % size; break;
                case 3 : index = (index + size / 4) % size; break;
            }
            index = get_opposite<Strategy>(index, ring);

            if (visited_indexes.count(index) > 0)
            {
                // Avoid trying the same starting point more than once
                continue;
            }

            std::rotate_copy(boost::begin(ring), range::pos(ring, index),
                             boost::end(ring), rotated.begin());

            // Close the rotated copy
            rotated.push_back(range::at(ring, index));

            simplify_range<0>::apply(rotated, out, max_distance, strategy);

            // Verify that what was positive, stays positive (or goes to 0)
            // and what was negative stays negative (or goes to 0)
            int const output_sign = area_sign(geometry::area(out));
            if (output_sign == input_sign)
            {
                // Result is considered as satisfactory (usually this is the
                // first iteration - only for small rings, having a scale
                // similar to simplify_distance, next iterations are tried
                return;
            }

            // Original is simplified away. Possibly there is a solution
            // when another starting point is used
            geometry::clear(out);

            if (iteration == 0
                && geometry::perimeter(ring) < 3 * max_distance)
            {
                // Check if it is useful to iterate. A minimal triangle has a
                // perimeter of a bit more than 3 times the simplify distance
                return;
            }

            // Prepare next try
            visited_indexes.insert(index);
            rotated.resize(size);
        }
    }
};

struct simplify_polygon
{
private:

    template
    <
        typename IteratorIn,
        typename InteriorRingsOut,
        typename Distance,
        typename Strategy
    >
    static inline void iterate(IteratorIn begin, IteratorIn end,
                    InteriorRingsOut& interior_rings_out,
                    Distance const& max_distance, Strategy const& strategy)
    {
        typedef typename boost::range_value<InteriorRingsOut>::type single_type;
        for (IteratorIn it = begin; it != end; ++it)
        {
            single_type out;
            simplify_ring::apply(*it, out, max_distance, strategy);
            if (! geometry::is_empty(out))
            {
                range::push_back(interior_rings_out, out);
            }
        }
    }

    template
    <
        typename InteriorRingsIn,
        typename InteriorRingsOut,
        typename Distance,
        typename Strategy
    >
    static inline void apply_interior_rings(
                    InteriorRingsIn const& interior_rings_in,
                    InteriorRingsOut& interior_rings_out,
                    Distance const& max_distance, Strategy const& strategy)
    {
        range::clear(interior_rings_out);

        iterate(
            boost::begin(interior_rings_in), boost::end(interior_rings_in),
            interior_rings_out,
            max_distance, strategy);
    }

public:
    template <typename Polygon, typename Strategy, typename Distance>
    static inline void apply(Polygon const& poly_in, Polygon& poly_out,
                    Distance const& max_distance, Strategy const& strategy)
    {
        // Note that if there are inner rings, and distance is too large,
        // they might intersect with the outer ring in the output,
        // while it didn't in the input.
        simplify_ring::apply(exterior_ring(poly_in), exterior_ring(poly_out),
            max_distance, strategy);

        apply_interior_rings(interior_rings(poly_in),
            interior_rings(poly_out), max_distance, strategy);
    }
};

template<typename Policy>
struct simplify_multi
{
    template <typename MultiGeometry, typename Strategy, typename Distance>
    static inline void apply(MultiGeometry const& multi, MultiGeometry& out,
                    Distance const& max_distance, Strategy const& strategy)
    {
        range::clear(out);

        typedef typename boost::range_value<MultiGeometry>::type single_type;

        for (typename boost::range_iterator<MultiGeometry const>::type
                it = boost::begin(multi); it != boost::end(multi); ++it)
        {
            single_type single_out;
            Policy::apply(*it, single_out, max_distance, strategy);
            if (! geometry::is_empty(single_out))
            {
                range::push_back(out, single_out);
            }
        }
    }
};

}} // namespace detail::simplify
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template
<
    typename Geometry,
    typename Tag = typename tag<Geometry>::type
>
struct simplify: not_implemented<Tag>
{};

template <typename Point>
struct simplify<Point, point_tag>
{
    template <typename Distance, typename Strategy>
    static inline void apply(Point const& point, Point& out,
                    Distance const& , Strategy const& )
    {
        geometry::convert(point, out);
    }
};

// Linestring, keep 2 points (unless those points are the same)
template <typename Linestring>
struct simplify<Linestring, linestring_tag>
    : detail::simplify::simplify_range<2>
{};

template <typename Ring>
struct simplify<Ring, ring_tag>
    : detail::simplify::simplify_ring
{};

template <typename Polygon>
struct simplify<Polygon, polygon_tag>
    : detail::simplify::simplify_polygon
{};

template
<
    typename Geometry,
    typename Tag = typename tag<Geometry>::type
>
struct simplify_insert: not_implemented<Tag>
{};

template <typename Linestring>
struct simplify_insert<Linestring, linestring_tag>
    : detail::simplify::simplify_range_insert
{};

template <typename Ring>
struct simplify_insert<Ring, ring_tag>
    : detail::simplify::simplify_range_insert
{};

template <typename MultiPoint>
struct simplify<MultiPoint, multi_point_tag>
    : detail::simplify::simplify_copy
{};

template <typename MultiLinestring>
struct simplify<MultiLinestring, multi_linestring_tag>
    : detail::simplify::simplify_multi<detail::simplify::simplify_range<2> >
{};

template <typename MultiPolygon>
struct simplify<MultiPolygon, multi_polygon_tag>
    : detail::simplify::simplify_multi<detail::simplify::simplify_polygon>
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

namespace resolve_strategy
{

struct simplify
{
    template <typename Geometry, typename Distance, typename Strategy>
    static inline void apply(Geometry const& geometry,
                             Geometry& out,
                             Distance const& max_distance,
                             Strategy const& strategy)
    {
        dispatch::simplify<Geometry>::apply(geometry, out, max_distance, strategy);
    }

    template <typename Geometry, typename Distance>
    static inline void apply(Geometry const& geometry,
                             Geometry& out,
                             Distance const& max_distance,
                             default_strategy)
    {
        typedef typename point_type<Geometry>::type point_type;

        typedef typename strategy::distance::services::default_strategy
        <
            point_tag, segment_tag, point_type
        >::type ds_strategy_type;

        typedef strategy::simplify::douglas_peucker
        <
            point_type, ds_strategy_type
        > strategy_type;

        BOOST_CONCEPT_ASSERT(
            (concepts::SimplifyStrategy<strategy_type, point_type>)
        );

        apply(geometry, out, max_distance, strategy_type());
    }
};

struct simplify_insert
{
    template
    <
        typename Geometry,
        typename OutputIterator,
        typename Distance,
        typename Strategy
    >
    static inline void apply(Geometry const& geometry,
                             OutputIterator& out,
                             Distance const& max_distance,
                             Strategy const& strategy)
    {
        dispatch::simplify_insert<Geometry>::apply(geometry, out, max_distance, strategy);
    }

    template <typename Geometry, typename OutputIterator, typename Distance>
    static inline void apply(Geometry const& geometry,
                             OutputIterator& out,
                             Distance const& max_distance,
                             default_strategy)
    {
        typedef typename point_type<Geometry>::type point_type;

        typedef typename strategy::distance::services::default_strategy
        <
            point_tag, segment_tag, point_type
        >::type ds_strategy_type;

        typedef strategy::simplify::douglas_peucker
        <
            point_type, ds_strategy_type
        > strategy_type;

        BOOST_CONCEPT_ASSERT(
            (concepts::SimplifyStrategy<strategy_type, point_type>)
        );

        apply(geometry, out, max_distance, strategy_type());
    }
};

} // namespace resolve_strategy

namespace resolve_variant {

template <typename Geometry>
struct simplify
{
    template <typename Distance, typename Strategy>
    static inline void apply(Geometry const& geometry,
                             Geometry& out,
                             Distance const& max_distance,
                             Strategy const& strategy)
    {
        resolve_strategy::simplify::apply(geometry, out, max_distance, strategy);
    }
};

template <BOOST_VARIANT_ENUM_PARAMS(typename T)>
struct simplify<boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> >
{
    template <typename Distance, typename Strategy>
    struct visitor: boost::static_visitor<void>
    {
        Distance const& m_max_distance;
        Strategy const& m_strategy;

        visitor(Distance const& max_distance, Strategy const& strategy)
            : m_max_distance(max_distance)
            , m_strategy(strategy)
        {}

        template <typename Geometry>
        void operator()(Geometry const& geometry, Geometry& out) const
        {
            simplify<Geometry>::apply(geometry, out, m_max_distance, m_strategy);
        }
    };

    template <typename Distance, typename Strategy>
    static inline void
    apply(boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> const& geometry,
          boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>& out,
          Distance const& max_distance,
          Strategy const& strategy)
    {
        boost::apply_visitor(
            visitor<Distance, Strategy>(max_distance, strategy),
            geometry,
            out
        );
    }
};

} // namespace resolve_variant

/*!
\brief Simplify a geometry using a specified strategy
\ingroup simplify
\tparam Geometry \tparam_geometry
\tparam Distance A numerical distance measure
\tparam Strategy A type fulfilling a SimplifyStrategy concept
\param strategy A strategy to calculate simplification
\param geometry input geometry, to be simplified
\param out output geometry, simplified version of the input geometry
\param max_distance distance (in units of input coordinates) of a vertex
    to other segments to be removed
\param strategy simplify strategy to be used for simplification, might
    include point-distance strategy

\image html svg_simplify_country.png "The image below presents the simplified country"
\qbk{distinguish,with strategy}
*/
template<typename Geometry, typename Distance, typename Strategy>
inline void simplify(Geometry const& geometry, Geometry& out,
                     Distance const& max_distance, Strategy const& strategy)
{
    concepts::check<Geometry>();

    geometry::clear(out);

    resolve_variant::simplify<Geometry>::apply(geometry, out, max_distance, strategy);
}

/*!
\brief Simplify a geometry
\ingroup simplify
\tparam Geometry \tparam_geometry
\tparam Distance \tparam_numeric
\note This version of simplify simplifies a geometry using the default
    strategy (Douglas Peucker),
\param geometry input geometry, to be simplified
\param out output geometry, simplified version of the input geometry
\param max_distance distance (in units of input coordinates) of a vertex
    to other segments to be removed

\qbk{[include reference/algorithms/simplify.qbk]}
 */
template<typename Geometry, typename Distance>
inline void simplify(Geometry const& geometry, Geometry& out,
                     Distance const& max_distance)
{
    concepts::check<Geometry>();

    geometry::simplify(geometry, out, max_distance, default_strategy());
}

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace simplify
{

/*!
\brief Simplify a geometry, using an output iterator
    and a specified strategy
\ingroup simplify
\tparam Geometry \tparam_geometry
\param geometry input geometry, to be simplified
\param out output iterator, outputs all simplified points
\param max_distance distance (in units of input coordinates) of a vertex
    to other segments to be removed
\param strategy simplify strategy to be used for simplification,
    might include point-distance strategy

\qbk{distinguish,with strategy}
\qbk{[include reference/algorithms/simplify.qbk]}
*/
template<typename Geometry, typename OutputIterator, typename Distance, typename Strategy>
inline void simplify_insert(Geometry const& geometry, OutputIterator out,
                            Distance const& max_distance, Strategy const& strategy)
{
    concepts::check<Geometry const>();

    resolve_strategy::simplify_insert::apply(geometry, out, max_distance, strategy);
}

/*!
\brief Simplify a geometry, using an output iterator
\ingroup simplify
\tparam Geometry \tparam_geometry
\param geometry input geometry, to be simplified
\param out output iterator, outputs all simplified points
\param max_distance distance (in units of input coordinates) of a vertex
    to other segments to be removed

\qbk{[include reference/algorithms/simplify_insert.qbk]}
 */
template<typename Geometry, typename OutputIterator, typename Distance>
inline void simplify_insert(Geometry const& geometry, OutputIterator out,
                            Distance const& max_distance)
{
    // Concept: output point type = point type of input geometry
    concepts::check<Geometry const>();
    concepts::check<typename point_type<Geometry>::type>();

    simplify_insert(geometry, out, max_distance, default_strategy());
}

}} // namespace detail::simplify
#endif // DOXYGEN_NO_DETAIL

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_SIMPLIFY_HPP


#include <cstdlib>
#include <list>
#include <string>
#include <iostream>
#include <map>
#include <regex>
#include <xercesc/dom/DOM.hpp>
#include <boost/algorithm/string/trim.hpp>
#include "Error.h"
#include "Array.h"
#include "cstringtools.h"
#include "BitArray.h"
#include "charptr_map.h"

#include "Pool.h"
#include "InputPool.h"

#include "UnicodeTools.h"
#include "XMLException.h"
#include "XMLElement.h"

#include "FluxMLContentObject.h"
#include "FluxMLUnicodeConstants.h"
#include "FluxMLInput.h"

#include "FluxMLDocument.h"

#include "ExprTree.h"
#include "LinearExpression.h"

#include "MathML.h"
#include "MathMLDocument.h"
#include "MathMLDeclare.h"
#include "MathMLExpression.h"

// Xerces C++ Namespace einbinden
XERCES_CPP_NAMESPACE_USE

using namespace flux::symb;

namespace flux {
namespace xml {

/**
 * Parst ein Input-Element aus einem DOM-Tree eines FluxML-Dokuments.
 *
 * @param node Knoten des Input-Elements
 */
void FluxMLInput::parseInput(DOMNode * input)
{
	DOMNode * label;
	DOMNamedNodeMap * nnm;
	DOMAttr * poolAttr, * typeAttr, * cfgAttr, * purityAttr, * costAttr, * idAttr;
        /*** Neue Prfoile-Version **/
        /** List der Bedingungen des Profiles */
	std::list<double> profile_conditions_;
        DOMAttr *profileAttr;
        profile_defined=false;
        /*** Ende: Neue Prfoile-Version **/
	int natoms = 0;
	double cost;
        Array< double > purity;
	charptr_map< Array< double > > values;
        charptr_map< Array< double > > purities;
        charptr_map< double > costs;
        charptr_map< data::InputProfile> profiles;
	BitArray mask;
	data::InputPool::Type ip_type =
        data::InputPool::ip_isotopomer; // "isotopomer" ist default

	if (input_pool_)
		fTHROW(XMLException,input,"FluxMLInput object already initialized!");

	if (not XMLElement::match(input,fml_input,fml_xmlns_uri))
		fTHROW(XMLException,input,"element node (input) expected.");

	nnm = input->getAttributes();
	if (nnm == 0)
		fTHROW(XMLException,input,"element node (input) lacks attributes.");

	// Attribut pool (Pool-Bezeichnung)
	poolAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_pool));
	if (poolAttr == 0)
		fTHROW(XMLException,input,"element node (input) lacks pool attribute.");
	U2A utf_pool_name(poolAttr->getValue());

	// Gibt es den Pool?
	data::Pool * ipool = lookupPool(utf_pool_name);
	if (ipool == 0)
	{
		fTHROW(XMLException,input,
				"unknown pool %s in input substrate spec",
				(char const*)utf_pool_name);
	}
        data::Configuration * cfg = doc_->getRootConfiguration();
	std::list< data::Constraint > const & eqConstraints = cfg->getEqualities();
	std::list< data::Constraint >::const_iterator eqConstraints_it;

	for (eqConstraints_it=eqConstraints.begin(); eqConstraints_it!=eqConstraints.end(); eqConstraints_it++)
	{
		charptr_array constraintsVars = eqConstraints_it->getConstraint()->getVarNames();
		if(constraintsVars.find((char const*)utf_pool_name)!=constraintsVars.end())
			fTHROW(XMLException,input,
				"input pool \"%s\" specified in equality constraint",
				(char const*)utf_pool_name);
	}

	std::list< data::Constraint > const & ineqConstraints = cfg->getInEqualities();
	std::list< data::Constraint >::const_iterator ineqConstraints_it;

	for (ineqConstraints_it=ineqConstraints.begin(); ineqConstraints_it!=ineqConstraints.end(); ineqConstraints_it++)
	{
		charptr_array constraintsVars = ineqConstraints_it->getConstraint()->getVarNames();
		if(constraintsVars.find((char const*)utf_pool_name)!=constraintsVars.end())
			fTHROW(XMLException,input,
				"input pool \"%s\" specified in inequality constraint",
				(char const*)utf_pool_name);
	}
	// Anzahl der Atome / Init. der Maske
	natoms = ipool->getNumAtoms();
	mask.resize(natoms,false);

	// Attribut id (Experimental Design)
	idAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_id));
	U2A utf_id(idAttr ? idAttr->getValue() : 0);

	// Attribut type (isotopomer/cumomer/emu)
	typeAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_type));
	if (typeAttr != 0)
	{
		if (XMLString::equals(typeAttr->getValue(), fml_isotopomer))
			ip_type = data::InputPool::ip_isotopomer;
		else if (XMLString::equals(typeAttr->getValue(),fml_cumomer))
			ip_type = data::InputPool::ip_cumomer;
		else if (XMLString::equals(typeAttr->getValue(),fml_emu))
			ip_type = data::InputPool::ip_emu;
		else
		{
			fTHROW(XMLException,input,
				"input pool %s: type is restricted to \"isotopomer\", "
				"\"cumomer\" or \"emu\"",
				ipool->getName());
		}
	}

        // Zweite Version der Input-Proflie definiert über profile-Attribut
        // gilt für alle Labels
	profileAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_profile));
	if (profileAttr != 0)
	{
            // profile definiert
            profile_defined=true;

        // füge den Startpunkt Null zu Bedinngungsliste hinzu
        profile_conditions_.push_back(0.);

        // Profile-Bedingungen parsen
        U2A utf_profile(profileAttr->getValue());
        charptr_array expr_list = charptr_array::split(utf_profile,",");
        charptr_array::const_iterator i;
        double prv_val= 0., act_val=0.;

        for (i=expr_list.begin(); i!=expr_list.end(); i++)
        {
            char const * w;
            for (w=*i; *w!='\0'; w++)
                if (*w > 32) break;
            if (*w == '\0')
                continue;

            act_val= std::atof(*i);

            // Überprüfung der INF und Null-Zeitangabe
            if(act_val==0. or std::isinf(act_val))
                fTHROW(XMLException,input,
                       "parse error in input profile attribute (%s): "
                       "The timestamp \"t=0\" and \"t=inf\" must not be explicitly stated!", *i);

            // Überprüfung von streng monoton aufsteigender kommagetrennter Zeiten
            if(act_val<= prv_val)
                fTHROW(XMLException,input,
                       "parse error in input profile attribute (%s): "
                       "The timestamps must be specified in a strictly monotonously manner", *i);

            profile_conditions_.push_back(act_val);
            prv_val= act_val;
        }
	} else // Prüfe die Input-Substrate ob es sich um eine Substrate-Profile handelt
    {
        label = XMLElement::skipJunkNodes(input->getFirstChild());
        do
        {
            if(label!= 0)
            {
                DOMNode * child = XMLElement::skipJunkNodes(label->getFirstChild());
                U2A data(static_cast< DOMText* >(child)->getData());
                std::string str= (char * )data;
                if (str.find("t") != std::string::npos)
                {
                    profile_defined=true;

                    // füge den Startpunkt Null zu Bedinngungsliste hinzu
                    profile_conditions_.push_back(0.);

                    break;
                }
            }

            // Next label-Element
            label = XMLElement::nextNode(label);
        }
        while (label != 0);
    }

	label = XMLElement::skipJunkNodes(input->getFirstChild());
	do
	{
		if (not XMLElement::match(label,fml_label,fml_xmlns_uri))
		{
			if (label == 0)
			{
				// keine label-Kinder
				// => pool als natürlich markierten
				// Isotopomer-Pool initialisieren
				input_pool_ = new data::InputPool(
					utf_id,
					ipool->getName(),
					mask,
					data::InputPool::ip_isotopomer
					);
				if (mask.size())
                                {
					// Registrierung von eingesetzten Isotope zur Ermöglichung von
                                        // multi-Isotopic Tracer MFA Simulation,
                                        charptr_map<int> iso_map = ipool->getIsotopesCfg();
                                        if(iso_map.size())
                                        {
                                            input_pool_->setIsotopeCfg(iso_map);
                                            charptr_map<int>::const_iterator ii =iso_map.begin();
                                            std::stringstream iso_elem;
                                            iso_elem<<" for [" <<ii->key;
                                            ii++;
                                            while(ii!=iso_map.end())
                                            {
                                               iso_elem<<", "<< ii->key;
                                               ii++;
                                            }
                                            iso_elem<<"]";
                                            fNOTICE("assuming natural abundance %s in pool \"%s\"", iso_elem.str().c_str(),
                                                    ipool->getName());
                                        }
                                        else
                                            fNOTICE("assuming natural abundance 13C in pool \"%s\"",
                                                    ipool->getName());
                                }
				return;
			}
			else
			{
				fTHROW(XMLException,label,
					"input pool %s: element node (label) expected",
					ipool->getName()
					);
			}
		}

		nnm = label->getAttributes();
		if (nnm == 0)
			fTHROW(XMLException,label,"element node (label) lacks attributes.");

                cfgAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_cfg));
		if (cfgAttr == 0)
			fTHROW(XMLException,label,"element node (label) lacks cfg attribute.");
		U2A utf_cfg(cfgAttr->getValue());

		// das cfg-Attribut muß einem speziellen Format entsprechen
		if (strlen(utf_cfg) != (size_t)natoms)
		{
			fTHROW(XMLException,label,
				"label spec. of pool %s [%s] with wrong size; expected size %i.",
				(char const *)utf_pool_name, (char const*)utf_cfg, natoms);
		}
		for (int i=0; i<natoms; i++)
		{
			switch (ip_type)
			{
			case data::InputPool::ip_cumomer:
			case data::InputPool::ip_emu:
				if (not (((char const*)utf_cfg)[i]=='x' or ((char const *)utf_cfg)[i]=='1'))
				{
					fTHROW(XMLException,label,
						"illegal %s label spec. [%s] for pool %s",
						ip_type == data::InputPool::ip_emu
							? "EMU" : "Cumomer",
						(char const*)utf_cfg,
						(char const*)utf_pool_name
						);
				}
				break;
			case data::InputPool::ip_isotopomer:
				if (not (((char const*)utf_cfg)[i]=='0' or ((char const*)utf_cfg)[i]=='1'))
				{
					fTHROW(XMLException,label,
						"illegal Isotopomer label spec. [%s] for pool %s",
						(char const*)utf_cfg, (char const*)utf_pool_name
						);
				}
			}
		}

		// den Wert aus [0,1] bzw. die Werteliste parsen
		size_t fvalues_len_exp;
		char const * w;

                Array< double > fvalues = parseLabel(static_cast< DOMElement * >(label));

                // Substrate profile soll zunächst nur für Isotopomer unterstützen
                if((ip_type!= data::InputPool::ip_isotopomer) && (profile_defined==true))
                    fTHROW(XMLException,label,
                            "the used substrate profiling in pool \"%s\" is only supported for isotopomer type yet!",
                            (char const*)utf_pool_name);
		switch (ip_type)
		{
                    case data::InputPool::ip_cumomer:
                    case data::InputPool::ip_isotopomer:
                            if ((fvalues.size() != 1) and not profile_defined)
                            {
                                    fTHROW(XMLException,label,
                                            "illegal label spec. (expecting exactly "
                                            "one fraction value)."
                                            );
                            }
                            break;
                    case data::InputPool::ip_emu:
                            for (w = (char const *)utf_cfg, fvalues_len_exp = 1;
                                    *w != '\0'; ++w)
                            {
                                    if (*w == '1') ++fvalues_len_exp;
                            }
                            if (fvalues.size() != fvalues_len_exp)
                            {
                                    fTHROW(XMLException,label,
                                            "invalid number of mass isotopomer fractions; "
                                            "found %i, expected %i",
                                            int(fvalues.size()), int(fvalues_len_exp));
                            }
		}

		// das purity-Attribut
		if ((purityAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_purity))) != 0)
		{
			if (ip_type != data::InputPool::ip_isotopomer)
			{
				fTHROW(XMLException,label,
					"the \"purity\" attribute is allowed only for "
					"isotopomer substrate specifications.");
			}
                        U2A utf_purity(purityAttr->getValue());
                        charptr_array purityList= charptr_array::split(utf_purity," ");
                        size_t i=0,size = purityList.size();
                        if(size!= ipool->getNumOfActiveIsotope())
                            fTHROW(XMLException,label,
                                            "label spec. of pool %s [%s]: illegal number of values "
                                            "in attribute purity: %s",
                                            (char const *)utf_pool_name,
                                            (char const *)utf_cfg,
                                            (char const *)utf_purity
                                            );
                        purity= Array<double>(size);
                        if(size==1)
                        {
                            if (not XMLElement::parseDouble(purityAttr->getValue(),purity[0])
				or (purity[0] < 0. or purity[0] > 1.))
                            {
                                    U2A utf_purity(purityAttr->getValue());
                                    fTHROW(XMLException,label,
                                            "label spec. of pool %s [%s]: illegal value "
                                            "for attribute purity: %s",
                                            (char const *)utf_pool_name,
                                            (char const *)utf_cfg,
                                            (char const *)utf_purity
                                            );
                            }
                        }
                        else if(size>1)
                        {
                            charptr_array::const_iterator pli;
                            for (pli=purityList.begin(), i=0; pli!=purityList.end(); ++pli,++i)
                            {
                                purity[i]= atof(*pli);
                                if ((purity[i] < 0. or purity[i] > 1.))
                                {
                                        U2A utf_purity(purityAttr->getValue());
                                        fTHROW(XMLException,label,
                                                "label spec. of pool %s [%s]: illegal value "
                                                "for attribute purity: %s",
                                                (char const *)utf_pool_name,
                                                (char const *)utf_cfg,
                                                (char const *)utf_purity
                                                );
                                }

                            }
                        }
                        else
                        {
                            fTHROW(XMLException,label,
					"error parsing the \"purity\" attribute.");
                        }
		}
		else
                {
                    purity=Array<double>(1);
                    purity[0]= 2.; // Berechnung abschalten (default)
                }

		// das cost-Attribut
		if ((costAttr = static_cast< DOMAttr* >(nnm->getNamedItem(fml_cost))) != 0)
		{
			if (ip_type != data::InputPool::ip_isotopomer)
			{
				fTHROW(XMLException,label,
					"the \"cost\" attribute is allowed only for "
					"isotopomer substrate specifications.");
			}

			if (not XMLElement::parseDouble(costAttr->getValue(),cost))
			{
				U2A utf_cost(costAttr->getValue());
				fTHROW(XMLException,label,
					"label spec. of pool %s [%s]: illegal value "
					"for attribute cost: %s",
					(char const *)utf_pool_name,
					(char const *)utf_cfg,
					(char const *)utf_cost
				        );
			}
		}
		else cost = 0.; // kostenlos

		// Konfiguration mit Fraction-Wert registrieren
		if (values.exists(utf_cfg))
			fTHROW(XMLException,label,"duplicate specification of fraction %s",
				(char const *)utf_cfg);
		values.insert(utf_cfg,fvalues);
                purities.insert(utf_cfg,purity);
		costs.insert(utf_cfg,cost);
        if(profile_defined)
        {
            /** Check duplicate **/
            if (profiles.exists(utf_cfg))
                fTHROW(XMLException,label,"duplicate profile specification of fraction %s in pool %s",
                       (char const *)utf_cfg, (char const *)utf_pool_name);

            /* add profile conditions to parsed label values*/
            std::list<double>::iterator pc;
            for(pc=profile_conditions_.begin(); pc!=profile_conditions_.end();++pc)
                input_profile_->addCondition(*pc);

            /** Check profile consistency **/
            fWARNING("#cond= %d  vs. #val= %d", int(input_profile_->getConditions().size()), int(input_profile_->getValues().size()));
            if(input_profile_->getConditions().size()!=input_profile_->getValues().size())
                fTHROW(XMLException,label,"The specified profile values of fraction %s and profile conditions are inconsistent with each other!",
                       (char const *)utf_cfg);

            profiles.insert(utf_cfg,*input_profile_);
        }
                // Next Sep-Element
		label = XMLElement::nextNode(label);
	}
	while (label != 0);

	// Konfigurationen der Fractions prüfen und Maske konfigurieren
	charptr_map< Array< double > >::const_iterator vi;
	for (vi=values.begin(); vi!=values.end(); vi++)
	{
            BitArray * vi_cfg = BitArray::parseBin(vi->key);
            fASSERT(vi_cfg);
            vi_cfg->resize(natoms,false);
            mask = mask | (*vi_cfg);
            delete vi_cfg;
	}

	// InputPool-Objekt anlegen
	input_pool_ = new data::InputPool(
			utf_id,
			(char const *)utf_pool_name,
			mask,
			ip_type
			);

	// Fractions eintragen
	for (vi=values.begin(); vi!=values.end(); vi++)
	{
		BitArray * vi_cfg = BitArray::parseBin(vi->key);
		fASSERT(vi_cfg);
		vi_cfg->resize(natoms,false);
		switch (ip_type)
		{
		case data::InputPool::ip_isotopomer:
			input_pool_->setIsotopomerValue(
					*vi_cfg,
					vi->value[0],
					purities[vi->key],
					costs[vi->key]
					);
			break;
		case data::InputPool::ip_cumomer:
			input_pool_->setCumomerValue(
					*vi_cfg,
					vi->value[0]
					);
			break;
		case data::InputPool::ip_emu:
			input_pool_->setEMUValue(
					*vi_cfg,
					vi->value
					);
			break;
		}
		delete vi_cfg;
	}

	// der geparste Pool wird bei der später folgenden Validierung ggfs.
	// von der Isotopomer-Form in die Cumomer-Form konvertiert

        // Profile eintragen
        charptr_map< data::InputProfile >::const_iterator pi;
	for (pi=profiles.begin(); pi!=profiles.end();pi++)
	{
            BitArray * vi_cfg = BitArray::parseBin(pi->key);
            fASSERT(vi_cfg);
            vi_cfg->resize(natoms,false);
                    input_pool_->setInputProfileValue(
                                    *vi_cfg,
                                    pi->value,
                                    purities[pi->key],
                                    costs[pi->key]
                                    );
            delete vi_cfg;
	}

        // Registrierung von Isotope-Konfigurationens zur Ermöglichung von
        // multi-Isotopic Tracer MFA Simulation,
        input_pool_->setIsotopeCfg(ipool->getIsotopesCfg());
}

data::Pool * FluxMLInput::lookupPool(char const * pool_name)
{
	charptr_map< data::Pool* > * pmap = doc_->getPoolMap();
	data::Pool ** pptr = pmap->findPtr(pool_name);
	if (pptr == 0)
		return 0;
	return *pptr;
}

Array< double > FluxMLInput::parseLabel(DOMElement * label)
{
	struct val_list_t
	{
		double value;
		val_list_t * next;

		val_list_t() : value(0),next(0) { }
		~val_list_t() { delete next; }
	} head, * walk;
	bool next_is_sep = false;
        double sum;
	size_t size = 0;

        DOMNode * child = XMLElement::skipJunkNodes(label->getFirstChild());
        walk = &head;
        while (child != 0)
        {
        	if(profile_defined) // parse the profile and skip this while loop
        	{
        		parseProfile(child);
        	}
            // standard labeling specification
        	else if (next_is_sep)
        	{
        		if (not XMLElement::match(child,fml_sep,fml_xmlns_uri))
        			fTHROW(XMLException,child,"element node (sep) expected.");
        	}
        	else
        	{
        		if (child->getNodeType() != DOMNode::TEXT_NODE)
        			fTHROW(XMLException,child,"#PCDATA element expected.");

        		if (not XMLElement::parseDouble(
        				static_cast< DOMText* >(child)->getData(),
						walk->value))
        		{
        			U2A utf_value(static_cast< DOMText* >(child)->getData());
        			fTHROW(XMLException,child,
        					"error parsing fraction value: %s",
							(char const*)utf_value
        			);
        		}

        		if (walk->value < 0. or walk->value > 1.)
        		{
        			U2A utf_value(static_cast< DOMText* >(child)->getData());
        			fTHROW(XMLException,child,
        					"labeling fraction value %s is out of range [0,1]",
							(char const*)utf_value
        			);
        		}

        		++size;
        		walk->next = new val_list_t;
        		walk = walk->next;
        	}
        	next_is_sep = not next_is_sep;
        	child = XMLElement::nextNode(child);
        }

        Array< double >values(size);
        walk = &head;
        sum = 0.;
        for (size_t k=0; k<size; ++k)
        {
        	values[k] = walk->value;
        	sum += walk->value;
        	walk = walk->next;
        }
        if (size > 1 and fabs(1.-sum) > 1e-5)
        {
        	fTHROW(XMLException,label,
        			"labeling fraction values are out of range [0,1]");
        }
        return values;
}

void  FluxMLInput::parseProfile(DOMNode * profile)
{
	input_profile_ =  new data::InputProfile();

	if (XMLElement::match(profile,fml_math,fml_xmlns_mathml_uri))
		parseProfileMathML(profile);
	else if (XMLElement::match(profile,fml_textual,fml_xmlns_uri))
		parseProfileTextual(profile);
	else
		fTHROW(XMLException,profile,
				"values in textual or MathML notation expected");
}

void FluxMLInput::parseProfileMathML(
	DOMNode * node
	)
{
	MathMLDocument * mathml;
	MathMLDeclare const * mathml_def;
	MathMLContentObject const * mathml_cont;
	MathMLExpression const * mathml_expr;
	std::string mathml_cname;
	ExprTree * expr;

	// den MathML-Parser anwerfen:
	try
	{
		mathml = new MathMLDocument( node );
	}
	catch (XMLException & e)
	{
		// MathML-Fehler in FluxML-Fehler konvertieren:
		fTHROW(XMLException,node,"MathML-error in profile: %s", e.toString());
	}

	// handelt es sich bei den Profile aus dem MathML-Dokument wirklich
	// um Profile?
	std::list< MathMLDeclare const * > mml_constr = mathml->getDefinitionsByRegExp(".*");

	// Sinnüberprüfung der Profile
	std::list< MathMLDeclare const * >::iterator l_iter = mml_constr.begin();
	while (l_iter != mml_constr.end())
	{
		mathml_def = *l_iter;
		mathml_cont = mathml_def->getValue();
		mathml_cname = mathml_def->getName();

		// Profile sind einfache MathMLExpressions:
		if (mathml_cont->getType() != MathMLContentObject::co_expression)
			fTHROW(XMLException,node,"Profile %s: simple MathML-expression expected.",
					mathml_cname.c_str());

		mathml_expr = static_cast<MathMLExpression const*>(mathml_cont);
		expr = mathml_expr->get();
                // Überprüfung der Variablennamen
                charptr_array vnames = expr->getVarNames();
                charptr_array::const_iterator vni;

                for (vni = vnames.begin(); vni != vnames.end(); vni++)
                {
                    if((std::strcmp(*vni,"t") != 0) and (std::strcmp(*vni,"otherwise") != 0) )
                            fTHROW(XMLException,node,
                                    "invalid variable name \"%s\" in "
                                    "profile specification \"%s\" (%s)",
                                    *vni, input_profile_->getName(), expr->toString().c_str());
                };

                input_profile_->addValue(expr);
                l_iter++;
	}

	// MathML-Dokument freigeben:
	delete mathml;
}

void FluxMLInput::parseProfileTextual(
	DOMNode * node
	)
{
	DOMNode * child;
	char const anon[] = "anonymous";
	if (not XMLElement::match(node,fml_textual,fml_xmlns_uri))
		fTHROW(XMLException,node,"element node (textual) expected.");
	child = node->getFirstChild();

	while (child != 0)
	{
		// unter Element textual ist alles erlaubt -- ausgewertet
		// werden nur TEXT_NODEs
		while (child != 0 && child->getNodeType() != DOMNode::TEXT_NODE)
			child = child->getNextSibling();

		if (child == 0)
			break;

		U2A cblock(static_cast< DOMText* >(child)->getData());
		charptr_array expr_list = charptr_array::split((char const*)cblock,";");
		charptr_array::const_iterator i;

		for (i=expr_list.begin(); i!=expr_list.end(); i++)
		{
			ExprTree * expr;
			int ci;
			char * cname, * u;
			char const * w;
			for (w=*i; *w!='\0'; w++)
				if (*w > 32) break;
			if (*w == '\0')
				continue;

			charptr_array nv_pair = charptr_array::split(*i,":");
			switch (nv_pair.size())
			{
			case 1:
				ci = 0;
				cname = strdup_alloc(anon);
				break;
			case 2:
				ci = 1;
				cname = strdup_alloc(nv_pair[0]);
				for (u=cname; *u!='\0'; u++)
					if (*u < 32) *u = ' ';
				strtrim_inplace(cname);
				break;
			default:
				fTHROW(XMLException,child,
					"parse error in profile (%s): "
					"confused by too many ':'", *i);
			}

			try
			{
				expr = ExprTree::parse(nv_pair[ci]);
			}
			catch (ExprParserException &)
			{
				fTHROW(XMLException,child,"parse error in profile %s(%s)",
					cname, nv_pair[ci]);
			}

                        // Überprüfung der Variablennamen
                        charptr_array vnames = expr->getVarNames();
                        charptr_array::const_iterator vni;

                        for (vni = vnames.begin(); vni != vnames.end(); vni++)
                        {
                            if((std::strcmp(*vni,"t") != 0) and (std::strcmp(*vni,"otherwise") != 0) )
                                    fTHROW(XMLException,child,
                                            "invalid variable name \"%s\" in "
                                            "profile specification \"%s\" (%s)",
                                            *vni, input_profile_->getName(), nv_pair[ci]);
                        };

                        input_profile_->addValue(expr);

			delete[] cname;
			delete expr;
		}
		child = child->getNextSibling();
	}
}
} // namespace flux::xml
} // namespace flux