/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.analytics.financial.model.option.pricing.fourier;

import java.util.Set;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.ZonedDateTime;

import com.opengamma.analytics.financial.greeks.Greek;
import com.opengamma.analytics.financial.greeks.GreekResultCollection;
import com.opengamma.analytics.financial.model.option.definition.BlackOptionDataBundle;
import com.opengamma.analytics.financial.model.option.definition.EuropeanVanillaOptionDefinition;
import com.opengamma.analytics.financial.model.option.pricing.OptionModel;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.BlackFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.math.integration.Integrator1D;

/**
 *
 */
public class FourierOptionModel implements OptionModel<EuropeanVanillaOptionDefinition, BlackOptionDataBundle> {
  private static final Logger LOGGER = LoggerFactory.getLogger(FourierOptionModel.class);
  private static final double DEFAULT_ALPHA = -0.5;
  private static final double DEFAULT_LIMIT_TOLERANCE = 1e-8;
  private static final boolean DEFAULT_USE_VARIANCE_REDUCTION = false;
  private final MartingaleCharacteristicExponent _characteristicExponent;
  private final FourierPricer _pricer;
  private final boolean _useVarianceReduction;
  private final double _limitTolerance;
  private final double _alpha;

  public FourierOptionModel(final MartingaleCharacteristicExponent characteristicExponent) {
    this(characteristicExponent, DEFAULT_ALPHA, DEFAULT_LIMIT_TOLERANCE, DEFAULT_USE_VARIANCE_REDUCTION);
  }

  public FourierOptionModel(final MartingaleCharacteristicExponent characteristicExponent, final boolean useVarianceReduction) {
    this(characteristicExponent, DEFAULT_ALPHA, DEFAULT_LIMIT_TOLERANCE, useVarianceReduction);
  }

  public FourierOptionModel(final MartingaleCharacteristicExponent characteristicExponent, final Integrator1D<Double, Double> integrator) {
    this(characteristicExponent, integrator, DEFAULT_ALPHA, DEFAULT_LIMIT_TOLERANCE, DEFAULT_USE_VARIANCE_REDUCTION);
  }

  public FourierOptionModel(final MartingaleCharacteristicExponent characteristicExponent, final Integrator1D<Double, Double> integrator,
      final boolean useVarianceReduction) {
    this(characteristicExponent, integrator, DEFAULT_ALPHA, DEFAULT_LIMIT_TOLERANCE, useVarianceReduction);
  }

  public FourierOptionModel(final MartingaleCharacteristicExponent characteristicExponent, final double alpha, final double limitTolerance,
      final boolean useVarianceReduction) {
    Validate.notNull(characteristicExponent, "characteristic exponent");
    Validate.isTrue(alpha != 0 && alpha != -1, "alpha cannot be equal to -1 or 0");
    Validate.isTrue(limitTolerance > 0, "limit tolerance > 0");
    _characteristicExponent = characteristicExponent;
    _pricer = new FourierPricer();
    _alpha = alpha;
    _limitTolerance = limitTolerance;
    _useVarianceReduction = useVarianceReduction;
  }

  public FourierOptionModel(final MartingaleCharacteristicExponent characteristicExponent, final Integrator1D<Double, Double> integrator, final double alpha,
      final double limitTolerance, final boolean useVarianceReduction) {
    Validate.notNull(characteristicExponent, "characteristic exponent");
    Validate.notNull(integrator, "integrator");
    Validate.isTrue(alpha != 0 && alpha != -1, "alpha cannot be equal to -1 or 0");
    Validate.isTrue(limitTolerance > 0, "limit tolerance > 0");
    _characteristicExponent = characteristicExponent;
    _pricer = new FourierPricer(integrator);
    _alpha = alpha;
    _limitTolerance = limitTolerance;
    _useVarianceReduction = useVarianceReduction;
  }

  @Override
  public GreekResultCollection getGreeks(final EuropeanVanillaOptionDefinition definition, final BlackOptionDataBundle dataBundle,
      final Set<Greek> requiredGreeks) {
    Validate.notNull(definition, "definition");
    Validate.notNull(dataBundle, "data bundle");
    Validate.notNull(requiredGreeks, "required greeks");
    if (!requiredGreeks.contains(Greek.FAIR_PRICE)) {
      throw new NotImplementedException("Can only calculate fair price at the moment: asked for " + requiredGreeks);
    }
    if (requiredGreeks.size() > 1) {
      LOGGER.warn("Can only calculate fair price - ignoring other greeks");
    }
    final ZonedDateTime date = dataBundle.getDate();
    final EuropeanVanillaOption option = EuropeanVanillaOption.fromDefinition(definition, date);
    final BlackFunctionData data = BlackFunctionData.fromDataBundle(dataBundle, definition);
    final double price = _pricer.price(data, option, _characteristicExponent, _alpha, _limitTolerance, _useVarianceReduction);
    final GreekResultCollection result = new GreekResultCollection();
    result.put(Greek.FAIR_PRICE, price);
    return result;
  }

}
