/**
 * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.sesame.bond;

import java.util.HashMap;
import java.util.Map;

import org.threeten.bp.ZonedDateTime;

import com.opengamma.analytics.financial.instrument.InstrumentDefinition;
import com.opengamma.analytics.financial.instrument.bond.BondFixedTransactionDefinition;
import com.opengamma.analytics.financial.interestrate.bond.calculator.YieldFromCleanPriceCalculator;
import com.opengamma.analytics.financial.interestrate.bond.definition.BondFixedTransaction;
import com.opengamma.analytics.financial.interestrate.bond.provider.BondSecurityDiscountingMethod;
import com.opengamma.analytics.financial.interestrate.bond.provider.BondTransactionDiscountingMethod;
import com.opengamma.analytics.financial.provider.calculator.discounting.PV01CurveParametersCalculator;
import com.opengamma.analytics.financial.provider.calculator.generic.MarketQuoteSensitivityBlockCalculator;
import com.opengamma.analytics.financial.provider.calculator.issuer.PresentValueCurveSensitivityIssuerCalculator;
import com.opengamma.analytics.financial.provider.calculator.issuer.PresentValueIssuerCalculator;
import com.opengamma.analytics.financial.provider.calculator.issuer.ZSpreadIssuerCalculator;
import com.opengamma.analytics.financial.provider.curve.CurveBuildingBlockBundle;
import com.opengamma.analytics.financial.provider.description.interestrate.IssuerProviderInterface;
import com.opengamma.analytics.financial.provider.description.interestrate.ParameterIssuerProviderInterface;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.parameter.ParameterSensitivityParameterCalculator;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.util.amount.ReferenceAmount;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.financial.analytics.DoubleLabelledMatrix1D;
import com.opengamma.financial.analytics.conversion.BondAndBondFutureTradeConverter;
import com.opengamma.financial.analytics.model.fixedincome.BucketedCurveSensitivities;
import com.opengamma.sesame.CurveMatrixLabeller;
import com.opengamma.sesame.Environment;
import com.opengamma.sesame.marketdata.FieldName;
import com.opengamma.sesame.marketdata.MarketDataFn;
import com.opengamma.sesame.trade.BondTrade;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.result.Result;
import com.opengamma.util.tuple.ObjectsPair;
import com.opengamma.util.tuple.Pair;

/**
 * Discounting calculator for bond.
 *
 * @deprecated use {@link DiscountingBondFn} instead of {@link DefaultBondFn}, the calculator and factory
 */
@Deprecated
public class DiscountingBondCalculator implements BondCalculator {

  /** Calculator for PV from curves */
  private static final PresentValueIssuerCalculator PVIC = PresentValueIssuerCalculator.getInstance();

  /** Calculators for PV, yield and price from yield, clean price or yield */
  private static final BondTransactionDiscountingMethod BTDM = BondTransactionDiscountingMethod.getInstance();
  private static final BondSecurityDiscountingMethod BSDM = BondSecurityDiscountingMethod.getInstance();

  private static final PV01CurveParametersCalculator<ParameterIssuerProviderInterface> PV01C =
      new PV01CurveParametersCalculator<>(PresentValueCurveSensitivityIssuerCalculator.getInstance());

  private static final PresentValueCurveSensitivityIssuerCalculator PVCSDC =
      PresentValueCurveSensitivityIssuerCalculator.getInstance();

  private static final ParameterSensitivityParameterCalculator<ParameterIssuerProviderInterface> PSC =
      new ParameterSensitivityParameterCalculator<>(PVCSDC);

  private static final MarketQuoteSensitivityBlockCalculator<ParameterIssuerProviderInterface> MQSBC =
      new MarketQuoteSensitivityBlockCalculator<>(PSC);

  private static final ZSpreadIssuerCalculator ZSC = ZSpreadIssuerCalculator.getInstance();

  private static final YieldFromCleanPriceCalculator YFCPC = YieldFromCleanPriceCalculator.getInstance();

  private static final double BASIS_POINT_FACTOR = 1.0E-4;

  private final BondFixedTransaction _derivative;

  private final ParameterIssuerProviderInterface _curves;

  private final CurveBuildingBlockBundle _blocks;

  private final Map<String, CurveMatrixLabeller> _curveLabellers;

  private final MarketDataFn _marketDataFn;

  private final Environment _env;

  private BondTrade _trade;

  /**
   * Creates a calculator for a InterestRateSwapSecurity.
   *
   * @param trade the bond trade to calculate values for
   * @param curves the ParameterIssuerProviderInterface
   * @param blocks the CurveBuildingBlockBundle
   * @param converter the BondAndBondFutureTradeConverter
   * @param env the Environment
   * @param curveLabellers the curve labellers
   * @param marketDataFn the Market Data Function
   */
  public DiscountingBondCalculator(BondTrade trade,
                                   ParameterIssuerProviderInterface curves,
                                   CurveBuildingBlockBundle blocks,
                                   BondAndBondFutureTradeConverter converter,
                                   Environment env,
                                   Map<String, CurveMatrixLabeller> curveLabellers,
                                   MarketDataFn marketDataFn) {

    ArgumentChecker.notNull(converter, "converter");

    _trade = ArgumentChecker.notNull(trade, "trade");
    _env = ArgumentChecker.notNull(env, "valuationTime");
    _blocks = ArgumentChecker.notNull(blocks, "blocks");
    _curves = ArgumentChecker.notNull(curves, "curves");
    _marketDataFn = ArgumentChecker.notNull(marketDataFn, "marketDataFn");
    _curveLabellers = ArgumentChecker.notNull(curveLabellers, "curveLabellers");
    _derivative = createInstrumentDerivative(trade, converter, env.getValuationTime());

  }

  @Override
  public Result<MultipleCurrencyAmount> calculatePresentValueFromCurves() {
    return Result.success(_derivative.accept(PVIC, _curves));
  }

  @Override
  public Result<MultipleCurrencyAmount> calculatePresentValueFromClean() {
    Result<Double> marketResult = calculateCleanPriceMarket();
    if (marketResult.isSuccess()) {
      return Result.success(BTDM.presentValueFromCleanPrice(_derivative,
                                                            _curves.getIssuerProvider(),
                                                            marketResult.getValue()));
    } else {
      return Result.failure(marketResult);
    }
  }

  @Override
  public Result<MultipleCurrencyAmount> calculatePresentValueFromYield() {
    Result<Double> marketYield = calculateYieldToMaturityMarket();
    if (marketYield.isSuccess()) {
      return Result.success(BTDM.presentValueFromYield(_derivative,
                                                       _curves.getIssuerProvider(),
                                                       marketYield.getValue()));
    } else {
      return Result.failure(marketYield);
    }
  }

  @Override
  public Result<Double> calculateCleanPriceMarket() {
    return _marketDataFn.getMarketValue(_env, _trade.getSecurity());
  }

  @Override
  public Result<Double> calculateCleanPriceFromCurves() {
    return Result.success(BSDM.cleanPriceFromCurves(_derivative.getBondStandard(), _curves.getIssuerProvider()));
  }

  @Override
  public Result<Double> calculateCleanPriceFromYield() {
    Result<Double> marketYield = calculateYieldToMaturityMarket();
    if (marketYield.isSuccess()) {
      return Result.success(BSDM.cleanPriceFromYield(_derivative.getBondStandard(), marketYield.getValue()));
    } else {
      return Result.failure(marketYield);
    }
  }

  @Override
  public Result<Double> calculateYieldToMaturityFromCleanPrice() {
    Result<Double> marketCleanPrice = calculateCleanPriceMarket();
    if (marketCleanPrice.isSuccess()) {
      return Result.success(BSDM.yieldFromCleanPrice(_derivative.getBondStandard(),
                                                     marketCleanPrice.getValue()));
    } else {
      return Result.failure(marketCleanPrice);
    }
  }

  @Override
  public Result<Double> calculateYieldToMaturityFromCurves() {
    return Result.success(BSDM.yieldFromCurves(_derivative.getBondStandard(), _curves.getIssuerProvider()));
  }

  @Override
  public Result<Double> calculateYieldToMaturityMarket() {
    FieldName fieldName = FieldName.of(MarketDataRequirementNames.YIELD_YIELD_TO_MATURITY_MID);
    return _marketDataFn.getValue(_env, _trade.getSecurity(), fieldName, Double.class);
  }

  @Override
  public Result<BucketedCurveSensitivities> calculateBucketedPV01() {
    MultipleCurrencyParameterSensitivity sensitivity = MQSBC
        .fromInstrument(_derivative, _curves, _blocks)
        .multipliedBy(BASIS_POINT_FACTOR);
    Map<Pair<String, Currency>, DoubleLabelledMatrix1D> labelledMatrix1DMap = new HashMap<>();
    for (Map.Entry<Pair<String, Currency>, DoubleMatrix1D> entry : sensitivity.getSensitivities().entrySet()) {
      CurveMatrixLabeller labeller = _curveLabellers.get(entry.getKey().getFirst());
      DoubleLabelledMatrix1D matrix = labeller.labelMatrix(entry.getValue());
      labelledMatrix1DMap.put(entry.getKey(), matrix);
    }
    return Result.success(BucketedCurveSensitivities.of(labelledMatrix1DMap));
  }

  @Override
  public Result<ReferenceAmount<Pair<String, Currency>>> calculatePV01() {
    return Result.success(_derivative.accept(PV01C, _curves));
  }
  
  @Override
  public Result<Double> calculateZSpread() {
    Result<Double> marketResult = calculateCleanPriceMarket();
    if (marketResult.isSuccess()) {
      ObjectsPair<IssuerProviderInterface, Double> pair = ObjectsPair.of(_curves.getIssuerProvider(),
                                                                         marketResult.getValue());
      return Result.success(_derivative.accept(ZSC, pair));
    } else {
      return Result.failure(marketResult);
    }
  }

  private BondFixedTransaction createInstrumentDerivative(BondTrade bondTrade,
                                                          BondAndBondFutureTradeConverter converter,
                                                          ZonedDateTime valuationTime) {
    InstrumentDefinition<?> definition = converter.convert(bondTrade.getTrade());
    return ((BondFixedTransactionDefinition) definition).toDerivative(valuationTime);
  }

}
