/*
 Copyright (C) 2016 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

/*! \file ored/utilities/parsers.cpp
    \brief
    \ingroup utilities
*/

#include <boost/algorithm/string.hpp>
#include <map>
#include <ored/utilities/log.hpp>
#include <ored/utilities/parsers.hpp>
#include <ql/currencies/all.hpp>
#include <ql/errors.hpp>
#include <ql/indexes/all.hpp>
#include <ql/time/calendars/all.hpp>
#include <ql/time/daycounters/all.hpp>
#include <ql/utilities/dataparsers.hpp>
#include <ql/version.hpp>
#include <qle/calendars/chile.hpp>
#include <qle/calendars/colombia.hpp>
#include <qle/calendars/france.hpp>
#include <qle/calendars/malaysia.hpp>
#include <qle/calendars/netherlands.hpp>
#include <qle/calendars/peru.hpp>
#include <qle/calendars/philippines.hpp>
#include <qle/calendars/thailand.hpp>
#include <qle/currencies/africa.hpp>
#include <qle/currencies/america.hpp>
#include <qle/currencies/asia.hpp>
#include <qle/currencies/metals.hpp>
#include <qle/time/yearcounter.hpp>

#include <boost/lexical_cast.hpp>

using namespace QuantLib;
using namespace QuantExt;
using namespace std;

namespace ore {
namespace data {

Date parseDate(const string& s) {
    // TODO: review

    if (s == "")
        return Date();

    // guess formats from token number and sizes
    // check permissible lengths
    QL_REQUIRE((s.size() >= 3 && s.size() <= 6) || s.size() == 8 || s.size() == 10,
               "invalid date format of " << s << ", date string length 8 or 10 or between 3 and 6 required");

    vector<string> tokens;
    boost::split(tokens, s, boost::is_any_of("-/.:"));

    if (tokens.size() == 1) {
        if (s.size() == 8) {
            // yyyymmdd
            int y = parseInteger(s.substr(0, 4));
            int m = parseInteger(s.substr(4, 2));
            int d = parseInteger(s.substr(6, 2));
            return Date(d, Month(m), y);
        } else if (s.size() >= 3 && s.size() <= 6) {
            // Excel format
            // Boundaries will be checked by Date constructor
            // Boundaries are minDate = 367 i.e. Jan 1st, 1901
            // and maxDate = 109574 i.e. Dec 31st, 2199
            BigInteger serial = parseInteger(s);
            return Date(serial);
        }
    } else if (tokens.size() == 3) {
        if (tokens[0].size() == 4) {
            // yyyy-mm-dd
            // yyyy/mm/dd
            // yyyy.mm.dd
            int y = parseInteger(tokens[0]);
            int m = parseInteger(tokens[1]);
            int d = parseInteger(tokens[2]);
            return Date(d, Month(m), y);
        } else if (tokens[0].size() == 2) {
            // dd-mm-yy
            // dd/mm/yy
            // dd.mm.yy
            // dd-mm-yyyy
            // dd/mm/yyyy
            // dd.mm.yyyy
            int d = parseInteger(tokens[0]);
            int m = parseInteger(tokens[1]);
            int y = parseInteger(tokens[2]);
            if (y < 100) {
                if (y > 80)
                    y += 1900;
                else
                    y += 2000;
            }
            return Date(d, Month(m), y);
        }
    }

    QL_FAIL("Cannot convert \"" << s << "\" to Date.");
}

Real parseReal(const string& s) {
    try {
        return std::stod(s);
    } catch (const std::exception& ex) {
        QL_FAIL("Failed to parseReal(\"" << s << "\") " << ex.what());
    }
}

bool tryParseReal(const string& s, QuantLib::Real& result) {
    try {
        result = std::stod(s);
    } catch (...) {
        result = Null<Real>();
        return false;
    }
    return true;
}

Integer parseInteger(const string& s) {
    try {
        return boost::lexical_cast<Integer>(s.c_str());
    } catch (std::exception& ex) {
        QL_FAIL("Failed to parseInteger(\"" << s << "\") " << ex.what());
    }
}

bool parseBool(const string& s) {
    static map<string, bool> b = {{"Y", true},  {"YES", true}, {"TRUE", true},   {"true", true},   {"1", true},
                                  {"N", false}, {"NO", false}, {"FALSE", false}, {"false", false}, {"0", false}};

    auto it = b.find(s);
    if (it != b.end()) {
        return it->second;
    } else {
        QL_FAIL("Cannot convert " << s << " to bool");
    }
}

Calendar parseCalendar(const string& s) {
    static map<string, Calendar> m = {{"TGT", TARGET()},
                                      {"TARGET", TARGET()},
                                      {"EUR", TARGET()},
                                      {"FR", France()},
                                      {"FRF", France()},
                                      {"IT", Italy()},
                                      {"ITL", Italy()},
                                      {"NL", Netherlands()},
                                      {"NGL", Netherlands()},
                                      {"ZUB", Switzerland()},
                                      {"CHF", Switzerland()},
                                      {"CHZU", Switzerland()},
                                      {"Switzerland", Switzerland()},
                                      {"US", UnitedStates()},
                                      {"USNY", UnitedStates()},
                                      {"USD", UnitedStates()},
                                      {"NYB", UnitedStates()},
                                      {"US-SET", UnitedStates(UnitedStates::Settlement)},
                                      {"US settlement", UnitedStates(UnitedStates::Settlement)},
                                      {"US-GOV", UnitedStates(UnitedStates::GovernmentBond)},
                                      {"US-FED", UnitedStates(UnitedStates::FederalReserve)},
                                      {"US-NYSE", UnitedStates(UnitedStates::NYSE)},
                                      {"New York stock exchange", UnitedStates(UnitedStates::NYSE)},
                                      {"US with Libor impact", UnitedStates(UnitedStates::LiborImpact)},
                                      {"US-NERC", UnitedStates(UnitedStates::NERC)},
                                      {"GB", UnitedKingdom()},
                                      {"GBP", UnitedKingdom()},
                                      {"UK", UnitedKingdom()},
                                      {"UK settlement", UnitedKingdom()},
                                      {"London stock exchange", UnitedKingdom(UnitedKingdom::Exchange)},
                                      {"LNB", UnitedKingdom()},
                                      {"CA", Canada()},
                                      {"TRB", Canada()},
                                      {"CAD", Canada()},
                                      {"CATO", Canada()},
                                      {"Canada", Canada()},
                                      {"SYB", Australia()},
                                      {"AU", Australia()},
                                      {"AUD", Australia()},
                                      {"Australia", Australia()},
                                      {"TKB", Japan()},
                                      {"JP", Japan()},
                                      {"JPTO", Japan()},
                                      {"JPY", Japan()},
                                      {"Japan", Japan()},
                                      {"ZAR", SouthAfrica()},
                                      {"SA", SouthAfrica()},
                                      {"SS", Sweden()},
                                      {"SEK", Sweden()},
                                      {"SEST", Sweden()},
                                      {"Sweden", Sweden()},
                                      {"ARS", Argentina()},
                                      {"BRL", Brazil()},
                                      {"BWP", Botswana()},
                                      {"CNH", China()},
                                      {"CNY", China()},
                                      {"CZK", CzechRepublic()},
                                      {"DKK", Denmark()},
                                      {"DEN", Denmark()},
                                      {"Denmark", Denmark()},
                                      {"FIN", Finland()},
                                      {"HKD", HongKong()},
                                      {"ISK", Iceland()},
                                      {"ILS", Israel()},
                                      {"INR", India()},
                                      {"IDR", Indonesia()},
                                      {"MXN", Mexico()},
                                      {"NZD", NewZealand()},
                                      {"NOK", Norway()},
                                      {"Norway", Norway()},
                                      {"PLN", Poland()},
                                      {"RON", Romania()},
                                      {"RUB", Russia()},
                                      {"SAR", SaudiArabia()},
                                      {"SGD", Singapore()},
                                      {"KRW", SouthKorea()},
                                      {"TWD", Taiwan()},
                                      {"TRY", Turkey()},
                                      {"TRIS", Turkey()},
                                      {"UAH", Ukraine()},
                                      {"HUF", Hungary()},
                                      {"GBLO", UnitedKingdom()},
                                      {"CLP", Chile()},
                                      {"THB", QuantExt::Thailand()},
                                      {"COP", Colombia()},
                                      {"PEN", Peru()},
                                      {"MYR", Malaysia()},
                                      {"PHP", Philippines()},
                                      // city specific calendars
                                      {"FRA", Germany(Germany::Settlement)},
                                      // fallback to TARGET for these emerging ccys
                                      {"KWD", TARGET()},
                                      {"TND", TARGET()},
                                      {"KZT", TARGET()},
                                      {"QAR", TARGET()},
                                      {"MXV", TARGET()},
                                      {"CLF", TARGET()},
                                      {"EGP", TARGET()},
                                      {"BHD", TARGET()},
                                      {"OMR", TARGET()},
                                      {"VND", TARGET()},
                                      {"AED", TARGET()},
                                      {"NGN", TARGET()},
                                      {"MAD", TARGET()},
                                      {"PKR", TARGET()},
                                      // ISDA http://www.fpml.org/coding-scheme/business-center-7-15.xml
                                      {"EUTA", TARGET()},
                                      {"BEBR", TARGET()}, // Belgium, Brussels not in QL
                                      {"WeekendsOnly", WeekendsOnly()},
                                      {"UNMAPPED", WeekendsOnly()},
                                      {"NullCalendar", NullCalendar()},
                                      {"", NullCalendar()}};

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        // Try to split them up
        vector<string> calendarNames;
        split(calendarNames, s, boost::is_any_of(",()")); // , is delimiter, the brackets may arise if joint calendar
        // now remove any leading strings indicating a joint calendar
        calendarNames.erase(std::remove(calendarNames.begin(), calendarNames.end(), "JoinHolidays"),
                            calendarNames.end());
        calendarNames.erase(std::remove(calendarNames.begin(), calendarNames.end(), "JoinBusinessDays"),
                            calendarNames.end());
        calendarNames.erase(std::remove(calendarNames.begin(), calendarNames.end(), ""), calendarNames.end());
        QL_REQUIRE(calendarNames.size() > 1 && calendarNames.size() <= 4, "Cannot convert " << s << " to Calendar");
        // Populate a vector of calendars.
        vector<Calendar> calendars;
        for (Size i = 0; i < calendarNames.size(); i++) {
            boost::trim(calendarNames[i]);
            try {
                calendars.push_back(parseCalendar(calendarNames[i]));
            } catch (std::exception& e) {
                QL_FAIL("Cannot convert " << s << " to Calendar [exception:" << e.what() << "]");
            } catch (...) {
                QL_FAIL("Cannot convert " << s << " to Calendar [unhandled exception]");
            }
        }

        switch (calendarNames.size()) {
        case 2:
            return JointCalendar(calendars[0], calendars[1]);
        case 3:
            return JointCalendar(calendars[0], calendars[1], calendars[2]);
        case 4:
            return JointCalendar(calendars[0], calendars[1], calendars[2], calendars[3]);
        default:
            QL_FAIL("Cannot convert " << s << " to Calendar");
        }
    }
}

Period parsePeriod(const string& s) { return PeriodParser::parse(s); }

BusinessDayConvention parseBusinessDayConvention(const string& s) {
    static map<string, BusinessDayConvention> m = {{"F", Following},
                                                   {"Following", Following},
                                                   {"FOLLOWING", Following},
                                                   {"MF", ModifiedFollowing},
                                                   {"ModifiedFollowing", ModifiedFollowing},
                                                   {"Modified Following", ModifiedFollowing},
                                                   {"MODIFIEDF", ModifiedFollowing},
                                                   {"MODFOLLOWING", ModifiedFollowing},
                                                   {"P", Preceding},
                                                   {"Preceding", Preceding},
                                                   {"PRECEDING", Preceding},
                                                   {"MP", ModifiedPreceding},
                                                   {"ModifiedPreceding", ModifiedPreceding},
                                                   {"Modified Preceding", ModifiedPreceding},
                                                   {"MODIFIEDP", ModifiedPreceding},
                                                   {"U", Unadjusted},
                                                   {"Unadjusted", Unadjusted},
                                                   {"INDIFF", Unadjusted},
                                                   {"NEAREST", Nearest},
                                                   {"NONE", Unadjusted},
                                                   {"NotApplicable", Unadjusted}};

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Cannot convert " << s << " to BusinessDayConvention");
    }
}

DayCounter parseDayCounter(const string& s) {
    static map<string, DayCounter> m = {{"A360", Actual360()},
                                        {"Actual/360", Actual360()},
                                        {"ACT/360", Actual360()},
                                        {"A365", Actual365Fixed()},
                                        {"A365F", Actual365Fixed()},
                                        {"Actual/365 (Fixed)", Actual365Fixed()},
                                        {"Actual/365 (fixed)", Actual365Fixed()},
                                        {"ACT/365.FIXED", Actual365Fixed()},
                                        {"ACT/365", Actual365Fixed()},
                                        {"ACT/365L", Actual365Fixed()},
                                        {"T360", Thirty360(Thirty360::USA)},
                                        {"30/360", Thirty360(Thirty360::USA)},
                                        {"30/360 (Bond Basis)", Thirty360(Thirty360::USA)},
                                        {"ACT/nACT", Thirty360(Thirty360::USA)},
                                        {"30E/360 (Eurobond Basis)", Thirty360(Thirty360::European)},
                                        {"30E/360", Thirty360(Thirty360::European)},
                                        {"30E/360.ISDA", Thirty360(Thirty360::European)},
                                        {"30/360 (Italian)", Thirty360(Thirty360::Italian)},
                                        {"ActActISDA", ActualActual(ActualActual::ISDA)},
                                        {"ACT/ACT.ISDA", ActualActual(ActualActual::ISDA)},
                                        {"Actual/Actual (ISDA)", ActualActual(ActualActual::ISDA)},
                                        {"ActualActual (ISDA)", ActualActual(ActualActual::ISDA)},
                                        {"ACT/ACT", ActualActual(ActualActual::ISDA)},
                                        {"ACT29", ActualActual(ActualActual::AFB)},
                                        {"ACT", ActualActual(ActualActual::ISDA)},
                                        {"ActActISMA", ActualActual(ActualActual::ISMA)},
                                        {"Actual/Actual (ISMA)", ActualActual(ActualActual::ISMA)},
                                        {"ActualActual (ISMA)", ActualActual(ActualActual::ISMA)},
                                        {"ActActAFB", ActualActual(ActualActual::AFB)},
                                        {"ACT/ACT.AFB", ActualActual(ActualActual::AFB)},
                                        {"ACT/ACT.ISMA", ActualActual(ActualActual::ISMA)},
                                        {"Actual/Actual (AFB)", ActualActual(ActualActual::AFB)},
                                        {"1/1", OneDayCounter()},
                                        {"BUS/252", Business252()},
                                        {"Business/252", Business252()},
                                        {"Actual/365 (No Leap)", Actual365Fixed(Actual365Fixed::NoLeap)},
                                        {"Act/365 (NL)", Actual365Fixed(Actual365Fixed::NoLeap)},
                                        {"NL/365", Actual365Fixed(Actual365Fixed::NoLeap)},
                                        {"Actual/365 (JGB)", Actual365Fixed(Actual365Fixed::NoLeap)},
                                        {"Simple", SimpleDayCounter()},
                                        {"Year", YearCounter()}
    };

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("DayCounter " << s << " not recognized");
    }
}

Currency parseCurrency(const string& s) {
    static map<string, Currency> m = {
        {"ATS", ATSCurrency()}, {"AUD", AUDCurrency()}, {"BEF", BEFCurrency()}, {"BRL", BRLCurrency()},
        {"CAD", CADCurrency()}, {"CHF", CHFCurrency()}, {"CNH", CNHCurrency()}, {"CNY", CNYCurrency()},
        {"CZK", CZKCurrency()}, {"DEM", DEMCurrency()}, {"DKK", DKKCurrency()}, {"EUR", EURCurrency()},
        {"ESP", ESPCurrency()}, {"FIM", FIMCurrency()}, {"FRF", FRFCurrency()}, {"GBP", GBPCurrency()},
        {"GRD", GRDCurrency()}, {"HKD", HKDCurrency()}, {"HUF", HUFCurrency()}, {"IEP", IEPCurrency()},
        {"ITL", ITLCurrency()}, {"INR", INRCurrency()}, {"ISK", ISKCurrency()}, {"JPY", JPYCurrency()},
        {"KRW", KRWCurrency()}, {"LUF", LUFCurrency()}, {"NLG", NLGCurrency()}, {"NOK", NOKCurrency()},
        {"NZD", NZDCurrency()}, {"PLN", PLNCurrency()}, {"PTE", PTECurrency()}, {"RON", RONCurrency()},
        {"SEK", SEKCurrency()}, {"SGD", SGDCurrency()}, {"THB", THBCurrency()}, {"TRY", TRYCurrency()},
        {"TWD", TWDCurrency()}, {"USD", USDCurrency()}, {"ZAR", ZARCurrency()}, {"ARS", ARSCurrency()},
        {"CLP", CLPCurrency()}, {"COP", COPCurrency()}, {"IDR", IDRCurrency()}, {"ILS", ILSCurrency()},
        {"KWD", KWDCurrency()}, {"PEN", PENCurrency()}, {"PKR", PKRCurrency()}, {"MXN", MXNCurrency()},
        {"SAR", SARCurrency()}, {"RUB", RUBCurrency()}, {"TND", TNDCurrency()}, {"MYR", MYRCurrency()},
        {"UAH", UAHCurrency()}, {"KZT", KZTCurrency()}, {"QAR", QARCurrency()}, {"MXV", MXVCurrency()},
        {"CLF", CLFCurrency()}, {"EGP", EGPCurrency()}, {"BHD", BHDCurrency()}, {"OMR", OMRCurrency()},
        {"VND", VNDCurrency()}, {"AED", AEDCurrency()}, {"PHP", PHPCurrency()}, {"NGN", NGNCurrency()},
        {"MAD", MADCurrency()}, {"XAU", XAUCurrency()}, {"XAG", XAGCurrency()}, {"XPD", XPDCurrency()},
        {"XPT", XPTCurrency()}};

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Currency " << s << " not recognized");
    }
}

DateGeneration::Rule parseDateGenerationRule(const string& s) {
    static map<string, DateGeneration::Rule> m = {{"Backward", DateGeneration::Backward},
                                                  {"Forward", DateGeneration::Forward},
                                                  {"Zero", DateGeneration::Zero},
                                                  {"ThirdWednesday", DateGeneration::ThirdWednesday},
                                                  {"Twentieth", DateGeneration::Twentieth},
                                                  {"TwentiethIMM", DateGeneration::TwentiethIMM},
                                                  {"OldCDS", DateGeneration::OldCDS},
                                                  {"CDS2015", DateGeneration::CDS2015},
                                                  {"CDS", DateGeneration::CDS}};

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        // fall back for CDS2015
        if (s == "CDS2015") {
            ALOG("Date Generation Rule CDS2015 replaced with CDS because QuantLib Version is < 1.10");
            return DateGeneration::CDS;
        }
        QL_FAIL("Date Generation Rule " << s << " not recognized");
    }
}

Frequency parseFrequency(const string& s) {
    static map<string, Frequency> m = {{"Z", Once},
                                       {"Once", Once},
                                       {"A", Annual},
                                       {"Annual", Annual},
                                       {"S", Semiannual},
                                       {"Semiannual", Semiannual},
                                       {"Q", Quarterly},
                                       {"Quarterly", Quarterly},
                                       {"B", Bimonthly},
                                       {"Bimonthly", Bimonthly},
                                       {"M", Monthly},
                                       {"Monthly", Monthly},
                                       {"L", EveryFourthWeek},
                                       {"Lunarmonth", EveryFourthWeek},
                                       {"W", Weekly},
                                       {"Weekly", Weekly},
                                       {"D", Daily},
                                       {"Daily", Daily}};

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Frequency \"" << s << "\" not recognized");
    }
}

Compounding parseCompounding(const string& s) {
    static map<string, Compounding> m = {
        {"Simple", Simple},
        {"Compounded", Compounded},
        {"Continuous", Continuous},
        {"SimpleThenCompounded", SimpleThenCompounded},
    };

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Compounding \"" << s << "\" not recognized");
    }
}

Position::Type parsePositionType(const std::string& s) {
    static map<string, Position::Type> m = {
        {"Long", Position::Long}, {"Short", Position::Short}, {"L", Position::Long}, {"S", Position::Short},
    };

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Position type \"" << s << "\" not recognized");
    }
}

Settlement::Type parseSettlementType(const std::string& s) {
    static map<string, Settlement::Type> m = {
        {"Cash", Settlement::Cash},
        {"Physical", Settlement::Physical},
        {"C", Settlement::Cash},
        {"P", Settlement::Physical},
    };

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Settlement type \"" << s << "\" not recognized");
    }
}

Settlement::Method parseSettlementMethod(const std::string& s) {
    static map<string, Settlement::Method> m = {
        {"PhysicalOTC", Settlement::PhysicalOTC},
        {"PhysicalCleared", Settlement::PhysicalCleared},
        {"CollateralizedCashPrice", Settlement::CollateralizedCashPrice},
        {"ParYieldCurve", Settlement::ParYieldCurve},
    };

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Settlement method \"" << s << "\" not recognized");
    }
}

Exercise::Type parseExerciseType(const std::string& s) {
    static map<string, Exercise::Type> m = {
        {"European", Exercise::European}, {"Bermudan", Exercise::Bermudan}, {"American", Exercise::American},
    };

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Exercise type \"" << s << "\" not recognized");
    }
}

Option::Type parseOptionType(const std::string& s) {
    static map<string, Option::Type> m = {{"Put", Option::Put}, {"Call", Option::Call}};

    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("Option type \"" << s << "\" not recognized");
    }
}

void parseDateOrPeriod(const string& s, Date& d, Period& p, bool& isDate) {
    QL_REQUIRE(!s.empty(), "Cannot parse empty string as date or period");
    string c(1, s.back());
    bool isPeriod = c.find_first_of("DdWwMmYy") != string::npos;
    if (isPeriod) {
        p = ore::data::parsePeriod(s);
        isDate = false;
    } else {
        d = ore::data::parseDate(s);
        QL_REQUIRE(d != Date(), "Cannot parse \"" << s << "\" as date");
        isDate = true;
    }
}

QuantLib::LsmBasisSystem::PolynomType parsePolynomType(const std::string& s) {
    static map<string, LsmBasisSystem::PolynomType> poly = {
        {"Monomial", LsmBasisSystem::PolynomType::Monomial},
        {"Laguerre", LsmBasisSystem::PolynomType::Laguerre},
        {"Hermite", LsmBasisSystem::PolynomType::Hermite},
        {"Hyperbolic", LsmBasisSystem::PolynomType::Hyperbolic},
        {"Legendre", LsmBasisSystem::PolynomType::Legendre},
        {"Chebyshev", LsmBasisSystem::PolynomType::Chebyshev},
        {"Chebyshev2nd", LsmBasisSystem::PolynomType::Chebyshev2nd},
    };

    auto it = poly.find(s);
    if (it != poly.end()) {
        return it->second;
    } else {
        QL_FAIL("Polynom type \"" << s << "\" not recognized");
    }
}

SobolBrownianGenerator::Ordering parseSobolBrownianGeneratorOrdering(const std::string& s) {
    static map<string, SobolBrownianGenerator::Ordering> m = {{"Factors", SobolBrownianGenerator::Ordering::Factors},
                                                              {"Steps", SobolBrownianGenerator::Ordering::Steps},
                                                              {"Diagonal", SobolBrownianGenerator::Ordering::Diagonal}};
    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("SobolBrownianGenerator ordering \"" << s << "\" not recognized");
    }
}

SobolRsg::DirectionIntegers parseSobolRsgDirectionIntegers(const std::string& s) {
    static map<string, SobolRsg::DirectionIntegers> m = {
        {"Unit", SobolRsg::DirectionIntegers::Unit},
        {"Jaeckel", SobolRsg::DirectionIntegers::Jaeckel},
        {"SobolLevitan", SobolRsg::DirectionIntegers::SobolLevitan},
        {"SobolLevitanLemieux", SobolRsg::DirectionIntegers::SobolLevitanLemieux},
        {"JoeKuoD5", SobolRsg::DirectionIntegers::JoeKuoD5},
        {"JoeKuoD6", SobolRsg::DirectionIntegers::JoeKuoD6},
        {"JoeKuoD7", SobolRsg::DirectionIntegers::JoeKuoD7},
        {"Kuo", SobolRsg::DirectionIntegers::Kuo},
        {"Kuo2", SobolRsg::DirectionIntegers::Kuo2},
        {"Kuo3", SobolRsg::DirectionIntegers::Kuo3}};
    auto it = m.find(s);
    if (it != m.end()) {
        return it->second;
    } else {
        QL_FAIL("SobolRsg direction integers \"" << s << "\" not recognized");
    }
}

std::vector<string> parseListOfValues(string s) {
    boost::trim(s);
    std::vector<string> vec;
    boost::char_separator<char> sep(",");
    boost::tokenizer<boost::char_separator<char>> tokens(s, sep);
    for (auto r : tokens) {
        boost::trim(r);
        vec.push_back(r);
    }
    return vec;
}

AmortizationType parseAmortizationType(const std::string& s) {
    static map<string, AmortizationType> type = {
        {"None", AmortizationType::None},
        {"FixedAmount", AmortizationType::FixedAmount},
        {"RelativeToInitialNotional", AmortizationType::RelativeToInitialNotional},
        {"RelativeToPreviousNotional", AmortizationType::RelativeToPreviousNotional},
        {"Annuity", AmortizationType::Annuity}};

    auto it = type.find(s);
    if (it != type.end()) {
        return it->second;
    } else {
        QL_FAIL("Amortitazion type \"" << s << "\" not recognized");
    }
}

SequenceType parseSequenceType(const std::string& s) {
    static map<string, SequenceType> seq = {{"MersenneTwister", SequenceType::MersenneTwister},
                                            {"MersenneTwisterAntithetic", SequenceType::MersenneTwisterAntithetic},
                                            {"Sobol", SequenceType::Sobol},
                                            {"SobolBrownianBridge", SequenceType::SobolBrownianBridge}};
    auto it = seq.find(s);
    if (it != seq.end())
        return it->second;
    else
        QL_FAIL("sequence type \"" << s << "\" not recognised");
}

} // namespace data
} // namespace ore


//   GAMBIT: Global and Modular BSM Inference Tool
//   *********************************************
///  \file
///
///  Example of GAMBIT DarkBit standalone
///  main program.
///
///  *********************************************
///
///  Authors (add name and date if you modify):
///
///  \author Christoph Weniger
///  \date 2016 Feb
///  \author Jonathan Cornell
///  \date 2016 July
///  \author Sebastian Wild
///  \date 2016 Aug
///
///  *********************************************

#include <iostream>
#include <fstream>

#include "gambit/Elements/standalone_module.hpp"
#include "gambit/DarkBit/DarkBit_rollcall.hpp"
#include "gambit/Elements/spectrum_factories.hpp"
#include "gambit/Utils/util_functions.hpp"

#include <boost/multi_array.hpp>

using namespace DarkBit::Functown;     // Functors wrapping the module's actual module functions
using namespace BackendIniBit::Functown;    // Functors wrapping the backend initialisation functions

QUICK_FUNCTION(DarkBit, TH_ProcessCatalog, OLD_CAPABILITY, TH_ProcessCatalog_WIMP, TH_ProcessCatalog, ())
QUICK_FUNCTION(DarkBit, DarkMatter_ID, OLD_CAPABILITY, DarkMatter_ID_WIMP, std::string, ())
QUICK_FUNCTION(DarkBit, DD_couplings, OLD_CAPABILITY, DD_couplings_WIMP, DM_nucleon_couplings, ())

void dump_array_to_file(const std::string & filename, const
    boost::multi_array<double, 2> & a, const std::vector<double> & x, const
    std::vector<double> & y)
{
  std::fstream file;
  file.open(filename, std::ios_base::out);
  file << "0.0 ";
  for (size_t i = 0; i < x.size(); i++)
    file << x[i] << " ";
  file << std::endl;
  for (size_t j = 0; j < y.size(); j++)
  {
    file << y[j] << " ";
    for (size_t i = 0; i < x.size(); i++)
    {
      file << a[i][j] << " ";
    }
    file << std::endl;
  }
  file.close();
}

void dumpSpectrum(std::string filename, double mWIMP, double sv, std::vector<double> brList, double mPhi = -1)
{
  DarkMatter_ID_WIMP.reset_and_calculate();
  TH_ProcessCatalog_WIMP.setOption<std::vector<double>>("brList", brList);
  TH_ProcessCatalog_WIMP.setOption<double>("mWIMP", mWIMP);
  TH_ProcessCatalog_WIMP.setOption<double>("sv", sv);
  if (mPhi != -1)
    TH_ProcessCatalog_WIMP.setOption<double>("mPhi", mPhi);
  TH_ProcessCatalog_WIMP.reset_and_calculate();
  RD_fraction_one.reset_and_calculate();
  SimYieldTable_DarkSUSY.reset_and_calculate();
  SimYieldTable_MicrOmegas.reset_and_calculate();
  GA_missingFinalStates.reset_and_calculate();
  cascadeMC_FinalStates.reset_and_calculate();
  cascadeMC_DecayTable.reset_and_calculate();
  cascadeMC_LoopManager.reset_and_calculate();
  cascadeMC_gammaSpectra.reset_and_calculate();
  GA_AnnYield_General.reset_and_calculate();
  dump_GammaSpectrum.setOption<std::string>("filename", filename);
  dump_GammaSpectrum.reset_and_calculate();
}

// ---- Set up basic internal structures for direct & indirect detection ----

namespace Gambit
{
  namespace DarkBit
  {

    void TH_ProcessCatalog_WIMP(DarkBit::TH_ProcessCatalog& result)
    {
      using namespace Pipes::TH_ProcessCatalog_WIMP;
      using std::vector;
      using std::string;

      // Initialize empty catalog and main annihilation process
      TH_ProcessCatalog catalog;
      TH_Process process_ann("WIMP", "WIMP");
      TH_Process process_dec("phi");
      TH_Process process_dec1("phi1");
      TH_Process process_dec2("phi2");

      ///////////////////////////////////////
      // Import particle masses and couplings
      ///////////////////////////////////////

#define addParticle(Name, Mass, spinX2)                                        \
      catalog.particleProperties.insert(std::pair<string, TH_ParticleProperty> \
      (Name , TH_ParticleProperty(Mass, spinX2)));

      /// Option mWIMP<double>: WIMP mass in GeV (required)
      double mWIMP = runOptions->getValue<double>("mWIMP");
      /// Option sv<double>: Cross-section in cm3/s (required)
      double sv = runOptions->getValue<double>("sv");
      double b = 0;  // defined as sv(v) = sv(v=0) + b*(sv=0)*v**2
      /// Option brList<std::vector<double>>: List of branching ratios (required)
      auto brList = runOptions->getValue<std::vector<double>>("brList");
      /// Option mWIMP<double>: WIMP mass in GeV (required)
      double mPhi = runOptions->getValueOrDef<double>(59.0, "mPhi");

      addParticle("gamma", 0.0,  2)
      addParticle("Z0", 91.2,  2)
      addParticle("W+", 80.39, 2)
      addParticle("W-", 80.39, 2)
      addParticle("e+_3", 1.8,  1)
      addParticle("e-_3", 1.8,  1)
      addParticle("e+_1", 0.00051, 1)
      addParticle("e-_1", 0.00051, 1)
      addParticle("b", 4.9,  1)
      addParticle("bbar", 4.9,  1)
      addParticle("d_3", 4.9,  1)
      addParticle("dbar_3", 4.9,  1)

      addParticle("WIMP", mWIMP,  0)
      addParticle("phi",  mPhi,  0)
      addParticle("phi1", 100.,  0)
      addParticle("phi2", 100.,  0)
#undef addParticle

      TH_Channel dec_channel(daFunk::vec<string>("gamma", "gamma"), daFunk::cnst(1.));
      process_dec.channelList.push_back(dec_channel);

      TH_Channel dec_channel1(daFunk::vec<string>("e+_3", "e-_3"), daFunk::cnst(1.));
      process_dec1.channelList.push_back(dec_channel1);

      TH_Channel dec_channel2(daFunk::vec<string>("d_3", "dbar_3"), daFunk::cnst(1.));
      process_dec2.channelList.push_back(dec_channel2);

      process_ann.resonances_thresholds.threshold_energy.push_back(2*mWIMP);
      auto p1 = daFunk::vec<string>("d_3", "gamma", "gamma", "e-_3", "W-", "e-_1", "phi");
      auto p2 = daFunk::vec<string>("dbar_3", "Z0", "gamma", "e+_3", "W+", "e+_1", "phi2");
      {
        for ( unsigned int i = 0; i < brList.size()-1; i++ )
        {
          double mtot_final =
            catalog.getParticleProperty(p1[i]).mass +
            catalog.getParticleProperty(p2[i]).mass;
          if ( mWIMP*2 > mtot_final && brList[i]!= 0.)
          {
            // std::cout << p1[i] << " " << p2[i] << " " << brList[i] << std::endl;
            daFunk::Funk kinematicFunction = (daFunk::one("v")+pow(daFunk::var("v"), 2)*b)*sv*brList[i];
            TH_Channel new_channel(
                daFunk::vec<string>(p1[i], p2[i]), kinematicFunction
                );
            process_ann.channelList.push_back(new_channel);
          }
          else
          {
            process_ann.resonances_thresholds.threshold_energy.
              push_back(mtot_final);
          }
        }
      }

      if ( brList[7] > 0. )
      {
        auto E = daFunk::var("E");
        // Note:: The below is an arbitrary form of the differential section for demonstration purposes
        daFunk::Funk kinematicFunction = daFunk::one("v", "E1")/(pow(E-50, 4)+1)*sv*brList[7];
        // Note: In the three body final states, the gamma yield from AnnYield currently is just the contribution
        // from the first particle in the list (here the photon):
        TH_Channel new_channel(daFunk::vec<string>("gamma", "e+_1", "e-_1"), kinematicFunction);
        process_ann.channelList.push_back(new_channel);
      }

      catalog.processList.push_back(process_ann);
      catalog.processList.push_back(process_dec);
      catalog.processList.push_back(process_dec1);
      catalog.processList.push_back(process_dec2);

      catalog.validate();

      result = catalog;
    } // function TH_ProcessCatalog_WIMP

    // Identifier for DM particle
    void DarkMatter_ID_WIMP(std::string& result)
    {
      result = "WIMP";
    }

    void DD_couplings_WIMP(DM_nucleon_couplings& result)
    {
      using namespace Pipes::DD_couplings_WIMP;
      /// Option gps<double>: gps (default 0)
      result.gps = runOptions->getValueOrDef<double>(0., "gps");
      /// Option gns<double>: gns (default 0)
      result.gns = runOptions->getValueOrDef<double>(0., "gns");
      /// Option gpa<double>: gpa (default 0)
      result.gpa = runOptions->getValueOrDef<double>(0., "gpa");
      /// Option gna<double>: gna (default 0)
      result.gna = runOptions->getValueOrDef<double>(0., "gna");
      //std::cout << "DD_coupling says" << std::endl;
      //std::cout << result.gps << std::endl;
    }
  }
}

int main(int argc, char* argv[])
{
    std::cout << std::endl;
    std::cout << "Welcome to the DarkBit Generic WIMP standalone program!" << std::endl;
    std::cout << std::endl;
    std::cout << "**************************************************************************************" << std::endl;
    std::cout << "This standalone example demonstrates how to calculate a range of observables and " << std::endl;
    std::cout << "likelihoods for a generic WIMP model defined by the WIMP mass and an annihilation (or " << std::endl;
    std::cout << "scattering) cross section. The model also contains three scalar particles which decay:" << std::endl;
    std::cout << "phi -> gamma gamma    phi_1 -> tau+ tau-  phi_2 -> b bbar" << std::endl;
    std::cout << std::endl;
    std::cout << "Usage: DarkBit_standalone_WIMP mode" << std::endl;
    std::cout << std::endl;
    std::cout << "Mode Options: " << std::endl;
    std::cout << "  0: Outputs spectrum of gamma rays from WIMP annihilation to b bbar (dPhi_dE0.dat)" << std::endl;
    std::cout << "  1: Outputs spectrum of gamma rays from WIMP annihilation to gamma Z_0 (dPhi_dE1.dat)" << std::endl;
    std::cout << "  2: Outputs spectrum of gamma rays from WIMP annihilation to gamma gamma (dPhi_dE2.dat)" << std::endl;
    std::cout << "  3: Outputs spectrum of gamma rays from WIMP annihilation to tau+ tau- (dPhi_dE3.dat)" << std::endl;
    std::cout << "  4: Outputs spectrum of gamma rays from WIMP annihilation to W+ W- (dPhi_dE4.dat)" << std::endl;
    std::cout << "  5: Outputs spectrum of gamma rays from WIMP annihilation to gamma e+ e- " << std::endl;
    std::cout << "      (dPhi_dE5.dat)" << std::endl;
    std::cout << "  6: Outputs tables of gamma-ray likelihoods and the relic density" << std::endl;
    std::cout << "      in <sigma v> / m_WIMP parameter space." << std::endl;
    std::cout << "  7: Outputs tables of direct detection likelihoods in sigma / m_WIMP parameter" << std::endl;
    std::cout << "      space." << std::endl;
    std::cout << "  >=10: Outputs spectrum of gamma rays from WIMP annihilation to phi phi_2. The" << std::endl;
    std::cout << "       mode value is m_phi while m_phi_2=100 GeV (dPhi_dE_FCMC_(mode).dat)" << std::endl;
    std::cout << " N.B. Here dPhi/dE = sigma v / m_chi^2 * dN/dE" << std::endl;
    std::cout << "**************************************************************************************" << std::endl;
    std::cout << std::endl;

  try
  {
    if (argc==1)
    {
      std::cout << "Please select test mode>=0" << std::endl;
      exit(1);
    }
    int mode = std::stoi((std::string)argv[1]);
    std::cout << "Starting with mode " << mode << std::endl;

    // ---- Initialise logging and exceptions ----

    initialise_standalone_logs("runs/DarkBit_standalone_WIMP/logs/");
    logger()<<"Running DarkBit standalone example"<<LogTags::info<<EOM;
    model_warning().set_fatal(true);

    // ---- Check that required backends are present ----

    if (not Backends::backendInfo().works["DarkSUSY5.1.3"]) backend_error().raise(LOCAL_INFO, "DarkSUSY 5.1.3 is missing!");
    if (not Backends::backendInfo().works["gamLike1.0.1"]) backend_error().raise(LOCAL_INFO, "gamLike 1.0.1 is missing!");
    if (not Backends::backendInfo().works["DDCalc2.2.0"]) backend_error().raise(LOCAL_INFO, "DDCalc 2.2.0 is missing!");
    if (not Backends::backendInfo().works["MicrOmegas_MSSM3.6.9.2"]) backend_error().raise(LOCAL_INFO, "MicrOmegas 3.6.9.2 for MSSM is missing!");

    // ---- Initialize models ----

    // Initialize halo model
    ModelParameters* Halo_primary_parameters = Models::Halo_Einasto::Functown::primary_parameters.getcontentsPtr();
    Halo_primary_parameters->setValue("vrot", 235.); // Local properties
    Halo_primary_parameters->setValue("v0", 235.);
    Halo_primary_parameters->setValue("vesc", 550.);
    Halo_primary_parameters->setValue("rho0", 0.4);
    Halo_primary_parameters->setValue("r_sun", 8.5);

    Halo_primary_parameters->setValue("rs", 20.);  // Global properties
    Halo_primary_parameters->setValue("rhos", 0.08);
    Halo_primary_parameters->setValue("alpha", 0.17);

    // --- Resolve halo dependencies ---
    ExtractLocalMaxwellianHalo.notifyOfModel("Halo_Einasto");
    ExtractLocalMaxwellianHalo.resolveDependency(&Models::Halo_Einasto::Functown::primary_parameters);
    ExtractLocalMaxwellianHalo.reset_and_calculate();

    GalacticHalo_Einasto.notifyOfModel("Halo_Einasto");
    GalacticHalo_Einasto.resolveDependency(&Models::Halo_Einasto::Functown::primary_parameters);
    GalacticHalo_Einasto.reset_and_calculate();

    // ---- Initialize backends ----

    // Assume for direct and indirect detection likelihoods that dark matter
    // density is always the measured one (despite relic density results)
    RD_fraction_one.reset_and_calculate();

    // Set up DDCalc backend initialization
    Backends::DDCalc_2_2_0::Functown::DDCalc_CalcRates_simple.setStatus(2);
    Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment.setStatus(2);
    Backends::DDCalc_2_2_0::Functown::DDCalc_LogLikelihood.setStatus(2);
    DDCalc_2_2_0_init.resolveDependency(&ExtractLocalMaxwellianHalo);
    // Assume for direct and indirect detection likelihoods that dark matter
    // density is always the measured one (despite relic density results)
    DDCalc_2_2_0_init.resolveDependency(&RD_fraction_one);
    DDCalc_2_2_0_init.resolveDependency(&mwimp_generic);
    DDCalc_2_2_0_init.resolveDependency(&DD_couplings_WIMP);

    // Initialize gamLike backend
    gamLike_1_0_1_init.reset_and_calculate();

    // Initialize DarkSUSY backend
    DarkSUSY_5_1_3_init.reset_and_calculate();

    // Initialize MicrOmegas backend
    // The below allows us to initialise MicrOmegas_MSSM without a particular MSSM model.
    MicrOmegas_MSSM_3_6_9_2_init.notifyOfModel("Halo_Einasto");
    MicrOmegas_MSSM_3_6_9_2_init.reset_and_calculate();

    // ---- Gamma-ray yields ----

    // Initialize tabulated gamma-ray yields
    SimYieldTable_DarkSUSY.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::dshayield);
    SimYieldTable_MicrOmegas.resolveBackendReq(&Backends::MicrOmegas_MSSM_3_6_9_2::Functown::dNdE);
    SimYieldTable_DarkSUSY.setOption<bool>("allow_yield_extrapolation", true);
    SimYieldTable_MicrOmegas.setOption<bool>("allow_yield_extrapolation", true);

    // Select SimYieldTable
    //auto SimYieldTablePointer = &SimYieldTable_MicrOmegas;
    auto SimYieldTablePointer = &SimYieldTable_DarkSUSY;

    // Collect missing final states for simulation in cascade MC
    GA_missingFinalStates.resolveDependency(&TH_ProcessCatalog_WIMP);
    GA_missingFinalStates.resolveDependency(SimYieldTablePointer);
    GA_missingFinalStates.resolveDependency(&DarkMatter_ID_WIMP);

    // Infer for which type of final states particles MC should be performed
    cascadeMC_FinalStates.setOption<std::vector<std::string>>("cMC_finalStates", daFunk::vec((std::string)"gamma"));

    // Collect decay information for cascade MC
    cascadeMC_DecayTable.resolveDependency(&TH_ProcessCatalog_WIMP);
    cascadeMC_DecayTable.resolveDependency(SimYieldTablePointer);

    // Set up MC loop manager for cascade MC
    cascadeMC_LoopManager.setOption<int>("cMC_maxEvents", 20000);
    cascadeMC_Histograms.setOption<double>("cMC_endCheckFrequency", 25);
    cascadeMC_Histograms.setOption<double>("cMC_gammaRelError", .05);
    cascadeMC_Histograms.setOption<int>("cMC_numSpecSamples", 25);
    cascadeMC_Histograms.setOption<int>("cMC_NhistBins", 300);
    cascadeMC_LoopManager.resolveDependency(&GA_missingFinalStates);
    std::vector<functor*> nested_functions = initVector<functor*>(
        &cascadeMC_InitialState, &cascadeMC_GenerateChain, &cascadeMC_Histograms, &cascadeMC_EventCount);
    cascadeMC_LoopManager.setNestedList(nested_functions);

    // Set up initial state for cascade MC step
    cascadeMC_InitialState.resolveDependency(&GA_missingFinalStates);
    cascadeMC_InitialState.resolveLoopManager(&cascadeMC_LoopManager);
    //cascadeMC_InitialState.reset_and_calculate();

    // Perform MC step for cascade MC
    cascadeMC_GenerateChain.resolveDependency(&cascadeMC_InitialState);
    cascadeMC_GenerateChain.resolveDependency(&cascadeMC_DecayTable);
    cascadeMC_GenerateChain.resolveLoopManager(&cascadeMC_LoopManager);
    //cascadeMC_GenerateChain.reset_and_calculate();

    // Generate histogram for cascade MC
    cascadeMC_Histograms.resolveDependency(&cascadeMC_InitialState);
    cascadeMC_Histograms.resolveDependency(&cascadeMC_GenerateChain);
    cascadeMC_Histograms.resolveDependency(&TH_ProcessCatalog_WIMP);
    cascadeMC_Histograms.resolveDependency(SimYieldTablePointer);
    cascadeMC_Histograms.resolveDependency(&cascadeMC_FinalStates);
    cascadeMC_Histograms.resolveLoopManager(&cascadeMC_LoopManager);
    //cascadeMC_Histograms.reset_and_calculate();

    // Check convergence of cascade MC
    cascadeMC_EventCount.resolveDependency(&cascadeMC_InitialState);
    cascadeMC_EventCount.resolveLoopManager(&cascadeMC_LoopManager);
    //cascadeMC_EventCount.reset_and_calculate();

    // Start cascade MC loop

    // Infer gamma-ray spectra for recorded MC results
    cascadeMC_gammaSpectra.resolveDependency(&GA_missingFinalStates);
    cascadeMC_gammaSpectra.resolveDependency(&cascadeMC_FinalStates);
    cascadeMC_gammaSpectra.resolveDependency(&cascadeMC_Histograms);
    cascadeMC_gammaSpectra.resolveDependency(&cascadeMC_EventCount);

    // Calculate total gamma-ray yield (cascade MC + tabulated results)
    GA_AnnYield_General.resolveDependency(&TH_ProcessCatalog_WIMP);
    GA_AnnYield_General.resolveDependency(SimYieldTablePointer);
    GA_AnnYield_General.resolveDependency(&DarkMatter_ID_WIMP);
    GA_AnnYield_General.resolveDependency(&cascadeMC_gammaSpectra);

    dump_GammaSpectrum.resolveDependency(&GA_AnnYield_General);

    // Resolve Galactic halo requirements for gamLike
    set_gamLike_GC_halo.resolveDependency(&GalacticHalo_Einasto);
    set_gamLike_GC_halo.resolveBackendReq(&Backends::gamLike_1_0_1::Functown::set_halo_profile);

    // Calculate Fermi LAT dwarf likelihood
    lnL_FermiLATdwarfs_gamLike.resolveDependency(&GA_AnnYield_General);
    // Assume for direct and indirect detection likelihoods that dark matter
    // density is always the measured one (despite relic density results)
    lnL_FermiLATdwarfs_gamLike.resolveDependency(&RD_fraction_one);
    lnL_FermiLATdwarfs_gamLike.resolveBackendReq(&Backends::gamLike_1_0_1::Functown::lnL);

    lnL_HESSGC_gamLike.resolveDependency(&GA_AnnYield_General);
    lnL_HESSGC_gamLike.resolveDependency(&RD_fraction_one);
    lnL_HESSGC_gamLike.resolveBackendReq(&Backends::gamLike_1_0_1::Functown::lnL);

    lnL_CTAGC_gamLike.resolveDependency(&GA_AnnYield_General);
    lnL_CTAGC_gamLike.resolveDependency(&RD_fraction_one);
    lnL_CTAGC_gamLike.resolveBackendReq(&Backends::gamLike_1_0_1::Functown::lnL);

    lnL_FermiGC_gamLike.resolveDependency(&GA_AnnYield_General);
    lnL_FermiGC_gamLike.resolveDependency(&RD_fraction_one);
    lnL_FermiGC_gamLike.resolveBackendReq(&Backends::gamLike_1_0_1::Functown::lnL);

    // -- Calculate relic density --
    RD_eff_annrate_from_ProcessCatalog.notifyOfModel("ScalarSingletDM_Z2");
    RD_eff_annrate_from_ProcessCatalog.resolveDependency(&TH_ProcessCatalog_WIMP);
    RD_eff_annrate_from_ProcessCatalog.resolveDependency(&DarkMatter_ID_WIMP);

    RD_spectrum_from_ProcessCatalog.resolveDependency(&TH_ProcessCatalog_WIMP);
    RD_spectrum_from_ProcessCatalog.resolveDependency(&DarkMatter_ID_WIMP);

    RD_spectrum_ordered_func.resolveDependency(&RD_spectrum_from_ProcessCatalog);

    RD_oh2_general.resolveDependency(&RD_spectrum_ordered_func);
    RD_oh2_general.resolveDependency(&RD_eff_annrate_from_ProcessCatalog);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::dsrdthlim);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::dsrdtab);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::dsrdeqn);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::dsrdwintp);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::widths);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdmgev);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdpth);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdpars);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdswitch);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdlun);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdpadd);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rddof);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rderrors);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::rdtime);
    RD_oh2_general.resolveBackendReq(&Backends::DarkSUSY_5_1_3::Functown::DSparticle_code);

    // ---- Calculate direct detection constraints ----

    // Calculate direct detection rates for LZ, PandaX 2017, Xenon 1T and PICO-60
    LZ_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    LZ_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_CalcRates_simple);
    PandaX_2017_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    PandaX_2017_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_CalcRates_simple);
    PICO_60_2017_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    PICO_60_2017_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_CalcRates_simple);
    XENON1T_2017_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    XENON1T_2017_Calc.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_CalcRates_simple);

    // Calculate direct detection likelihood for LZ, PandaX 2017, Xenon 1T and PICO-60
    LZ_GetLogLikelihood.resolveDependency(&LZ_Calc);
    LZ_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    LZ_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_LogLikelihood);
    PandaX_2017_GetLogLikelihood.resolveDependency(&PandaX_2017_Calc);
    PandaX_2017_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    PandaX_2017_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_LogLikelihood);
    XENON1T_2017_GetLogLikelihood.resolveDependency(&XENON1T_2017_Calc);
    XENON1T_2017_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    XENON1T_2017_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_LogLikelihood);
    PICO_60_2017_GetLogLikelihood.resolveDependency(&PICO_60_2017_Calc);
    PICO_60_2017_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    PICO_60_2017_GetLogLikelihood.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_LogLikelihood);

    // Provide bin number in LZ
    LZ_GetBinSignal.resolveDependency(&LZ_Calc);
    LZ_GetBinSignal.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Experiment);
    LZ_GetBinSignal.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_Bins);
    LZ_GetBinSignal.resolveBackendReq(&Backends::DDCalc_2_2_0::Functown::DDCalc_BinSignal);

    // Set generic WIMP mass object
    mwimp_generic.resolveDependency(&TH_ProcessCatalog_WIMP);
    mwimp_generic.resolveDependency(&DarkMatter_ID_WIMP);
    sigma_SI_p_simple.resolveDependency(&DD_couplings_WIMP);
    sigma_SI_p_simple.resolveDependency(&mwimp_generic);

    // Generate gamma-ray spectra for various final states
    if ( (mode >= 0) and (mode < 6) )
    {
      std::cout << "Producing test spectra." << std::endl;
      double mass = 100.;
      double sv = 3e-26;
      // The array that is being passed to dumpSpectrum give the branching fraction to various final states.
      // They are (as defined in TH_ProcessCatalog_WIMP):
      // 0: b bbar
      // 1: gamma Z_0
      // 2: gamma gamma
      // 3: tau+ tau-
      // 4: W+ W-
      // 5: e+ e-
      // 6: phi phi2
      // 7: gamma e+ e-
      if (mode==5) dumpSpectrum("dPhi_dE5.dat", mass, sv*0.1, daFunk::vec<double>(0., 0., 0., 0., 0., 0., 0., 1.));
      if (mode==0) dumpSpectrum("dPhi_dE0.dat", mass, sv, daFunk::vec<double>(1., 0., 0., 0., 0., 0., 0., 0.));
      if (mode==1) dumpSpectrum("dPhi_dE1.dat", mass, sv, daFunk::vec<double>(0., 1., 0., 0., 0., 0., 0., 0.));
      if (mode==2) dumpSpectrum("dPhi_dE2.dat", mass, sv, daFunk::vec<double>(0., 0., 1., 0., 0., 0., 0., 0.));
      if (mode==3) dumpSpectrum("dPhi_dE3.dat", mass, sv, daFunk::vec<double>(0., 0., 0., 1., 0., 0., 0., 0.));
      if (mode==4) dumpSpectrum("dPhi_dE4.dat", mass, sv, daFunk::vec<double>(0., 0., 0., 0., 1., 0., 0., 0.));
    }

    // Generate gamma-ray spectra for various masses
    if (mode >= 10)
    {
      std::cout << "Producing test spectra." << std::endl;
      double mass = 100.;
      double sv = 3e-26;
      std::string filename = "dPhi_dE_FCMC_" + std::to_string(mode) + ".dat";
      dumpSpectrum(filename, mass, sv, daFunk::vec<double>(0., 0., 0., 0., 0., 0., 1., 0.), mode);
    }

    // Systematic parameter maps annihilation
    if (mode==6)
    {
      std::cout << "Producing gamma ray test maps." << std::endl;
      int mBins = 60;
      int svBins = 60;
      double oh2, lnL;
      std::vector<double> sv_list, m_list;

      GalacticHalo_Einasto.reset_and_calculate();
      set_gamLike_GC_halo.reset_and_calculate();

      boost::multi_array<double, 2>
        lnL_b_array{boost::extents[mBins][svBins]},
        lnL_b_array2{boost::extents[mBins][svBins]},
        lnL_b_array3{boost::extents[mBins][svBins]},
        lnL_b_array4{boost::extents[mBins][svBins]},
        lnL_tau_array{boost::extents[mBins][svBins]};
      boost::multi_array<double, 2> oh2_array{boost::extents[mBins][svBins]};

      sv_list = daFunk::logspace(-28.0, -22.0, svBins);

      std::cout << "Calculating gamma-ray likelihood tables for annihilation to b bbar." << std::endl;
      m_list = daFunk::logspace(log10(5.), 4., mBins);
      for (size_t i = 0; i < m_list.size(); i++)
      {
        for (size_t j = 0; j < sv_list.size(); j++)
        {
          TH_ProcessCatalog_WIMP.setOption<double>("mWIMP", m_list[i]);
          TH_ProcessCatalog_WIMP.setOption<double>("sv", sv_list[j]);
          //std::cout << "Parameters: " << m_list[i] << " " << sv_list[j] << std::endl;

          TH_ProcessCatalog_WIMP.setOption<std::vector<double>>("brList", daFunk::vec<double>(1., 0., 0., 0., 0., 0., 0., 0.));
          DarkMatter_ID_WIMP.reset_and_calculate();
          TH_ProcessCatalog_WIMP.reset_and_calculate();
          RD_fraction_one.reset_and_calculate();
          SimYieldTable_DarkSUSY.reset_and_calculate();
          SimYieldTable_MicrOmegas.reset_and_calculate();
          GA_missingFinalStates.reset_and_calculate();
          cascadeMC_FinalStates.reset_and_calculate();
          cascadeMC_DecayTable.reset_and_calculate();
          cascadeMC_LoopManager.reset_and_calculate();
          cascadeMC_gammaSpectra.reset_and_calculate();
          GA_AnnYield_General.reset_and_calculate();
          lnL_FermiLATdwarfs_gamLike.setOption<std::string>("version", "pass8");
          lnL_FermiLATdwarfs_gamLike.reset_and_calculate();
          lnL = lnL_FermiLATdwarfs_gamLike(0);
          //std::cout << "Fermi dwarf likelihood: " << lnL << std::endl;
          lnL_b_array[i][j] = lnL;
          lnL_HESSGC_gamLike.setOption<std::string>("version", "integral_fixedJ");
          lnL_HESSGC_gamLike.reset_and_calculate();
          lnL = lnL_HESSGC_gamLike(0);
          //std::cout << "HESS GC likelihood: " << lnL << std::endl;
          lnL_b_array2[i][j] = lnL;
          lnL_CTAGC_gamLike.reset_and_calculate();
          lnL = lnL_CTAGC_gamLike(0);
          //std::cout << "CTA GC likelihood: " << lnL << std::endl;
          lnL_b_array3[i][j] = lnL;
          lnL_FermiGC_gamLike.setOption<std::string>("version", "fixedJ");
          lnL_FermiGC_gamLike.reset_and_calculate();
          lnL = lnL_FermiGC_gamLike(0);
          lnL_b_array4[i][j] = lnL;
          //std::cout << "Fermi GC likelihood: " << lnL << std::endl;
        }
      }

      dump_array_to_file("FermiD_b_table.dat", lnL_b_array, m_list, sv_list);
      dump_array_to_file("HESSGC_b_table.dat", lnL_b_array2, m_list, sv_list);
      dump_array_to_file("CTAGC_b_table.dat", lnL_b_array3, m_list, sv_list);
      dump_array_to_file("FermiGC_b_table.dat", lnL_b_array4, m_list, sv_list);

      std::cout << "Calculating Fermi-LAT dwarf spheroidal likehood table for annihilation to tau+ tau-." << std::endl;
      m_list = daFunk::logspace(log10(1.9), 4., mBins);
      for (size_t i = 0; i < m_list.size(); i++)
      {
        for (size_t j = 0; j < sv_list.size(); j++)
        {
          TH_ProcessCatalog_WIMP.setOption<double>("mWIMP", m_list[i]);
          TH_ProcessCatalog_WIMP.setOption<double>("sv", sv_list[j]);
          //std::cout << "Parameters: " << m_list[i] << " " << sv_list[j] << std::endl;

          TH_ProcessCatalog_WIMP.setOption<std::vector<double>>("brList", daFunk::vec<double>(0., 0., 0., 1., 0., 0., 0., 0.));
          DarkMatter_ID_WIMP.reset_and_calculate();
          TH_ProcessCatalog_WIMP.reset_and_calculate();
          RD_fraction_one.reset_and_calculate();
          SimYieldTable_DarkSUSY.reset_and_calculate();
          SimYieldTable_MicrOmegas.reset_and_calculate();
          GA_missingFinalStates.reset_and_calculate();
          cascadeMC_FinalStates.reset_and_calculate();
          cascadeMC_DecayTable.reset_and_calculate();
          cascadeMC_LoopManager.reset_and_calculate();
          cascadeMC_gammaSpectra.reset_and_calculate();
          GA_AnnYield_General.reset_and_calculate();
          lnL_FermiLATdwarfs_gamLike.reset_and_calculate();
          lnL = lnL_FermiLATdwarfs_gamLike(0);
          //std::cout << "Fermi LAT likelihood: " << lnL << std::endl;
          lnL_tau_array[i][j] = lnL;
        }
      }

      dump_array_to_file("FermiD_tau_table.dat", lnL_tau_array, m_list, sv_list);

      std::cout << "Calculating table of Omega h^2 values." << std::endl;
      m_list = daFunk::logspace(-1.0, 4., mBins);
      for (size_t i = 0; i < m_list.size(); i++)
      {
        for (size_t j = 0; j < sv_list.size(); j++)
        {
          TH_ProcessCatalog_WIMP.setOption<double>("mWIMP", m_list[i]);
          TH_ProcessCatalog_WIMP.setOption<double>("sv", sv_list[j]);
          //std::cout << "Parameters: " << m_list[i] << " " << sv_list[j] << std::endl;

          TH_ProcessCatalog_WIMP.setOption<std::vector<double>>("brList", daFunk::vec<double>(0., 0., 0., 0., 0., 1., 0., 0.));
          DarkMatter_ID_WIMP.reset_and_calculate();
          TH_ProcessCatalog_WIMP.reset_and_calculate();
          RD_eff_annrate_from_ProcessCatalog.reset_and_calculate();
          RD_spectrum_from_ProcessCatalog.reset_and_calculate();
          RD_spectrum_ordered_func.reset_and_calculate();
          RD_oh2_general.reset_and_calculate();
          oh2 = RD_oh2_general(0);
          //std::cout << "Omega h^2 = " << oh2 << std::endl;
          oh2_array[i][j] = oh2;
        }
      }

      dump_array_to_file("oh2_table.dat", oh2_array, m_list, sv_list);
    }

    // Systematic parameter maps scattering
    if (mode==7)
    {
      std::cout << "Producing direct detection test maps." << std::endl;
      double lnL1, lnL2, lnL3, lnL4;
      int nbins;
      double g, reduced_mass;
      //int mBins = 300;
      //int sBins = 200;
      int mBins = 120;
      int sBins = 80;
      const double mN = (m_proton + m_neutron) / 2;
      std::vector<double> m_list = daFunk::logspace(0.0, 4.0, mBins);
      std::vector<double> s_list;
      boost::multi_array<double, 2> lnL_array1{boost::extents[mBins][sBins]},
          lnL_array2{boost::extents[mBins][sBins]}, lnL_array3{boost::extents[mBins][sBins]},
          lnL_array4{boost::extents[mBins][sBins]};
      TH_ProcessCatalog_WIMP.setOption<double>("sv", 0.);
      TH_ProcessCatalog_WIMP.setOption<std::vector<double>>("brList", daFunk::vec<double>(1., 0., 0., 0., 0., 0., 0., 0.));

      s_list = daFunk::logspace(-47., -40., sBins);
      // Calculate array of sigma_SI and lnL values for LZ, PandaX, XENON1T and PICO-60
      // assuming gps=gns

      std::cout << "Calculating tables of SI likelihoods." << std::endl;
      for (size_t i = 0; i < m_list.size(); i++)
      {
        for (size_t j = 0; j < s_list.size(); j++)
        {
          // Re-initialize DDCalc with LZ/Xenon/PandaX halo parameters
          Halo_primary_parameters->setValue("rho0", 0.3);
          Halo_primary_parameters->setValue("vrot", 232.7); // v_Earth = 245 km/s
          Halo_primary_parameters->setValue("v0", 220.);
          Halo_primary_parameters->setValue("vesc", 544.);
          ExtractLocalMaxwellianHalo.reset_and_calculate();

          TH_ProcessCatalog_WIMP.setOption<double>("mWIMP", m_list[i]);
          //std::cout << "Parameters: " << m_list[i] << " " << s_list[j] << std::endl;
          reduced_mass = (m_list[i] * mN) / (mN + m_list[i]);
          g = sqrt(s_list[j]*pi/gev2cm2) / (reduced_mass);
          DarkMatter_ID_WIMP.reset_and_calculate();
          TH_ProcessCatalog_WIMP.reset_and_calculate();
          // Assume for direct and indirect detection likelihoods that dark matter
          // density is always the measured one (despite relic density results)
          RD_fraction_one.reset_and_calculate();
          DD_couplings_WIMP.setOption<double>("gps", g);
          DD_couplings_WIMP.setOption<double>("gns", g);
          DD_couplings_WIMP.setOption<double>("gpa", 0.);
          DD_couplings_WIMP.setOption<double>("gna", 0.);
          DD_couplings_WIMP.reset_and_calculate();
          mwimp_generic.reset_and_calculate();

          DDCalc_2_2_0_init.reset_and_calculate();
          LZ_Calc.reset_and_calculate();
          LZ_GetLogLikelihood.reset_and_calculate();

          XENON1T_2017_Calc.reset_and_calculate();
          XENON1T_2017_GetLogLikelihood.reset_and_calculate();
          PandaX_2017_Calc.reset_and_calculate();
          PandaX_2017_GetLogLikelihood.reset_and_calculate();

          lnL1 = LZ_GetLogLikelihood(0);
          lnL2 = PandaX_2017_GetLogLikelihood(0);
          lnL3 = XENON1T_2017_GetLogLikelihood(0);

          // Set LocalHalo Model parameters to PICO-60 values
          Halo_primary_parameters->setValue("rho0", 0.3);
          Halo_primary_parameters->setValue("vrot", 220.);
          Halo_primary_parameters->setValue("v0", 220.);
          Halo_primary_parameters->setValue("vesc", 544.);
          ExtractLocalMaxwellianHalo.reset_and_calculate();

          DDCalc_2_2_0_init.reset_and_calculate();
          PICO_60_2017_Calc.reset_and_calculate();
          PICO_60_2017_GetLogLikelihood.reset_and_calculate();
          lnL4 = PICO_60_2017_GetLogLikelihood(0);

          //std::cout << "LZ SI lnL = " << lnL1 << std::endl;
          //std::cout << "PandaX_2017 SI lnL = " << lnL2 << std::endl;
          //std::cout << "XENON1T_2017 SI lnL = " << lnL3 << std::endl;
          //std::cout << "PICO_60_2017 SI lnL = " << lnL4 << std::endl;

          DDCalc_2_2_0_init.reset_and_calculate();
          LZ_Calc.reset_and_calculate();
          std::vector<double> events;
          LZ_GetBinSignal.reset_and_calculate();
          events = LZ_GetBinSignal(0);
          nbins = events.size();
          std::cout << "Number of LZ bins: " << nbins << std::endl;
          std::cout << "Predicted signal: ";
          for (int ibin=0;ibin<=nbins-1;ibin++) {
            std::cout << events[ibin] << " ";
          }
          std::cout << std::endl;

          lnL_array1[i][j] = lnL1;
          lnL_array2[i][j] = lnL2;
          lnL_array3[i][j] = lnL3;
          lnL_array4[i][j] = lnL4;
        }
      }

      dump_array_to_file("LZ_SI_table.dat", lnL_array1, m_list, s_list);
      dump_array_to_file("PandaX_2017_SI_table.dat", lnL_array2, m_list, s_list);
      dump_array_to_file("XENON1T_2017_SI_table.dat", lnL_array3, m_list, s_list);
      dump_array_to_file("PICO_60_2017_SI_table.dat", lnL_array4, m_list, s_list);

      s_list = daFunk::logspace(-42., -35., sBins);
      // Calculate array of sigma_SI and lnL values for LZ, PandaX, XENON1T and PICO-60
      // assuming gna=0 (proton-only)

      std::cout << "Calculating tables of SD likelihoods." << std::endl;
      for (size_t i = 0; i < m_list.size(); i++)
      {
        for (size_t j = 0; j < s_list.size(); j++)
        {
          // Re-initialize DDCalc with LZ/Xenon/PandaX halo parameters
          Halo_primary_parameters->setValue("rho0", 0.3);
          Halo_primary_parameters->setValue("vrot", 232.7); // v_Earth = 245 km/s
          Halo_primary_parameters->setValue("v0", 220.);
          Halo_primary_parameters->setValue("vesc", 544.);
          ExtractLocalMaxwellianHalo.reset_and_calculate();

          TH_ProcessCatalog_WIMP.setOption<double>("mWIMP", m_list[i]);
          //std::cout << "Parameters: " << m_list[i] << " " << s_list[j] << std::endl;
          reduced_mass = (m_list[i] * m_proton) / (m_proton + m_list[i]);
          g = sqrt(s_list[j]*pi/(3*gev2cm2)) / (reduced_mass);
          DarkMatter_ID_WIMP.reset_and_calculate();
          TH_ProcessCatalog_WIMP.reset_and_calculate();
          RD_fraction_one.reset_and_calculate();
          DD_couplings_WIMP.setOption<double>("gps", 0.);
          DD_couplings_WIMP.setOption<double>("gns", 0.);
          DD_couplings_WIMP.setOption<double>("gpa", g);
          DD_couplings_WIMP.setOption<double>("gna", 0.);
          DD_couplings_WIMP.reset_and_calculate();
          mwimp_generic.reset_and_calculate();

          DDCalc_2_2_0_init.reset_and_calculate();
          LZ_Calc.reset_and_calculate();
          LZ_GetLogLikelihood.reset_and_calculate();
          XENON1T_2017_Calc.reset_and_calculate();
          XENON1T_2017_GetLogLikelihood.reset_and_calculate();
          PandaX_2017_Calc.reset_and_calculate();
          PandaX_2017_GetLogLikelihood.reset_and_calculate();
          lnL1 = LZ_GetLogLikelihood(0);
          lnL2 = PandaX_2017_GetLogLikelihood(0);
          lnL3 = XENON1T_2017_GetLogLikelihood(0);

          // Set LocalHalo Model parameters to PICO-60 values
          Halo_primary_parameters->setValue("rho0", 0.3);
          Halo_primary_parameters->setValue("vrot", 220.);
          Halo_primary_parameters->setValue("v0", 220.);
          Halo_primary_parameters->setValue("vesc", 544.);
          ExtractLocalMaxwellianHalo.reset_and_calculate();

          DDCalc_2_2_0_init.reset_and_calculate();
          PICO_60_2017_Calc.reset_and_calculate();
          PICO_60_2017_GetLogLikelihood.reset_and_calculate();
          lnL4 = PICO_60_2017_GetLogLikelihood(0);

          //std::cout << "LZ SD lnL = " << lnL1 << std::endl;
          //std::cout << "PandaX_2017 SD lnL = " << lnL2 << std::endl;
          //std::cout << "XENON1T_2017 SD lnL = " << lnL3 << std::endl;
          //std::cout << "PICO_60_2017 SD lnL = " << lnL4 << std::endl;

          lnL_array1[i][j] = lnL1;
          lnL_array2[i][j] = lnL2;
          lnL_array3[i][j] = lnL3;
          lnL_array4[i][j] = lnL4;
        }
      }

      dump_array_to_file("LZ_SD_table.dat", lnL_array1, m_list, s_list);
      dump_array_to_file("PandaX_2017_SD_table.dat", lnL_array2, m_list, s_list);
      dump_array_to_file("XENON1T_2017_SD_table.dat", lnL_array3, m_list, s_list);
      dump_array_to_file("PICO_60_2017_SD_table.dat", lnL_array4, m_list, s_list);

      // Reset halo parameters to DarkBit defaults.
      Halo_primary_parameters->setValue("rho0", 0.4);
      Halo_primary_parameters->setValue("vrot", 235.);
      Halo_primary_parameters->setValue("v0", 235.);
      Halo_primary_parameters->setValue("vesc", 550.);
      ExtractLocalMaxwellianHalo.reset_and_calculate();
      GalacticHalo_Einasto.reset_and_calculate();

    }
  }

  catch (std::exception& e)
  {
    std::cout << "DarkBit_standalone_WIMP has exited with fatal exception: " << e.what() << std::endl;
  }

  return 0;
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_MODE_PADDING_HPP
#define CRYPTO3_MODE_PADDING_HPP

#include <nil/crypto3/utilities/secmem.hpp>

#include <string>

#include <boost/static_assert.hpp>

namespace nil {
    namespace crypto3 {
        namespace block {
            namespace modes {
                namespace padding {
                    /*!
                     * @brief  Block Cipher Mode Padding Method
                     * @tparam Cipher Block cipher used
                     *
                     * This class is pretty limited, it cannot deal well with
                     * randomized padding methods, or any padding method that
                     * wants to add more than one block. For instance, it should
                     * be possible to define cipher text stealing mode as simply
                     * a padding mode for CBC, which happens to consume the last
                     * two block (and requires use of the block cipher).
                     */
                    template<typename Cipher>
                    struct basic_padding {
                        typedef std::size_t size_type;

                        typedef Cipher cipher_type;

                        constexpr static const size_type block_bits = cipher_type::block_bits;
                        constexpr static const size_type block_words = cipher_type::block_words;
                        typedef typename cipher_type::block_type block_type;
                    };

                    /*!
                     * @tparam Cipher Block cipher used
                     */
                    template<typename Cipher>
                    struct zeros : public basic_padding<Cipher> {
                        typedef typename basic_padding<Cipher>::size_type size_type;

                        typedef typename basic_padding<Cipher>::cipher_type cipher_type;

                        constexpr static const size_type block_bits = basic_padding<Cipher>::block_bits;
                        constexpr static const size_type block_words = basic_padding<Cipher>::block_words;
                        typedef typename basic_padding<Cipher>::block_type block_type;

                        constexpr static const bool always_pad = false;

                        inline static size_type required_output_size(size_type len, size_type block_size) {
                            if (len) {
                                const size_type rem = len % block_size;
                                if (rem) {
                                    if (std::numeric_limits<size_type>::max() - len < block_size) {
                                        throw std::overflow_error("message length is too long");
                                    } else {
                                        return len - rem + block_size;
                                    }
                                }
                            }
                            return len;
                        }

                        inline static block_type pad(const block_type &block, size_type bytes_in_block) {
                            block_type result = block;
                            uint8_t *ptr = static_cast<uint8_t *>(&*result.data());
                            std::memset(ptr + bytes_in_block, 0, block_bits / CHAR_BIT - bytes_in_block);

                            return result;
                        }

                        inline static block_type unpad(const block_type &block, size_type bytes_in_block) {
                            ct::poison(block, bytes_in_block);
                            uint8_t bad_input = 0;
                            uint8_t seen_one = 0;
                            size_t pad_pos = bytes_in_block - 1;
                            size_t i = bytes_in_block;

                            while (i) {
                                seen_one |= ct::is_equal<uint8_t>(block[i - 1], 0x80);
                                pad_pos -= ct::select<uint8_t>(~seen_one, 1, 0);
                                bad_input |= ~ct::is_zero<uint8_t>(block[i - 1]) & ~seen_one;
                                i--;
                            }
                            bad_input |= ~seen_one;

                            ct::conditional_copy_mem(size_t(bad_input), &pad_pos, &bytes_in_block, &pad_pos, 1);
                            ct::unpoison(block, bytes_in_block);
                            ct::unpoison(pad_pos);
                        }
                    };

                    /*!
                     * @brief (ISO/IEC 9797-1, padding method 2)
                     * @tparam Cipher Block cipher used
                     * 0x80 0x00 0x00 ....
                     */
                    template<typename Cipher>
                    struct one_and_zeros : public basic_padding<Cipher> {
                        typedef typename basic_padding<Cipher>::size_type size_type;

                        typedef typename basic_padding<Cipher>::cipher_type cipher_type;

                        constexpr static const size_type block_bits = basic_padding<Cipher>::block_bits;
                        constexpr static const size_type block_words = basic_padding<Cipher>::block_words;
                        typedef typename basic_padding<Cipher>::block_type block_type;

                        BOOST_STATIC_ASSERT(block_bits > 0);

                        constexpr static const bool always_pad = true;

                        inline static size_type required_output_size(size_type len, size_type block_size) {
                            if (len) {
                                const size_type rem = len % block_size;
                                if (rem) {
                                    if (std::numeric_limits<size_type>::max() - len < block_size) {
                                        throw std::overflow_error("message length is too long");
                                    } else {
                                        return len - rem + block_size;
                                    }
                                }
                            }
                            return len + block_size;
                        }

                        inline static block_type pad(const block_type &block, size_type bytes_in_block) {
                            block_type result = block;
                            uint8_t *ptr = static_cast<uint8_t *>(result);
                            ptr += bytes_in_block;
                            *ptr++ = 0x80;
                            ++bytes_in_block;
                            std::memset(ptr, 0, block_bits / CHAR_BIT - bytes_in_block);
                            return result;
                        }
                    };

                    /*!
                     * @brief
                     * @tparam Cipher Block cipher used
                     */
                    template<typename Cipher>
                    struct trailing_bit : public basic_padding<Cipher> {
                        typedef typename basic_padding<Cipher>::size_type size_type;

                        typedef typename basic_padding<Cipher>::cipher_type cipher_type;

                        constexpr static const size_type block_bits = basic_padding<Cipher>::block_bits;
                        constexpr static const size_type block_words = basic_padding<Cipher>::block_words;
                        typedef typename basic_padding<Cipher>::block_type block_type;
                    };

                    /*!
                     * @tparam Cipher Block cipher used
                     * RFC 2315 (10.3.2)
                     * Some content-encryption algorithms assume the
                     * input length is a multiple of k octets, where k > 1, and
                     * let the application define a method for handling inputs
                     * whose lengths are not a multiple of k octets. For such
                     * algorithms, the method shall be to pad the input at the
                     * trailing end with k - (l mod k) octets all having value k -
                     * (l mod k), where l is the length of the input. In other
                     * words, the input is padded at the trailing end with one of
                     * the following strings:
                     *
                     *          01 -- if l mod k = k-1
                     *         02 02 -- if l mod k = k-2
                     *                     .
                     *                     .
                     *                     .
                     *       k k ... k k -- if l mod k = 0
                     *
                     * The padding can be removed unambiguously since all input is
                     * padded and no padding string is a suffix of another. This
                     * padding method is well-defined if and only if k < 256;
                     * methods for larger k are an open issue for further study.
                     */
                    template<typename Cipher>
                    struct pkcs7 : public basic_padding<Cipher> {
                        typedef typename basic_padding<Cipher>::size_type size_type;

                        typedef typename basic_padding<Cipher>::cipher_type cipher_type;

                        constexpr static const size_type block_bits = basic_padding<Cipher>::block_bits;
                        constexpr static const size_type block_words = basic_padding<Cipher>::block_words;
                        typedef typename basic_padding<Cipher>::block_type block_type;

                        BOOST_STATIC_ASSERT(block_bits > 0 && block_bits < 256);

                        inline static void pad(const block_type &block, size_type bytes_in_block) {
                            const uint8_t pad_value = static_cast<uint8_t>(block_size - last_byte_pos);

                            for (size_t i = 0; i != pad_value; ++i) {
                                buffer.push_back(pad_value);
                            }
                        }

                        inline static void unpad(const block_type &block, size_type bytes_in_block) {
                            ct::poison(block, bytes_in_block);
                            size_t bad_input = 0;
                            const uint8_t last_byte = block[bytes_in_block - 1];

                            bad_input |= ct::expand_mask<size_t>(last_byte > bytes_in_block);

                            size_t pad_pos = bytes_in_block - last_byte;
                            size_t i = bytes_in_block - 2;
                            while (i) {
                                bad_input |=
                                    (~ct::is_equal(block[i], last_byte)) & ct::expand_mask<uint8_t>(i >= pad_pos);
                                --i;
                            }

                            ct::conditional_copy_mem(bad_input, &pad_pos, &bytes_in_block, &pad_pos, 1);
                            ct::unpoison(block, bytes_in_block);
                            ct::unpoison(pad_pos);
                        }
                    };

                    /*!
                     * @brief ESP Padding (RFC 4304)
                     * @tparam Cipher
                     */
                    template<typename Cipher>
                    struct esp : public basic_padding<Cipher> {
                        typedef typename basic_padding<Cipher>::size_type size_type;

                        typedef typename basic_padding<Cipher>::cipher_type cipher_type;

                        constexpr static const size_type block_bits = basic_padding<Cipher>::block_bits;
                        constexpr static const size_type block_words = basic_padding<Cipher>::block_words;
                        typedef typename basic_padding<Cipher>::block_type block_type;

                        inline static void pad(const block_type &block, size_type bytes_in_block) {
                            uint8_t pad_value = 0x01;

                            for (size_t i = last_byte_pos; i < bytes_in_block; ++i) {
                                buffer.push_back(pad_value++);
                            }
                        }

                        inline static void unpad(const block_type &block, size_type size) {
                            ct::poison(block, size);

                            const size_t last_byte = block[size - 1];
                            size_t bad_input = 0;
                            bad_input |= ct::expand_mask<size_t>(last_byte > size);

                            size_t pad_pos = size - last_byte;
                            size_t i = size - 1;
                            while (i) {
                                bad_input |= ~ct::is_equal<uint8_t>(size_t(block[i - 1]), size_t(block[i]) - 1) &
                                             ct::expand_mask<uint8_t>(i > pad_pos);
                                --i;
                            }
                            ct::conditional_copy_mem(bad_input, &pad_pos, &size, &pad_pos, 1);
                            ct::unpoison(block, size);
                            ct::unpoison(pad_pos);
                        }
                    };

                    /*!
                     * @brief ANSI X9.23 Padding
                     * @tparam Cipher
                     */
                    template<typename Cipher>
                    struct ansi_x923 : public basic_padding<Cipher> {
                        typedef typename basic_padding<Cipher>::size_type size_type;

                        typedef typename basic_padding<Cipher>::cipher_type cipher_type;

                        constexpr static const size_type block_bits = basic_padding<Cipher>::block_bits;
                        constexpr static const size_type block_words = basic_padding<Cipher>::block_words;
                        typedef typename basic_padding<Cipher>::block_type block_type;

                        BOOST_STATIC_ASSERT(block_bits > 0 && block_bits < 256);

                        inline static void pad(const block_type &block, size_type bytes_in_block) {
                            const uint8_t pad_value = static_cast<uint8_t>(bytes_in_block - last_byte_pos);

                            for (size_t i = last_byte_pos; i < bytes_in_block - 1; ++i) {
                                buffer.push_back(0);
                            }
                            buffer.push_back(pad_value);
                        }

                        inline static void unpad(const block_type &block, size_type size) {
                            ct::poison(block, size);
                            size_t bad_input = 0;
                            const size_t last_byte = block[size - 1];

                            bad_input |= ct::expand_mask<size_t>(last_byte > size);

                            size_t pad_pos = size - last_byte;
                            size_t i = size - 2;
                            while (i) {
                                bad_input |= (~ct::is_zero(block[i])) & ct::expand_mask<uint8_t>(i >= pad_pos);
                                --i;
                            }
                            ct::conditional_copy_mem(bad_input, &pad_pos, &size, &pad_pos, 1);
                            ct::unpoison(block, size);
                            ct::unpoison(pad_pos);
                        }
                    };
                }    // namespace padding
            }        // namespace modes
        }            // namespace block
    }                // namespace crypto3
}    // namespace nil

#endif


/*    Copyright (c) 2010-2015, Delft University of Technology
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without modification, are
 *    permitted provided that the following conditions are met:
 *      - 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 the Delft University of Technology nor the names of its contributors
 *        may be used to endorse or promote products derived from this software without specific
 *        prior written permission.
 *
 *    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 THE
 *    COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 *
 *    Changelog
 *      YYMMDD    Author            Comment
 *      120717    D. Dirkx          Creation of file.
 *      130120    D. Dirkx          Updated with new Julian day + seconds since Julian day input.
 *      130226    K. Kumar          Updated return-type for getCartesianStateFromEphemeris().
 *
 *    References
 *
 *    Notes
 *
 */

#include <stdexcept>

#include <boost/lexical_cast.hpp>
#include <boost/exception/all.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/physicalConstants.h"

#include "Tudat/External/SpiceInterface/spiceEphemeris.h"

namespace tudat
{

namespace ephemerides
{

//! Constructor
SpiceEphemeris::SpiceEphemeris( const std::string& targetBodyName,
                                const std::string& observerBodyName,
                                const bool correctForStellarAbberation,
                                const bool correctForLightTimeAbberation,
                                const bool convergeLighTimeAbberation,
                                const std::string& referenceFrameName )
    : Ephemeris( observerBodyName, referenceFrameName ),
      targetBodyName_( targetBodyName )
{
    // Check consistency of input.
    if ( correctForLightTimeAbberation == 0 && convergeLighTimeAbberation == 1 )
    {
        boost::throw_exception(
                    boost::enable_error_info(
                        std::runtime_error(
                            "Warning, requested multiple iterations for light time correction, "
                            "but not light time correction itself." ) ) );
    }

    if ( correctForLightTimeAbberation == 0 && correctForStellarAbberation ==  1 )
    {

        boost::throw_exception(
                    boost::enable_error_info(
                        std::runtime_error
                        ( "Warning, requested stellar aberration, but not light-time correction, "
                          "this is not supprted by spice." ) ) );
    }

    // Set aberration corrections variable.
    abberationCorrections_ = "";
    if ( correctForLightTimeAbberation && !convergeLighTimeAbberation )
    {
        abberationCorrections_.append( "LT" );
    }

    else if ( correctForLightTimeAbberation && convergeLighTimeAbberation )
    {
        abberationCorrections_.append( "CN" );
    }

    else if ( !correctForLightTimeAbberation && !correctForStellarAbberation )
    {
        abberationCorrections_.append( "NONE" );
    }

    if ( correctForLightTimeAbberation && correctForStellarAbberation )
    {
        abberationCorrections_.append( "+S" );
    }
}

//! Get Cartesian state from ephemeris.
basic_mathematics::Vector6d SpiceEphemeris::getCartesianStateFromEphemeris(
        const double secondsSinceEpoch, const double julianDayAtEpoch )
{
    using namespace basic_astrodynamics;

    // Retrieve body state at given ephemeris time, using settings passed to constructor of this
    // object.

    // Calculate ephemeris time at which cartesian state is to be determind.
    const double ephemerisTime = secondsSinceEpoch + ( JULIAN_DAY_ON_J2000 - julianDayAtEpoch ) *
            physical_constants::JULIAN_DAY;

    // Retrieve Cartesian state from spice.
    const basic_mathematics::Vector6d cartesianStateAtEpoch =
            spice_interface::getBodyCartesianStateAtEpoch(
                targetBodyName_, referenceFrameOrigin_, referenceFrameOrientation_,
                abberationCorrections_, ephemerisTime );

    return cartesianStateAtEpoch;
}

} // namespace ephemerides
} // namespace tudat