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

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

import org.joda.beans.Bean;
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.ImmutableValidator;
import org.joda.beans.gen.PropertyDefinition;
import org.joda.beans.impl.direct.DirectFieldsBeanBuilder;
import org.joda.beans.impl.direct.DirectMetaBean;
import org.joda.beans.impl.direct.DirectMetaProperty;
import org.joda.beans.impl.direct.DirectMetaPropertyMap;

import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.date.HolidayCalendar;
import com.opengamma.strata.basics.index.OvernightIndex;
import com.opengamma.strata.collect.ArgChecker;

/**
 * Defines the computation of an averaged daily rate for a single Overnight index.
 * <p>
 * An interest rate determined directly from an Overnight index by averaging the value
 * of each day's rate over the period strictly between the start date and end date. 
 * <p>
 * The start date and end date can be non-business days. 
 * The average is taken on calendar days between the start and end dates.
 * <p>
 * If a day in the period is not a business day on the fixing calendar of the Overnight index, 
 * the overnight rate fixed on the previous business day is used.
 * <p>
 * For example, a rate determined averaging values from 'USD-FED-FUND'.
 */
@BeanDefinition
public final class OvernightAveragedDailyRateComputation
    implements OvernightRateComputation, ImmutableBean, Serializable {

  /**
   * The Overnight index.
   * <p>
   * The rate to be paid is based on this index.
   * It will be a well known market index such as 'GBP-SONIA'.
   */
  @PropertyDefinition(validate = "notNull", overrideGet = true)
  private final OvernightIndex index;
  /**
   * The resolved calendar that the index uses.
   */
  @PropertyDefinition(validate = "notNull", overrideGet = true)
  private final HolidayCalendar fixingCalendar;
  /**
   * The start date of the accrual period.
   * <p>
   * This is not necessarily a valid business day.
   * In this case, the first fixing date is the previous business day of the start date on {@code fixingCalendar}.
   */
  @PropertyDefinition(validate = "notNull", overrideGet = true)
  private final LocalDate startDate;
  /**
   * The end date of the accrual period.
   * <p>
   * This is not necessarily a valid business day.
   * In this case, the last fixing date is the previous business day of the end date on {@code fixingCalendar}.
   */
  @PropertyDefinition(validate = "notNull", overrideGet = true)
  private final LocalDate endDate;

  //-------------------------------------------------------------------------
  /**
   * Creates an instance from an index and accrual period dates
   * <p>
   * The dates represent the accrual period.
   * 
   * @param index  the index
   * @param startDate  the first date of the accrual period
   * @param endDate  the last date of the accrual period
   * @param refData  the reference data to use when resolving holiday calendars
   * @return the rate computation
   */
  public static OvernightAveragedDailyRateComputation of(
      OvernightIndex index,
      LocalDate startDate,
      LocalDate endDate,
      ReferenceData refData) {

    return OvernightAveragedDailyRateComputation.builder()
        .index(index)
        .fixingCalendar(index.getFixingCalendar().resolve(refData))
        .startDate(startDate)
        .endDate(endDate)
        .build();
  }

  //-------------------------------------------------------------------------
  @ImmutableValidator
  private void validate() {
    ArgChecker.inOrderNotEqual(startDate, endDate, "startDate", "endDate");
  }

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

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

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

  /**
   * Returns a builder used to create an instance of the bean.
   * @return the builder, not null
   */
  public static OvernightAveragedDailyRateComputation.Builder builder() {
    return new OvernightAveragedDailyRateComputation.Builder();
  }

  private OvernightAveragedDailyRateComputation(
      OvernightIndex index,
      HolidayCalendar fixingCalendar,
      LocalDate startDate,
      LocalDate endDate) {
    JodaBeanUtils.notNull(index, "index");
    JodaBeanUtils.notNull(fixingCalendar, "fixingCalendar");
    JodaBeanUtils.notNull(startDate, "startDate");
    JodaBeanUtils.notNull(endDate, "endDate");
    this.index = index;
    this.fixingCalendar = fixingCalendar;
    this.startDate = startDate;
    this.endDate = endDate;
    validate();
  }

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

  //-----------------------------------------------------------------------
  /**
   * Gets the Overnight index.
   * <p>
   * The rate to be paid is based on this index.
   * It will be a well known market index such as 'GBP-SONIA'.
   * @return the value of the property, not null
   */
  @Override
  public OvernightIndex getIndex() {
    return index;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the resolved calendar that the index uses.
   * @return the value of the property, not null
   */
  @Override
  public HolidayCalendar getFixingCalendar() {
    return fixingCalendar;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the start date of the accrual period.
   * <p>
   * This is not necessarily a valid business day.
   * In this case, the first fixing date is the previous business day of the start date on {@code fixingCalendar}.
   * @return the value of the property, not null
   */
  @Override
  public LocalDate getStartDate() {
    return startDate;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the end date of the accrual period.
   * <p>
   * This is not necessarily a valid business day.
   * In this case, the last fixing date is the previous business day of the end date on {@code fixingCalendar}.
   * @return the value of the property, not null
   */
  @Override
  public LocalDate getEndDate() {
    return endDate;
  }

  //-----------------------------------------------------------------------
  /**
   * Returns a builder that allows this bean to be mutated.
   * @return the mutable builder, not null
   */
  public Builder toBuilder() {
    return new Builder(this);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      OvernightAveragedDailyRateComputation other = (OvernightAveragedDailyRateComputation) obj;
      return JodaBeanUtils.equal(index, other.index) &&
          JodaBeanUtils.equal(fixingCalendar, other.fixingCalendar) &&
          JodaBeanUtils.equal(startDate, other.startDate) &&
          JodaBeanUtils.equal(endDate, other.endDate);
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    hash = hash * 31 + JodaBeanUtils.hashCode(index);
    hash = hash * 31 + JodaBeanUtils.hashCode(fixingCalendar);
    hash = hash * 31 + JodaBeanUtils.hashCode(startDate);
    hash = hash * 31 + JodaBeanUtils.hashCode(endDate);
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(160);
    buf.append("OvernightAveragedDailyRateComputation{");
    buf.append("index").append('=').append(JodaBeanUtils.toString(index)).append(',').append(' ');
    buf.append("fixingCalendar").append('=').append(JodaBeanUtils.toString(fixingCalendar)).append(',').append(' ');
    buf.append("startDate").append('=').append(JodaBeanUtils.toString(startDate)).append(',').append(' ');
    buf.append("endDate").append('=').append(JodaBeanUtils.toString(endDate));
    buf.append('}');
    return buf.toString();
  }

  //-----------------------------------------------------------------------
  /**
   * The meta-bean for {@code OvernightAveragedDailyRateComputation}.
   */
  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 index} property.
     */
    private final MetaProperty<OvernightIndex> index = DirectMetaProperty.ofImmutable(
        this, "index", OvernightAveragedDailyRateComputation.class, OvernightIndex.class);
    /**
     * The meta-property for the {@code fixingCalendar} property.
     */
    private final MetaProperty<HolidayCalendar> fixingCalendar = DirectMetaProperty.ofImmutable(
        this, "fixingCalendar", OvernightAveragedDailyRateComputation.class, HolidayCalendar.class);
    /**
     * The meta-property for the {@code startDate} property.
     */
    private final MetaProperty<LocalDate> startDate = DirectMetaProperty.ofImmutable(
        this, "startDate", OvernightAveragedDailyRateComputation.class, LocalDate.class);
    /**
     * The meta-property for the {@code endDate} property.
     */
    private final MetaProperty<LocalDate> endDate = DirectMetaProperty.ofImmutable(
        this, "endDate", OvernightAveragedDailyRateComputation.class, LocalDate.class);
    /**
     * The meta-properties.
     */
    private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap(
        this, null,
        "index",
        "fixingCalendar",
        "startDate",
        "endDate");

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

    @Override
    protected MetaProperty<?> metaPropertyGet(String propertyName) {
      switch (propertyName.hashCode()) {
        case 100346066:  // index
          return index;
        case 394230283:  // fixingCalendar
          return fixingCalendar;
        case -2129778896:  // startDate
          return startDate;
        case -1607727319:  // endDate
          return endDate;
      }
      return super.metaPropertyGet(propertyName);
    }

    @Override
    public OvernightAveragedDailyRateComputation.Builder builder() {
      return new OvernightAveragedDailyRateComputation.Builder();
    }

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

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

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

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

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

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

    //-----------------------------------------------------------------------
    @Override
    protected Object propertyGet(Bean bean, String propertyName, boolean quiet) {
      switch (propertyName.hashCode()) {
        case 100346066:  // index
          return ((OvernightAveragedDailyRateComputation) bean).getIndex();
        case 394230283:  // fixingCalendar
          return ((OvernightAveragedDailyRateComputation) bean).getFixingCalendar();
        case -2129778896:  // startDate
          return ((OvernightAveragedDailyRateComputation) bean).getStartDate();
        case -1607727319:  // endDate
          return ((OvernightAveragedDailyRateComputation) bean).getEndDate();
      }
      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 OvernightAveragedDailyRateComputation}.
   */
  public static final class Builder extends DirectFieldsBeanBuilder<OvernightAveragedDailyRateComputation> {

    private OvernightIndex index;
    private HolidayCalendar fixingCalendar;
    private LocalDate startDate;
    private LocalDate endDate;

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

    /**
     * Restricted copy constructor.
     * @param beanToCopy  the bean to copy from, not null
     */
    private Builder(OvernightAveragedDailyRateComputation beanToCopy) {
      this.index = beanToCopy.getIndex();
      this.fixingCalendar = beanToCopy.getFixingCalendar();
      this.startDate = beanToCopy.getStartDate();
      this.endDate = beanToCopy.getEndDate();
    }

    //-----------------------------------------------------------------------
    @Override
    public Object get(String propertyName) {
      switch (propertyName.hashCode()) {
        case 100346066:  // index
          return index;
        case 394230283:  // fixingCalendar
          return fixingCalendar;
        case -2129778896:  // startDate
          return startDate;
        case -1607727319:  // endDate
          return endDate;
        default:
          throw new NoSuchElementException("Unknown property: " + propertyName);
      }
    }

    @Override
    public Builder set(String propertyName, Object newValue) {
      switch (propertyName.hashCode()) {
        case 100346066:  // index
          this.index = (OvernightIndex) newValue;
          break;
        case 394230283:  // fixingCalendar
          this.fixingCalendar = (HolidayCalendar) newValue;
          break;
        case -2129778896:  // startDate
          this.startDate = (LocalDate) newValue;
          break;
        case -1607727319:  // endDate
          this.endDate = (LocalDate) newValue;
          break;
        default:
          throw new NoSuchElementException("Unknown property: " + propertyName);
      }
      return this;
    }

    @Override
    public Builder set(MetaProperty<?> property, Object value) {
      super.set(property, value);
      return this;
    }

    @Override
    public OvernightAveragedDailyRateComputation build() {
      return new OvernightAveragedDailyRateComputation(
          index,
          fixingCalendar,
          startDate,
          endDate);
    }

    //-----------------------------------------------------------------------
    /**
     * Sets the Overnight index.
     * <p>
     * The rate to be paid is based on this index.
     * It will be a well known market index such as 'GBP-SONIA'.
     * @param index  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder index(OvernightIndex index) {
      JodaBeanUtils.notNull(index, "index");
      this.index = index;
      return this;
    }

    /**
     * Sets the resolved calendar that the index uses.
     * @param fixingCalendar  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder fixingCalendar(HolidayCalendar fixingCalendar) {
      JodaBeanUtils.notNull(fixingCalendar, "fixingCalendar");
      this.fixingCalendar = fixingCalendar;
      return this;
    }

    /**
     * Sets the start date of the accrual period.
     * <p>
     * This is not necessarily a valid business day.
     * In this case, the first fixing date is the previous business day of the start date on {@code fixingCalendar}.
     * @param startDate  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder startDate(LocalDate startDate) {
      JodaBeanUtils.notNull(startDate, "startDate");
      this.startDate = startDate;
      return this;
    }

    /**
     * Sets the end date of the accrual period.
     * <p>
     * This is not necessarily a valid business day.
     * In this case, the last fixing date is the previous business day of the end date on {@code fixingCalendar}.
     * @param endDate  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder endDate(LocalDate endDate) {
      JodaBeanUtils.notNull(endDate, "endDate");
      this.endDate = endDate;
      return this;
    }

    //-----------------------------------------------------------------------
    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(160);
      buf.append("OvernightAveragedDailyRateComputation.Builder{");
      buf.append("index").append('=').append(JodaBeanUtils.toString(index)).append(',').append(' ');
      buf.append("fixingCalendar").append('=').append(JodaBeanUtils.toString(fixingCalendar)).append(',').append(' ');
      buf.append("startDate").append('=').append(JodaBeanUtils.toString(startDate)).append(',').append(' ');
      buf.append("endDate").append('=').append(JodaBeanUtils.toString(endDate));
      buf.append('}');
      return buf.toString();
    }

  }

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