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

/*
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
 Copyright (C) 2008 StatPro Italia srl

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

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

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

/*! \file impliedtermstructure.hpp
    \brief Implied term structure
*/

#ifndef quantlib_implied_term_structure_hpp
#define quantlib_implied_term_structure_hpp

#include <ql/termstructures/yieldtermstructure.hpp>
#include <ql/utilities/null.hpp>
#include <utility>

namespace QuantLib {

    //! Implied term structure at a given date in the future
    /*! The given date will be the implied reference date.

        \note This term structure will remain linked to the original
              structure, i.e., any changes in the latter will be
              reflected in this structure as well.

        \ingroup yieldtermstructures

        \test
        - the correctness of the returned values is tested by
          checking them against numerical calculations.
        - observability against changes in the underlying term
          structure is checked.
    */
    class ImpliedTermStructure : public YieldTermStructure {
      public:
        ImpliedTermStructure(Handle<YieldTermStructure>, const Date& referenceDate);
        //! \name YieldTermStructure interface
        //@{
        DayCounter dayCounter() const override;
        Calendar calendar() const override;
        Natural settlementDays() const override;
        Date maxDate() const override;

      protected:
        DiscountFactor discountImpl(Time) const override;
        //@}
        //! \name Observer interface
        //@{
        void update() override;
        //@}
      private:
        Handle<YieldTermStructure> originalCurve_;
        mutable DiscountFactor refDf_ = Null<DiscountFactor>();
        mutable Time refTime_ = Null<Time>();
    };


    // inline definitions

    inline ImpliedTermStructure::ImpliedTermStructure(Handle<YieldTermStructure> h,
                                                      const Date& referenceDate)
    : YieldTermStructure(referenceDate), originalCurve_(std::move(h)) {
        if (!originalCurve_.empty())
            enableExtrapolation(originalCurve_->allowsExtrapolation());
        registerWith(originalCurve_);
    }

    inline DayCounter ImpliedTermStructure::dayCounter() const {
        return originalCurve_->dayCounter();
    }

    inline Calendar ImpliedTermStructure::calendar() const {
        return originalCurve_->calendar();
    }

    inline Natural ImpliedTermStructure::settlementDays() const {
        return originalCurve_->settlementDays();
    }

    inline Date ImpliedTermStructure::maxDate() const {
        return originalCurve_->maxDate();
    }

    inline void ImpliedTermStructure::update() {
        refDf_ = Null<DiscountFactor>();
        refTime_ = Null<Time>();
        if (!originalCurve_.empty())
            enableExtrapolation(originalCurve_->allowsExtrapolation());
        YieldTermStructure::update();
    }

    inline DiscountFactor ImpliedTermStructure::discountImpl(Time t) const {
        /* t is relative to the current reference date
           and needs to be converted to the time relative
           to the reference date of the original curve */
        if (refDf_ == Null<DiscountFactor>()) {
            const Date ref = referenceDate();
            refTime_ = dayCounter().yearFraction(originalCurve_->referenceDate(), ref);
            refDf_ = originalCurve_->discount(ref, true);
        }
        return originalCurve_->discount(t + refTime_, true) / refDf_;
    }

}


#endif
