//
// Created by dragos on 28.11.2019.
//

#include "BugReport.h"
#include "AnalysisContext.h"
#include "PropagateFormulas.h"
#include <boost/pending/disjoint_sets.hpp>
#include <boost/property_map/property_map.hpp>
#include <fstream>

namespace analysis {

BugReport::BugReport(P4::ReferenceMap *refMap, P4::TypeMap *typeMap,
                     bug_report_options brp)
    : refMap(refMap), typeMap(typeMap), bugReportOptions(std::move(brp)) {
  passes.push_back(new VisitFunctor([this](const IR::Node *n) {
    analyzeProgram(n->to<IR::P4Program>());
    return n;
  }));
}

void BugReport::analyzeProgram(const IR::P4Program *program) {
  Analysis analyzer(refMap, typeMap, program, "run");
  auto main = analyzer.getMain();
  analysis::node_t basicBlockStart;
  analysis::EdgeHolder basicBlocks, rBasicBlocks;
  *main = push_ifs(*main, refMap, typeMap);
  basic_blocks(main->holder, main->start_node, basicBlocks, rBasicBlocks,
               basicBlockStart);
  make_ssa(basicBlocks, rBasicBlocks, basicBlockStart, refMap, typeMap,
           analyzer.getFunMap());
  auto sorted = analysis::topo_sort(&basicBlocks, basicBlockStart);
  auto p_ctx = new z3::context();
  EdgeFormulas edgeFormulas(typeMap, refMap, p_ctx);
  z3::solver direct_(*p_ctx);
  z3::solver dual_(*p_ctx);
  packet_solver_ direct(direct_, edgeFormulas.packetTheory);
  packet_solver_ dual(direct_, edgeFormulas.packetTheory);
  z3::expr_vector bugs(*p_ctx);
  // ensure reverse postorder holds, will help a lot in debugging
  for (auto I = sorted.rbegin(); I != sorted.rend(); ++I)
    (void)edgeFormulas.nodeLabel(*I);
  std::unordered_set<node_t> dead, buggy;
  std::vector<node_t> sortedBuggy;
  for (auto &n : sorted) {
    auto lst = last(n);
    if (lst) {
      if (auto mcs = is_extern_method_call(lst)) {
        if (is_bug(mcs->methodCallStatement)) {
          buggy.emplace(n);
          sortedBuggy.emplace_back(n);
        } else if (is_terminal(mcs->methodCallStatement)) {
          // replace basic block ending in send/drop with assume false and
          // simply continue
          dead.emplace(n);
          continue;
        }
      }
    }
  }

  for (auto &n : sorted) {
    auto Cl = edgeFormulas.node(n);
    auto nl = edgeFormulas.nodeLabel(n);
    z3::expr_vector succv(*p_ctx);
    auto lst = last(n);
    if (lst) {
      if (auto mcs = is_extern_method_call(lst)) {
        if (is_bug(mcs->methodCallStatement)) {
          bugs.push_back(edgeFormulas.nodeLabel(n));
        } else if (is_terminal(mcs->methodCallStatement)) {
          // replace basic block ending in send/drop with assume false and
          // simply continue
          direct.add(!edgeFormulas.nodeLabel(n));
          continue;
        }
      }
    }
    auto Isuccs = basicBlocks.find(n);
    bool empty = true;
    if (Isuccs != basicBlocks.end()) {
      for (auto &succ : Isuccs->second) {
        empty = false;
        if (!dead.count(succ.first)) {
          succv.push_back(edgeFormulas.nodeLabel(succ.first));
        }
      }
    }
    if (!succv.empty()) {
      direct.add(z3::implies(edgeFormulas.nodeLabel(n),
                             (Cl && z3::mk_or(succv)).simplify()));
    } else {
      if (empty) {
        direct.add(z3::implies(edgeFormulas.nodeLabel(n), Cl));
      } else {
        direct.add(!edgeFormulas.nodeLabel(n));
      }
    }
  }
  // direct now encodes reachability of bug (see reachability modulo theories
  // paper - aka Corral). If direct is unsat => no bug there
  // if direct is sat => need to model check the path. see code below
  direct.add(edgeFormulas.nodeLabel(basicBlockStart));

  auto get_path = [&](z3::model &model) {
    std::unordered_set<node_t> active;
    for (auto &n : sorted) {
      switch (model.eval(edgeFormulas.nodeLabel(n)).bool_value()) {
      case Z3_L_TRUE:
        active.emplace(n);
      default:
        break;
      }
    }
    std::function<void(std::vector<node_t> &, bool &)> rec;
    rec = [&](std::vector<node_t> &crt, bool &ret) {
      if (auto lst = last(crt.back())) {
        if (auto mcs = is_extern_method_call(lst)) {
          if (is_bug(mcs->methodCallStatement)) {
            ret = true;
            return;
          }
        }
      }
      auto Isuccs = basicBlocks.find(crt.back());
      for (auto &n : Isuccs->second) {
        if (!active.count(n.first))
          continue;
        crt.push_back(n.first);
        rec(crt, ret);
        if (ret)
          return;
        crt.pop_back();
      }
      ret = false;
    };
    std::vector<node_t> pth({basicBlockStart});
    bool found = false;
    rec(pth, found);
    BUG_CHECK(found, "model says path found, but none there");
    return pth;
  };
  NodeValues<std::unordered_set<z3::expr>> packetAtoms;
  for (auto &node : sorted) {
    auto Cl = edgeFormulas.node(node);
    if (Cl.is_and()) {
      for (unsigned i = 0, e = Cl.num_args(); i != e; ++i) {
        auto exp = Cl.arg(i);
        if (exp.is_eq()) {
          auto arg0 = exp.arg(0);
          auto arg1 = exp.arg(1);
          if (edgeFormulas.packetTheory.isPacket(arg0)) {
            packetAtoms[node].emplace(exp);
          }
        }
      }
    }
  }
  while (direct.check() == z3::check_result::sat) {
    auto model = direct.get_model();
    std::unordered_set<node_t> active;
    auto pth = get_path(model);
    std::copy(pth.begin(), pth.end(), std::inserter(active, active.begin()));

    auto smtPrint = [&](std::ostream &os, const node_t &n) {
      if (dead.count(n)) {
        os << n.nodeId()
           << "[style=filled,label=\"dead\",filledcolor=gray47];\n";
        return;
      }
      os << n.nodeId() << "[shape=box,label=\"";
      auto Cl = edgeFormulas.node(n);
      if (Cl.is_and()) {
        for (unsigned i = 0, e = Cl.num_args(); i != e; ++i) {
          os << Cl.arg(i) << "\n";
        }
      } else {
        os << Cl;
      }
      os << "\"";
      if (active.count(n)) {
        os << ",style=filled,color=forestgreen";
      }
      os << "]\n";
    };
    std::unordered_map<z3::expr, unsigned> termindex;
    std::vector<z3::expr> revtermindex;
    std::unordered_map<unsigned, std::set<unsigned>> deps;
    typedef std::map<unsigned, std::size_t> rank_t; // => order on Element
    typedef std::map<unsigned, unsigned> parent_t;
    rank_t rank_map;
    parent_t parent_map;
    boost::associative_property_map<rank_t> rank_pmap(rank_map);
    boost::associative_property_map<parent_t> parent_pmap(parent_map);
    boost::disjoint_sets<boost::associative_property_map<rank_t>,
                         boost::associative_property_map<parent_t>>
        djsets(rank_pmap, parent_pmap);
    auto mkSet = [&](const z3::expr &e0) -> unsigned {
      auto EMI0 = termindex.emplace(e0, termindex.size());
      if (EMI0.second) {
        revtermindex.push_back(e0);
        djsets.make_set(EMI0.first->second);
      }
      return EMI0.first->second;
    };
    auto split = [&](const z3::expr &e) {
      if (edgeFormulas.packetTheory.isPrepend(e)) {
        auto e0 = e.arg(0);
        auto e1 = e.arg(1);
        deps[mkSet(e)].emplace(mkSet(e0));
        deps[mkSet(e)].emplace(mkSet(e1));
      }
    };
    z3::expr_vector allConstraints(*p_ctx);
    auto isPrepend = [&](const z3::expr &e) {
      return edgeFormulas.packetTheory.isPrepend(e);
    };
    auto tmpPack = [&]() {
      return z3::to_expr(
          *p_ctx, Z3_mk_fresh_const(*p_ctx, "pack",
                                    edgeFormulas.packetTheory.packetSort));
    };
    auto normalizePrepend = [&](const z3::expr &e0,
                                std::vector<z3::expr> &extra_equalities) {
      if (isPrepend(e0)) {
        auto e01 = e0.arg(1);
        auto rep = djsets.find_set(termindex[e01]);
        auto &repe = revtermindex[rep];
        if (isPrepend(repe)) {
          auto tmp = tmpPack();
          extra_equalities.push_back(
              tmp == edgeFormulas.packetTheory.prepend(e0.arg(0), repe.arg(0)));
          return edgeFormulas.packetTheory.prepend(tmp, repe.arg(1));
        }
      }
      return e0;
    };

    std::function<void(const z3::expr &)> handleEquality;
    handleEquality = [&](const z3::expr &eq) {
      std::vector<z3::expr> extra_equalities;
      auto e0 = eq.arg(0);
      auto e1 = eq.arg(1);
      split(e0);
      split(e1);
      // normalize prepends into right normal form:
      // prepend(a, prepend(b, c)) == prepend(prepend(a, b), c)
      // add tmp == prepend(a, b) && e0 <- prepend(tmp, c)
      e0 = normalizePrepend(e0, extra_equalities);
      e1 = normalizePrepend(e1, extra_equalities);
      for (auto &xx : extra_equalities) {
        handleEquality(xx);
      }
      auto ei0 = djsets.find_set(mkSet(e0));
      auto ei1 = djsets.find_set(mkSet(e1));
      // this is equality is redundant, move on
      if (ei0 == ei1)
        return;
      auto &ax0 = revtermindex[ei0];
      auto &ax1 = revtermindex[ei1];
      if (isPrepend(ax0) && isPrepend(ax1)) {
        // two prepends are trying to get into the same class
        // xy = ut. Given property of right known length, we know
        // that y = emit_N (X1) && t = emit_M (X2)
        // case split on N vs M
        auto x = ax0.arg(0);
        auto y = ax0.arg(1);
        auto u = ax1.arg(0);
        auto t = ax1.arg(1);
        auto emN = edgeFormulas.packetTheory.isEmit(y.decl());
        auto emM = edgeFormulas.packetTheory.isEmit(t.decl());
        BUG_CHECK(emN && emM,
                  "can't handle prepend with unknown rights %1% vs %2%", ax0,
                  ax1);
        if (*emN == *emM) {
          auto X1 = y.arg(0);
          auto X2 = t.arg(0);
          auto propagate = X1 == X2;
          LOG4("propagating " << propagate);
          allConstraints.push_back(propagate);
          handleEquality(x == u);
        } else {
          if (*emN > *emM) {
            std::swap(x, u);
            std::swap(y, t);
            emN.swap(emM);
          }
          auto X1 = y.arg(0);
          auto X2 = t.arg(0);
          auto propagate = X1 == X2.extract(*emM - 1, *emM - *emN);
          LOG4("propagating " << propagate);
          allConstraints.push_back(propagate);
          handleEquality(x ==
                         edgeFormulas.packetTheory.prepend(
                             u,
                             edgeFormulas.packetTheory.emit(*emM - *emN)(
                                 X2.extract(*emM - *emN - 1, 0))));
        }
      } else {
        // link classes
        djsets.link(ei0, ei1);
        auto rep = djsets.find_set(ei0);
        // if either one of the equated (x, y) are
        // in the same eq class with eps then ensure
        // that the representative of the newly formed
        // set corresponds to eps
        if (edgeFormulas.packetTheory.isZero(ax0) ||
            edgeFormulas.packetTheory.isZero(ax1)) {
          if (!edgeFormulas.packetTheory.isZero(revtermindex[rep])) {
            auto zerorep =
                djsets.find_set(mkSet(edgeFormulas.packetTheory.zero()));
            std::swap(termindex[revtermindex[rep]],
                      termindex[revtermindex[zerorep]]);
            std::swap(revtermindex[rep], revtermindex[zerorep]);
          }
        }
      }
    };

    for (auto &n : pth) {
      allConstraints.push_back(edgeFormulas.nodeLabel(n));
      auto &patoms = packetAtoms[n];
      for (auto &eq : patoms) {
        handleEquality(eq);
      }
    }
    auto cr = direct.check(allConstraints);
    if (cr == z3::check_result::sat) {
      z3::expr_vector block(*p_ctx);
      for (auto &n : pth) {
        block.push_back(!edgeFormulas.nodeLabel(n));
      }
      {
        std::ofstream path(refMap->newName("path") + ".dot");
        CFG cfg(nullptr, std::move(basicBlocks));
        cfg.start_node = basicBlockStart;
        cfg.toDot(path, smtPrint);
        basicBlocks = std::move(cfg.holder);
      }
      auto mdl = direct.get_model();
      // TODO: find initial packet from program and solve for it
      // TODO: magic with mdl to get the packet + djsets and friends
      // TODO: dump packet to options.packet_file
      // TODO: uncomment if need more examples
      std::ofstream report(bugReportOptions.packet_file);
      //      direct.add(z3::mk_or(block));
      LOG4("bug reachable!");
      break;
    } else {
      LOG4("spurious path detected");
      auto uc = direct.unsat_core();
      direct.add(!z3::mk_and(uc));
    }
  }
}
}


#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/args.hpp>
#include <boost/python/def.hpp>

#include "iotbx/xplor/map_reader.h"
#include "iotbx/xplor/map_writer.h"

#include <cctbx/uctbx.h>

namespace af = scitbx::af;

namespace iotbx { namespace boost_python { namespace xplor_ext {

  void init_module()
  {
    using namespace boost::python;
    using namespace iotbx::xplor;

    class_<map_reader>("map_reader", no_init)
      .def(init<std::string const&, std::size_t, af::flex_grid<> const&>(
        (arg("file_name"), arg("n_header_lines"), arg("grid"))))
      .def_readonly("data", &map_reader::data)
      .def_readonly("average", &map_reader::average)
      .def_readonly("standard_deviation", &map_reader::standard_deviation)
    ;

    def("map_writer", map_writer_box,
      (arg("file_name"), arg("unit_cell"),
       arg("data"),
       arg("average"), arg("standard_deviation")));
    def("map_writer", map_writer_p1_cell,
      (arg("file_name"), arg("unit_cell"),
       arg("gridding_first"), arg("gridding_last"), arg("data"),
       arg("average"), arg("standard_deviation")));
  }

}}} // namespace iotbx::boost_python::xplor_ext

BOOST_PYTHON_MODULE(iotbx_xplor_ext)
{
  iotbx::boost_python::xplor_ext::init_module();
}


/*
 * ray_predictor.cc
 *
 *  Copyright (C) 2013 Diamond Light Source
 *
 *  Author: James Parkhurst
 *
 *  This code is distributed under the BSD license, a copy of which is
 *  included in the root directory of this package.
 */
#include <boost/python.hpp>
#include <boost/python/def.hpp>
#include <dials/array_family/reflection_table.h>
#include <dials/algorithms/spot_prediction/ray_predictor.h>
#include <dxtbx/model/scan.h>
#include <dxtbx/model/beam.h>
#include <dxtbx/model/goniometer.h>
#include <dxtbx/model/detector.h>

namespace dials { namespace algorithms { namespace boost_python {

  using namespace boost::python;

  static af::reflection_table call_with_miller_index_array(
    const ScanStaticRayPredictor &self,
    const af::const_ref<cctbx::miller::index<> > &h,
    const mat3<double> &UB) {
    af::reflection_table result;
    af::shared<cctbx::miller::index<> > hkl = result["miller_index"];
    af::shared<vec3<double> > s1 = result["s1"];
    af::shared<bool> entering = result["entering"];
    af::shared<double> phi = result["phi"];
    for (std::size_t i = 0; i < h.size(); ++i) {
      af::small<Ray, 2> rays = self(h[i], UB);
      for (std::size_t j = 0; j < rays.size(); ++j) {
        hkl.push_back(h[i]);
        s1.push_back(rays[j].s1);
        entering.push_back(rays[j].entering);
        phi.push_back(rays[j].angle);
      }
    }
    DIALS_ASSERT(result.is_consistent());
    return result;
  }

  void export_ray_predictor() {
    // Create and return the wrapper for the spot predictor object
    class_<ScanStaticRayPredictor>("ScanStaticRayPredictor", no_init)
      .def(init<vec3<double>, vec3<double>, mat3<double>, mat3<double>, vec2<double> >(
        (arg("s0"),
         arg("m2"),
         arg("fixed_rotation"),
         arg("setting_rotation"),
         arg("dphi"))))
      .def("__call__",
           &ScanStaticRayPredictor::operator(),
           (arg("miller_index"), arg("UB")))
      .def("__call__", &ScanStaticRayPredictor::from_reciprocal_lattice_vector)
      .def("__call__", &call_with_miller_index_array);
  }

}}}  // namespace dials::algorithms::boost_python


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2020.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Douglas McCloskey $
// $Authors: Douglas McCloskey, Pasquale Domenico Colaianni $
// --------------------------------------------------------------------------

#include <OpenMS/ANALYSIS/QUANTITATION/AbsoluteQuantitation.h>

//Kernal classes
#include <OpenMS/KERNEL/StandardTypes.h>
#include <OpenMS/KERNEL/MSExperiment.h>
#include <OpenMS/KERNEL/MSChromatogram.h>
#include <OpenMS/KERNEL/FeatureMap.h>
#include <OpenMS/KERNEL/MRMTransitionGroup.h>
#include <OpenMS/KERNEL/MRMFeature.h>

//OpenSWATH classes
#include <OpenMS/ANALYSIS/OPENSWATH/MRMRTNormalizer.h>

//Analysis classes
#include <OpenMS/ANALYSIS/MAPMATCHING/TransformationModel.h>
#include <OpenMS/ANALYSIS/MAPMATCHING/TransformationDescription.h>
#include <OpenMS/ANALYSIS/TARGETED/TargetedExperiment.h>

//Quantitation classes
#include <OpenMS/METADATA/AbsoluteQuantitationStandards.h>
#include <OpenMS/ANALYSIS/QUANTITATION/AbsoluteQuantitationMethod.h>

//Math classes
#include <OpenMS/MATH/STATISTICS/StatisticFunctions.h>

//Standard library
#include <cstddef> // for size_t & ptrdiff_t
#include <vector>
#include <cmath>
#include <numeric>
#include <boost/math/special_functions/erf.hpp>
#include <algorithm>

namespace OpenMS
{

  AbsoluteQuantitation::AbsoluteQuantitation() :
  DefaultParamHandler("AbsoluteQuantitation")
  {
    defaults_.setValue("min_points", 4, "The minimum number of calibrator points.");

    defaults_.setValue("max_bias", 30.0, "The maximum percent bias of any point in the calibration curve.");

    defaults_.setValue("min_correlation_coefficient", 0.9, "The minimum correlation coefficient value of the calibration curve.");

    defaults_.setValue("max_iters", 100, "The maximum number of iterations to find an optimal set of calibration curve points and parameters.");

    defaults_.setValue("outlier_detection_method", "iter_jackknife", "Outlier detection method to find and remove bad calibration points.");
    defaults_.setValidStrings("outlier_detection_method", ListUtils::create<String>("iter_jackknife,iter_residual"));

    defaults_.setValue("use_chauvenet", "true", "Whether to only remove outliers that fulfill Chauvenet's criterion for outliers (otherwise it will remove any outlier candidate regardless of the criterion).");
    defaults_.setValidStrings("use_chauvenet", ListUtils::create<String>("true,false"));

    defaults_.setValue("optimization_method", "iterative", "Calibrator optimization method to find the best set of calibration points for each method.");
    defaults_.setValidStrings("optimization_method", ListUtils::create<String>("iterative"));

    // write defaults into Param object param_
    defaultsToParam_();
    updateMembers_();
  }

  void AbsoluteQuantitation::updateMembers_()
  {
    min_points_ = (size_t)param_.getValue("min_points");
    max_bias_ = (double)param_.getValue("max_bias");
    min_correlation_coefficient_ = (double)param_.getValue("min_correlation_coefficient");
    max_iters_ = (size_t)param_.getValue("max_iters");
    outlier_detection_method_ = param_.getValue("outlier_detection_method");
    use_chauvenet_ = (bool)param_.getValue("use_chauvenet").toBool();
    optimization_method_ = param_.getValue("optimization_method");
  }

  AbsoluteQuantitation::~AbsoluteQuantitation()
  {
  }

  void AbsoluteQuantitation::setQuantMethods(std::vector<AbsoluteQuantitationMethod>& quant_methods)
  {
    quant_methods_.clear();
    for (size_t i = 0; i < quant_methods.size(); i++)
    {
      String component_name = quant_methods[i].getComponentName();
      quant_methods_[component_name] = quant_methods[i];
    }
  }

  std::vector<AbsoluteQuantitationMethod> AbsoluteQuantitation::getQuantMethods()
  {
    std::vector<AbsoluteQuantitationMethod> quant_methods;
    for (auto const& quant_method : quant_methods_)
    {
      quant_methods.push_back(quant_method.second);
    }
    return quant_methods;
  }

  std::map<String, AbsoluteQuantitationMethod> AbsoluteQuantitation::getQuantMethodsAsMap()
  {
    return quant_methods_;
  }

  double AbsoluteQuantitation::calculateRatio(const Feature & component_1, const Feature & component_2, const String & feature_name)
  {
    double ratio = 0.0;
    // member feature_name access
    if (feature_name == "intensity")
    {
      if (component_1.metaValueExists("native_id") && component_2.metaValueExists("native_id"))
      {
        const double feature_1 = component_1.getIntensity();
        const double feature_2 = component_2.getIntensity();
        ratio = feature_1 / feature_2;
      }
      else if (component_1.metaValueExists("native_id"))
      {
        OPENMS_LOG_DEBUG << "Warning: no IS found for component " << component_1.getMetaValue("native_id") << ".";
        const double feature_1 = component_1.getIntensity();
        ratio = feature_1;
      }
    }
    // metaValue feature_name access
    else
    {
      if (component_1.metaValueExists(feature_name) && component_2.metaValueExists(feature_name))
      {
        const double feature_1 = component_1.getMetaValue(feature_name);
        const double feature_2 = component_2.getMetaValue(feature_name);
        ratio = feature_1/feature_2;
      }
      else if (component_1.metaValueExists(feature_name))
      {
        OPENMS_LOG_DEBUG << "Warning: no IS found for component " << component_1.getMetaValue("native_id") << ".";
        const double feature_1 = component_1.getMetaValue(feature_name);
        ratio = feature_1;
      }
      else
      {
        OPENMS_LOG_DEBUG << "Feature metaValue " << feature_name << " not found for components " << component_1.getMetaValue("native_id") << " and " << component_2.getMetaValue("native_id") << ".";
      }
    }

    return ratio;
  }

  double AbsoluteQuantitation::calculateBias(const double & actual_concentration, const double & calculated_concentration)
  {
    double bias = fabs(actual_concentration - calculated_concentration)/actual_concentration*100;
    return bias;
  }

  Param AbsoluteQuantitation::fitCalibration(
    const std::vector<AbsoluteQuantitationStandards::featureConcentration> & component_concentrations,
    const String & feature_name,
    const String & transformation_model,
    const Param & transformation_model_params)
  {
    // extract out the calibration points
    TransformationModel::DataPoints data;
    TransformationModel::DataPoint point;
    for (size_t i = 0; i < component_concentrations.size(); i++)
    {
      point.first = component_concentrations[i].actual_concentration / component_concentrations[i].IS_actual_concentration / component_concentrations[i].dilution_factor; // adjust based on the dilution factor
      double ratio = calculateRatio(component_concentrations[i].feature, component_concentrations[i].IS_feature,feature_name);
      point.second = ratio;
      data.push_back(point);
    }

    // fit the data to the model
    TransformationDescription tmd(data);
    // tmd.setDataPoints(data);
    tmd.fitModel(transformation_model, transformation_model_params);
    Param params = tmd.getModelParameters();
    // AbsoluteQuantitationMethod aqm;
    // Param params = aqm.fitTransformationModel(transformation_model, data, transformation_model_params);

    // store the information about the fit
    return params;
  }

  void AbsoluteQuantitation::calculateBiasAndR(
    const std::vector<AbsoluteQuantitationStandards::featureConcentration> & component_concentrations,
    const String & feature_name,
    const String & transformation_model,
    const Param & transformation_model_params,
    std::vector<double> & biases,
    double & correlation_coefficient)
  {
    // reset biases
    biases.clear();

    // extract out the calibration points
    std::vector<double> concentration_ratios, feature_amounts_ratios;
    TransformationModel::DataPoints data;
    TransformationModel::DataPoint point;
    for (size_t i = 0; i < component_concentrations.size(); ++i)
    {

      // calculate the actual and calculated concentration ratios
      double calculated_concentration_ratio = applyCalibration(component_concentrations[i].feature,
        component_concentrations[i].IS_feature,
        feature_name,
        transformation_model,
        transformation_model_params);

      double actual_concentration_ratio = component_concentrations[i].actual_concentration/
        component_concentrations[i].IS_actual_concentration / component_concentrations[i].dilution_factor;
      concentration_ratios.push_back(component_concentrations[i].actual_concentration);

      // extract out the feature amount ratios
      double feature_amount_ratio = calculateRatio(component_concentrations[i].feature,
        component_concentrations[i].IS_feature,
        feature_name);
      feature_amounts_ratios.push_back(feature_amount_ratio);

      // calculate the bias
      double bias = calculateBias(actual_concentration_ratio, calculated_concentration_ratio);
      biases.push_back(bias);

      point.first = actual_concentration_ratio;
      point.second = feature_amount_ratio;
      data.push_back(point);
    }

    // apply weighting to the feature amounts and actual concentration ratios
    TransformationModel tm(data, transformation_model_params);
    tm.weightData(data);
    std::vector<double> concentration_ratios_weighted, feature_amounts_ratios_weighted;
    for (size_t i = 0; i < data.size(); ++i)
    {
      concentration_ratios_weighted.push_back(data[i].first);
      feature_amounts_ratios_weighted.push_back(data[i].second);
    }

    // calculate the R2 (R2 = Pearson_R^2)
    correlation_coefficient = Math::pearsonCorrelationCoefficient(
      concentration_ratios_weighted.begin(), concentration_ratios_weighted.begin() + concentration_ratios_weighted.size(),
      feature_amounts_ratios_weighted.begin(), feature_amounts_ratios_weighted.begin() + feature_amounts_ratios_weighted.size()
    );
  }

  double AbsoluteQuantitation::applyCalibration(const Feature & component,
    const Feature & IS_component,
    const String & feature_name,
    const String & transformation_model,
    const Param & transformation_model_params)
  {
    // calculate the ratio
    double ratio = calculateRatio(component, IS_component, feature_name);

    // calculate the absolute concentration
    TransformationModel::DataPoints data;
    TransformationDescription tmd(data);
    // tmd.setDataPoints(data);
    tmd.fitModel(transformation_model, transformation_model_params);
    tmd.invert();
    double calculated_concentration = tmd.apply(ratio);

    // AbsoluteQuantitationMethod aqm;
    // double calculated_concentration = aqm.evaluateTransformationModel(
    //   transformation_model, ratio, transformation_model_params);

    // check for less than zero
    if (calculated_concentration < 0.0)
    {
      calculated_concentration = 0.0;
    }

    return calculated_concentration;
  }

  void AbsoluteQuantitation::quantifyComponents(FeatureMap& unknowns)
  {
    //Potential Optimizations: create a map for each unknown FeatureMap
    // to reduce multiple loops

    // initialize all other variables
    Feature empty_feature;
    size_t IS_component_it(0), IS_component_group_it(0);

    // // iterate through the unknowns
    // for (size_t i = 0; i < unknowns.size(); i++)
    // {

    // iterate through each component_group/feature
    for (size_t feature_it = 0; feature_it < unknowns.size(); ++feature_it)
    {
      String component_group_name = (String)unknowns[feature_it].getMetaValue("PeptideRef");
      Feature unknowns_quant_feature;

      // iterate through each component/sub-feature
      for (size_t sub_it = 0; sub_it < unknowns[feature_it].getSubordinates().size(); ++sub_it)
      {
        String component_name = (String)unknowns[feature_it].getSubordinates()[sub_it].getMetaValue("native_id");

        // apply the calibration curve to components that are in the quant_method
        if (quant_methods_.count(component_name)>0)
        {
          double calculated_concentration = 0.0;
          std::map<String,AbsoluteQuantitationMethod>::iterator quant_methods_it = quant_methods_.find(component_name);
          String quant_component_name = quant_methods_it->second.getComponentName();
          String quant_IS_component_name = quant_methods_it->second.getISName();
          String quant_feature_name = quant_methods_it->second.getFeatureName();
          if (quant_IS_component_name != "")
          {
            // look up the internal standard for the component
            bool IS_found = false;
            // Optimization: 90% of the IS will be in the same component_group/feature
            for (size_t is_sub_it = 0; is_sub_it < unknowns[feature_it].getSubordinates().size(); ++is_sub_it)
            {
              String IS_component_name = (String)unknowns[feature_it].getSubordinates()[is_sub_it].getMetaValue("native_id");
              if (quant_IS_component_name == IS_component_name)
              {
                IS_found = true;
                IS_component_group_it = feature_it;
                IS_component_it = is_sub_it;
                break;
              }
            }
            if (!IS_found)
            {// expand IS search to all components
              // iterate through each component_group/feature
              for (size_t is_feature_it = 0; is_feature_it < unknowns.size(); ++is_feature_it)
              {
                //iterate through each component/sub-feature
                for (size_t is_sub_it = 0; is_sub_it < unknowns[is_feature_it].getSubordinates().size(); ++is_sub_it)
                {
                  String IS_component_name = (String)unknowns[is_feature_it].getSubordinates()[is_sub_it].getMetaValue("native_id");
                  if (quant_IS_component_name == IS_component_name)
                  {
                    IS_found = true;
                    IS_component_group_it = is_feature_it;
                    IS_component_it = is_sub_it;
                    break;
                  }
                }
                if (IS_found)
                {
                  break;
                }
              }
            }
            if (IS_found)
            {
              String transformation_model = quant_methods_it->second.getTransformationModel();
              Param transformation_model_params = quant_methods_it->second.getTransformationModelParams();
              calculated_concentration = applyCalibration(
                unknowns[feature_it].getSubordinates()[sub_it],
                unknowns[IS_component_group_it].getSubordinates()[IS_component_it],
                quant_feature_name,transformation_model,transformation_model_params);
            }
            else
            {
              OPENMS_LOG_INFO << "Component " << component_name << " IS " << quant_IS_component_name << " was not found.";
              OPENMS_LOG_INFO << "No concentration will be calculated.\n";
            }
          }
          else
          {
            String transformation_model = quant_methods_it->second.getTransformationModel();
            Param transformation_model_params = quant_methods_it->second.getTransformationModelParams();
            calculated_concentration = applyCalibration(
              unknowns[feature_it].getSubordinates()[sub_it],
              empty_feature,
              quant_feature_name,transformation_model,transformation_model_params);
          }

          // add new metadata (calculated_concentration, concentration_units) to the component
          unknowns[feature_it].getSubordinates()[sub_it].setMetaValue("calculated_concentration",calculated_concentration);
          String concentration_units = quant_methods_it->second.getConcentrationUnits();
          unknowns[feature_it].getSubordinates()[sub_it].setMetaValue("concentration_units",concentration_units);
          // calculate the bias?
        }
        else
        {
          OPENMS_LOG_INFO << "Component " << component_name << " does not have a quantitation method.";
          OPENMS_LOG_INFO << "No concentration will be calculated.\n";
          unknowns[feature_it].getSubordinates()[sub_it].setMetaValue("calculated_concentration","");
          unknowns[feature_it].getSubordinates()[sub_it].setMetaValue("concentration_units","");
        }
      }
    }
    // }
  }

  bool AbsoluteQuantitation::optimizeCalibrationCurveIterative(
    std::vector<AbsoluteQuantitationStandards::featureConcentration> & component_concentrations,
    const String & feature_name,
    const String & transformation_model,
    const Param & transformation_model_params,
    Param & optimized_params)
  {

    // sort from min to max concentration
    std::vector<AbsoluteQuantitationStandards::featureConcentration> component_concentrations_sorted = component_concentrations;
    std::sort(component_concentrations_sorted.begin(), component_concentrations_sorted.end(),
      [](AbsoluteQuantitationStandards::featureConcentration lhs, AbsoluteQuantitationStandards::featureConcentration rhs)
      {
        return lhs.actual_concentration < rhs.actual_concentration; //ascending order
      }
    );

    // indices of component_concentrations
    std::vector<size_t> component_concentrations_sorted_indices;// loop from all points to min_points
    for (size_t index = 0; index < component_concentrations_sorted.size(); ++index)
    {
      component_concentrations_sorted_indices.push_back(index);
    }

    // starting parameters
    optimized_params = transformation_model_params;

    // for (size_t n_iters = 0; n_iters < max_iters_; ++n_iters)
    for (size_t n_iters = 0; n_iters < component_concentrations_sorted.size(); ++n_iters)
    {

      // extract out components
      const std::vector<AbsoluteQuantitationStandards::featureConcentration> component_concentrations_sub = extractComponents_(
        component_concentrations_sorted, component_concentrations_sorted_indices);

      // check if the min number of calibration points has been broken
      if (component_concentrations_sorted_indices.size() < min_points_)
      {
        OPENMS_LOG_INFO << "No optimal calibration found for " << component_concentrations_sub[0].feature.getMetaValue("native_id") << " .";
        return false;  //no optimal calibration found
      }

      // fit the model
      optimized_params = fitCalibration(component_concentrations_sub,
        feature_name,
        transformation_model,
        optimized_params);

      // calculate the R2 and bias
      std::vector<double> biases; // not needed (method parameters)
      double correlation_coefficient = 0.0; // not needed (method parameters)
      calculateBiasAndR(
        component_concentrations_sub,
        feature_name,
        transformation_model,
        optimized_params,
        biases,
        correlation_coefficient);

      // check R2 and biases
      bool bias_check = true;
      for (size_t bias_it = 0; bias_it < biases.size(); ++bias_it)
      {
        if (biases[bias_it] > max_bias_)
        {
          bias_check = false;
        }
      }
      if (bias_check && correlation_coefficient > min_correlation_coefficient_)
      {
        OPENMS_LOG_INFO << "Valid calibration found for " << component_concentrations_sub[0].feature.getMetaValue("native_id") << " .";

        // copy over the final optimized points before exiting
        component_concentrations = component_concentrations_sub;
        return true;  //optimal calibration found
      }

      // R2 and biases check failed, determine potential outlier
      int pos;
      if (outlier_detection_method_ == "iter_jackknife")
      {
        // get candidate outlier: removal of which datapoint results in best rsq?
        pos = jackknifeOutlierCandidate_(
          component_concentrations_sub,
          feature_name,
          transformation_model,
          optimized_params);
      }
      else if (outlier_detection_method_ == "iter_residual")
      {
        // get candidate outlier: removal of datapoint with largest residual?
        pos = residualOutlierCandidate_(
          component_concentrations_sub,
          feature_name,
          transformation_model,
          optimized_params);
      }
      else
      {
        throw Exception::IllegalArgument(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION,
          String("Method ") + outlier_detection_method_ + " is not a valid method for optimizeCalibrationCurveIterative");
      }

      // remove if residual is an outlier according to Chauvenet's criterion
      // or if testing is turned off
      if (!use_chauvenet_ || MRMRTNormalizer::chauvenet(biases, pos))
      {
        component_concentrations_sorted_indices.erase(component_concentrations_sorted_indices.begin() + pos);
      }
      else
      {
        return false;  //no optimal calibration found
      }
    }
    return false;  //no optimal calibration found
  }

  std::vector<AbsoluteQuantitationStandards::featureConcentration> AbsoluteQuantitation::extractComponents_(
    const std::vector<AbsoluteQuantitationStandards::featureConcentration> & component_concentrations,
    const std::vector<size_t>& component_concentrations_indices)
  {
    std::vector<AbsoluteQuantitationStandards::featureConcentration> component_concentrations_sub;
    for (size_t iter = 0; iter < component_concentrations_indices.size(); ++iter)
    {
      component_concentrations_sub.push_back(component_concentrations[component_concentrations_indices[iter]]);
    }
    return component_concentrations_sub;

  }

  int AbsoluteQuantitation::jackknifeOutlierCandidate_(
    const std::vector<AbsoluteQuantitationStandards::featureConcentration>& component_concentrations,
    const String & feature_name,
    const String & transformation_model,
    const Param & transformation_model_params)
  {
    // Returns candidate outlier: A linear regression and rsq is calculated for
    // the data points with one removed pair. The combination resulting in
    // highest rsq is considered corresponding to the outlier candidate. The
    // corresponding iterator position is then returned.
    std::vector<double> rsq_tmp;
    Param optimized_params = transformation_model_params;

    for (Size i = 0; i < component_concentrations.size(); i++)
    {
      std::vector<AbsoluteQuantitationStandards::featureConcentration> component_concentrations_tmp = component_concentrations;
      component_concentrations_tmp.erase(component_concentrations_tmp.begin() + i);

      // fit the model
      optimized_params = fitCalibration(component_concentrations_tmp,
        feature_name,
        transformation_model,
        optimized_params);

      // calculate the R2 and bias
      std::vector<double> biases;
      double correlation_coefficient = 0.0;
      calculateBiasAndR(
        component_concentrations_tmp,
        feature_name,
        transformation_model,
        optimized_params,
        biases,
        correlation_coefficient);

      rsq_tmp.push_back(correlation_coefficient);
    }
    return max_element(rsq_tmp.begin(), rsq_tmp.end()) - rsq_tmp.begin();
  }

  int AbsoluteQuantitation::residualOutlierCandidate_(
    const std::vector<AbsoluteQuantitationStandards::featureConcentration>& component_concentrations,
    const String & feature_name,
    const String & transformation_model,
    const Param & transformation_model_params)
  {
    // Returns candidate outlier: A linear regression and residuals are calculated for
    // the data points. The one with highest residual error is selected as the outlier candidate. The
    // corresponding iterator position is then returned.

    // fit the model
    Param optimized_params = fitCalibration(component_concentrations,
      feature_name,
      transformation_model,
      transformation_model_params);

    // calculate the R2 and bias
    std::vector<double> biases;
    double correlation_coefficient = 0.0;
    calculateBiasAndR(
      component_concentrations,
      feature_name,
      transformation_model,
      optimized_params,
      biases,
      correlation_coefficient);

    return max_element(biases.begin(), biases.end()) - biases.begin();
  }

  void AbsoluteQuantitation::optimizeCalibrationCurves(
    std::map<String, std::vector<AbsoluteQuantitationStandards::featureConcentration>> & components_concentrations)
  {
    std::map<String, std::vector<AbsoluteQuantitationStandards::featureConcentration>>& cc = components_concentrations;
    for (std::pair<const String, AbsoluteQuantitationMethod>& quant_method : quant_methods_)
    {
      const String& component_name = quant_method.first;
      AbsoluteQuantitationMethod& component_aqm = quant_method.second;
      if (cc.count(component_name) && optimization_method_ == "iterative")
      {
        // optimize the calibration curve for the component
        Param optimized_params;
        bool optimal_calibration_found = optimizeCalibrationCurveIterative(
          cc[component_name],
          component_aqm.getFeatureName(),
          component_aqm.getTransformationModel(),
          component_aqm.getTransformationModelParams(),
          optimized_params);

        // order component concentrations and update the lloq and uloq
        std::vector<AbsoluteQuantitationStandards::featureConcentration>::const_iterator it;
        it = std::min_element(cc[component_name].begin(), cc[component_name].end(), [](
            const AbsoluteQuantitationStandards::featureConcentration& lhs,
            const AbsoluteQuantitationStandards::featureConcentration& rhs
          )
          {
            return lhs.actual_concentration < rhs.actual_concentration;
          }
        );
        component_aqm.setLLOQ(it->actual_concentration);
        it = std::max_element(cc[component_name].begin(), cc[component_name].end(), [](
            const AbsoluteQuantitationStandards::featureConcentration& lhs,
            const AbsoluteQuantitationStandards::featureConcentration& rhs
          )
          {
            return lhs.actual_concentration < rhs.actual_concentration;
          }
        );
        component_aqm.setULOQ(it->actual_concentration);

        if (optimal_calibration_found)
        {
          // calculate the R2 and bias
          std::vector<double> biases;
          double correlation_coefficient = 0.0;
          calculateBiasAndR(
            cc[component_name],
            component_aqm.getFeatureName(),
            component_aqm.getTransformationModel(),
            optimized_params,
            biases,
            correlation_coefficient);

          // record the updated information
          component_aqm.setCorrelationCoefficient(correlation_coefficient);
          component_aqm.setTransformationModelParams(optimized_params);
          component_aqm.setNPoints(cc[component_name].size());
        }
        else
        {
          component_aqm.setCorrelationCoefficient(0.0);
          component_aqm.setNPoints(0);
          component_aqm.setLLOQ(0.0);
          component_aqm.setULOQ(0.0);
        }
      }
      else if (optimization_method_ != "iterative")
      {
        throw Exception::IllegalArgument(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION,
          "Unsupported calibration curve optimization method '" + optimization_method_ + "'.");
      }
      else
      {
        OPENMS_LOG_DEBUG << "Warning: Standards not found for component " << component_name << ".";
      }
    }
  }

  void AbsoluteQuantitation::optimizeSingleCalibrationCurve(
    const String& component_name,
    std::vector<AbsoluteQuantitationStandards::featureConcentration>& component_concentrations
  )
  {
    std::map<String, std::vector<AbsoluteQuantitationStandards::featureConcentration>> cc_map;
    cc_map.insert({component_name, component_concentrations});
    optimizeCalibrationCurves(cc_map);
    component_concentrations = cc_map.at(component_name);
  }
} // namespace


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

#include "special_info.h"
#include "world.h"

#include <boost/algorithm/string.hpp>

using namespace std;

GPS_Altitude::GPS_Altitude(){
    interval=0.25;
    rows=721;
    columns=1440;
}

void GPS_Altitude::setup(){
    if(!is_setup()){
        File_IO_Binary_Load load("data/egm96/WW15MGH.DAC");

        if(load.file_loaded()){
            string egm96_data=load.get_data();

            for(size_t i=0;i<egm96_data.length();i+=2){
                if(i+1<egm96_data.length()){
                    int16_t number=(egm96_data[i]<<8)|(egm96_data[i+1]&0xff);

                    egm96_nums.push_back(number);
                }
            }
        }
    }
}

void GPS_Altitude::stop(){
    egm96_nums.clear();
}

bool GPS_Altitude::is_setup(){
    return egm96_nums.size()!=0;
}

double GPS_Altitude::get_offset(double gps_latitude,double gps_longitude){
    double latitude=gps_latitude;
    double longitude=gps_longitude>=0.0 ? gps_longitude : gps_longitude+360.0;

    int top_row=(int)((90.0-latitude)/interval);
    if(latitude<=-90.0){
        top_row=rows-2;
    }
    int bottom_row=top_row+1;

    int left_column=(int)(longitude/interval);
    int right_column=left_column+1;
    if(longitude>=360.0-interval){
        left_column=columns-1;
        right_column=0;
    }

    double latitude_top=90.0-top_row*interval;
    double longitude_left=left_column*interval;

    double ul=game.world.gps_altitude.get_post_offset(top_row,left_column);
    double ll=game.world.gps_altitude.get_post_offset(bottom_row,left_column);
    double lr=game.world.gps_altitude.get_post_offset(bottom_row,right_column);
    double ur=game.world.gps_altitude.get_post_offset(top_row,right_column);

    double u=(longitude-longitude_left)/interval;
    double v=(latitude_top-latitude)/interval;

    double pll=(1.0-u)*(1.0-v);
    double plr=u*(1.0-v);
    double pur=u*v;
    double pul=(1.0-u)*v;

    //meters
    return (pll*ll+plr*lr+pur*ur+pul*ul)/100.0;
}

double GPS_Altitude::get_post_offset(int row,int column){
    int k=row*columns+column;

    if(k<egm96_nums.size()){
        return egm96_nums[k];
    }
    else{
        return 0.0;
    }
}

string Special_Info::get_sensor_string(string sensor_name){
    string text="";

    if(android.get_sensor_availability(sensor_name)){
        if(android.get_sensor_state(sensor_name)){
            float sensor_values[SENSOR_VALUES_MAX];
            android.get_sensor_values(sensor_name,sensor_values);

            string sensor_value_labels[SENSOR_VALUES_MAX];
            android.get_sensor_value_labels(sensor_name,sensor_value_labels);

            int sensor_value_count=android.get_sensor_value_count(sensor_name);

            for(int i=0;i<sensor_value_count;i++){
                string label="";
                if(sensor_value_labels[i].length()>0){
                    label=sensor_value_labels[i]+": ";
                }

                text+=label+Strings::num_to_string(sensor_values[i])+" "+android.get_sensor_units(sensor_name);

                if(i<sensor_value_count-1){
                    text+="\n";
                }
            }
        }
        else{
            text+="Sensor offline";
        }
    }
    else{
        text+="Sensor unavailable";
    }

    return text;
}

string Special_Info::get_special_info_text(string special_info){
    string text="";

    if(special_info.length()>0){
        if(special_info=="configure_command"){
            if(engine_interface.configure_command!=-1 && engine_interface.configure_command<engine_interface.game_commands.size()){
                text+="Inputs currently bound to \""+engine_interface.game_commands[engine_interface.configure_command].title+"\":"+"\n\n";

                const char* ckey=SDL_GetScancodeName(engine_interface.game_commands[engine_interface.configure_command].key);
                const char* cbutton=SDL_GameControllerGetStringForButton(engine_interface.game_commands[engine_interface.configure_command].button);
                const char* caxis=SDL_GameControllerGetStringForAxis(engine_interface.game_commands[engine_interface.configure_command].axis);

                bool allow_keys_and_buttons=true;
                bool allow_axes=true;
                if(caxis!=0 && engine_interface.game_commands[engine_interface.configure_command].axis!=SDL_CONTROLLER_AXIS_INVALID){
                    allow_keys_and_buttons=false;
                }
                else{
                    allow_axes=false;
                }

                if(allow_keys_and_buttons){
                    text+="Keyboard Key: ";
                    if(ckey!=0 && engine_interface.game_commands[engine_interface.configure_command].key!=SDL_SCANCODE_UNKNOWN){
                        text+=Strings::first_letter_capital(ckey);
                    }
                    else{
                        text+="<NOT SET>";
                    }
                    text+="\n\n";

                    text+="Controller Button: ";
                    if(cbutton!=0 && engine_interface.game_commands[engine_interface.configure_command].button!=SDL_CONTROLLER_BUTTON_INVALID){
                        text+=Strings::first_letter_capital(cbutton);
                    }
                    else{
                        text+="<NOT SET>";
                    }
                    text+="\n\n";
                }

                if(allow_axes){
                    text+="Controller Axis: ";
                    if(caxis!=0 && engine_interface.game_commands[engine_interface.configure_command].axis!=SDL_CONTROLLER_AXIS_INVALID){
                        text+=Strings::first_letter_capital(caxis);
                    }
                    else{
                        text+="<NOT SET>";
                    }
                    text+="\n\n";
                }
            }
        }
        else if(special_info=="scan_acceleration"){
            text+=get_sensor_string("accelerometer");
            text+="\n\n";

            text+=get_sensor_string("gravity");
            text+="\n\n";

            text+=get_sensor_string("linear_acceleration");
            text+="\n\n";
        }
        else if(special_info=="scan_environment"){
            text+=get_sensor_string("ambient_temperature");
            text+="\n\n";

            text+=get_sensor_string("pressure");
            text+="\n\n";

            text+=get_sensor_string("light");
            text+="\n\n";

            text+=get_sensor_string("relative_humidity");
            text+="\n\n";

            if(android.get_sensor_availability("ambient_temperature") && android.get_sensor_state("ambient_temperature") &&
               android.get_sensor_availability("relative_humidity") && android.get_sensor_state("relative_humidity")){
                float sensor_values[SENSOR_VALUES_MAX];

                android.get_sensor_values("ambient_temperature",sensor_values);
                float ambient_temperature=sensor_values[0];

                android.get_sensor_values("relative_humidity",sensor_values);
                float relative_humidity=sensor_values[0];

                float m=17.62f;
                float tn=243.12f;

                float a=6.112f;
                float absolute_humidity=216.7f*(((relative_humidity/100.0f)*a*exp((m*ambient_temperature)/(tn+ambient_temperature)))/(273.15f+ambient_temperature));

                float numerator=log(relative_humidity/100.0f)+(m*ambient_temperature)/(tn+ambient_temperature);
                float dewpoint=tn*(numerator/(m-numerator));

                text+="Dew point: "+Strings::num_to_string(dewpoint)+" "+Symbols::degrees()+"C\n\n";

                text+="Absolute ambient humidity: "+Strings::num_to_string(absolute_humidity)+" g/m"+Symbols::cubed()+"\n\n";
            }
            else{
                text+="Dew point and absolute ambient humidity could not be calculated\n\n";
            }
        }
        else if(special_info=="scan_magnetic"){
            text+=get_sensor_string("magnetic_field");
            text+="\n\n";
        }
        else if(special_info=="scan_rotation"){
            text+=get_sensor_string("gyroscope");
            text+="\n\n";

            text+="Rotation vector (gyroscope):\n";
            text+=get_sensor_string("rotation_vector");
            text+="\n\n";

            text+="Geomagnetic rotation vector (magnetometer):\n";
            text+=get_sensor_string("geomagnetic_rotation_vector");
            text+="\n\n";
        }
        else if(special_info=="scan_other"){
            text+=get_sensor_string("proximity");
            text+="\n\n";

            text+=get_sensor_string("step_counter");
            text+="\n\n";
        }
        else if(special_info=="scan_position"){
            if(android.get_sensor_availability("pressure") && android.get_sensor_state("pressure")){
                float sensor_values[SENSOR_VALUES_MAX];

                android.get_sensor_values("pressure",sensor_values);
                //pascals
                float pressure=sensor_values[0]*100.0f;

                //pascals
                float base_pressure=101325.0f;
                //kelvin
                float base_temperature=288.15f;
                //meters/second^2
                float acceleration_due_to_gravity=9.80665f;
                //kelvin/meter
                float lapse_rate=-0.0065f;
                //joules/(kilogram*kelvin)
                float gas_constant_for_air=287.053f;
                //meters
                float altitude=(base_temperature/lapse_rate)*(pow((pressure/base_pressure),-lapse_rate*gas_constant_for_air/acceleration_due_to_gravity)-1.0f);

                text+="Altitude (calculated from pressure): "+Strings::num_to_string(altitude)+" m\n\n";
            }
            else{
                text+="Altitude could not be calculated from pressure\n\n";
            }

            if(android.get_gps_availability()){
                if(android.get_gps_accessibility()){
                    if(android.get_gps_state()){
                        Android_GPS gps_data=android.get_gps_readout();

                        if(game.world.gps_altitude.is_setup()){
                            double offset=game.world.gps_altitude.get_offset(gps_data.latitude,gps_data.longitude);

                            text+="Altitude (calculated from GPS): "+Strings::num_to_string(gps_data.altitude)+" "+Android_GPS::UNITS_ALTITUDE+"\n\n";
                            text+="Altitude (calculated from GPS, adjusted to MSL): "+Strings::num_to_string(gps_data.altitude+offset)+" "+Android_GPS::UNITS_ALTITUDE+"\n\n";
                        }
                        else{
                            text+="Altitude could not be calculated from GPS\n\n";
                        }

                        text+="Latitude: "+Strings::num_to_string(gps_data.latitude)+Android_GPS::UNITS_LATITUDE+"\n\n";
                        text+="Longitude: "+Strings::num_to_string(gps_data.longitude)+Android_GPS::UNITS_LONGITUDE+"\n\n";
                        text+="Bearing: "+Strings::num_to_string(gps_data.bearing)+Android_GPS::UNITS_BEARING+"\n\n";
                        text+="Speed: "+Strings::num_to_string(gps_data.speed)+" "+Android_GPS::UNITS_SPEED+"\n\n";
                        text+="GPS accuracy: "+Strings::num_to_string(gps_data.accuracy)+" "+Android_GPS::UNITS_ACCURACY+"\n\n";
                    }
                    else{
                        text+="GPS offline\n\n";
                    }
                }
                else{
                    text+="GPS inaccessible\n\n";
                }
            }
            else{
                text+="GPS unavailable\n\n";
            }
        }
        else{
            Log::add_error("Invalid special info text: '"+special_info+"'");
        }
    }

    return text;
}

string Special_Info::get_special_info_sprite(string special_info){
    string str_sprite_name="";

    if(special_info.length()>0){
        if(special_info=="example"){
            str_sprite_name="";
        }
        else{
            Log::add_error("Invalid special info sprite: '"+special_info+"'");
        }
    }

    return str_sprite_name;
}


//          Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef METRO_REGRESSION_FIT_MODEL_HPP
#define METRO_REGRESSION_FIT_MODEL_HPP

#include <string>
#include <vector>
#include <Eigen/Core>
#include <Eigen/Cholesky>
#include "boost/function.hpp"
#include "metro/ModifiedCholesky.hpp"
#include "metro/SmoothFunction.hpp"

namespace metro {
	struct Stepper {
	public:
		typedef SmoothFunction Function ;
		typedef Eigen::VectorXd Vector ;
		typedef Eigen::MatrixXd Matrix ;
		typedef boost::function<
			void(
				int iteration,
				double ll,
				double target_ll,
				Vector const& point,
				Vector const& derivative,
				Vector const& step,
				bool converged
			)
		> Tracer ;
	public:
		virtual ~Stepper() ;
		virtual bool diverged() const = 0 ;
		virtual bool step( Function& function, Vector* result ) = 0 ;
		virtual std::size_t number_of_iterations() const = 0 ;
	} ;

	std::pair< bool, int > fit_model(
		metro::SmoothFunction& ll,
		std::string const& model_name,
		Eigen::VectorXd const& starting_point,
		Stepper& stopping_condition,
		std::vector< std::string >* comments
	) ;
}

#endif


// Boost.Units - A C++ library for zero-overhead dimensional analysis and
// unit/quantity manipulation and conversion
//
// Copyright (C) 2003-2008 Matthias Christian Schabel
// Copyright (C) 2007-2008 Steven Watanabe
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_UNITS_ANGLE_GRADIAN_BASE_UNIT_HPP
#define BOOST_UNITS_ANGLE_GRADIAN_BASE_UNIT_HPP

#include <boost/units/conversion.hpp>
#include <boost/units/base_units/angle/radian.hpp>

BOOST_UNITS_DEFINE_BASE_UNIT_WITH_CONVERSIONS(angle,gradian,"gradian","grad",6.28318530718/400.,boost::units::angle::radian_base_unit,-102);

#if BOOST_UNITS_HAS_BOOST_TYPEOF

#include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()

BOOST_TYPEOF_REGISTER_TYPE(boost::units::angle::gradian_base_unit)

#endif

#endif // BOOST_UNITS_ANGLE_GRADIAN_BASE_UNIT_HPP


#include <boost/multiprecision/cpp_int.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <iomanip>
#include <string.h>
using boost::multiprecision::cpp_int;
using namespace std;
int main()
{
	cpp_int n = 123456789;
	cpp_int n1 = n;
	int i,j,k,len;
	string str;
	int x = 5;
	for(i=0;i<1000;i++)
	{
	   std::string str = boost::lexical_cast<std::string>(n);
	   len = str.length();

	   for(j=0;j<len;j++)
	   {
	   	 if(j<(len-9) && j==(len-x))
	   	 {

	   	 	if(str[j]=='9' && str[j+1]=='3')
	   	 	{
                if(str[j+8]=='8' && str[j+9] == '6')
                {
                    printf("Indira's Number = ");
                	for(k=0;k<10;k++)
                	{
                		cout<<""<<str[j+k];
					}

				}

			}

	  }

	}
	n*=n1;
	x = x+5;
}
	return 0;
}