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

import java.io.Serializable;
import java.util.Map;
import java.util.NoSuchElementException;

import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.gen.BeanDefinition;
import org.joda.beans.gen.DerivedProperty;
import org.joda.beans.gen.ImmutableConstructor;
import org.joda.beans.gen.PropertyDefinition;
import org.joda.beans.impl.direct.DirectMetaBean;
import org.joda.beans.impl.direct.DirectMetaProperty;
import org.joda.beans.impl.direct.DirectMetaPropertyMap;
import org.joda.beans.impl.direct.DirectPrivateBeanBuilder;

import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.collect.ArgChecker;

/**
 * Defines the meaning of the security price.
 * <p>
 * A value of a security is measured in terms of a <i>price</i> which is not typically a monetary amount.
 * Instead the price is an arbitrary number that can be converted to a monetary amount.
 * The price will move up and down in <i>ticks</i>. This class provides the size and monetary
 * value of each tick, allowing changes in the price to be converted to a monetary amount.
 * <p>
 * Three properties define the necessary information:
 * Tick size is the minimum movement in the price of the security (the tick).
 * Tick value is the monetary value gained or lost when the price changes by one tick.
 * Contract size is the quantity of the underlying present in each derivative contract,
 * which acts as a multiplier.
 * <p>
 * For example, the price of an ICE Brent Crude future is based on the price of a barrel of crude oil in USD.
 * The tick size of this contract is 0.01 (equivalent to 1 cent).
 * The contract size is 1,000 barrels.
 * Therefore the tick value is 0.01 * 1,000 = 10 USD.
 */
@BeanDefinition(builderScope = "private")
public final class SecurityPriceInfo
    implements ImmutableBean, Serializable {

  /**
   * The size of each tick.
   * <p>
   * Tick size is the minimum movement in the price of the security.
   * For example, the price might move up or down in units of 0.01
   * It must be a positive decimal number.
   */
  @PropertyDefinition(validate = "ArgChecker.notNegativeOrZero")
  private final double tickSize;
  /**
   * The monetary value of one tick.
   * <p>
   * Tick value is the monetary value of the minimum movement in the price of the security.
   * When the price changes by one tick, this amount is gained or lost.
   */
  @PropertyDefinition(validate = "notNull")
  private final CurrencyAmount tickValue;
  /**
   * The size of each contract.
   * <p>
   * Contract size is the quantity of the underlying present in each derivative contract.
   * For example, an equity option typically consists of 100 shares.
   */
  @PropertyDefinition(validate = "ArgChecker.notNegativeOrZero")
  private final double contractSize;
  /**
   * Multiplier to apply to the price.
   */
  private final transient double tradeUnitValue;  // derived, not a property

  //-------------------------------------------------------------------------
  /**
   * Obtains an instance from the tick size and tick value.
   * <p>
   * The contract size will be set to 1.
   * 
   * @param tickSize  the size of each tick, not negative or zero
   * @param tickValue  the value of each tick
   * @return the security price information
   */
  public static SecurityPriceInfo of(double tickSize, CurrencyAmount tickValue) {
    return new SecurityPriceInfo(tickSize, tickValue, 1);
  }

  /**
   * Obtains an instance from the tick size, tick value and contract size.
   * 
   * @param tickSize  the size of each tick, not negative or zero
   * @param tickValue  the value of each tick
   * @param contractSize  the contract size
   * @return the security price information
   */
  public static SecurityPriceInfo of(double tickSize, CurrencyAmount tickValue, double contractSize) {
    return new SecurityPriceInfo(tickSize, tickValue, contractSize);
  }

  /**
   * Obtains an instance from the currency and the value of a single tradeable unit.
   *
   * @param currency  the currency in which the security is traded
   * @param tradeUnitValue  the value of a single tradeable unit of the security
   * @return the security price information
   */
  public static SecurityPriceInfo of(Currency currency, double tradeUnitValue) {
    return new SecurityPriceInfo(1, CurrencyAmount.of(currency, 1), tradeUnitValue);
  }

  /**
   * Obtains an instance from the currency.
   * <p>
   * This sets the tick size and tick value to the minor unit of the currency.
   * For example, for USD this will set the tick size to 0.01 and the tick value to $0.01.
   * This typically matches the conventions of equities and bonds.
   * 
   * @param currency  the currency to derive the price information from
   * @return the security price information
   */
  public static SecurityPriceInfo ofCurrencyMinorUnit(Currency currency) {
    int digits = currency.getMinorUnitDigits();
    double unitAmount = Math.pow(10, -digits);
    return new SecurityPriceInfo(unitAmount, CurrencyAmount.of(currency, unitAmount), 1);
  }

  @ImmutableConstructor
  private SecurityPriceInfo(
      double tickSize,
      CurrencyAmount tickValue,
      double contractSize) {
    ArgChecker.notNegativeOrZero(tickSize, "tickSize");
    JodaBeanUtils.notNull(tickValue, "tickValue");
    ArgChecker.notNegativeOrZero(contractSize, "contractSize");
    this.tickSize = tickSize;
    this.tickValue = tickValue;
    this.contractSize = contractSize;
    this.tradeUnitValue = (tickValue.getAmount() * contractSize) / tickSize;
  }

  // ensure standard constructor is invoked
  private Object readResolve() {
    return new SecurityPriceInfo(tickSize, tickValue, contractSize);
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the currency that the security is traded in.
   * <p>
   * The currency is derived from the tick value.
   * 
   * @return the currency
   */
  @DerivedProperty
  public Currency getCurrency() {
    return tickValue.getCurrency();
  }

  //-------------------------------------------------------------------------
  /**
   * Calculates the monetary value of the specified quantity and price.
   * <p>
   * This calculates a monetary value using the stored price information.
   * For equities, this is the premium that will be paid.
   * For bonds, this will be the premium if the price specified is the <i>dirty</i> price.
   * For margined ETDs, the profit or loss per day is the monetary difference
   * between two calls to this method with the price on each day.
   * <p>
   * This returns {@link #calculateMonetaryValue(double, double)} as a {@link CurrencyAmount}.
   *
   * @param quantity  the quantity, such as the number of shares or number of future contracts
   * @param price  the price, typically from the market
   * @return the monetary value combining the tick size, tick value, contract size, quantity and price.
   */
  public CurrencyAmount calculateMonetaryAmount(double quantity, double price) {
    return CurrencyAmount.of(tickValue.getCurrency(), calculateMonetaryValue(quantity, price));
  }

  /**
   * Calculates the monetary value of the specified quantity and price.
   * <p>
   * This calculates a monetary value using the stored price information.
   * For equities, this is the premium that will be paid.
   * For bonds, this will be the premium if the price specified is the <i>dirty</i> price.
   * For margined ETDs, the profit or loss per day is the monetary difference
   * between two calls to this method with the price on each day.
   *
   * @param quantity  the quantity, such as the number of shares or number of future contracts
   * @param price  the price, typically from the market
   * @return the monetary value combining the tick size, tick value, contract size, quantity and price.
   */
  public double calculateMonetaryValue(double quantity, double price) {
    return price * quantity * tradeUnitValue;
  }

  /**
   * Returns the value of a single tradeable unit of the security.
   * <p>
   * The monetary value of a position in a security is
   * <pre>tradeUnitValue * price * quantity</pre>
   * <p>
   * This value is normally derived from the tick size, tick value and contract size:
   * <pre>tradeUnitValue = tickValue * contractSize / tickSize</pre>
   *
   * @return the value of a single tradeable unit of the security
   */
  public double getTradeUnitValue() {
    return tradeUnitValue;
  }

  //------------------------- AUTOGENERATED START -------------------------
  /**
   * The meta-bean for {@code SecurityPriceInfo}.
   * @return the meta-bean, not null
   */
  public static SecurityPriceInfo.Meta meta() {
    return SecurityPriceInfo.Meta.INSTANCE;
  }

  static {
    MetaBean.register(SecurityPriceInfo.Meta.INSTANCE);
  }

  /**
   * The serialization version id.
   */
  private static final long serialVersionUID = 1L;

  @Override
  public SecurityPriceInfo.Meta metaBean() {
    return SecurityPriceInfo.Meta.INSTANCE;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the size of each tick.
   * <p>
   * Tick size is the minimum movement in the price of the security.
   * For example, the price might move up or down in units of 0.01
   * It must be a positive decimal number.
   * @return the value of the property
   */
  public double getTickSize() {
    return tickSize;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the monetary value of one tick.
   * <p>
   * Tick value is the monetary value of the minimum movement in the price of the security.
   * When the price changes by one tick, this amount is gained or lost.
   * @return the value of the property, not null
   */
  public CurrencyAmount getTickValue() {
    return tickValue;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the size of each contract.
   * <p>
   * Contract size is the quantity of the underlying present in each derivative contract.
   * For example, an equity option typically consists of 100 shares.
   * @return the value of the property
   */
  public double getContractSize() {
    return contractSize;
  }

  //-----------------------------------------------------------------------
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      SecurityPriceInfo other = (SecurityPriceInfo) obj;
      return JodaBeanUtils.equal(tickSize, other.tickSize) &&
          JodaBeanUtils.equal(tickValue, other.tickValue) &&
          JodaBeanUtils.equal(contractSize, other.contractSize);
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    hash = hash * 31 + JodaBeanUtils.hashCode(tickSize);
    hash = hash * 31 + JodaBeanUtils.hashCode(tickValue);
    hash = hash * 31 + JodaBeanUtils.hashCode(contractSize);
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(160);
    buf.append("SecurityPriceInfo{");
    buf.append("tickSize").append('=').append(JodaBeanUtils.toString(tickSize)).append(',').append(' ');
    buf.append("tickValue").append('=').append(JodaBeanUtils.toString(tickValue)).append(',').append(' ');
    buf.append("contractSize").append('=').append(JodaBeanUtils.toString(contractSize)).append(',').append(' ');
    buf.append("currency").append('=').append(JodaBeanUtils.toString(getCurrency()));
    buf.append('}');
    return buf.toString();
  }

  //-----------------------------------------------------------------------
  /**
   * The meta-bean for {@code SecurityPriceInfo}.
   */
  public static final class Meta extends DirectMetaBean {
    /**
     * The singleton instance of the meta-bean.
     */
    static final Meta INSTANCE = new Meta();

    /**
     * The meta-property for the {@code tickSize} property.
     */
    private final MetaProperty<Double> tickSize = DirectMetaProperty.ofImmutable(
        this, "tickSize", SecurityPriceInfo.class, Double.TYPE);
    /**
     * The meta-property for the {@code tickValue} property.
     */
    private final MetaProperty<CurrencyAmount> tickValue = DirectMetaProperty.ofImmutable(
        this, "tickValue", SecurityPriceInfo.class, CurrencyAmount.class);
    /**
     * The meta-property for the {@code contractSize} property.
     */
    private final MetaProperty<Double> contractSize = DirectMetaProperty.ofImmutable(
        this, "contractSize", SecurityPriceInfo.class, Double.TYPE);
    /**
     * The meta-property for the {@code currency} property.
     */
    private final MetaProperty<Currency> currency = DirectMetaProperty.ofDerived(
        this, "currency", SecurityPriceInfo.class, Currency.class);
    /**
     * The meta-properties.
     */
    private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap(
        this, null,
        "tickSize",
        "tickValue",
        "contractSize",
        "currency");

    /**
     * Restricted constructor.
     */
    private Meta() {
    }

    @Override
    protected MetaProperty<?> metaPropertyGet(String propertyName) {
      switch (propertyName.hashCode()) {
        case 1936822078:  // tickSize
          return tickSize;
        case -85538348:  // tickValue
          return tickValue;
        case -1402368973:  // contractSize
          return contractSize;
        case 575402001:  // currency
          return currency;
      }
      return super.metaPropertyGet(propertyName);
    }

    @Override
    public BeanBuilder<? extends SecurityPriceInfo> builder() {
      return new SecurityPriceInfo.Builder();
    }

    @Override
    public Class<? extends SecurityPriceInfo> beanType() {
      return SecurityPriceInfo.class;
    }

    @Override
    public Map<String, MetaProperty<?>> metaPropertyMap() {
      return metaPropertyMap$;
    }

    //-----------------------------------------------------------------------
    /**
     * The meta-property for the {@code tickSize} property.
     * @return the meta-property, not null
     */
    public MetaProperty<Double> tickSize() {
      return tickSize;
    }

    /**
     * The meta-property for the {@code tickValue} property.
     * @return the meta-property, not null
     */
    public MetaProperty<CurrencyAmount> tickValue() {
      return tickValue;
    }

    /**
     * The meta-property for the {@code contractSize} property.
     * @return the meta-property, not null
     */
    public MetaProperty<Double> contractSize() {
      return contractSize;
    }

    /**
     * The meta-property for the {@code currency} property.
     * @return the meta-property, not null
     */
    public MetaProperty<Currency> currency() {
      return currency;
    }

    //-----------------------------------------------------------------------
    @Override
    protected Object propertyGet(Bean bean, String propertyName, boolean quiet) {
      switch (propertyName.hashCode()) {
        case 1936822078:  // tickSize
          return ((SecurityPriceInfo) bean).getTickSize();
        case -85538348:  // tickValue
          return ((SecurityPriceInfo) bean).getTickValue();
        case -1402368973:  // contractSize
          return ((SecurityPriceInfo) bean).getContractSize();
        case 575402001:  // currency
          return ((SecurityPriceInfo) bean).getCurrency();
      }
      return super.propertyGet(bean, propertyName, quiet);
    }

    @Override
    protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) {
      metaProperty(propertyName);
      if (quiet) {
        return;
      }
      throw new UnsupportedOperationException("Property cannot be written: " + propertyName);
    }

  }

  //-----------------------------------------------------------------------
  /**
   * The bean-builder for {@code SecurityPriceInfo}.
   */
  private static final class Builder extends DirectPrivateBeanBuilder<SecurityPriceInfo> {

    private double tickSize;
    private CurrencyAmount tickValue;
    private double contractSize;

    /**
     * Restricted constructor.
     */
    private Builder() {
    }

    //-----------------------------------------------------------------------
    @Override
    public Object get(String propertyName) {
      switch (propertyName.hashCode()) {
        case 1936822078:  // tickSize
          return tickSize;
        case -85538348:  // tickValue
          return tickValue;
        case -1402368973:  // contractSize
          return contractSize;
        default:
          throw new NoSuchElementException("Unknown property: " + propertyName);
      }
    }

    @Override
    public Builder set(String propertyName, Object newValue) {
      switch (propertyName.hashCode()) {
        case 1936822078:  // tickSize
          this.tickSize = (Double) newValue;
          break;
        case -85538348:  // tickValue
          this.tickValue = (CurrencyAmount) newValue;
          break;
        case -1402368973:  // contractSize
          this.contractSize = (Double) newValue;
          break;
        default:
          throw new NoSuchElementException("Unknown property: " + propertyName);
      }
      return this;
    }

    @Override
    public SecurityPriceInfo build() {
      return new SecurityPriceInfo(
          tickSize,
          tickValue,
          contractSize);
    }

    //-----------------------------------------------------------------------
    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(160);
      buf.append("SecurityPriceInfo.Builder{");
      buf.append("tickSize").append('=').append(JodaBeanUtils.toString(tickSize)).append(',').append(' ');
      buf.append("tickValue").append('=').append(JodaBeanUtils.toString(tickValue)).append(',').append(' ');
      buf.append("contractSize").append('=').append(JodaBeanUtils.toString(contractSize)).append(',').append(' ');
      buf.append("currency").append('=').append(JodaBeanUtils.toString(null));
      buf.append('}');
      return buf.toString();
    }

  }

  //-------------------------- AUTOGENERATED END --------------------------
}
