/*
 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.
*/

#include <ql/cashflows/coupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/exercise.hpp>
#include <ql/instruments/compositeinstrument.hpp>
#include <ql/instruments/nonstandardswaption.hpp>
#include <ql/instruments/swaption.hpp>
#include <ql/time/daycounters/actualactual.hpp>

#include <qle/instruments/rebatedexercise.hpp>

#include <ored/portfolio/builders/swap.hpp>
#include <ored/portfolio/builders/swaption.hpp>
#include <ored/portfolio/optionwrapper.hpp>
#include <ored/portfolio/swaption.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/to_string.hpp>

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/timer/timer.hpp>

#include <algorithm>

using boost::timer::cpu_timer;
using boost::timer::default_places;
using namespace QuantLib;
using std::lower_bound;
using std::sort;

namespace ore {
namespace data {

void Swaption::build(const boost::shared_ptr<EngineFactory>& engineFactory) {

    QL_REQUIRE(swap_.size() == 2, "underlying swap must have 2 legs");

    // we can assume 2 legs now
    bool isCrossCcy = swap_[0].currency() != swap_[1].currency();

    bool isFixedFloating = (swap_[0].legType() == "Fixed" && swap_[1].legType() == "Floating") ||
                           (swap_[1].legType() == "Fixed" && swap_[0].legType() == "Floating");

    // First elimate these
    QL_REQUIRE(!isCrossCcy, "Cross Currency Swaptions not supported");
    QL_REQUIRE(isFixedFloating, "Basis Swaptions not supported");

    // check at least one exercise date is given
    QL_REQUIRE(!option_.exerciseDates().empty(), "No exercise dates given");

    // check if all exercise dates (adjusted by the notice period) are in the past and if so, build an expired
    // instrument
    Period noticePeriod = option_.noticePeriod().empty() ? 0 * Days : parsePeriod(option_.noticePeriod());
    Calendar noticeCal = option_.noticeCalendar().empty() ? NullCalendar() : parseCalendar(option_.noticeCalendar());
    BusinessDayConvention noticeBdc =
        option_.noticeConvention().empty() ? Unadjusted : parseBusinessDayConvention(option_.noticeConvention());
    std::vector<Date> exerciseDates(option_.exerciseDates().size());
    for (Size i = 0; i < option_.exerciseDates().size(); ++i) {
        exerciseDates.push_back(noticeCal.advance(parseDate(option_.exerciseDates()[i]), -noticePeriod, noticeBdc));
    }

    Date latestExerciseDate = *std::max_element(exerciseDates.begin(), exerciseDates.end());
    if (QuantLib::detail::simple_event(latestExerciseDate).hasOccurred()) {
        exercise_ = boost::make_shared<BermudanExercise>(exerciseDates);
        legs_ = {{boost::make_shared<QuantLib::SimpleCashFlow>(0.0, latestExerciseDate)}};
        instrument_ = boost::shared_ptr<InstrumentWrapper>(
            new VanillaInstrument(boost::make_shared<QuantLib::Swap>(legs_, std::vector<bool>(1, false)), 1.0, {}, {}));
        legCurrencies_ = {swap_[0].currency()};
        legPayers_ = {false};
        npvCurrency_ = swap_[0].currency();
        notional_ = 0.0;
        notionalCurrency_ = npvCurrency_;
        maturity_ = latestExerciseDate;
        return;
    }

    // build swaption with at least one alive exercise date
    bool fixedFirst = swap_[0].legType() == "Fixed";
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedFirst ? 0 : 1].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[fixedFirst ? 1 : 0].concreteLegData());
    underlyingIndex_ = floatingLegData->index();

    bool isNonStandard =
        (swap_[0].notionals().size() > 1 || swap_[1].notionals().size() > 1 || floatingLegData->spreads().size() > 1 ||
         floatingLegData->gearings().size() > 1 || fixedLegData->rates().size() > 1);

    Exercise::Type exerciseType = parseExerciseType(option_.style());

    // treat non standard europeans as bermudans, also if exercise fees are present
    QL_REQUIRE(exerciseType == Exercise::Bermudan || exerciseType == Exercise::European,
               "Exercise type " << option_.style() << " not implemented for Swaptions");
    if (exerciseType == Exercise::Bermudan || (isNonStandard && exerciseType == Exercise::European) ||
        !option_.exerciseFees().empty())
        buildBermudan(engineFactory);
    else
        buildEuropean(engineFactory);

    // add required fixings, we add the required fixing for the underlying swap, which might be more
    // than actually required, i.e. we are conservative here
    addToRequiredFixings(underlyingLeg_,
                         boost::make_shared<FixingDateGetter>(
                             requiredFixings_, std::map<string, string>{{underlyingIndexQlName_, underlyingIndex_}}));
}

namespace {
QuantLib::Settlement::Method defaultMethod(const QuantLib::Settlement::Type t) {
    if (t == QuantLib::Settlement::Physical)
        return QuantLib::Settlement::PhysicalOTC;
    else
        return QuantLib::Settlement::ParYieldCurve; // ql < 1.14 behaviour
}
} // namespace

void Swaption::buildEuropean(const boost::shared_ptr<EngineFactory>& engineFactory) {

    LOG("Building European Swaption " << id());

    // Swaption details
    Settlement::Type settleType = parseSettlementType(option_.settlement());
    Settlement::Method settleMethod = option_.settlementMethod() == ""
                                          ? defaultMethod(settleType)
                                          : parseSettlementMethod(option_.settlementMethod());
    QL_REQUIRE(option_.exerciseDates().size() == 1, "Only one exercise date expected for European Option");

    Period noticePeriod = option_.noticePeriod().empty() ? 0 * Days : parsePeriod(option_.noticePeriod());
    Calendar noticeCal = option_.noticeCalendar().empty() ? NullCalendar() : parseCalendar(option_.noticeCalendar());
    BusinessDayConvention noticeBdc =
        option_.noticeConvention().empty() ? Unadjusted : parseBusinessDayConvention(option_.noticeConvention());
    Date exDate = noticeCal.advance(parseDate(option_.exerciseDates().front()), -noticePeriod, noticeBdc);
    QL_REQUIRE(exDate >= Settings::instance().evaluationDate(), "Exercise date expected in the future.");

    boost::shared_ptr<VanillaSwap> swap = buildVanillaSwap(engineFactory, exDate);
    underlyingLeg_ = swap->floatingLeg();

    string ccy = swap_[0].currency();
    Currency currency = parseCurrency(ccy);

    exercise_ = boost::make_shared<EuropeanExercise>(exDate);

    // Build Swaption
    boost::shared_ptr<QuantLib::Swaption> swaption(new QuantLib::Swaption(swap, exercise_, settleType, settleMethod));

    // Add Engine
    string tt("EuropeanSwaption");
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder(tt);
    QL_REQUIRE(builder, "No builder found for " << tt);
    boost::shared_ptr<EuropeanSwaptionEngineBuilder> swaptionBuilder =
        boost::dynamic_pointer_cast<EuropeanSwaptionEngineBuilder>(builder);
    swaption->setPricingEngine(swaptionBuilder->engine(currency));

    Position::Type positionType = parsePositionType(option_.longShort());
    Real multiplier = (positionType == QuantLib::Position::Long ? 1.0 : -1.0);

    // If premium data is provided
    // 1) build the fee trade and pass it to the instrument wrapper for pricing
    // 2) add fee payment as additional trade leg for cash flow reporting
    std::vector<boost::shared_ptr<Instrument>> additionalInstruments;
    std::vector<Real> additionalMultipliers;
    if (option_.premiumPayDate() != "" && option_.premiumCcy() != "") {
        Real premiumAmount = -multiplier * option_.premium(); // pay if long, receive if short
        Currency premiumCurrency = parseCurrency(option_.premiumCcy());
        Date premiumDate = parseDate(option_.premiumPayDate());
        addPayment(additionalInstruments, additionalMultipliers, premiumDate, premiumAmount, premiumCurrency, currency,
                   engineFactory, swaptionBuilder->configuration(MarketContext::pricing));
        DLOG("option premium added for european swaption " << id());
    }

    // Now set the instrument wrapper, depending on delivery
    if (settleType == Settlement::Physical) {
        // tracks state for any option flavour, including physical delivery
        instrument_ = boost::shared_ptr<InstrumentWrapper>(
            new EuropeanOptionWrapper(swaption, positionType == Position::Long ? true : false, exDate,
                                      settleType == Settlement::Physical ? true : false, swap, 1.0, 1.0,
                                      additionalInstruments, additionalMultipliers));
        // maturity of underlying
        maturity_ = std::max(swap->fixedSchedule().dates().back(), swap->floatingSchedule().dates().back());
    } else {
        instrument_ = boost::shared_ptr<InstrumentWrapper>(
            new VanillaInstrument(swaption, multiplier, additionalInstruments, additionalMultipliers));
        maturity_ = exDate;
    }

    DLOG("Building European Swaption done");
}

void Swaption::buildBermudan(const boost::shared_ptr<EngineFactory>& engineFactory) {
    DLOG("Building Bermudan Swaption " << id());

    QL_REQUIRE(swap_.size() >= 1, "underlying swap must have at least 1 leg");

    string ccy_str = swap_[0].currency();
    Currency currency = parseCurrency(ccy_str);

    bool fixedFirst = swap_[0].legType() == "Fixed";
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedFirst ? 0 : 1].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[fixedFirst ? 1 : 0].concreteLegData());
    underlyingIndex_ = floatingLegData->index();

    bool isNonStandard =
        (swap_[0].notionals().size() > 1 || swap_[1].notionals().size() > 1 || floatingLegData->spreads().size() > 1 ||
         floatingLegData->gearings().size() > 1 || fixedLegData->rates().size() > 1);

    boost::shared_ptr<VanillaSwap> vanillaSwap;
    boost::shared_ptr<NonstandardSwap> nonstandardSwap;
    boost::shared_ptr<Swap> swap;
    if (!isNonStandard) {
        vanillaSwap = buildVanillaSwap(engineFactory);
        underlyingLeg_ = vanillaSwap->floatingLeg();
        swap = vanillaSwap;
    } else {
        nonstandardSwap = buildNonStandardSwap(engineFactory);
        underlyingLeg_ = nonstandardSwap->floatingLeg();
        swap = nonstandardSwap;
    }

    DLOG("Swaption::Build(): Underlying Start = " << QuantLib::io::iso_date(swap->startDate()));

    // build exercise: only keep a) future exercise dates and b) exercise dates that exercise into a whole
    // accrual period of the underlying; TODO handle exercises into broken periods?
    Date lastAccrualStartDate = Date::minDate();
    for (Size i = 0; i < 2; ++i) {
        for (auto const& c : swap->leg(i)) {
            if (auto cpn = boost::dynamic_pointer_cast<Coupon>(c))
                lastAccrualStartDate = std::max(lastAccrualStartDate, cpn->accrualStartDate());
        }
    }
    Period noticePeriod = option_.noticePeriod().empty() ? 0 * Days : parsePeriod(option_.noticePeriod());
    Calendar noticeCal = option_.noticeCalendar().empty() ? NullCalendar() : parseCalendar(option_.noticeCalendar());
    BusinessDayConvention noticeBdc =
        option_.noticeConvention().empty() ? Unadjusted : parseBusinessDayConvention(option_.noticeConvention());
    std::vector<QuantLib::Date> sortedExerciseDates;
    for (auto const& d : option_.exerciseDates())
        sortedExerciseDates.push_back(parseDate(d));
    std::sort(sortedExerciseDates.begin(), sortedExerciseDates.end());
    std::vector<QuantLib::Date> noticeDates, exerciseDates;
    std::vector<bool> isExerciseDateAlive(sortedExerciseDates.size(), false);
    for (Size i = 0; i < sortedExerciseDates.size(); i++) {
        Date noticeDate = noticeCal.advance(sortedExerciseDates[i], -noticePeriod, noticeBdc);
        if (noticeDate > Settings::instance().evaluationDate() && noticeDate <= lastAccrualStartDate) {
            isExerciseDateAlive[i] = true;
            noticeDates.push_back(noticeDate);
            exerciseDates.push_back(sortedExerciseDates[i]);
            DLOG("Got notice date " << QuantLib::io::iso_date(noticeDate) << " using notice period " << noticePeriod
                                    << ", convention " << noticeBdc << ", calendar " << noticeCal.name()
                                    << " from exercise date " << exerciseDates.back());
        }
        if (noticeDate > lastAccrualStartDate)
            WLOG("Remove notice date " << ore::data::to_string(noticeDate) << " (exercise date "
                                       << sortedExerciseDates[i] << ") after last accrual start date "
                                       << ore ::data::to_string(lastAccrualStartDate));
    }
    QL_REQUIRE(noticeDates.size() > 0, "Bermudan Swaption does not have any alive exercise dates");
    maturity_ = noticeDates.back();
    exercise_ = boost::make_shared<BermudanExercise>(noticeDates);

    // check for exercise fees, if present build a rebated exercise with rebates = -fee
    if (!option_.exerciseFees().empty()) {
        // build an exercise date "schedule" by adding the maximum possible date at the end
        std::vector<Date> exDatesPlusInf(sortedExerciseDates);
        exDatesPlusInf.push_back(Date::maxDate());
        vector<double> allRebates =
            buildScheduledVectorNormalised(option_.exerciseFees(), option_.exerciseFeeDates(), exDatesPlusInf, 0.0);
        // filter on alive rebates, so that we can a vector of rebates corresponding to the exerciseDates vector
        vector<double> rebates;
        for (Size i = 0; i < sortedExerciseDates.size(); ++i) {
            if (isExerciseDateAlive[i])
                rebates.push_back(allRebates[i]);
        }
        // flip the sign of the fee to get a rebate
        for (auto& r : rebates)
            r = -r;
        vector<string> feeType = buildScheduledVectorNormalised<string>(option_.exerciseFeeTypes(),
                                                                        option_.exerciseFeeDates(), exDatesPlusInf, "");
        // convert relative to absolute fees if required
        for (Size i = 0; i < rebates.size(); ++i) {
            // default to Absolute
            if (feeType[i].empty())
                feeType[i] = "Absolute";
            if (feeType[i] == "Percentage") {
                // get next float coupon after exercise to determine relevant notional
                Real feeNotional = Null<Real>();
                for (auto const& c : swap->leg(1)) {
                    if (auto cpn = boost::dynamic_pointer_cast<Coupon>(c)) {
                        if (feeNotional == Null<Real>() && cpn->accrualStartDate() >= exerciseDates[i])
                            feeNotional = cpn->nominal();
                    }
                }
                if (feeNotional == Null<Real>())
                    rebates[i] = 0.0; // notional is zero
                else {
                    DLOG("Convert percentage rebate "
                         << rebates[i] << " to absolute reabte " << rebates[i] * feeNotional << " using nominal "
                         << feeNotional << " for exercise date " << QuantLib::io::iso_date(exerciseDates[i]));
                    rebates[i] *= feeNotional; // multiply percentage fee by relevant notional
                }
            } else {
                QL_REQUIRE(feeType[i] == "Absolute", "fee type must be Absolute or Relative");
            }
        }
        Period feeSettlPeriod = option_.exerciseFeeSettlementPeriod().empty()
                                    ? 0 * Days
                                    : parsePeriod(option_.exerciseFeeSettlementPeriod());
        Calendar feeSettlCal = option_.exerciseFeeSettlementCalendar().empty()
                                   ? NullCalendar()
                                   : parseCalendar(option_.exerciseFeeSettlementCalendar());
        BusinessDayConvention feeSettlBdc = option_.exerciseFeeSettlementConvention().empty()
                                                ? Unadjusted
                                                : parseBusinessDayConvention(option_.exerciseFeeSettlementConvention());
        exercise_ = boost::make_shared<QuantExt::RebatedExercise>(*exercise_, exerciseDates, rebates, feeSettlPeriod,
                                                                  feeSettlCal, feeSettlBdc);
        // log rebates
        auto dbgEx = boost::static_pointer_cast<QuantExt::RebatedExercise>(exercise_);
        for (Size i = 0; i < exerciseDates.size(); ++i) {
            DLOG("Got rebate " << dbgEx->rebate(i) << " with payment date "
                               << QuantLib::io::iso_date(dbgEx->rebatePaymentDate(i)) << " (exercise date="
                               << QuantLib::io::iso_date(exerciseDates[i]) << ") using rebate settl period "
                               << feeSettlPeriod << ", calendar " << feeSettlCal << ", convention " << feeSettlBdc);
        }
    }

    Settlement::Type delivery = parseSettlementType(option_.settlement());
    Settlement::Method deliveryMethod =
        option_.settlementMethod() == "" ? defaultMethod(delivery) : parseSettlementMethod(option_.settlementMethod());

    if (delivery == Settlement::Cash && deliveryMethod == Settlement::ParYieldCurve)
        WLOG("Cash-settled Bermudan Swaption (id = "
             << id() << ") with ParYieldCurve settlement method not supported by Lgm engine. "
             << "Approximate pricing using CollateralizedCashPrice pricing methodology");

    // Build swaption
    DLOG("Build Swaption instrument");
    boost::shared_ptr<QuantLib::Instrument> swaption;
    if (isNonStandard) {
        swaption = boost::shared_ptr<QuantLib::Instrument>(
            new QuantLib::NonstandardSwaption(nonstandardSwap, exercise_, delivery, deliveryMethod));
        // workaround for missing registration in QL versions < 1.13
        swaption->registerWithObservables(nonstandardSwap);
    } else
        swaption = boost::shared_ptr<QuantLib::Instrument>(
            new QuantLib::Swaption(vanillaSwap, exercise_, delivery, deliveryMethod));

    QuantLib::Position::Type positionType = parsePositionType(option_.longShort());
    if (delivery == Settlement::Physical)
        maturity_ = swap->maturityDate();

    DLOG("Get/Build Bermudan Swaption engine");
    cpu_timer timer;

    string tt("BermudanSwaption");
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder(tt);
    QL_REQUIRE(builder, "No builder found for " << tt);
    boost::shared_ptr<BermudanSwaptionEngineBuilder> swaptionBuilder =
        boost::dynamic_pointer_cast<BermudanSwaptionEngineBuilder>(builder);

    // determine strikes for calibration basket (simple approach, a la summit)
    std::vector<Real> strikes(noticeDates.size(), Null<Real>());
    if (!isNonStandard) {
        Real tmp = vanillaSwap->fixedRate() - vanillaSwap->spread();
        std::fill(strikes.begin(), strikes.end(), tmp);
        DLOG("calibration strike is " << tmp << "(fixed rate " << vanillaSwap->fixedRate() << ", spread "
                                      << vanillaSwap->spread() << ")");
    } else {
        for (Size i = 0; i < noticeDates.size(); ++i) {
            const Schedule& fix = nonstandardSwap->fixedSchedule();
            const Schedule& flt = nonstandardSwap->floatingSchedule();
            Size fixIdx =
                std::lower_bound(fix.dates().begin(), fix.dates().end(), noticeDates[i]) - fix.dates().begin();
            Size fltIdx =
                std::lower_bound(flt.dates().begin(), flt.dates().end(), noticeDates[i]) - flt.dates().begin();
            // play safe
            fixIdx = std::min(fixIdx, nonstandardSwap->fixedRate().size() - 1);
            fltIdx = std::min(fltIdx, nonstandardSwap->spreads().size() - 1);
            strikes[i] = nonstandardSwap->fixedRate()[fixIdx] - nonstandardSwap->spreads()[fltIdx];
            DLOG("calibration strike for ex date " << QuantLib::io::iso_date(noticeDates[i]) << " is " << strikes[i]
                                                   << " (fixed rate " << nonstandardSwap->fixedRate()[fixIdx]
                                                   << ", spread " << nonstandardSwap->spreads()[fltIdx] << ")");
        }
    }

    boost::shared_ptr<PricingEngine> engine =
        swaptionBuilder->engine(id(), isNonStandard, ccy_str, noticeDates, swap->maturityDate(), strikes);

    timer.stop();
    DLOG("Swaption model calibration time: " << timer.format(default_places, "%w") << " s");

    swaption->setPricingEngine(engine);

    // timer.restart();
    // Real npv = swaption->NPV();
    // Real pt = timer.elapsed();
    // DLOG("Swaption pricing time: " << pt*1000 << " ms");

    boost::shared_ptr<EngineBuilder> tmp = engineFactory->builder("Swap");
    boost::shared_ptr<SwapEngineBuilderBase> swapBuilder = boost::dynamic_pointer_cast<SwapEngineBuilderBase>(tmp);
    QL_REQUIRE(swapBuilder, "No Swap Builder found for Swaption " << id());
    boost::shared_ptr<PricingEngine> swapEngine = swapBuilder->engine(currency);

    std::vector<boost::shared_ptr<Instrument>> underlyingSwaps = buildUnderlyingSwaps(swapEngine, swap, noticeDates);

    // If premium data is provided
    // 1) build the fee trade and pass it to the instrument wrapper for pricing
    // 2) add fee payment as additional trade leg for cash flow reporting
    std::vector<boost::shared_ptr<Instrument>> additionalInstruments;
    std::vector<Real> additionalMultipliers;
    if (option_.premiumPayDate() != "" && option_.premiumCcy() != "") {
        Real multiplier = positionType == Position::Long ? 1.0 : -1.0;
        Real premiumAmount = -multiplier * option_.premium(); // pay if long, receive if short
        Currency premiumCurrency = parseCurrency(option_.premiumCcy());
        Date premiumDate = parseDate(option_.premiumPayDate());
        addPayment(additionalInstruments, additionalMultipliers, premiumDate, premiumAmount, premiumCurrency, currency,
                   engineFactory, swaptionBuilder->configuration(MarketContext::pricing));
        DLOG("option premium added for bermudan swaption " << id());
    }

    // instrument_ = boost::shared_ptr<InstrumentWrapper> (new VanillaInstrument (swaption, multiplier));
    instrument_ = boost::shared_ptr<InstrumentWrapper>(
        new BermudanOptionWrapper(swaption, positionType == Position::Long ? true : false, noticeDates,
                                  delivery == Settlement::Physical ? true : false, underlyingSwaps, 1.0, 1.0,
                                  additionalInstruments, additionalMultipliers));

    DLOG("Building Bermudan Swaption done");
}

boost::shared_ptr<VanillaSwap> Swaption::buildVanillaSwap(const boost::shared_ptr<EngineFactory>& engineFactory,
                                                          const Date& firstExerciseDate) {
    // Limitation to vanilla for now
    QL_REQUIRE(swap_.size() == 2 && swap_[0].currency() == swap_[1].currency() &&
                   swap_[0].isPayer() != swap_[1].isPayer() && swap_[0].notionals() == swap_[1].notionals() &&
                   swap_[0].notionals().size() == 1,
               "Not a Vanilla Swap");

    Size fixedLegIndex, floatingLegIndex;
    if (swap_[0].legType() == "Floating" && swap_[1].legType() == "Fixed") {
        floatingLegIndex = 0;
        fixedLegIndex = 1;
    } else if (swap_[1].legType() == "Floating" && swap_[0].legType() == "Fixed") {
        floatingLegIndex = 1;
        fixedLegIndex = 0;
    } else {
        QL_FAIL("Invalid leg types " << swap_[0].legType() << " + " << swap_[1].legType());
    }
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedLegIndex].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[floatingLegIndex].concreteLegData());

    QL_REQUIRE(fixedLegData->rates().size() == 1, "Vanilla Swaption: constant rate required");
    QL_REQUIRE(floatingLegData->spreads().size() <= 1, "Vanilla Swaption: constant spread required");

    boost::shared_ptr<EngineBuilder> tmp = engineFactory->builder("Swap");
    boost::shared_ptr<SwapEngineBuilderBase> swapBuilder = boost::dynamic_pointer_cast<SwapEngineBuilderBase>(tmp);
    QL_REQUIRE(swapBuilder, "No Swap Builder found for Swaption " << id());

    // Get Trade details
    string ccy = swap_[0].currency();
    Currency currency = parseCurrency(ccy);
    Real nominal = swap_[0].notionals().back();

    Real rate = fixedLegData->rates().front();
    Real spread = floatingLegData->spreads().empty() ? 0.0 : floatingLegData->spreads().front();
    string indexName = floatingLegData->index();

    Schedule fixedSchedule = makeSchedule(swap_[fixedLegIndex].schedule());
    DayCounter fixedDayCounter = parseDayCounter(swap_[fixedLegIndex].dayCounter());

    Schedule floatingSchedule = makeSchedule(swap_[floatingLegIndex].schedule());
    Handle<IborIndex> index =
        engineFactory->market()->iborIndex(indexName, swapBuilder->configuration(MarketContext::pricing));
    underlyingIndexQlName_ = index->name();
    DayCounter floatingDayCounter = parseDayCounter(swap_[floatingLegIndex].dayCounter());

    BusinessDayConvention paymentConvention = parseBusinessDayConvention(swap_[floatingLegIndex].paymentConvention());

    VanillaSwap::Type type = swap_[fixedLegIndex].isPayer() ? VanillaSwap::Payer : VanillaSwap::Receiver;

    // We treat overnight and bma indices approximately as ibor indices and warn about this in the log
    if (boost::dynamic_pointer_cast<OvernightIndex>(*index) ||
        boost ::dynamic_pointer_cast<QuantExt::BMAIndexWrapper>(*index))
        ALOG("Swaption trade " << id() << " on ON or BMA index '" << underlyingIndex_
                               << "' built, will treat the index approximately as an ibor index");

    // only take into account accrual periods with start date on or after first exercise date (if given)
    if (firstExerciseDate != Null<Date>()) {
        std::vector<Date> fixDates = fixedSchedule.dates();
        auto it1 = std::lower_bound(fixDates.begin(), fixDates.end(), firstExerciseDate);
        fixDates.erase(fixDates.begin(), it1);
        // check we have at least 1 to stop set fault on vector(fixDates.size() - 1, true) but maybe check should be 2
        QL_REQUIRE(fixDates.size() >= 2,
                   "Not enough schedule dates are left in Swaption fixed leg (check exercise dates)");
        fixedSchedule = Schedule(fixDates, fixedSchedule.calendar(), Unadjusted, boost::none, boost::none, boost::none,
                                 boost::none, std::vector<bool>(fixDates.size() - 1, true));
        std::vector<Date> floatingDates = floatingSchedule.dates();
        auto it2 = std::lower_bound(floatingDates.begin(), floatingDates.end(), firstExerciseDate);
        floatingDates.erase(floatingDates.begin(), it2);
        QL_REQUIRE(floatingDates.size() >= 2,
                   "Not enough schedule dates are left in Swaption floating leg (check exercise dates)");
        floatingSchedule = Schedule(floatingDates, floatingSchedule.calendar(), Unadjusted, boost::none, boost::none,
                                    boost::none, boost::none, std::vector<bool>(floatingDates.size() - 1, true));
    }

    // Build a vanilla (bullet) swap underlying
    boost::shared_ptr<VanillaSwap> swap(new VanillaSwap(type, nominal, fixedSchedule, rate, fixedDayCounter,
                                                        floatingSchedule, *index, spread, floatingDayCounter,
                                                        paymentConvention));

    swap->setPricingEngine(swapBuilder->engine(currency));

    // Set other ore::data::Trade details
    npvCurrency_ = ccy;
    notional_ = nominal;
    notionalCurrency_ = ccy;
    legCurrencies_ = vector<string>(2, ccy);
    legs_.push_back(swap->fixedLeg());
    legs_.push_back(swap->floatingLeg());
    legPayers_.push_back(swap_[fixedLegIndex].isPayer());
    legPayers_.push_back(swap_[floatingLegIndex].isPayer());

    return swap;
}

boost::shared_ptr<NonstandardSwap>
Swaption::buildNonStandardSwap(const boost::shared_ptr<EngineFactory>& engineFactory) {
    QL_REQUIRE(swap_.size() == 2, "Two swap legs expected");
    QL_REQUIRE(swap_[0].currency() == swap_[1].currency(), "single currency swap expected");
    QL_REQUIRE(swap_[0].isPayer() != swap_[1].isPayer(), "pay and receive leg expected");

    Size fixedLegIndex, floatingLegIndex;
    if (swap_[0].legType() == "Floating" && swap_[1].legType() == "Fixed") {
        floatingLegIndex = 0;
        fixedLegIndex = 1;
    } else if (swap_[1].legType() == "Floating" && swap_[0].legType() == "Fixed") {
        floatingLegIndex = 1;
        fixedLegIndex = 0;
    } else {
        QL_FAIL("Invalid leg types " << swap_[0].legType() << " + " << swap_[1].legType());
    }
    boost::shared_ptr<FixedLegData> fixedLegData =
        boost::dynamic_pointer_cast<FixedLegData>(swap_[fixedLegIndex].concreteLegData());
    boost::shared_ptr<FloatingLegData> floatingLegData =
        boost::dynamic_pointer_cast<FloatingLegData>(swap_[floatingLegIndex].concreteLegData());

    boost::shared_ptr<EngineBuilder> tmp = engineFactory->builder("Swap");
    boost::shared_ptr<SwapEngineBuilderBase> swapBuilder = boost::dynamic_pointer_cast<SwapEngineBuilderBase>(tmp);
    QL_REQUIRE(swapBuilder, "No Swap Builder found for Swaption " << id());

    // Get Trade details
    string ccy = swap_[0].currency();
    Currency currency = parseCurrency(ccy);
    Schedule fixedSchedule = makeSchedule(swap_[fixedLegIndex].schedule());
    Schedule floatingSchedule = makeSchedule(swap_[floatingLegIndex].schedule());
    vector<Real> fixedNominal = buildScheduledVectorNormalised(
        swap_[fixedLegIndex].notionals(), swap_[fixedLegIndex].notionalDates(), fixedSchedule, (Real)Null<Real>());
    vector<Real> floatNominal =
        buildScheduledVectorNormalised(swap_[floatingLegIndex].notionals(), swap_[floatingLegIndex].notionalDates(),
                                       floatingSchedule, (Real)Null<Real>());
    vector<Real> fixedRate = buildScheduledVectorNormalised(fixedLegData->rates(), fixedLegData->rateDates(),
                                                            fixedSchedule, (Real)Null<Real>());
    vector<Real> spreads = buildScheduledVectorNormalised(floatingLegData->spreads(), floatingLegData->spreadDates(),
                                                          floatingSchedule, (Real)Null<Real>());
    // gearings are optional, i.e. may be empty
    vector<Real> gearings = buildScheduledVectorNormalised(floatingLegData->gearings(), floatingLegData->gearingDates(),
                                                           floatingSchedule, 1.0);
    string indexName = floatingLegData->index();
    DayCounter fixedDayCounter = parseDayCounter(swap_[fixedLegIndex].dayCounter());
    Handle<IborIndex> index =
        engineFactory->market()->iborIndex(indexName, swapBuilder->configuration(MarketContext::pricing));
    underlyingIndexQlName_ = index->name();
    DayCounter floatingDayCounter = parseDayCounter(swap_[floatingLegIndex].dayCounter());
    BusinessDayConvention paymentConvention = parseBusinessDayConvention(swap_[floatingLegIndex].paymentConvention());

    // We treat overnight and bma indices approximately as ibor indices and warn about this in the log
    if (boost::dynamic_pointer_cast<OvernightIndex>(*index) ||
        boost ::dynamic_pointer_cast<QuantExt::BMAIndexWrapper>(*index))
        ALOG("Swaption trade " << id() << " on ON or BMA index '" << underlyingIndex_
                               << "' built, will treat the index approximately as an ibor index");

    VanillaSwap::Type type = swap_[fixedLegIndex].isPayer() ? VanillaSwap::Payer : VanillaSwap::Receiver;

    // Build a vanilla (bullet) swap underlying
    boost::shared_ptr<NonstandardSwap> swap(new NonstandardSwap(type, fixedNominal, floatNominal, fixedSchedule,
                                                                fixedRate, fixedDayCounter, floatingSchedule, *index,
                                                                gearings, spreads, floatingDayCounter,
                                                                false, // no intermediate notional exchanges
                                                                false, // no final notional exchanges
                                                                paymentConvention));

    swap->setPricingEngine(swapBuilder->engine(currency));

    // Set other ore::data::Trade details
    npvCurrency_ = ccy;
    notional_ = std::max(currentNotional(swap->fixedLeg()), currentNotional(swap->floatingLeg()));
    notionalCurrency_ = npvCurrency_;
    legCurrencies_ = vector<string>(2, ccy);
    legs_.push_back(swap->fixedLeg());
    legs_.push_back(swap->floatingLeg());
    legPayers_.push_back(swap_[fixedLegIndex].isPayer());
    legPayers_.push_back(swap_[floatingLegIndex].isPayer());

    return swap;
}

std::vector<boost::shared_ptr<Instrument>>
Swaption::buildUnderlyingSwaps(const boost::shared_ptr<PricingEngine>& swapEngine,
                               const boost::shared_ptr<Swap>& underlyingSwap, const std::vector<Date>& exerciseDates) {
    std::vector<boost::shared_ptr<Instrument>> swaps;
    for (Size i = 0; i < exerciseDates.size(); ++i) {
        std::vector<Leg> legs(2);
        std::vector<bool> payer(2);
        for (Size j = 0; j < legs.size(); ++j) {
            legs[j] = underlyingSwap->leg(j);
            // if in the swap data the fixed leg is the first component, the legs in the underlying
            // swap match the order of the legs in the swap data, otherwise they are swapped
            payer[j] = swap_[0].legType() == "Fixed" ? swap_[j].isPayer() : swap_[1 - j].isPayer();
            boost::shared_ptr<Coupon> coupon = boost::dynamic_pointer_cast<Coupon>(legs[j].front());
            while (legs[j].size() > 0 && coupon->accrualStartDate() < exerciseDates[i]) {
                legs[j].erase(legs[j].begin());
                coupon = boost::dynamic_pointer_cast<Coupon>(legs[j].front());
            }
        }
        boost::shared_ptr<Swap> newSwap(new Swap(legs, payer));
        newSwap->setPricingEngine(swapEngine);
        swaps.push_back(newSwap);
        if (legs[0].size() > 0 && legs[1].size() > 0) {
            boost::shared_ptr<Coupon> coupon1 = boost::dynamic_pointer_cast<Coupon>(legs[0].front());
            boost::shared_ptr<Coupon> coupon2 = boost::dynamic_pointer_cast<Coupon>(legs[1].front());
            DLOG("Added underlying Swap start " << QuantLib::io::iso_date(coupon1->accrualStartDate()) << " "
                                                << QuantLib::io::iso_date(coupon2->accrualStartDate()) << " "
                                                << "exercise " << QuantLib::io::iso_date(exerciseDates[i]));
        } else {
            WLOG("Added underlying Swap with at least one empty leg for exercise "
                 << QuantLib::io::iso_date(exerciseDates[i]) << "!");
        }
    }
    return swaps;
}

void Swaption::fromXML(XMLNode* node) {
    Trade::fromXML(node);
    XMLNode* swapNode = XMLUtils::getChildNode(node, "SwaptionData");
    option_.fromXML(XMLUtils::getChildNode(swapNode, "OptionData"));
    swap_.clear();
    vector<XMLNode*> nodes = XMLUtils::getChildrenNodes(swapNode, "LegData");
    for (Size i = 0; i < nodes.size(); i++) {
        LegData ld;
        ld.fromXML(nodes[i]);
        swap_.push_back(ld);
    }
}

XMLNode* Swaption::toXML(XMLDocument& doc) {
    XMLNode* node = Trade::toXML(doc);
    XMLNode* swaptionNode = doc.allocNode("SwaptionData");
    XMLUtils::appendNode(node, swaptionNode);

    XMLUtils::appendNode(swaptionNode, option_.toXML(doc));
    for (Size i = 0; i < swap_.size(); i++)
        XMLUtils::appendNode(swaptionNode, swap_[i].toXML(doc));

    return node;
}
} // namespace data
} // namespace ore


#ifndef BOOST_GEOMETRY_PROJECTIONS_LSAT_HPP
#define BOOST_GEOMETRY_PROJECTIONS_LSAT_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Original copyright notice:

// 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.

#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/extensions/gis/projections/impl/base_static.hpp>
#include <boost/geometry/extensions/gis/projections/impl/base_dynamic.hpp>
#include <boost/geometry/extensions/gis/projections/impl/projects.hpp>
#include <boost/geometry/extensions/gis/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry { namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace lsat{
            static const double TOL = 1e-7;
            static const double PI_HALFPI = 4.71238898038468985766;
            static const double TWOPI_HALFPI = 7.85398163397448309610;

            struct par_lsat
            {
                double a2, a4, b, c1, c3;
                double q, t, u, w, p22, sa, ca, xj, rlm, rlm2;
            };
            /* based upon Snyder and Linck, USGS-NMD */
            template <typename Parameters>
                inline void
            seraz0(double lam, double mult, Parameters& par, par_lsat& proj_parm) {
                double sdsq, h, s, fc, sd, sq, d__1;

                lam *= DEG_TO_RAD;
                sd = sin(lam);
                sdsq = sd * sd;
                s = proj_parm.p22 * proj_parm.sa * cos(lam) * sqrt((1. + proj_parm.t * sdsq) / ((
                    1. + proj_parm.w * sdsq) * (1. + proj_parm.q * sdsq)));
                d__1 = 1. + proj_parm.q * sdsq;
                h = sqrt((1. + proj_parm.q * sdsq) / (1. + proj_parm.w * sdsq)) * ((1. +
                    proj_parm.w * sdsq) / (d__1 * d__1) - proj_parm.p22 * proj_parm.ca);
                sq = sqrt(proj_parm.xj * proj_parm.xj + s * s);
                proj_parm.b += fc = mult * (h * proj_parm.xj - s * s) / sq;
                proj_parm.a2 += fc * cos(lam + lam);
                proj_parm.a4 += fc * cos(lam * 4.);
                fc = mult * s * (h + proj_parm.xj) / sq;
                proj_parm.c1 += fc * cos(lam);
                proj_parm.c3 += fc * cos(lam * 3.);
            }

            // template class, using CRTP to implement forward/inverse
            template <typename Geographic, typename Cartesian, typename Parameters>
            struct base_lsat_ellipsoid : public base_t_fi<base_lsat_ellipsoid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>
            {

                 typedef double geographic_type;
                 typedef double cartesian_type;

                par_lsat m_proj_parm;

                inline base_lsat_ellipsoid(const Parameters& par)
                    : base_t_fi<base_lsat_ellipsoid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>(*this, par) {}

                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    int l, nn;
                    double lamt, xlam, sdsq, c, d, s, lamdp, phidp, lampp, tanph,
                        lamtp, cl, sd, sp, fac, sav, tanphi;

                    if (lp_lat > HALFPI)
                        lp_lat = HALFPI;
                    else if (lp_lat < -HALFPI)
                        lp_lat = -HALFPI;
                    lampp = lp_lat >= 0. ? HALFPI : PI_HALFPI;
                    tanphi = tan(lp_lat);
                    for (nn = 0;;) {
                        sav = lampp;
                        lamtp = lp_lon + this->m_proj_parm.p22 * lampp;
                        cl = cos(lamtp);
                        if (fabs(cl) < TOL)
                            lamtp -= TOL;
                        fac = lampp - sin(lampp) * (cl < 0. ? -HALFPI : HALFPI);
                        for (l = 50; l; --l) {
                            lamt = lp_lon + this->m_proj_parm.p22 * sav;
                            if (fabs(c = cos(lamt)) < TOL)
                                lamt -= TOL;
                            xlam = (this->m_par.one_es * tanphi * this->m_proj_parm.sa + sin(lamt) * this->m_proj_parm.ca) / c;
                            lamdp = atan(xlam) + fac;
                            if (fabs(fabs(sav) - fabs(lamdp)) < TOL)
                                break;
                            sav = lamdp;
                        }
                        if (!l || ++nn >= 3 || (lamdp > this->m_proj_parm.rlm && lamdp < this->m_proj_parm.rlm2))
                            break;
                        if (lamdp <= this->m_proj_parm.rlm)
                            lampp = TWOPI_HALFPI;
                        else if (lamdp >= this->m_proj_parm.rlm2)
                            lampp = HALFPI;
                    }
                    if (l) {
                        sp = sin(lp_lat);
                        phidp = aasin((this->m_par.one_es * this->m_proj_parm.ca * sp - this->m_proj_parm.sa * cos(lp_lat) *
                            sin(lamt)) / sqrt(1. - this->m_par.es * sp * sp));
                        tanph = log(tan(FORTPI + .5 * phidp));
                        sd = sin(lamdp);
                        sdsq = sd * sd;
                        s = this->m_proj_parm.p22 * this->m_proj_parm.sa * cos(lamdp) * sqrt((1. + this->m_proj_parm.t * sdsq)
                             / ((1. + this->m_proj_parm.w * sdsq) * (1. + this->m_proj_parm.q * sdsq)));
                        d = sqrt(this->m_proj_parm.xj * this->m_proj_parm.xj + s * s);
                        xy_x = this->m_proj_parm.b * lamdp + this->m_proj_parm.a2 * sin(2. * lamdp) + this->m_proj_parm.a4 *
                            sin(lamdp * 4.) - tanph * s / d;
                        xy_y = this->m_proj_parm.c1 * sd + this->m_proj_parm.c3 * sin(lamdp * 3.) + tanph * this->m_proj_parm.xj / d;
                    } else
                        xy_x = xy_y = HUGE_VAL;
                }

                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    int nn;
                    double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp;

                    lamdp = xy_x / this->m_proj_parm.b;
                    nn = 50;
                    do {
                        sav = lamdp;
                        sd = sin(lamdp);
                        sdsq = sd * sd;
                        s = this->m_proj_parm.p22 * this->m_proj_parm.sa * cos(lamdp) * sqrt((1. + this->m_proj_parm.t * sdsq)
                             / ((1. + this->m_proj_parm.w * sdsq) * (1. + this->m_proj_parm.q * sdsq)));
                        lamdp = xy_x + xy_y * s / this->m_proj_parm.xj - this->m_proj_parm.a2 * sin(
                            2. * lamdp) - this->m_proj_parm.a4 * sin(lamdp * 4.) - s / this->m_proj_parm.xj * (
                            this->m_proj_parm.c1 * sin(lamdp) + this->m_proj_parm.c3 * sin(lamdp * 3.));
                        lamdp /= this->m_proj_parm.b;
                    } while (fabs(lamdp - sav) >= TOL && --nn);
                    sl = sin(lamdp);
                    fac = exp(sqrt(1. + s * s / this->m_proj_parm.xj / this->m_proj_parm.xj) * (xy_y -
                        this->m_proj_parm.c1 * sl - this->m_proj_parm.c3 * sin(lamdp * 3.)));
                    phidp = 2. * (atan(fac) - FORTPI);
                    dd = sl * sl;
                    if (fabs(cos(lamdp)) < TOL)
                        lamdp -= TOL;
                    spp = sin(phidp);
                    sppsq = spp * spp;
                    lamt = atan(((1. - sppsq * this->m_par.rone_es) * tan(lamdp) *
                        this->m_proj_parm.ca - spp * this->m_proj_parm.sa * sqrt((1. + this->m_proj_parm.q * dd) * (
                        1. - sppsq) - sppsq * this->m_proj_parm.u) / cos(lamdp)) / (1. - sppsq
                        * (1. + this->m_proj_parm.u)));
                    sl = lamt >= 0. ? 1. : -1.;
                    scl = cos(lamdp) >= 0. ? 1. : -1;
                    lamt -= HALFPI * (1. - scl) * sl;
                    lp_lon = lamt - this->m_proj_parm.p22 * lamdp;
                    if (fabs(this->m_proj_parm.sa) < TOL)
                        lp_lat = aasin(spp / sqrt(this->m_par.one_es * this->m_par.one_es + this->m_par.es * sppsq));
                    else
                        lp_lat = atan((tan(lamdp) * cos(lamt) - this->m_proj_parm.ca * sin(lamt)) /
                            (this->m_par.one_es * this->m_proj_parm.sa));
                }
            };

            // Space oblique for LANDSAT
            template <typename Parameters>
            void setup_lsat(Parameters& par, par_lsat& proj_parm)
            {
                int land, path;
                double lam, alf, esc, ess;
                land = pj_param(par.params, "ilsat").i;
                if (land <= 0 || land > 5) throw proj_exception(-28);
                path = pj_param(par.params, "ipath").i;
                if (path <= 0 || path > (land <= 3 ? 251 : 233)) throw proj_exception(-29);
                if (land <= 3) {
                    par.lam0 = DEG_TO_RAD * 128.87 - TWOPI / 251. * path;
                    proj_parm.p22 = 103.2669323;
                    alf = DEG_TO_RAD * 99.092;
                } else {
                    par.lam0 = DEG_TO_RAD * 129.3 - TWOPI / 233. * path;
                    proj_parm.p22 = 98.8841202;
                    alf = DEG_TO_RAD * 98.2;
                }
                proj_parm.p22 /= 1440.;
                proj_parm.sa = sin(alf);
                proj_parm.ca = cos(alf);
                if (fabs(proj_parm.ca) < 1e-9)
                    proj_parm.ca = 1e-9;
                esc = par.es * proj_parm.ca * proj_parm.ca;
                ess = par.es * proj_parm.sa * proj_parm.sa;
                proj_parm.w = (1. - esc) * par.rone_es;
                proj_parm.w = proj_parm.w * proj_parm.w - 1.;
                proj_parm.q = ess * par.rone_es;
                proj_parm.t = ess * (2. - par.es) * par.rone_es * par.rone_es;
                proj_parm.u = esc * par.rone_es;
                proj_parm.xj = par.one_es * par.one_es * par.one_es;
                proj_parm.rlm = PI * (1. / 248. + .5161290322580645);
                proj_parm.rlm2 = proj_parm.rlm + TWOPI;
                proj_parm.a2 = proj_parm.a4 = proj_parm.b = proj_parm.c1 = proj_parm.c3 = 0.;
                seraz0(0., 1., par, proj_parm);
                for (lam = 9.;
             lam <= 81.0001;
             lam += 18.)
                    seraz0(lam, 4., par, proj_parm);
                for (lam = 18;
             lam <= 72.0001;
             lam += 18.)
                    seraz0(lam, 2., par, proj_parm);
                seraz0(90., 1., par, proj_parm);
                proj_parm.a2 /= 30.;
                proj_parm.a4 /= 60.;
                proj_parm.b /= 30.;
                proj_parm.c1 /= 15.;
                proj_parm.c3 /= 45.;
                // par.inv = e_inverse;
                // par.fwd = e_forward;
            }

        }} // namespace detail::lsat
    #endif // doxygen

    /*!
        \brief Space oblique for LANDSAT projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
         - Ellipsoid
         - lsat= path=
        \par Example
        \image html ex_lsat.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct lsat_ellipsoid : public detail::lsat::base_lsat_ellipsoid<Geographic, Cartesian, Parameters>
    {
        inline lsat_ellipsoid(const Parameters& par) : detail::lsat::base_lsat_ellipsoid<Geographic, Cartesian, Parameters>(par)
        {
            detail::lsat::setup_lsat(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Factory entry(s)
        template <typename Geographic, typename Cartesian, typename Parameters>
        class lsat_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<lsat_ellipsoid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        inline void lsat_init(detail::base_factory<Geographic, Cartesian, Parameters>& factory)
        {
            factory.add_to_factory("lsat", new lsat_entry<Geographic, Cartesian, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_LSAT_HPP


// Copyright (c) 2020 Vitaly Chipounov
//
// 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.

#include <boost/foreach.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <iomanip>
#include <iostream>
#include <math.h>

#include <gpsmap/gpx.h>

namespace pt = boost::property_tree;

namespace gpsmap {

std::ostream &operator<<(std::ostream &os, TrackItem const &m) {
    os << "TrackItem " << m.OriginalTimestamp << " lat=" << m.Latitude << " lon=" << m.Longitude << " speed=" << m.Speed
       << " alt=" << m.Elevation;
    return os;
}

time_t parse_time(const std::string &iso) {
    std::stringstream ss(iso);
    std::tm t = {};
    if (ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%SZ")) {
        return std::mktime(&t) - timezone;
    }
    return 0;
}

std::string time_to_str(time_t t) {
    char buff[64];
    struct tm *timeinfo;
    timeinfo = localtime(&t);
    strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", timeinfo);
    return buff;
}

// Returns the distance in meters between the given coordinates (in degrees)
static double distance(double lat1, double lon1, double lat2, double lon2) {
    // Radius of Earth in meters, R = 6371 * 1000
    long double R = 6371 * 1000;

    lat1 = to_rad(lat1);
    lon1 = to_rad(lon1);
    lat2 = to_rad(lat2);
    lon2 = to_rad(lon2);

    // Haversine Formula
    long double dlon = lon2 - lon1;
    long double dlat = lat2 - lat1;

    long double ans = pow(sin(dlat / 2), 2) + cos(lat1) * cos(lat2) * pow(sin(dlon / 2), 2);

    return R * 2 * asin(sqrt(ans));
}

static double bearing(double lat1, double lon1, double lat2, double lon2) {
    lat1 = to_rad(lat1);
    lon1 = to_rad(lon1);
    lat2 = to_rad(lat2);
    lon2 = to_rad(lon2);

    auto x = cos(lat2) * sin(lon2 - lon1);
    auto y = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2 - lon1);
    auto bearing = atan2(x, y);
    return to_deg(bearing);
}

static double bearing(const TrackItem &i1, const TrackItem &i2) {
    return bearing(i1.Latitude, i1.Longitude, i2.Latitude, i2.Longitude);
}

static double GetGrade(const std::vector<TrackItem> &items, int i) {
    const double maxDist = 50.0;

    // Get altitude and cumulative distance on the left
    double ldist = 0.0;
    double lelev = 0.0;
    for (auto l = i; l >= 0; --l) {
        ldist += items[l].DistanceDelta;
        lelev = items[l].Elevation;
        if (ldist >= maxDist) {
            break;
        }
    }

    // Get altitude and cumulative distance on the right
    double rdist = 0.0;
    double relev = 0.0;
    for (size_t l = i; l < items.size(); ++l) {
        rdist += items[l].DistanceDelta;
        relev = items[l].Elevation;
        if (rdist >= maxDist) {
            break;
        }
    }

    auto td = ldist + rdist;
    if (td < 0.01) {
        return 0.0;
    }

    return (relev - lelev) / td * 100;
}

std::shared_ptr<GPX> GPX::Create() {
    return std::shared_ptr<GPX>(new GPX());
}

void GPX::LoadFromFile(const std::string &path) {
    pt::ptree tree;
    pt::read_xml(path, tree);

    bool first = true;

    BOOST_FOREACH (pt::ptree::value_type &trkseg, tree.get_child("gpx.trk")) {
        BOOST_FOREACH (pt::ptree::value_type &trkpt, trkseg.second) {
            TrackItem item;
            auto lat = trkpt.second.get_child("<xmlattr>.lat").get_value("");
            auto lon = trkpt.second.get_child("<xmlattr>.lon").get_value("");
            auto speed = trkpt.second.get_child("speed").get_value("");
            auto elevation = trkpt.second.get_child("ele").get_value("");
            auto time = trkpt.second.get_child("time").get_value("");

            item.Speed = strtod(speed.c_str(), nullptr);
            item.Latitude = strtod(lat.c_str(), nullptr);
            item.Longitude = strtod(lon.c_str(), nullptr);
            item.Elevation = strtod(elevation.c_str(), nullptr);
            item.OriginalTimestamp = time;
            item.Timestamp = parse_time(time);
            item.Grade = 0.0;
            item.DistanceDelta = 0.0;
            item.TotalDistance = 0.0;
            item.Bearing = 0.0;
            item.IsTrackStart = first;
            first = false;

            m_trackItems.push_back(item);
        }
    }

    std::sort(m_trackItems.begin(), m_trackItems.end());

    for (size_t i = 1; i < m_trackItems.size(); ++i) {
        auto &ti0 = m_trackItems[i - 1];
        if (i == 1) {
            ti0.TotalDistance = m_initialDistance;
        }

        auto &ti1 = m_trackItems[i];
        ti1.DistanceDelta = distance(ti0.Latitude, ti0.Longitude, ti1.Latitude, ti1.Longitude);
        ti1.TotalDistance = ti0.TotalDistance + ti1.DistanceDelta;
    }

    for (size_t i = 0; i < m_trackItems.size() - 1; ++i) {
        m_trackItems[i].Grade = GetGrade(m_trackItems, i);
        auto b = bearing(m_trackItems[i], m_trackItems[i + 1]);
        if (b == 0) {
            if (i > 1) {
                m_trackItems[i].Bearing = m_trackItems[i - 1].Bearing;
            }
        } else {
            m_trackItems[i].Bearing = b;
        }
    }
}

void GPX::CreateSegments() {
    std::vector<int> boundaries;

    if (m_trackItems.size() == 0) {
        return;
    }

    boundaries.push_back(0);

    for (size_t i = 1; i < m_trackItems.size(); ++i) {
        const auto &prevItem = m_trackItems[i - 1];
        const auto &item = m_trackItems[i];

        if (item.Latitude == prevItem.Latitude && item.Longitude == prevItem.Longitude) {
            boundaries.push_back(i);
        }
    }

    auto bs = boundaries.size();
    for (size_t i = 0; i < bs; ++i) {
        int start = boundaries[i];
        int end = i == bs - 1 ? m_trackItems.size() - 1 : boundaries[i + 1];

        if (i == bs - 1) {
            if (start < end) {
                m_segments.push_back(Segment(start, end));
            }
        } else {
            assert(start < end);
            m_segments.push_back(Segment(start, end));
        }
    }
}

bool GPX::GetItem(size_t index, TrackItem &item) {
    if (index >= m_trackItems.size() || index < 0) {
        return false;
    }

    item = m_trackItems[index];
    return true;
}

// Return the first element i such that i.timestamp <= timestamp < (i+1).timestamp
bool GPX::GetClosestItem(time_t timestamp, size_t &nextItem, TrackItem &item) {
    if (nextItem >= m_trackItems.size() || nextItem < 0) {
        return false;
    }

    if (timestamp < m_trackItems[nextItem].Timestamp) {
        return false;
    }

    for (auto i = nextItem; i + 1 < m_trackItems.size(); ++i) {
        const auto &i1 = m_trackItems[i];
        const auto &i2 = m_trackItems[i + 1];
        if (i1.Timestamp <= timestamp && timestamp < i2.Timestamp) {
            item = i1;
            nextItem = i;
            return true;
        }
    }

    return false;
}

} // namespace gpsmap


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_RIPEMD_FUNCTIONS_HPP
#define CRYPTO3_RIPEMD_FUNCTIONS_HPP

#include <boost/crypto3/hash/detail/ripemd/ripemd_policy.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            namespace detail {
                template<std::size_t DigestBits>
                struct ripemd_functions : public ripemd_policy<DigestBits> {
                    typedef ripemd_policy<DigestBits> policy_type;

                    typedef typename policy_type::word_type word_type;

                    struct f1 {
                        inline word_type operator()(word_type x, word_type y, word_type z) const {
                            return x ^ y ^ z;
                        }
                    };

                    struct f2 {
                        inline word_type operator()(word_type x, word_type y, word_type z) const {
                            return (x & y) | (~x & z);
                        }
                    };

                    struct f3 {
                        inline word_type operator()(word_type x, word_type y, word_type z) const {
                            return (x | ~y) ^ z;
                        }
                    };

                    struct f4 {
                        inline word_type operator()(word_type x, word_type y, word_type z) const {
                            return (x & z) | (y & ~z);
                        }
                    };

                    struct f5 {
                        inline word_type operator()(word_type x, word_type y, word_type z) const {
                            return x ^ (y | ~z);
                        }
                    };

                    template<typename F>
                    inline static void transform(word_type &a, word_type &b, word_type &c, word_type &d, word_type x,
                                                 word_type k, word_type s) {
                        word_type T = policy_type::rotl(a + F()(b, c, d) + x + k, s);
                        a = d;
                        d = c;
                        c = b;
                        b = T;
                    }

                    template<typename Functor>
                    inline static void transform(word_type &a, word_type &b, word_type &c, word_type &d, word_type &e,
                                                 word_type x, word_type k, word_type s) {
                        word_type T = policy_type::rotl(a + Functor()(b, c, d) + x + k, s) + e;
                        a = e;
                        e = d;
                        d = policy_type::template rotl<10>(c);
                        c = b;
                        b = T;
                    }
                };
            }    // namespace detail
        }        // namespace hashes
    }            // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_RIPEMD_FUNCTIONS_HPP


/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2014 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba 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
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <mitsuba/bidir/manifold.h>
#include <mitsuba/bidir/path.h>
#include <mitsuba/core/statistics.h>
#define EIGEN_DONT_PARALLELIZE
#define EIGEN_NO_DEBUG
#include <Eigen/LU>
#include <Eigen/Geometry>

MTS_NAMESPACE_BEGIN

/* Some statistics counters */
static StatsCounter statsStepFailed(
		"Specular manifold", "Retries (step failed)");
static StatsCounter statsStepTooFar(
		"Specular manifold", "Retries (step increased distance)");
static StatsCounter statsStepSuccess(
		"Specular manifold", "Successful steps");
static StatsCounter statsAvgIterations(
		"Specular manifold", "Avg. iterations per walk", EAverage);
static StatsCounter statsAvgIterationsSuccess(
		"Specular manifold", "Avg. iterations per successful walk", EAverage);
static StatsCounter statsAvgManifoldSize(
		"Specular manifold", "Avg. manifold size", EAverage);
static StatsCounter statsSuccessfulWalks(
		"Specular manifold", "Successful walks", EPercentage);
static StatsCounter statsMediumSuccess(
		"Specular manifold", "Successful walks w/ media", EPercentage);
static StatsCounter statsNonManifold(
		"Specular manifold", "Non-manifold", EPercentage);
static StatsCounter statsUpdateFailed(
		"Specular manifold", "Update failed");
static StatsCounter statsMaxManifold(
		"Specular manifold", "Max. manifold size", EMaximumValue);

SpecularManifold::SpecularManifold(const Scene *scene, int maxIterations)
  : m_scene(scene) {
	m_maxIterations = maxIterations > 0 ? maxIterations :
		MTS_MANIFOLD_MAX_ITERATIONS;
}

bool SpecularManifold::init(const Path &path, int start, int end) {
	int step = start < end ? 1 : -1;
	if (path.vertex(start)->isSupernode())
		start += step;
	if (path.vertex(end)->isSupernode())
		end -= step;

	const PathVertex
		*vs = path.vertex(start),
		*ve = path.vertex(end);

	/* Create the initial vertex that is pinned in position by default */
	SimpleVertex v(EPinnedPosition, vs->getPosition());

	/* When the endpoint is on an orthographic camera or directional light
	   source, switch to a directionally pinned vertex instead */
	if (vs->getType() & (PathVertex::ESensorSample | PathVertex::EEmitterSample)) {
		const PositionSamplingRecord &pRec
			= vs->getPositionSamplingRecord();
		uint32_t type = static_cast<const AbstractEmitter *>(pRec.object)->getType()
			& (AbstractEmitter::EDeltaDirection | AbstractEmitter::EDeltaPosition);
		if (type == AbstractEmitter::EDeltaDirection) {
			v.type = EPinnedDirection;
			v.gn = v.n = pRec.n;
			coordinateSystem(pRec.n, v.dpdu, v.dpdv);
		}
	}

	m_time = vs->getTime();
	m_vertices.clear();
	m_vertices.push_back(v);

	for (int i=start + step; i != end; i += step) {
		const PathVertex
			*pred = path.vertex(i-step),
			*vertex = path.vertex(i),
			*succ = path.vertex(i+step);

		if (vertex->isSurfaceInteraction()) {
			const Intersection &its = vertex->getIntersection();
			const BSDF *bsdf = its.getBSDF();

			v.p = its.p;
			v.gn = its.geoFrame.n;
			v.n = its.shFrame.n;
			v.dpdu = its.dpdu;
			v.dpdv = its.dpdv;
			v.object = bsdf;
			v.degenerate = !vertex->isConnectable();
			const Shape *shape = its.instance != NULL ? its.instance : its.shape;
			shape->getNormalDerivative(its, v.dndu, v.dndv);

			/* Turn into an orthonormal parameterization at 'p' */
			Float invLen = 1 / v.dpdu.length();
			v.dpdu *= invLen;
			v.dndu *= invLen;
			Float dp = dot(v.dpdu, v.dpdv);
			Vector dpdv = v.dpdv - dp * v.dpdu;
			Vector dndv = v.dndv - dp * v.dndu;
			invLen = 1 / dpdv.length();
			v.dpdv = dpdv * invLen;
			v.dndv = dndv * invLen;

			Vector wPred = pred->getPosition() - v.p;
			Vector wSucc = succ->getPosition() - v.p;

			if (dot(v.gn, wPred) * dot(v.gn, wSucc) < 0) {
				v.type = ERefraction;
				v.eta = bsdf->getEta();
			} else {
				v.type = EReflection;
				v.eta = 1.0f;
			}
		} else if (vertex->isMediumInteraction()) {
			const MediumSamplingRecord &mRec = vertex->getMediumSamplingRecord();

			Vector wi = pred->getPosition() - mRec.p;
			Float invLength = 1.0f / wi.length();
			wi *= invLength;

			v.p = mRec.p;
			v.gn = v.n = Normal(0.0f);

			Vector s, t;
			coordinateSystem(wi, s, t);

		 	v.dpdu = s;
			v.dpdv = t;
		 	v.dndu = s * invLength;
			v.dndv = t * invLength;

			v.object = mRec.getPhaseFunction();
			v.eta = 1.0f;
			v.degenerate = false;
			v.type = EMedium;
		} else {
			Log(EError, "Unknown vertex type! : %s", vertex->toString().c_str());
		}

		m_vertices.push_back(v);
	}

	v = SimpleVertex(EMovable, ve->getPosition());
	m_vertices.push_back(v);

	#if MTS_MANIFOLD_DEBUG == 1
		cout << "==========================================" << endl;
		cout << "Initialized specular manifold: " << toString() << endl;
	#endif

	return true;
}

bool SpecularManifold::computeTangents() {
	const int n = static_cast<int>(m_vertices.size() - 1);

	m_vertices[0].Tp.setZero();
	m_vertices[m_vertices.size()-1].Tp.setIdentity();

	if (m_vertices.size() == 2) /* Nothing to do */
		return true;

	/* Matrix assembly stage */
	for (int i=0; i<n; ++i) {
		SimpleVertex *v = &m_vertices[i];

		Vector wo = v[1].p - v[0].p;
		Float ilo = wo.length();

		if (ilo == 0)
			return false;
		ilo = 1/ilo; wo *= ilo;

		if (v[0].type == EPinnedPosition) {
			v[0].a.setZero();
			v[0].b.setIdentity();
			v[0].c.setZero();
			continue;
		} else if (v[0].type == EPinnedDirection) {

			Vector dC_dnext_u = (v[1].dpdu - wo * dot(wo, v[1].dpdu)) * ilo;
			Vector dC_dnext_v = (v[1].dpdv - wo * dot(wo, v[1].dpdv)) * ilo;
			Vector dC_dcur_u = (wo * dot(wo, v[0].dpdu) - v[0].dpdu) * ilo;
			Vector dC_dcur_v = (wo * dot(wo, v[0].dpdv) - v[0].dpdv) * ilo;

			v[0].a.setZero();
			v[0].b = Matrix2x2(
				Vector2(dot(dC_dcur_u, v[0].dpdu), dot(dC_dcur_u, v[0].dpdv)),
				Vector2(dot(dC_dcur_v, v[0].dpdu), dot(dC_dcur_v, v[0].dpdv))
			);
			v[0].c = Matrix2x2(
				Vector2(dot(dC_dnext_u, v[0].dpdu), dot(dC_dnext_u, v[0].dpdv)),
				Vector2(dot(dC_dnext_v, v[0].dpdu), dot(dC_dnext_v, v[0].dpdv))
			);
			continue;
		}

		Vector wi = v[-1].p - v[0].p;
		Float ili = wi.length();

		if (ili == 0)
			return false;

		ili = 1/ili; wi *= ili;

		if (v[0].type == EReflection || v[0].type == ERefraction) {
			Float eta = v[0].eta;
			bool normalizeH = !(v[0].type == ERefraction && eta == 1);

			/* Compute the half vector and a few useful projections */
			Vector H;
			Float ilh;
			if (normalizeH) {
				/* Generally compute derivatives with respect to the normalized
				   half-vector. When given an index-matched refraction event,
				   don't perform this normalization, since the desired vertex
				   configuration is actually where H = 0. */

				if (dot(wi, v[0].gn) < 0)
					eta = 1 / eta;

				H = wi + eta * wo;
				ilh = 1 / H.length();
				H *= ilh;
			} else {
				H = wi + wo;
				ilh = 1.0f;
			}

			/* Orient the half-vector so that it points in the same
			   hemisphere as the geometric surface normal */

			Float dot_H_n    = dot(v[0].n, H),
			      dot_H_dndu = dot(v[0].dndu, H),
			      dot_H_dndv = dot(v[0].dndv, H),
			      dot_u_n    = dot(v[0].dpdu, v[0].n),
			      dot_v_n    = dot(v[0].dpdv, v[0].n);

			/* Local shading tangent frame */
			Vector s = v[0].dpdu - dot_u_n * v[0].n;
			Vector t = v[0].dpdv - dot_v_n * v[0].n;

			ilo *= eta * ilh; ili *= ilh;

			/* Derivatives of C with respect to x_{i-1} */
			Vector
			    dH_du = (v[-1].dpdu - wi * dot(wi, v[-1].dpdu)) * ili,
			    dH_dv = (v[-1].dpdv - wi * dot(wi, v[-1].dpdv)) * ili;

			if (normalizeH) {
				dH_du -= H * dot(dH_du, H);
				dH_dv -= H * dot(dH_dv, H);
			}

			v[0].a = Matrix2x2(
			    dot(dH_du, s), dot(dH_dv, s),
			    dot(dH_du, t), dot(dH_dv, t));

			/* Derivatives of C with respect to x_i */
			dH_du = -v[0].dpdu * (ili + ilo) + wi * (dot(wi, v[0].dpdu) * ili)
			                                 + wo * (dot(wo, v[0].dpdu) * ilo);
			dH_dv = -v[0].dpdv * (ili + ilo) + wi * (dot(wi, v[0].dpdv) * ili)
			                                 + wo * (dot(wo, v[0].dpdv) * ilo);

			if (normalizeH) {
				dH_du -= H * dot(dH_du, H);
				dH_dv -= H * dot(dH_dv, H);
			}

			v[0].b = Matrix2x2(
			    dot(dH_du, s) - dot(v[0].dpdu, v[0].dndu) * dot_H_n - dot_u_n * dot_H_dndu,
			    dot(dH_dv, s) - dot(v[0].dpdu, v[0].dndv) * dot_H_n - dot_u_n * dot_H_dndv,
			    dot(dH_du, t) - dot(v[0].dpdv, v[0].dndu) * dot_H_n - dot_v_n * dot_H_dndu,
			    dot(dH_dv, t) - dot(v[0].dpdv, v[0].dndv) * dot_H_n - dot_v_n * dot_H_dndv);

			/* Derivatives of C with respect to x_{i+1} */
			dH_du = (v[1].dpdu - wo * dot(wo, v[1].dpdu)) * ilo;
			dH_dv = (v[1].dpdv - wo * dot(wo, v[1].dpdv)) * ilo;

			if (normalizeH) {
				dH_du -= H * dot(dH_du, H);
				dH_dv -= H * dot(dH_dv, H);
			}

			v[0].c = Matrix2x2(
			    dot(dH_du, s), dot(dH_dv, s),
			    dot(dH_du, t), dot(dH_dv, t));

			/* Store the microfacet normal wrt. the local (orthonormal) shading frame */
			s = normalize(s);
			t = cross(v[0].n, s);
			v[0].m = Vector(dot(s, H), dot(t, H), dot(v[0].n, H));
			if (dot(H, v[0].gn) < 0)
				v[0].m = -v[0].m;
		} else if (v[0].type == EMedium) {
			Vector dwi_dpred_u = (v[-1].dpdu - wi * dot(wi, v[-1].dpdu)) * ili;
			Vector dwi_dpred_v = (v[-1].dpdv - wi * dot(wi, v[-1].dpdv)) * ili;
			Vector dwi_dcur_u  = (-v[0].dpdu + wi * dot(wi, v[ 0].dpdu)) * ili;
			Vector dwi_dcur_v  = (-v[0].dpdv + wi * dot(wi, v[ 0].dpdv)) * ili;

			Vector t, dt_dpred_u, dt_dpred_v, dt_dcur_u, dt_dcur_v;

			/* Compute the local frame and derivatives thereof */
			if (std::abs(wi.x) > std::abs(wi.y)) {
				Float tl = 1.0f / std::sqrt(wi.x * wi.x + wi.z * wi.z);
				t = Vector(wi.z * tl, 0.0f, -wi.x * tl);

				dt_dpred_u = Vector(dwi_dpred_u.z*tl, 0.0f, -dwi_dpred_u.x*tl);
				dt_dpred_v = Vector(dwi_dpred_v.z*tl, 0.0f, -dwi_dpred_v.x*tl);
				dt_dcur_u  = Vector(dwi_dcur_u.z*tl,  0.0f, -dwi_dcur_u.x*tl);
				dt_dcur_v  = Vector(dwi_dcur_v.z*tl,  0.0f, -dwi_dcur_v.x*tl);
			} else {
				Float tl = 1.0f / std::sqrt(wi.y * wi.y + wi.z * wi.z);
				t = Vector(0.0f, wi.z * tl, -wi.y * tl);

				dt_dpred_u = Vector(0.0f, dwi_dpred_u.z*tl, -dwi_dpred_u.y*tl);
				dt_dpred_v = Vector(0.0f, dwi_dpred_v.z*tl, -dwi_dpred_v.y*tl);
				dt_dcur_u  = Vector(0.0f, dwi_dcur_u.z*tl,  -dwi_dcur_u.y*tl);
				dt_dcur_v  = Vector(0.0f, dwi_dcur_v.z*tl,  -dwi_dcur_v.y*tl);
			}

			dt_dpred_u -= t * dot(t, dt_dpred_u);
			dt_dpred_v -= t * dot(t, dt_dpred_v);
			dt_dcur_u  -= t * dot(t, dt_dcur_u);
			dt_dcur_v  -= t * dot(t, dt_dcur_v);

			Vector s = cross(t, wi);
			Vector ds_dpred_u = cross(dt_dpred_u, wi) + cross(t, dwi_dpred_u);
			Vector ds_dpred_v = cross(dt_dpred_v, wi) + cross(t, dwi_dpred_v);
			Vector ds_dcur_u  = cross(dt_dcur_u, wi)  + cross(t, dwi_dcur_u);
			Vector ds_dcur_v  = cross(dt_dcur_v, wi)  + cross(t, dwi_dcur_v);

			/* Some tangential projections */
			Vector2
				t_cur_dpdu (dot(v[ 0].dpdu, s), dot(v[ 0].dpdu, t)),
				t_cur_dpdv (dot(v[ 0].dpdv, s), dot(v[ 0].dpdv, t)),
				t_next_dpdu(dot(v[ 1].dpdu, s), dot(v[ 1].dpdu, t)),
				t_next_dpdv(dot(v[ 1].dpdv, s), dot(v[ 1].dpdv, t)),
				t_wo = Vector2(dot(wo, s), dot(wo, t));

			v[0].a = Matrix2x2(
				Vector2(dot(ds_dpred_u, wo), dot(dt_dpred_u, wo)),
				Vector2(dot(ds_dpred_v, wo), dot(dt_dpred_v, wo))
			);

			v[0].b = Matrix2x2(
				(t_wo * dot(wo, v[0].dpdu) - t_cur_dpdu) * ilo +
				Vector2(dot(ds_dcur_u, wo), dot(dt_dcur_u, wo)),
				(t_wo * dot(wo, v[0].dpdv) - t_cur_dpdv) * ilo +
				Vector2(dot(ds_dcur_v, wo), dot(dt_dcur_v, wo)));

			v[0].c = Matrix2x2(
				(t_next_dpdu - t_wo * dot(wo, v[1].dpdu)) * ilo,
				(t_next_dpdv - t_wo * dot(wo, v[1].dpdv)) * ilo);

			v[0].m = Vector(dot(s, wo), dot(t, wo), dot(wi, wo));
		} else {
			Log(EError, "Unknown vertex type!");
		}
	}

	/* Find the tangent space with respect to translation of the last
	   vertex. For this, we must solve a tridiagonal system. The following is
	   simplified version of the block tridiagonal LU factorization algorithm
	   for this specific problem */
	Matrix2x2 Li;
	if (!m_vertices[0].b.invert(Li))
		return false;

	for (int i=0; i < n - 1; ++i) {
		m_vertices[i].u = Li * m_vertices[i].c;
		Matrix2x2 temp = m_vertices[i+1].b - m_vertices[i+1].a * m_vertices[i].u;
		if (!temp.invert(Li))
			return false;
	}

	m_vertices[n-1].Tp = -Li * m_vertices[n-1].c;

	for (int i=n-2; i>=0; --i)
		m_vertices[i].Tp = -m_vertices[i].u * m_vertices[i+1].Tp;
	return true;
}

bool SpecularManifold::project(const Vector &d) {
	const SimpleVertex &last = m_vertices[m_vertices.size()-1];
	Float du = dot(d, last.dpdu), dv = dot(d, last.dpdv);

	Ray ray(Point(0.0f), Vector(1.0f), 0); // make gcc happy
	Intersection its;

	m_proposal.clear();
	for (size_t i=0; i<m_vertices.size(); ++i) {
		m_proposal.push_back(m_vertices[i]);
		SimpleVertex &vertex = m_proposal[i];

		if (i == 0) {
			Point p0 = m_vertices[0].p + m_vertices[0].map(du, dv);
			Point p1 = m_vertices[1].p + m_vertices[1].map(du, dv);

			ray = Ray(p0, normalize(p1 - p0), m_time);
			vertex.p = ray.o;
			continue;
		} else if (vertex.type == EMovable) {
			Float dp = dot(ray.d, vertex.n);
			if (std::abs(dp) < Epsilon)
				return false;

			Float t = dot(vertex.p - ray.o, vertex.n) / dp;
			vertex.p = ray(t);
			break;
		} else if (vertex.type == EReflection) {
			if (!m_scene->rayIntersect(ray, its))
				return false;

			Vector n = its.shFrame.n,
				   s = its.dpdu, t;
			s = normalize(s - n * dot(n, s));
			t = cross(n, s);

			Vector m = s * vertex.m[0] + t * vertex.m[1] + n * vertex.m[2];

			ray.setOrigin(its.p);
			ray.setDirection(reflect(-ray.d, m));
		} else if (vertex.type == ERefraction) {
			if (!m_scene->rayIntersect(ray, its))
				return false;

			Vector n = its.shFrame.n,
				   s = its.dpdu, t;
			s = normalize(s - n * dot(n, s));
			t = cross(n, s);

			Vector m = s * vertex.m[0] + t * vertex.m[1] + n * vertex.m[2];
			Vector refracted = refract(-ray.d, m, its.shape->getBSDF()->getEta());

			if (refracted.isZero())
				return false;

			ray.setOrigin(its.p);
			ray.setDirection(refracted);
		} else if (vertex.type == EMedium) {
			Float length = (m_vertices[i].p - m_vertices[i-1].p).length(),
				  invLength = 1.0f / length;

			/* Check for occlusion */
			if (m_scene->rayIntersect(Ray(ray, Epsilon, length)))
				return false;

			vertex.p = ray(length);
			vertex.n = Vector(0.0f);

			Vector wi = -ray.d, s, t;
			coordinateSystem(wi, s, t);

		 	vertex.dpdu = s;
			vertex.dpdv = t;
		 	vertex.dndu = s * invLength;
			vertex.dndv = t * invLength;

			ray.setOrigin(vertex.p);
			ray.setDirection(s * vertex.m[0] + t * vertex.m[1] + wi * vertex.m[2]);
		} else {
			Log(EError, "Unsupported vertex type!");
		}

		if (vertex.type != EMedium) {
			if (vertex.object != its.shape->getBSDF())
				return false;

			vertex.p = its.p;
			vertex.n = its.shFrame.n;
			vertex.gn = its.geoFrame.n;
			vertex.dpdu = its.dpdu;
			vertex.dpdv = its.dpdv;

			const Shape *shape = its.instance != NULL ? its.instance : its.shape;
			shape->getNormalDerivative(its,
				vertex.dndu, vertex.dndv);

			/* Turn into an orthonormal parameterization at 'p' */
			Float invLen = 1 / vertex.dpdu.length();
			vertex.dpdu *= invLen; vertex.dndu *= invLen;
			Float dp = dot(vertex.dpdu, vertex.dpdv);
			Vector dpdv = vertex.dpdv - dp * vertex.dpdu;
			Vector dndv = vertex.dndv - dp * vertex.dndu;
			invLen = 1 / dpdv.length();
			vertex.dpdv = dpdv * invLen;
			vertex.dndv = dndv * invLen;
		}
	}
	return true;
}

bool SpecularManifold::move(const Point &target, const Normal &n) {
	SimpleVertex &last = m_vertices[m_vertices.size()-1];

	#if MTS_MANIFOLD_DEBUG == 1
		cout << "moveTo(" << last.p.toString() << " => " << target.toString() << ", n=" << n.toString() << ")" << endl;
	#endif

	if (m_vertices.size() == 2 && m_vertices[0].type == EPinnedPosition) {
		/* Nothing to do */
		return true;
	}

	bool medium = false;
	for (size_t i=0; i<m_vertices.size(); ++i) {
		if (m_vertices[i].type == EMedium)
			medium = true;
	}

	if (medium)
		statsMediumSuccess.incrementBase();

	statsAvgManifoldSize.incrementBase();
	statsAvgManifoldSize += m_vertices.size();
	statsMaxManifold.recordMaximum(m_vertices.size());

	statsSuccessfulWalks.incrementBase();

	Float invScale = 1.0f / std::max(std::max(std::abs(target.x),
			std::abs(target.y)), std::abs(target.z));
	Float stepSize = 1;

	BDAssert(last.type == EMovable);
	coordinateSystem(n, last.dpdu, last.dpdv);
	last.n = n;

	m_proposal.reserve(m_vertices.size());
	m_iterations = 0;
	statsAvgIterations.incrementBase();
	while (m_iterations < m_maxIterations) {
		Vector rel = target - m_vertices[m_vertices.size()-1].p;
		Float dist = rel.length(), newDist;
		if (dist * invScale < MTS_MANIFOLD_EPSILON) {
			/* Check for an annoying corner-case where the last
			   two vertices converge to the same point (this can
			   happen e.g. on rough planar reflectors) */
			dist = (m_vertices[m_vertices.size()-1].p
			      - m_vertices[m_vertices.size()-2].p).length();
			if (dist * invScale < Epsilon) {
				return false;
			}

			/* The manifold walk converged. */
			++statsSuccessfulWalks;
			statsAvgIterationsSuccess.incrementBase();
			statsAvgIterationsSuccess += m_iterations;
			if (medium)
				++statsMediumSuccess;
			#if MTS_MANIFOLD_DEBUG == 1
				cout << "move(): converged after " << m_iterations << " iterations" << endl;
				cout << "Final configuration:" << toString() << endl;
			#endif
			return true;
		}
		m_iterations++;
		++statsAvgIterations;

		/* Compute the tangent vectors for the current path */
		statsNonManifold.incrementBase();
		if (!computeTangents()) {
			++statsNonManifold;
			#if MTS_MANIFOLD_DEBUG == 1
				cout << "move(): unable to compute tangents!" << endl;
			#endif
			return false;
		}

		/* Take a step using the computed tangents and project
		   back on the manifold */
		#if MTS_MANIFOLD_DEBUG == 1
			const SimpleVertex &last = m_vertices[m_vertices.size()-1];
			Float du = dot(rel, last.dpdu), dv = dot(rel, last.dpdv);
			cout << "project(du=" << du << ", dv=" << dv << ", stepSize=" << stepSize << ")" << endl;
		#endif

		if (!project(rel * stepSize)) {
			#if MTS_MANIFOLD_DEBUG == 1
				cout << "project failed!" << endl;
			#endif
			++statsStepFailed;
			goto failure;
		}

		/* Reject if the step increased the distance */
		newDist = (target - m_proposal[m_proposal.size()-1].p).length();
		#if MTS_MANIFOLD_DEBUG == 1
			cout << "Distance: " << dist << " -> " << newDist << endl;
		#endif
		if (newDist > dist) {
			++statsStepTooFar;
			#if MTS_MANIFOLD_DEBUG == 1
				cout << "-> Rejecting!" << endl;
			#endif
			goto failure;
		}
		#if MTS_MANIFOLD_DEBUG == 1
			cout << "-> Accepting!" << endl;
		#endif
		++statsStepSuccess;

		m_proposal.swap(m_vertices);

		/* Increase the step size */
		stepSize = std::min((Float) 1.0f, stepSize * 2.0f);
		continue;
	failure:
		/* Reduce the step size */
		stepSize /= 2.0f;
	}
	#if MTS_MANIFOLD_DEBUG == 1
		cout << "Exceeded the max. iteration count!" << endl;
	#endif

	return false;
}

bool SpecularManifold::update(Path &path, int start, int end) {
	int step;
	ETransportMode mode;

	if (start < end) {
		step = 1; mode = EImportance;
	} else {
		step = -1; mode = ERadiance;
	}

	int last = (int) m_vertices.size() - 2;
	if (m_vertices[0].type == EPinnedDirection)
		last = std::max(last, 1);

	for (int j=0, i=start; j < last; ++j, i += step) {
		const SimpleVertex
			&v = m_vertices[j],
			&vn = m_vertices[j+1];

		PathVertex
			*pred   = path.vertexOrNull(i-step),
			*vertex = path.vertex(i),
			*succ   = path.vertex(i+step);

		int predEdgeIdx = (mode == EImportance) ? i-step : i-step-1;
		PathEdge *predEdge = path.edgeOrNull(predEdgeIdx),
		         *succEdge = path.edge(predEdgeIdx + step);

		Vector d = vn.p - v.p;
		Float length = d.length();
		d /= length;
		PathVertex::EVertexType desiredType = vn.type == EMedium ?
			PathVertex::EMediumInteraction : PathVertex::ESurfaceInteraction;

		if (v.type == EPinnedDirection) {
			/* Create a fake vertex and use it to call sampleDirect(). This is
			   kind of terrible -- a nicer API is needed to cleanly support this */
			PathVertex temp;
			temp.type = PathVertex::EMediumInteraction;
			temp.degenerate = false;
			temp.measure = EArea;
			MediumSamplingRecord &mRec = temp.getMediumSamplingRecord();
			mRec.time = m_time;
			mRec.p = vn.p;

			if (temp.sampleDirect(m_scene, NULL, vertex, succEdge, succ, mode).isZero()) {
				#if MTS_MANIFOLD_DEBUG == 1
					cout << "update(): failed in sampleDirect()!" << endl;
				#endif
				++statsUpdateFailed;
				return false;
			}

			if (m_vertices.size() >= 3) {
				PathVertex *succ2 = path.vertex(i+2*step);
				PathEdge *succ2Edge = path.edge(predEdgeIdx + 2*step);
				if (!succ->sampleNext(m_scene, NULL, vertex, succEdge, succ2Edge, succ2, mode)) {
					#if MTS_MANIFOLD_DEBUG == 1
						cout << "update(): failed in sampleNext() / pinned direction!" << endl;
					#endif
					++statsUpdateFailed;
					return false;
				}
			}
			i += step;
		} else if (!v.degenerate) {
			if (!vertex->perturbDirection(m_scene,
					pred, predEdge, succEdge, succ, d,
					length, desiredType, mode)) {
				#if MTS_MANIFOLD_DEBUG == 1
					cout << "update(): failed in perturbDirection()" << endl;
				#endif
				++statsUpdateFailed;
				return false;
			}

			Float relerr = (vn.p - succ->getPosition()).length() /
				std::max(std::max(std::abs(vn.p.x),
					std::abs(vn.p.y)), std::abs(vn.p.z));

			if (relerr > 1e-3f) {
				// be extra-cautious
				#if MTS_MANIFOLD_DEBUG == 1
					cout << "update(): failed, relative error of perturbDirection() too high:" << relerr << endl;
				#endif
				++statsUpdateFailed;
				return false;
			}
		} else {
			unsigned int compType;
			if (v.type == ERefraction)
				compType = v.eta != 1 ? BSDF::EDeltaTransmission : (BSDF::ENull | BSDF::EDeltaTransmission);
			else
				compType = BSDF::EDeltaReflection;

			if (!vertex->propagatePerturbation(m_scene,
					pred, predEdge, succEdge, succ, compType,
					length, desiredType, mode)) {
				#if MTS_MANIFOLD_DEBUG == 1
					cout << "update(): failed in propagatePerturbation()" << endl;
				#endif
				++statsUpdateFailed;
				return false;
			}

			Float relerr = (vn.p - succ->getPosition()).length() /
				std::max(std::max(std::abs(vn.p.x),
					std::abs(vn.p.y)), std::abs(vn.p.z));
			if (relerr > 1e-3f) {
				// be extra-cautious
				#if MTS_MANIFOLD_DEBUG == 1
					cout << "update(): failed, relative error of propagatePerturbation() too high:" << relerr << endl;
				#endif
				++statsUpdateFailed;
				return false;
			}
		}
	}

	return true;
}

Float SpecularManifold::det(const Path &path, int a, int b, int c) {
	int k = path.length();

	if (a == 0 || a == k)
		std::swap(a, c);

	int step = b > a ? 1 : -1, nGlossy = 0, nSpecular = 0;

	for (int i=a + step; i != c; i += step) {
		if (path.vertex(i)->isConnectable())
			++nGlossy;
		else
			++nSpecular;
	}

	if (nGlossy <= 1) /* No glossy materials -- we don't need this derivative */
		return 1.0f;

	bool success = init(path, a, c);
	BDAssert(success);

	int b_idx = std::abs(b-a);
	SimpleVertex &vb = m_vertices[b_idx];
	const PathVertex *pb = path.vertex(b);

	if (pb->isMediumInteraction()) {
		vb.n = Vector(path.edge(a < b ? (b-1) : b)->d);
	} else {
		vb.n = pb->getShadingNormal();
	}
	coordinateSystem(vb.n, vb.dpdu, vb.dpdv);

	if (!computeTangents()) {
		Log(EWarn, "Could not compute tangents!");
		return 0.0f;
	}

	m_vertices[b_idx].a.setZero();
	m_vertices[b_idx].b.setIdentity();
	m_vertices[b_idx].c.setZero();

	if (nSpecular == 0) {
		/* The chain only consists of glossy vertices -- simply compute the
		   determinant of the block tridiagonal matrix A.

		   See D.K. Salkuyeh, Comments on "A note on a three-term recurrence for a
		   tridiagonal matrix", Appl. Math. Comput. 176 (2006) 442-444. */

		Matrix2x2 Di(0.0f), D = m_vertices[1].b;

		Float det = D.det();
		for (size_t i=2; i<m_vertices.size()-1; ++i) {
			if (!D.invert(Di)) {
				Log(EWarn, "Could not invert matrix!");
				return 0.0f;
			}

			D = m_vertices[i].b - m_vertices[i].a * Di * m_vertices[i-1].c;
			det *= D.det();
		}

		return std::abs(1 / det);
	} else {
		/* The chain contains both glossy and specular materials. Compute the
		   determinant of A^-1, where rows corresponding to specular vertices
		   have been crossed out. The performance of the following is probably
		   terrible (lots of dynamic memory allocation), but it works and
		   this case happens rarely enough .. */

		Eigen::Matrix<Float, Eigen::Dynamic, Eigen::Dynamic> A(2*(nGlossy + nSpecular), 2*(nGlossy + nSpecular));
		A.setZero();

		for (int j=0, i=0; j<nGlossy+nSpecular; ++j) {
			if (j-1 >= 0) {
				A(2*i,   2*(j-1))   = m_vertices[j+1].a(0,0);
				A(2*i,   2*(j-1)+1) = m_vertices[j+1].a(0,1);
				A(2*i+1, 2*(j-1))   = m_vertices[j+1].a(1,0);
				A(2*i+1, 2*(j-1)+1) = m_vertices[j+1].a(1,1);
			}
			A(2*i,   2*j)   = m_vertices[j+1].b(0,0);
			A(2*i,   2*j+1) = m_vertices[j+1].b(0,1);
			A(2*i+1, 2*j)   = m_vertices[j+1].b(1,0);
			A(2*i+1, 2*j+1) = m_vertices[j+1].b(1,1);

			if (j+1 < nGlossy + nSpecular) {
				A(2*i,   2*(j+1))   = m_vertices[j+1].c(0,0);
				A(2*i,   2*(j+1)+1) = m_vertices[j+1].c(0,1);
				A(2*i+1, 2*(j+1))   = m_vertices[j+1].c(1,0);
				A(2*i+1, 2*(j+1)+1) = m_vertices[j+1].c(1,1);
			}
			++i;
		}

		/* Compute the inverse and "cross out" irrelevant columns and rows */
		Eigen::Matrix<Float, Eigen::Dynamic, Eigen::Dynamic> Ai = A.inverse();

		for (int i=0; i<nGlossy+nSpecular; ++i) {
			if (!m_vertices[i+1].degenerate)
				continue;

			Ai.row(2*i).setZero();
			Ai.col(2*i).setZero();
			Ai.row(2*i+1).setZero();
			Ai.col(2*i+1).setZero();

			Ai.block<2,2>(2*i, 2*i).setIdentity();
		}

		return std::abs(Ai.determinant());
	}
}

Float SpecularManifold::multiG(const Path &path, int a, int b) {
	if (a == 0)
		++a;
	else if (a == path.length())
		--a;
	if (b == 0)
		++b;
	else if (b == path.length())
		--b;

	int step = b > a ? 1 : -1;
	while (!path.vertex(b)->isConnectable())
		b -= step;
	while (!path.vertex(a)->isConnectable())
		a += step;

	Float result = 1;

	BDAssert(path.vertex(a)->isConnectable() && path.vertex(b)->isConnectable());
	for (int i = a + step, start = a; i != b + step; i += step) {
		if (path.vertex(i)->isConnectable()) {
			result *= G(path, start, i);
			start = i;
		}
	}

	return result;
}

Float SpecularManifold::G(const Path &path, int a, int b) {
	if (std::abs(a-b) == 1) {
		if (a > b)
			std::swap(a, b);
		return path.edge(a)->evalCached(path.vertex(a),
			path.vertex(b), PathEdge::EGeometricTerm)[0];
	}

	Assert(path.vertex(a)->isConnectable());
	Assert(path.vertex(b)->isConnectable());
	int step = b > a ? 1 : -1;

	bool success = init(path, a, b);
	BDAssert(success);

	SimpleVertex &last = m_vertices[m_vertices.size()-1];
	const PathVertex *vb = path.vertex(b);
	if (!vb->isOnSurface()) {
		last.n = Vector(path.edge(a < b ? (b-1) : b)->d);
	} else {
		last.n = vb->getShadingNormal();
	}
	coordinateSystem(last.n, last.dpdu, last.dpdv);

	statsNonManifold.incrementBase();
	if (!computeTangents()) {
		++statsNonManifold;
		Log(EWarn, "SpecularManifold::evalG(): non-manifold configuration!");
		return 0;
	}

	Float result;
	if (m_vertices[0].type == EPinnedDirection) {
		result = cross(m_vertices[0].map(1, 0), m_vertices[0].map(0, 1)).length();
	} else if (m_vertices[0].type == EPinnedPosition) {
		Vector d = m_vertices[1].p - m_vertices[0].p;
		Float lengthSqr = d.lengthSquared(), invLength = 1/std::sqrt(lengthSqr);

		result = cross(m_vertices[1].map(1, 0), m_vertices[1].map(0, 1)).length() / lengthSqr;

		if (path.vertex(a)->isOnSurface())
			result *= absDot(d, path.vertex(a)->getShadingNormal()) * invLength;

		if (path.vertex(a+step)->isOnSurface())
			result *= absDot(d, path.vertex(a+step)->getShadingNormal()) * invLength;
	} else {
		Log(EError, "Invalid vertex type!");
		return 0;
	}

	return result;
}

std::string SpecularManifold::SimpleVertex::toString() const {
	std::ostringstream oss;

	oss << "SimpleVertex[" << endl
		<< "  type = ";

	switch (type) {
		case EPinnedPosition: oss << "pinnedPosition"; break;
		case EPinnedDirection: oss << "pinnedDirection"; break;
		case EReflection: oss << "reflection"; break;
		case ERefraction: oss << "refraction"; break;
		case EMedium: oss << "medium"; break;
		case EMovable: oss << "movable"; break;
		default: SLog(EError, "Unknown vertex type!");
	}

	oss << "," << endl
		<< "  p = " << p.toString() << "," << endl
		<< "  n = " << n.toString() << "," << endl
		<< "  m = " << m.toString() << "," << endl
		<< "  dpdu = " << dpdu.toString() << "," << endl
		<< "  dpdv = " << dpdv.toString() << "," << endl
		<< "  dndu = " << dndu.toString() << "," << endl
		<< "  dndv = " << dndv.toString() << "," << endl
		<< "  eta = " << eta << "," << endl
		<< "  object = " << (object ? indent(object->toString()).c_str() : "null") << endl
		<< "]";

	return oss.str();
}

std::string SpecularManifold::toString() const {
	std::ostringstream oss;

	oss << "SpecularManifold[" << endl;
	for (size_t i=0; i<m_vertices.size(); ++i) {
		oss << "  " << i << " => " << indent(m_vertices[i].toString());
		if (i+1 < m_vertices.size())
			oss << ",";
		oss << endl;
	}
	oss << "]";

	return oss.str();
}

MTS_IMPLEMENT_CLASS(SpecularManifold, false, Object)
MTS_NAMESPACE_END