#include <iostream>
#include <chrono>
#include <NTL/ZZ.h>
#include <NTL/ZZ_p.h>
#include "source/setup.h"
#include "server/query.h"
#include "client/verify_intersection.h"
#include "client/verify_tree.h"
#include "client/verify_union.h"
#include "client/verify_subset.h"
#include "client/verify_difference.h"

#define NODEBUG
#define SET_SIZE 10000
#define SETS_NO 32

void test_intersection(int round, int size, int intersection_size, Key *k) {
    using namespace std::chrono;
    high_resolution_clock::time_point t1, t3;
    high_resolution_clock::time_point t2, t4;
    t3 = high_resolution_clock::now();
    //generate sets
    DataStructure *dataStructure = new DataStructure(SETS_NO, k); //TODO memleak
    for (int i = 1; i <= intersection_size; i++) {
        NTL::ZZ_p j = NTL::random_ZZ_p();
        for (int set_index = 0; set_index < dataStructure->m; set_index++) {
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    }
    std::cout << size << "\t";
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        for (int i = 1; i <= size - intersection_size; i++) {
            NTL::ZZ_p j = NTL::random_ZZ_p();
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    t4 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\t";

    //query intersection
    std::vector<int> v;
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        v.push_back(set_index);

    t3 = high_resolution_clock::now();
    Intersection *intersection = new Intersection(v, k->get_public_key(), dataStructure);
    intersection->intersect();
    t1 = high_resolution_clock::now();
    intersection->subset_witness();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";

    t1 = high_resolution_clock::now();
    intersection->completeness_witness();
    t2 = high_resolution_clock::now();
    t4 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";
    duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\n";
//    log_info("Intersection query time:\t%d", duration);
    //verify tree
    //verify intersection
    t1 = high_resolution_clock::now();
    VerifyTree *verifyTree = new VerifyTree;
    verifyTree->verifyTree(dataStructure, v);
//    log_info("Tree verification result:\t%x", verifyTree->verifiedtree);
    VerifyIntersection *verifyIntersection = new VerifyIntersection(k->get_public_key(),
                                                                    intersection->I, intersection->W, intersection->Q,
                                                                    dataStructure->AuthD, dataStructure->m, v);
    bool b = verifyIntersection->verify_intersection();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    delete verifyIntersection;
    delete verifyTree;
    delete intersection;
    delete dataStructure;
//    log_info("Intersection verification time:\t%d", duration);
//    log_info("Intersection verification result:\t%x", b);
}

void test_union(int round, int size, int intersection_size, Key *k) {
    using namespace std::chrono;
    high_resolution_clock::time_point t1, t3;
    high_resolution_clock::time_point t2, t4;
    t3 = high_resolution_clock::now();
    //generate sets
    DataStructure *dataStructure = new DataStructure(SETS_NO, k);

    for (int i = 1; i <= intersection_size; i++) {
        NTL::ZZ_p j = NTL::random_ZZ_p();
        for (int set_index = 0; set_index < dataStructure->m; set_index++) {
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    }

    std::cout << size << "\t";
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        for (int i = 1; i <= size - intersection_size; i++) {
            NTL::ZZ_p j = NTL::random_ZZ_p();
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }

    t4 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\t";

    //query intersection
    std::vector<int> v;
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        v.push_back(set_index);
    t3 = high_resolution_clock::now();
    Union *un = new Union(v, k->get_public_key(), dataStructure);
    t1 = high_resolution_clock::now();
    un->unionSets();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";
    t4 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\t";
    t1 = high_resolution_clock::now();
    VerifyTree *verifyTree = new VerifyTree;
    verifyTree->verifyTree(dataStructure, v);
    VerifyUnion *verifyUnion = new VerifyUnion(k->get_public_key(), un->U, un->tree, dataStructure->m, un->set_indices);
    bool b = verifyUnion->verify_union();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\n";
    delete verifyTree;
    delete verifyUnion;
    delete un;
    delete dataStructure;
//    log_info("Union verification time:\t%d", duration);
    log_info("Union verification result:\t%x", b);
}

void test_union2(int round, int size, int intersection_size, Key *k) {
    using namespace std::chrono;
    high_resolution_clock::time_point t1, t3;
    high_resolution_clock::time_point t2, t4;
    t3 = high_resolution_clock::now();
    //generate sets
    DataStructure *dataStructure = new DataStructure(SETS_NO, k);

    for (int i = 1; i <= intersection_size; i++) {
        NTL::ZZ_p j = NTL::random_ZZ_p();
        for (int set_index = 0; set_index < dataStructure->m; set_index++) {
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    }

    std::cout << size << "\t";
    for (int set_index = 0; set_index < dataStructure->m; set_index++) {
        for (int i = 1; i <= size - intersection_size; i++) {
            NTL::ZZ_p j = NTL::random_ZZ_p();
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    }

    t4 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\t";

    //query intersection
    std::vector<int> v;
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        v.push_back(set_index);
    t3 = high_resolution_clock::now();
    Union2 *un = new Union2(v, k->get_public_key(), dataStructure);
    un->unionSets();
    t1 = high_resolution_clock::now();
    un->membership_witness();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";

    t1 = high_resolution_clock::now();
    un->superset_witness();
    t2 = high_resolution_clock::now();
    t4 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";
    duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\t";
    t1 = high_resolution_clock::now();
    VerifyTree *verifyTree = new VerifyTree;
    verifyTree->verifyTree(dataStructure, v);
    VerifyUnion2 *verifyUnion = new VerifyUnion2(k->get_public_key(), un->U, un->W1, un->W2, dataStructure->AuthD,
                                               dataStructure->m, v, un->set_indices);
    verifyUnion->verify_union();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\n";
    bool b = verifyUnion->membershipwitness and verifyUnion->membershipwitness;
    delete verifyTree;
    delete verifyUnion;
    delete un;
    delete dataStructure;
    log_info("Union verification time:\t%d", duration);
    log_info("Union verification result:\t%x", b);
}

void test_difference(int round, int size, int intersection_size, Key *k) {
    using namespace std::chrono;
    high_resolution_clock::time_point t1, t3;
    high_resolution_clock::time_point t2, t4;
    t3 = high_resolution_clock::now();
    //generate sets
    DataStructure *dataStructure = new DataStructure(SETS_NO, k);

    for (int i = 1; i <= intersection_size; i++) {
        NTL::ZZ_p j = NTL::random_ZZ_p();
        for (int set_index = 0; set_index < dataStructure->m; set_index++) {
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    }

    std::cout << size << "\t";
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        for (int i = 1; i <= size - intersection_size; i++) {
            NTL::ZZ_p j = NTL::random_ZZ_p();
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }

    t4 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t4 - t3).count();
    std::cout << duration << "\t";

    //query intersection
    int index[2];
    index[0] = 0;
    index[1] = 1;
    t1 = high_resolution_clock::now();
    Difference *difference = new Difference(index, k->get_public_key(), dataStructure);
    difference->difference();
    difference->witness();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();

    std::cout << duration << "\t";
    t1 = high_resolution_clock::now();
    VerifyDifference *verifyDifference = new VerifyDifference(k->get_public_key(), dataStructure, difference->D,
                                                              difference->I, difference->W, difference->Wd,
                                                              difference->Q, difference->index);
    verifyDifference->verify_difference();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\n";
    bool b = verifyDifference->verified_witness;
//    log_info("Difference verification result:\t%x", b);
    delete verifyDifference;
    delete difference;
    delete dataStructure;
}

void test_subset(int round, int size, int intersection_size, Key *k) {
    using namespace std::chrono;
    high_resolution_clock::time_point t1, t2;
    DataStructure *dataStructure = new DataStructure(SETS_NO, k);

    for (int i = 1; i <= intersection_size; i++) {
        NTL::ZZ_p j = NTL::random_ZZ_p();
        for (int set_index = 0; set_index < dataStructure->m; set_index++) {
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
        }
    }

    std::cout << size << "\t";
    for (int set_index = 0; set_index < dataStructure->m; set_index++)
        for (int i = 1; i <= size - intersection_size; i++) {
            NTL::ZZ_p j = NTL::random_ZZ_p();
            dataStructure->insert(set_index, j, k->get_public_key(), k->get_secret_key());
            dataStructure->insert(0, j, k->get_public_key(), k->get_secret_key());
        }
    t1 = high_resolution_clock::now();
    Subset *subset = new Subset(0, 1, k->get_public_key(), dataStructure);
    subset->subset();
    t2 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";
    t1 = high_resolution_clock::now();
    if (subset->answer)
        subset->positiveWitness();
    else
        subset->negativeWitness();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\t";
    t1 = high_resolution_clock::now();
    VerifySubset *verifySubset = new VerifySubset(k->get_public_key(), dataStructure, subset->Q, subset->W,
                                                  subset->answer,
                                                  subset->index[0], subset->index[1], subset->y);
    verifySubset->verify_subset();
    t2 = high_resolution_clock::now();
    duration = duration_cast<milliseconds>(t2 - t1).count();
    std::cout << duration << "\n";
    bool b = verifySubset->verified_subset;
//    log_info("Subset verification result:\t%x", b);
    delete verifySubset;
    delete subset;
    delete dataStructure;
}

int main() {
    using namespace std::chrono;
    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    NTL::ZZ p = NTL::conv<NTL::ZZ>("16798108731015832284940804142231733909759579603404752749028378864165570215949");
    NTL::ZZ_p::init(p);
    Key *k = new Key(p);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t2 - t1).count();
    log_info("Key generation time:\t%d", duration);
//    std::cerr<<"size\tsetup\tsubet\tcompleteness\ttotal\n";
    for (int test_size = 10; test_size <= 10; test_size +=500)
//        for(int i = 0; i < 10; i++)
            test_intersection(0, test_size, test_size / 10, k);

//   std::cerr<<"size\tsetup\tmembership\tsuperset_witness\ttotal\n";
//   for (int test_size = 10; test_size <= 10; test_size +=100)
//       for(int i = 0; i < 10; i++)
//           test_union2(0, test_size, test_size/10, k);
//    for (int test_size = 0; test_size <= 400; test_size +=200)
//        for(int i = 0; i < 10; i++)
//            test_subset(i, test_size, test_size / 10, k);
//    for (int test_size = 0; test_size <= 400; test_size +=200)
//        for(int i = 0; i < 10; i++)
//            test_difference(i, test_size, test_size / 10, k);
    delete k;
    return 0;
}


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2015 Peter Caspers

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file mcfxtarfengine.hpp
    \brief Monte Carlo engine for FX Tarf
*/

#ifndef quantlib_pricingengines_mc_fxtarf_hpp
#define quantlib_pricingengines_mc_fxtarf_hpp

#include <ql/experimental/fx/fxtarfengine.hpp>
#include <ql/event.hpp>
#include <ql/pricingengines/mcsimulation.hpp>
#include <ql/processes/blackscholesprocess.hpp>
#include <ql/math/generallinearleastsquares.hpp>

#include <boost/make_shared.hpp>

namespace QuantLib {

namespace {

// raw data for proxy estimation
// the data vector is organized as follows
// level 0 = openFixings (e.g. 6 5 4 3 2 1 = Indices 5 ... 0)
// level 1 = accumulated amount buckets
//    [ previously accumuated amount = a(0), a(1) ]
//    [ a(1), a(2) ]
//    ...
//    [ a(n-1), a(n) = target ]
// level 2 = vector of pairs (spot, npv), sorted asc by spot values

typedef std::vector<std::vector<std::vector<std::pair<Real, Real> > > >
    ProxyData;

// regression function basis
Real basis0(const double) { return 1; }
Real basis1(const double x) { return x; }
Real basis2(const double x) { return x * x; }

} // empty namespace

template <class RNG = PseudoRandom, class S = Statistics>
class McFxTarfEngine : public FxTarfEngine,
                       public McSimulation<SingleVariate, RNG, S> {
  public:
    /*! proxy function giving a function spot => npv for one segment
        (bucket accumulated amount, number of open fixings)
        the function is given by two quadratic polynomials on intervals
        (-\infty,cutoff] and (cutoff,\infty).
        Only the ascending (long calls) or descending (long puts) branch
        is used and then extrapolated flat.
        For calls the extrapolation below the given lowerCutoff is done
        linear (for puts above this value).
    */
    class QuadraticProxyFunction : public FxTarf::Proxy::ProxyFunction {
      public:
        QuadraticProxyFunction(Option::Type type, const Real cutoff,
                               const Real a1, const Real b1, const Real c1,
                               const Real a2, const Real b2, const Real c2,
                               const Real lowerCutoff, const Real coreRegionMin,
                               const Real coreRegionMax);
        Real operator()(const Real spot) const;
        std::pair<Real, Real> coreRegion() const {
            return std::make_pair(coreRegionMin_, coreRegionMax_);
        }

      private:
        Option::Type type_;
        const Real a1_, b1_, c1_, a2_, b2_, c2_;
        const Real cutoff_, lowerCutoff_;
        const Real coreRegionMin_, coreRegionMax_;
        int flatExtrapolationType1_,
            flatExtrapolationType2_; // +1 = right, -1 = left
        Real extrapolationPoint1_, extrapolationPoint2_;
    };

    /*! typedefs */
    typedef typename McSimulation<SingleVariate, RNG, S>::path_generator_type
        path_generator_type;
    typedef typename McSimulation<SingleVariate, RNG, S>::path_pricer_type
        path_pricer_type;
    typedef typename McSimulation<SingleVariate, RNG, S>::stats_type stats_type;

    //! constructor
    McFxTarfEngine(
        const boost::shared_ptr<GeneralizedBlackScholesProcess> &process,
        Size timeSteps, Size timeStepsPerYears, bool brownianBridge,
        bool antitheticVariate, Size requiredSamples, Real requiredTolerance,
        Size maxSamples, BigNatural seed,
        const Handle<YieldTermStructure> discount, const bool generateProxy);

    void calculate() const;
    void reset();

  protected:
    // McSimulation Interface
    TimeGrid timeGrid() const;
    boost::shared_ptr<path_generator_type> pathGenerator() const;
    boost::shared_ptr<path_pricer_type> pathPricer() const;
    // data members
    boost::shared_ptr<GeneralizedBlackScholesProcess> process_;
    Size timeSteps_, timeStepsPerYear_;
    Size requiredSamples_, maxSamples_;
    Real requiredTolerance_;
    bool brownianBridge_;
    BigNatural seed_;
    bool generateProxy_;
    mutable std::vector<Real> fixingTimes_, discounts_;
    // proxy information generated by the engine
    mutable boost::shared_ptr<FxTarf::Proxy> proxy_;
    // simulation data on which the proxy is estimated
    mutable ProxyData data_;
    // bucket limits for collected data
    mutable std::vector<Real> accBucketLimits_;
};

//! Proxy function
template <class RNG, class S>
McFxTarfEngine<RNG, S>::QuadraticProxyFunction::QuadraticProxyFunction(
    Option::Type type, const Real cutoff, const Real a1, const Real b1,
    const Real c1, const Real a2, const Real b2, const Real c2,
    const Real lowerCutoff, const Real coreRegionMin, const Real coreRegionMax)
    : type_(type), cutoff_(cutoff), a1_(a1), b1_(b1), c1_(c1), a2_(a2), b2_(b2),
      c2_(c2), lowerCutoff_(lowerCutoff), coreRegionMin_(coreRegionMin),
      coreRegionMax_(coreRegionMax) {
    QL_REQUIRE((type == Option::Call && lowerCutoff_ <= cutoff) ||
                   (type == Option::Put && lowerCutoff_ >= cutoff),
               "lowerCutoff (" << lowerCutoff_
                               << ") must be less or equal (call) or greater "
                                  "equal (put) than cutoff ("
                               << cutoff
                               << ") for type "
                               << type_);
    // for calls we want ascending, for puts descending functions
    if (close(a1_, 0.0)) {
        // for minspot = maxspot a constant function with a=b=0
        // is constructed, so this requirement must be dropped
        // QL_REQUIRE(b1_ > 0.0, "for a call and a1=0 b ("
        //                           << b1_ << ") must be positive");
    } else {
        extrapolationPoint1_ = -b1_ / (2.0 * a1_);
        flatExtrapolationType1_ =
            (type_ == Option::Call ? 1 : -1) * (a1_ > 0.0 ? -1 : 1);
    }
    if (close(a2_, 0.0)) {
        // for minspot = maxspot a constant function with a=b=0
        // is constructed, so this requirement must be dropped
        // QL_REQUIRE(b2_ > 0.0, "for a call and a2=0 b ("
        //                           << b2_ << ") must be positive");
    } else {
        extrapolationPoint2_ = -b2_ / (2.0 * a2_);
        flatExtrapolationType2_ =
            (type_ == Option::Call ? 1 : -1) * (a2_ > 0.0 ? -1 : 1);
    }
}

template <class RNG, class S>
Real McFxTarfEngine<RNG, S>::QuadraticProxyFunction::
operator()(const Real spot) const {
    Real x = spot;
    if (spot <= cutoff_) {
        if (spot <= lowerCutoff_ && type_ == Option::Call &&
            flatExtrapolationType1_ == 1) {
            // linear extrapolation instead of quadratic outside lowerCutoff
            // if flat extrapolation is to the right)
            return (2.0 * a1_ * lowerCutoff_ + b1_) * spot + c1_ -
                   a1_ * lowerCutoff_ * lowerCutoff_;
        }
        x = flatExtrapolationType1_ *
            std::min(flatExtrapolationType1_ * extrapolationPoint1_,
                     flatExtrapolationType1_ * x);
        Real tmp = a1_ * x * x + b1_ * x + c1_;
        // ensure global monotonicity
        // if (type_ == Option::Put) {
        //     Real ct = flatExtrapolationType2_ *
        //               std::min(flatExtrapolationType2_ *
        //               extrapolationPoint2_,
        //                        flatExtrapolationType2_ * cutoff_);
        //     tmp = std::max(a2_ * ct * ct + b2_ * ct + c2_, tmp);
        // }
        return tmp;
    } else {
        if (spot >= lowerCutoff_ && type_ == Option::Put &&
            flatExtrapolationType2_ == -1) {
            // linear extrapolation instead of quadratic outside lowerCutoff if
            // flat extrapolation is to the left
            return (2.0 * a2_ * lowerCutoff_ + b2_) * spot + c2_ -
                   a2_ * lowerCutoff_ * lowerCutoff_;
        }
        x = flatExtrapolationType2_ *
            std::min(flatExtrapolationType2_ * extrapolationPoint2_,
                     flatExtrapolationType2_ * x);
        Real tmp = a2_ * x * x + b2_ * x + c2_;
        // ensure global monotonicity
        // if (type_ == Option::Call) {
        //     Real ct = flatExtrapolationType1_ *
        //               std::min(flatExtrapolationType1_ *
        //               extrapolationPoint1_,
        //                        flatExtrapolationType1_ * cutoff_);
        //     tmp = std::max(a1_ * ct * ct + b1_ * ct + c1_, tmp);
        // }
        return tmp;
    }
}

//! Monte Carlo fx-tarf engine factory
template <class RNG = PseudoRandom, class S = Statistics>
class MakeMcFxTarfEngine {
  public:
    MakeMcFxTarfEngine(
        const boost::shared_ptr<GeneralizedBlackScholesProcess> &);
    // named parameters
    MakeMcFxTarfEngine &withSteps(Size steps);
    MakeMcFxTarfEngine &withStepsPerYear(Size steps);
    MakeMcFxTarfEngine &withBrownianBridge(bool b = true);
    MakeMcFxTarfEngine &withAntitheticVariate(bool b = true);
    MakeMcFxTarfEngine &withSamples(Size samples);
    MakeMcFxTarfEngine &withAbsoluteTolerance(Real tolerance);
    MakeMcFxTarfEngine &withMaxSamples(Size samples);
    MakeMcFxTarfEngine &withSeed(BigNatural seed);
    MakeMcFxTarfEngine &
    withDiscount(const Handle<YieldTermStructure> &discount);
    MakeMcFxTarfEngine &withProxy(bool b = true);
    // conversion to pricing engine
    operator boost::shared_ptr<PricingEngine>() const;

  private:
    boost::shared_ptr<GeneralizedBlackScholesProcess> process_;
    bool brownianBridge_, antithetic_;
    Size steps_, stepsPerYear_, samples_, maxSamples_;
    Real tolerance_;
    BigNatural seed_;
    Handle<YieldTermStructure> discount_;
    bool generateProxy_;
};

//! Path Pricer
class FxTarfPathPricer : public PathPricer<Path> {
  public:
    FxTarfPathPricer(const std::vector<Real> &fixingTimes,
                     const std::vector<Real> &discounts,
                     const Real accumulatedAmount, const Real sourceNominal,
                     const Real target, const FxTarf *instrument,
                     ProxyData &data, const std::vector<Real> &accBucketLimits,
                     const Date lastPaymentDate,
                     const Handle<YieldTermStructure> &discount,
                     const bool generateProxy);
    Real operator()(const Path &path) const;

  private:
    const std::vector<Real> &fixingTimes_, &discounts_;
    const Real accumulatedAmount_, sourceNominal_, target_;
    const FxTarf *instrument_;
    mutable std::vector<Size> fixingIndices_;
    ProxyData &data_;
    const std::vector<Real> &accBucketLimits_;
    const Date lastPaymentDate_;
    const Handle<YieldTermStructure> discount_;
    const bool generateProxy_;
};

// Implementation

template <class RNG, class S>
McFxTarfEngine<RNG, S>::McFxTarfEngine(
    const boost::shared_ptr<GeneralizedBlackScholesProcess> &process,
    Size timeSteps, Size timeStepsPerYear, bool brownianBridge,
    bool antitheticVariate, Size requiredSamples, Real requiredTolerance,
    Size maxSamples, BigNatural seed, const Handle<YieldTermStructure> discount,
    const bool generateProxy)
    : FxTarfEngine(discount),
      McSimulation<SingleVariate, RNG, S>(antitheticVariate, false),
      process_(process), timeSteps_(timeSteps),
      timeStepsPerYear_(timeStepsPerYear), requiredSamples_(requiredSamples),
      maxSamples_(maxSamples), requiredTolerance_(requiredTolerance),
      brownianBridge_(brownianBridge), seed_(seed),
      generateProxy_(generateProxy) {
    QL_REQUIRE(timeSteps != Null<Size>() || timeStepsPerYear != Null<Size>(),
               "no time steps provided");
    QL_REQUIRE(timeSteps == Null<Size>() || timeStepsPerYear == Null<Size>(),
               "both time steps and time steps per year were provided");
    QL_REQUIRE(timeSteps != 0, "timeSteps must be positive, "
                                   << timeSteps << " not allowed");
    QL_REQUIRE(timeStepsPerYear != 0, "timeStepsPerYear must be positive, "
                                          << timeStepsPerYear
                                          << " not allowed");
    QL_REQUIRE(!discount_.empty(), "no discount curve given");
    registerWith(process_);
}

template <class RNG, class S> void McFxTarfEngine<RNG, S>::reset() {
    FxTarfEngine::reset();
    fixingTimes_.clear();
    discounts_.clear();
    proxy_ = boost::shared_ptr<FxTarf::Proxy>();
    data_.clear();
    accBucketLimits_.clear();
}

template <class RNG, class S> void McFxTarfEngine<RNG, S>::calculate() const {

    Date today = Settings::instance().evaluationDate();

    // handle the trivial cases
    FxTarfEngine::calculate();

    // are we already done, i.e. has the base engine set the npv ?
    if (results_.value != Null<Real>())
        return;

    // we have at least one fixing left which is tommorow or later
    for (Size i = 0; i < arguments_.openFixingDates.size(); ++i) {
        fixingTimes_.push_back(process_->time(arguments_.openFixingDates[i]));
        discounts_.push_back(
            discount_->discount(arguments_.openPaymentDates[i]));
    }

    // prepare the data container on which the proxy pricing is estimated
    // later

    // we use a number of heuristics in the following
    // number of buckets for accumulated amounts
    // which are merged though if the data in the buckets
    // does not meet the requirement below
    Size nAccBuckets = 5;
    // first the data points per accumulated amount bucket should
    // be more than the total number of data points divided by dFactor
    Size dFactor = 10;
    // then (spot,npv) pairs are divided into two segments (for calls)
    // [spotMin,spotMin+relCutoff*(spotMax-spotMin)) and
    // [spotMin+relCutoff*(spotMax-spotMin,spotMax]
    // for puts we use 1.0-relCutoff to divide the intervall instead
    Real relCutoff = 0.80;
    // we require minCutoffRatio*(1.0-relCutoff)*totalNoDataPoints
    // to be still in the smaller (spot,npv) segment, otherwise
    // the cutoff will be lowered (calls) by a factor of
    // cutoffShrinkFactor until we reach this critical size
    Real minCutoffRatio = 0.25;
    Real cutoffShrinkFactor = 0.99;
    // on the lower bound (for calls) a lowerCutoff is determined
    // such that more than 1-minLowerExtr points are above this
    // cutoff. Below this threshhold, a linear extrapolation is
    // used.
    Real minLowerExtr = 0.05;
    // if the intersection of the two quadratic functions lies
    // within (1-smoothInt)*cutoff, (1+smoothInt)*cutoff
    // the cutoff is moved to the intersection points
    Real smoothInt = 0.05;
    // the "trusted" region (aka core region) is defined by chopping
    // off the lower and upper coreCutoff part of the data
    Real coreCutoff = 0.001;

    // this is the minimum number of points required for regression
    Size minRegPoints = 3;

    if (generateProxy_) {
        // create the buckets
        for (Size i = 0; i < nAccBuckets; ++i) {
            accBucketLimits_.push_back(
                static_cast<Real>(i) / static_cast<Real>(nAccBuckets) *
                    (arguments_.target - arguments_.accumulatedAmount) +
                arguments_.accumulatedAmount);
        }

        // we set the first bucket limit to zero, which does not change
        // anything, but leaves no room that the given accumulated amount
        // is below the first limit
        accBucketLimits_[0] = 0.0;

        // initialize the data container
        for (Size i1 = 0; i1 < fixingTimes_.size(); ++i1) {
            std::vector<std::vector<std::pair<Real, Real> > > level1Tmp;
            for (Size i2 = 0; i2 < nAccBuckets; ++i2) {
                level1Tmp.push_back(std::vector<std::pair<Real, Real> >(0));
            }
            data_.push_back(level1Tmp);
        }
    }

    // do the main calculation using the mc machinery
    McSimulation<SingleVariate, RNG, S>::calculate(
        requiredTolerance_, requiredSamples_, maxSamples_);
    results_.value =
        this->mcModel_->sampleAccumulator().mean() + unsettledAmountNpv_;
    if (RNG::allowsErrorEstimate)
        results_.errorEstimate =
            this->mcModel_->sampleAccumulator().errorEstimate();

    if (!generateProxy_)
        return;

    // create the proxy object and initialize the members
    proxy_ = boost::make_shared<FxTarf::Proxy>();
    proxy_->origEvalDate = today;
    proxy_->openFixingDates = arguments_.openFixingDates;
    proxy_->accBucketLimits = accBucketLimits_;
    proxy_->lastPaymentDate = arguments_.schedule.dates().back();
    for (Size i = 0; i < proxy_->openFixingDates.size(); ++i) {
        std::vector<boost::shared_ptr<FxTarf::Proxy::ProxyFunction> > fctVecTmp(
            accBucketLimits_.size());
        proxy_->functions.push_back(fctVecTmp);
    }

    // do the regression on appropriately merged data sets
    for (Size i = 0; i < arguments_.openFixingDates.size(); ++i) {
        // get the data for the specific number of open fixing times
        std::vector<std::vector<std::pair<Real, Real> > > &tmp = data_[i];

        // how many data points do we have over all
        // accumulated amount buckets ?
        Size numberOfDataPoints = 0;
        for (Size j = 0; j < tmp.size(); ++j)
            numberOfDataPoints += tmp[j].size();

        // merge data if pieces are too small
        Size k0 = 0, k0Before = 0;
        do {
            std::vector<std::pair<Real, Real> > xTmp;
            Real spotMin = QL_MAX_REAL, spotMax = QL_MIN_REAL;
            do {
                std::vector<std::pair<Real, Real> > xTmp2(xTmp.size() +
                                                          tmp[k0].size());
                std::merge(xTmp.begin(), xTmp.end(), tmp[k0].begin(),
                           tmp[k0].end(), xTmp2.begin());
                xTmp.swap(xTmp2);
                if (tmp[k0].size() > 0) {
                    if (tmp[k0].front().first < spotMin)
                        spotMin = tmp[k0].front().first;
                    if (tmp[k0].back().first > spotMax)
                        spotMax = tmp[k0].back().first;
                }
                k0++;
            } while (dFactor * xTmp.size() < numberOfDataPoints);

            // count the number of remaining data points ...
            Size remainingDataPoints = 0;
            for (Size j = k0; j < tmp.size(); ++j)
                remainingDataPoints += tmp[j].size();

            // ... and join the rest of data if they are to few
            if (dFactor * remainingDataPoints < numberOfDataPoints) {
                for (Size j = k0; j < tmp.size(); ++j) {
                    std::vector<std::pair<Real, Real> > xTmp2(xTmp.size() +
                                                              tmp[j].size());
                    std::merge(xTmp.begin(), xTmp.end(), tmp[j].begin(),
                               tmp[j].end(), xTmp2.begin());
                    xTmp.swap(xTmp2);
                    if (tmp[j].size() > 0) {
                        if (tmp[j].front().first < spotMin)
                            spotMin = tmp[j].front().first;
                        if (tmp[j].back().first > spotMax)
                            spotMax = tmp[j].back().first;
                    }
                }
                k0 = tmp.size();
            }

            // we rearrange the data to get two segments for the spot
            bool isCall = arguments_.longPositionType == Option::Call;
            Real relCutoffTmp = isCall ? relCutoff : 1.0 - relCutoff;
            std::vector<Real> xTmp1, xTmp2, yTmp1, yTmp2;
            Real cutoff = spotMin + relCutoffTmp * (spotMax - spotMin);

            // we want a certain percentage of data still in the smaller
            // data set, otherwise we lower the cutoff
            Size minDataSegment =
                static_cast<Size>((1.0 - relCutoffTmp) * minCutoffRatio *
                                  xTmp.size()) +
                1;
            Size sizeA, sizeB, criticalSize;
            do {
                sizeA = std::upper_bound(xTmp.begin(), xTmp.end(),
                                         std::make_pair(cutoff, 0.0)) -
                        xTmp.begin();
                sizeB = xTmp.size() - sizeA;
                criticalSize = isCall ? sizeB : sizeA;
                if (((isCall && relCutoffTmp > 0.5) ||
                     (!isCall && relCutoffTmp < 0.5)) &&
                    (criticalSize < minDataSegment ||
                     criticalSize < minRegPoints)) {
                    if (isCall)
                        relCutoffTmp *= cutoffShrinkFactor;
                    else
                        relCutoffTmp /= std::min(cutoffShrinkFactor, 1.0);
                    cutoff = spotMin + relCutoffTmp * (spotMax - spotMin);
                }
            } while (
                ((isCall && relCutoffTmp > 0.5) ||
                 (!isCall && relCutoffTmp < 0.5)) &&
                (criticalSize < minDataSegment || criticalSize < minRegPoints));

            // copy the data to the final vectors used for the regression
            for (Size ii = 0; ii < xTmp.size(); ++ii) {
                if (xTmp[ii].first <= cutoff) {
                    xTmp1.push_back(xTmp[ii].first);
                    yTmp1.push_back(xTmp[ii].second);
                } else {
                    xTmp2.push_back(xTmp[ii].first);
                    yTmp2.push_back(xTmp[ii].second);
                }
            }

            // determine lower cutoff (in terms of calls), below which we
            // extrapolate linear
            Real lowerCutoff =
                xTmp[static_cast<int>(xTmp.size() * (isCall
                                                         ? minLowerExtr
                                                         : 1.0 - minLowerExtr))]
                    .first;
            // determine the core (trusted) region
            Real coreRegionMin =
                xTmp[static_cast<int>(xTmp.size() * coreCutoff)].first;
            Real coreRegionMax =
                xTmp[static_cast<int>(xTmp.size() * (1.0 - coreCutoff))].first;

            // the function object
            boost::shared_ptr<FxTarf::Proxy::ProxyFunction> fct;

            // if minSpot = cutoff = maxSpot (this may happen at t = 0) we
            // just return a constant function being the average over all
            // data points
            if (std::fabs(spotMax - spotMin) < QL_EPSILON) {
                Real avg = 0.0;
                for (Size i = 0; i < xTmp1.size(); ++i)
                    avg += yTmp1[i];
                for (Size i = 0; i < xTmp2.size(); ++i)
                    avg += yTmp2[i];
                avg /= static_cast<double>(xTmp1.size() + xTmp2.size());
                fct = boost::shared_ptr<QuadraticProxyFunction>(
                    new QuadraticProxyFunction(
                        arguments_.longPositionType, cutoff, 0.0, 0.0, avg, 0.0,
                        0.0, avg, -QL_MAX_REAL, spotMin, spotMax));
            } else {

                // final sanity check before regression
                QL_REQUIRE(xTmp1.size() >= 3,
                           "too few points for regression in set 1 ("
                               << xTmp1.size() << ")");
                QL_REQUIRE(xTmp2.size() >= 3,
                           "too few points for regression in set 2 ("
                               << xTmp2.size() << ")");

                // regression
                std::vector<boost::function<Real(Real)> > v;
                v.push_back(&basis0);
                v.push_back(&basis1);
                v.push_back(&basis2);

                GeneralLinearLeastSquares ls1(xTmp1, yTmp1, v);
                Array result1 = ls1.coefficients();

                GeneralLinearLeastSquares ls2(xTmp2, yTmp2, v);
                Array result2 = ls2.coefficients();

                // check if the cutoff should be moved to the intersection
                // point of the two overlapping functions
                Real a1 = result1[2];
                Real b1 = result1[1];
                Real c1 = result1[0];
                Real a2 = result2[2];
                Real b2 = result2[1];
                Real c2 = result2[0];
                if (close(a1, a2)) {
                    if (!close(b1, b2)) {
                        Real tmp = -(c1 - c2) / (b1 - b2);
                        if (fabs((tmp - cutoff) / cutoff) < smoothInt) {
                            cutoff = tmp;
                        }
                    }
                } else {
                    Real tmp1 =
                        (-(b1 - b2) + std::sqrt((b1 - b2) * (b1 - b2) -
                                                4.0 * (a1 - a2) * (c1 - c2))) /
                        (2.0 * (a1 - a2));
                    Real tmp2 =
                        (-(b1 - b2) - std::sqrt((b1 - b2) * (b1 - b2) -
                                                4.0 * (a1 - a2) * (c1 - c2))) /
                        (2.0 * (a1 - a2));
                    if (fabs((tmp1 - cutoff) / cutoff) < smoothInt)
                        cutoff = tmp1;
                    if (fabs((tmp2 - cutoff) / cutoff) < smoothInt)
                        cutoff = tmp2;
                }

                // make sure that lower cutoff is still left from cutoff
                lowerCutoff = std::min(lowerCutoff, cutoff);

                fct = boost::shared_ptr<QuadraticProxyFunction>(
                    new QuadraticProxyFunction(
                        arguments_.longPositionType, cutoff, a1, b1, c1, a2, b2,
                        c2, lowerCutoff, coreRegionMin, coreRegionMax));
            }

            // store the proxy function, please note that
            // due to merging we may have the same function for several
            // accumulated amount segments
            for (Size kk = k0Before; kk < k0; ++kk) {
                proxy_->functions[i][kk] = fct;
            }
            k0Before = k0;
        } while (k0 < tmp.size()); // do-while over accumulated amount buckets
    }                              // for openFixingTimes

    // set proxy information generated during simulation as result

    results_.proxy = this->proxy_;

    // clear the collected data

    data_.clear();
}

template <class RNG, class S>
TimeGrid McFxTarfEngine<RNG, S>::timeGrid() const {
    if (timeSteps_ != Null<Size>()) {
        return TimeGrid(fixingTimes_.begin(), fixingTimes_.end(), timeSteps_);
    } else if (timeStepsPerYear_ != Null<Size>()) {
        Size steps = static_cast<Size>(timeStepsPerYear_ * fixingTimes_.back());
        return TimeGrid(fixingTimes_.begin(), fixingTimes_.end(),
                        std::max<Size>(steps, 1));
    } else {
        QL_FAIL("time steps not specified");
    }
}

template <class RNG, class S>
boost::shared_ptr<typename McFxTarfEngine<RNG, S>::path_generator_type>
McFxTarfEngine<RNG, S>::pathGenerator() const {
    TimeGrid grid = timeGrid();
    typename RNG::rsg_type gen =
        RNG::make_sequence_generator(grid.size() - 1, seed_);
    return boost::make_shared<path_generator_type>(process_, grid, gen,
                                                   brownianBridge_);
}

template <class RNG, class S>
boost::shared_ptr<typename McFxTarfEngine<RNG, S>::path_pricer_type>
McFxTarfEngine<RNG, S>::pathPricer() const {
    return boost::shared_ptr<FxTarfPathPricer>(new FxTarfPathPricer(
        fixingTimes_, discounts_, arguments_.accumulatedAmount,
        arguments_.sourceNominal, arguments_.target, arguments_.instrument,
        this->data_, this->accBucketLimits_, arguments_.schedule.dates().back(),
        this->discount_, this->generateProxy_));
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S>::MakeMcFxTarfEngine(
    const boost::shared_ptr<GeneralizedBlackScholesProcess> &process)
    : process_(process), brownianBridge_(false), antithetic_(false),
      steps_(Null<Size>()), stepsPerYear_(Null<Size>()), samples_(Null<Size>()),
      maxSamples_(Null<Size>()), tolerance_(Null<Real>()), seed_(0),
      discount_(process->riskFreeRate()), generateProxy_(false) {}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withSteps(Size steps) {
    steps_ = steps;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withStepsPerYear(Size steps) {
    stepsPerYear_ = steps;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withBrownianBridge(bool brownianBridge) {
    brownianBridge_ = brownianBridge;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withAntitheticVariate(bool b) {
    antithetic_ = b;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withSamples(Size samples) {
    QL_REQUIRE(tolerance_ == Null<Real>(), "tolerance already set");
    samples_ = samples;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withAbsoluteTolerance(Real tolerance) {
    QL_REQUIRE(samples_ == Null<Size>(), "number of samples already set");
    QL_REQUIRE(RNG::allowsErrorEstimate, "chosen random generator policy "
                                         "does not allow an error estimate");
    tolerance_ = tolerance;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withMaxSamples(Size samples) {
    maxSamples_ = samples;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withSeed(BigNatural seed) {
    seed_ = seed;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &MakeMcFxTarfEngine<RNG, S>::withDiscount(
    const Handle<YieldTermStructure> &discount) {
    discount_ = discount;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S> &
MakeMcFxTarfEngine<RNG, S>::withProxy(bool b) {
    generateProxy_ = b;
    return *this;
}

template <class RNG, class S>
inline MakeMcFxTarfEngine<RNG, S>::
operator boost::shared_ptr<PricingEngine>() const {
    QL_REQUIRE(steps_ != Null<Size>() || stepsPerYear_ != Null<Size>(),
               "number of steps not given");
    QL_REQUIRE(steps_ == Null<Size>() || stepsPerYear_ == Null<Size>(),
               "number of steps overspecified");
    return boost::shared_ptr<PricingEngine>(new McFxTarfEngine<RNG, S>(
        process_, steps_, stepsPerYear_, brownianBridge_, antithetic_, samples_,
        tolerance_, maxSamples_, seed_, discount_, generateProxy_));
}

} // namespace QuantLib
#endif


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

// Third party includes
#include <boost/math/constants/constants.hpp>

// VOTCA includes
#include <votca/tools/constants.h>

// Local VOTCA includes
#include <votca/xtp/orbitals.h>

// Local private VOTCA includes
#include "spectrum.h"

namespace votca {
namespace xtp {

void Spectrum::ParseOptions(const tools::Property& options) {

  // orbitals file or pure DFT output
  orbfile_ = options.ifExistsReturnElseReturnDefault<std::string>(
      ".input", job_name_ + ".orb");

  output_file_ = options.ifExistsReturnElseReturnDefault<std::string>(
      ".output", job_name_ + "_spectrum.dat");

  n_pt_ = options.get(".points").as<Index>();
  lower_ = options.get(".lower").as<double>();
  upper_ = options.get(".upper").as<double>();
  fwhm_ = options.get(".fwhm").as<double>();

  spectrum_type_ = options.get(".type").as<std::string>();
  minexc_ = options.get(".minexc").as<Index>();
  maxexc_ = options.get(".maxexc").as<Index>();
  shiftby_ = options.get(".shift").as<double>();
}

bool Spectrum::Run() {
  log_.setReportLevel(Log::current_level);
  log_.setMultithreading(true);

  log_.setCommonPreface("\n... ...");

  XTP_LOG(Log::error, log_)
      << "Calculating absorption spectrum plot " << orbfile_ << std::flush;

  Orbitals orbitals;
  // load the QM data from serialized orbitals object
  XTP_LOG(Log::error, log_)
      << " Loading QM data from " << orbfile_ << std::flush;
  orbitals.ReadFromCpt(orbfile_);

  // check if orbitals contains singlet energies and transition dipoles
  if (!orbitals.hasBSESinglets()) {
    throw std::runtime_error(
        "BSE singlet energies not stored in QM data file!");
  }

  if (!orbitals.hasTransitionDipoles()) {
    throw std::runtime_error(
        "BSE transition dipoles not stored in QM data file!");
  }

  const Eigen::VectorXd BSESingletEnergies =
      orbitals.BSESinglets().eigenvalues() * tools::conv::hrt2ev;
  const std::vector<Eigen::Vector3d>& TransitionDipoles =
      orbitals.TransitionDipoles();
  Eigen::VectorXd osc = orbitals.Oscillatorstrengths();

  if (maxexc_ > Index(TransitionDipoles.size())) {
    maxexc_ = Index(TransitionDipoles.size()) - 1;
  }

  Index n_exc = maxexc_ - minexc_ + 1;
  XTP_LOG(Log::error, log_)
      << " Considering " << n_exc << " excitation with max energy "
      << BSESingletEnergies(maxexc_) << " eV / min wave length "
      << evtonm(BSESingletEnergies[maxexc_ - 1]) << " nm" << std::flush;

  /*
   *
   * For a single excitation, broaden by Lineshape function L(v-W)
   *    eps(v) = f * L(v-W)
   *
   * where
   *       v: energy
   *       f: oscillator strength in dipole-length gauge
   *       W: excitation energy
   *
   * Lineshape function depend on FWHM and can be
   *
   *      Gaussian
   *          L(v-W) = 1/(sqrt(2pi)sigma) * exp(-0.5 (v-W)^2/sigma^2
   *
   *
   *            with sigma: derived from FWHM (FWHM/2.3548)
   *
   *     Lorentzian
   *          L(v-W) = 1/pi * 0.5 FWHM/( (v-w)^2 + 0.25*FWHM^2 )
   *
   * Full spectrum is superposition of individual spectra.
   *
   *  Alternatively, one can calculate the imaginary part of the
   *  frequency-dependent dielectric function
   *
   *   IM(eps(v)) ~ 1/v^2 * W^2 * |td|^2 * L(v-W)
   *              = 1/v^2 * W   * f      * L(v-W)
   *
   *
   */

  std::ofstream ofs(output_file_, std::ofstream::out);

  if (spectrum_type_ == "energy") {
    ofs << "# E(eV)    epsGaussian    IM(eps)Gaussian   epsLorentz    "
           "Im(esp)Lorentz\n";
    for (Index i_pt = 0; i_pt <= n_pt_; i_pt++) {

      double e = (lower_ + double(i_pt) * (upper_ - lower_) / double(n_pt_));

      double eps_Gaussian = 0.0;
      double imeps_Gaussian = 0.0;
      double eps_Lorentzian = 0.0;
      double imeps_Lorentzian = 0.0;

      for (Index i_exc = minexc_; i_exc <= maxexc_; i_exc++) {
        eps_Gaussian +=
            osc[i_exc] *
            Gaussian(e, BSESingletEnergies(i_exc) + shiftby_, fwhm_);
        imeps_Gaussian += osc[i_exc] * BSESingletEnergies(i_exc) *
                          Gaussian(e, BSESingletEnergies(i_exc), fwhm_);
        eps_Lorentzian +=
            osc[i_exc] * Lorentzian(e, BSESingletEnergies(i_exc), fwhm_);
        imeps_Lorentzian += osc[i_exc] * BSESingletEnergies(i_exc) *
                            Lorentzian(e, BSESingletEnergies(i_exc), fwhm_);
      }

      ofs << e << "    " << eps_Gaussian << "   " << imeps_Gaussian << "   "
          << eps_Lorentzian << "   " << imeps_Lorentzian << std::endl;
    }

    XTP_LOG(Log::error, log_)
        << " Spectrum in energy range from  " << lower_ << " to " << upper_
        << " eV and with broadening of FWHM " << fwhm_
        << " eV written to file  " << output_file_ << std::flush;
  }

  if (spectrum_type_ == "wavelength") {

    ofs << "# lambda(nm)    epsGaussian    IM(eps)Gaussian   epsLorentz    "
           "Im(esp)Lorentz\n";
    for (Index i_pt = 0; i_pt <= n_pt_; i_pt++) {

      double lambda =
          (lower_ + double(i_pt) * (upper_ - lower_) / double(n_pt_));
      double eps_Gaussian = 0.0;
      double imeps_Gaussian = 0.0;
      double eps_Lorentzian = 0.0;
      double imeps_Lorentzian = 0.0;

      for (Index i_exc = minexc_; i_exc <= maxexc_; i_exc++) {
        double exc_lambda = nmtoev(BSESingletEnergies(i_exc) + shiftby_);
        eps_Gaussian += osc[i_exc] * Gaussian(lambda, exc_lambda, fwhm_);
        imeps_Gaussian +=
            osc[i_exc] * exc_lambda * Gaussian(lambda, exc_lambda, fwhm_);
        eps_Lorentzian += osc[i_exc] * Lorentzian(lambda, exc_lambda, fwhm_);
        imeps_Lorentzian +=
            osc[i_exc] * exc_lambda * Lorentzian(lambda, exc_lambda, fwhm_);
      }

      ofs << lambda << "    " << eps_Gaussian << "   " << imeps_Gaussian
          << "   " << eps_Lorentzian << "   " << imeps_Lorentzian << std::endl;
    }
    XTP_LOG(Log::error, log_)
        << " Spectrum in wavelength range from  " << lower_ << " to " << upper_
        << " nm and with broadening of FWHM " << fwhm_
        << " nm written to file  " << output_file_ << std::flush;
  }

  ofs.close();
  return true;
}

double Spectrum::Lorentzian(double x, double center, double fwhm) {
  return 0.5 * fwhm / (std::pow(x - center, 2) + 0.25 * fwhm * fwhm) /
         boost::math::constants::pi<double>();
}

double Spectrum::Gaussian(double x, double center, double fwhm) {
  // FWHM = 2*sqrt(2 ln2) sigma = 2.3548 sigma
  double sigma = fwhm / 2.3548;
  return std::exp(-0.5 * std::pow((x - center) / sigma, 2)) / sigma /
         sqrt(2.0 * boost::math::constants::pi<double>());
}

double Spectrum::evtonm(double eV) { return 1241.0 / eV; }

double Spectrum::evtoinvcm(double eV) { return 8065.73 * eV; }

double Spectrum::nmtoinvcm(double nm) { return 1241.0 * 8065.73 / nm; }

double Spectrum::invcmtonm(double invcm) { return 1.0e7 / invcm; }

double Spectrum::nmtoev(double nm) { return 1241.0 / nm; }

}  // namespace xtp
}  // namespace votca