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

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

import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.TypedMetaBean;
import org.joda.beans.gen.BeanDefinition;
import org.joda.beans.gen.PropertyDefinition;
import org.joda.beans.impl.direct.DirectFieldsBeanBuilder;
import org.joda.beans.impl.direct.MinimalMetaBean;

import com.google.common.collect.ComparisonChain;

/**
 * One possible date for swaption exercise, resolved for pricing.
 * <p>
 * This is the resolved form of {@link SwaptionExercise} and is an input to the pricers.
 * <p>
 * This class is bound to data that changes over time, such as holiday calendars.
 * If the data changes, such as the addition of a new holiday, the resolved form will not be updated.
 * Care must be taken when placing the resolved form in a cache or persistence layer.
 */
@BeanDefinition(style = "minimal", factoryName = "of")
public final class SwaptionExerciseDate
    implements Comparable<SwaptionExerciseDate>, ImmutableBean, Serializable {

  /**
   * The adjusted exercise date.
   * <p>
   * This date has been adjusted to be a business day.
   */
  @PropertyDefinition(validate = "notNull")
  private final LocalDate exerciseDate;
  /**
   * The unadjusted exercise date.
   * <p>
   * This date may be a non-business day.
   */
  @PropertyDefinition(validate = "notNull")
  private final LocalDate unadjustedExerciseDate;
  /**
   * The adjusted swap start date.
   * <p>
   * This date has been adjusted to be a business day.
   */
  @PropertyDefinition(validate = "notNull")
  private final LocalDate swapStartDate;

  //-------------------------------------------------------------------------
  @Override
  public int compareTo(SwaptionExerciseDate other) {
    return ComparisonChain.start()
        .compare(unadjustedExerciseDate, other.unadjustedExerciseDate)
        .compare(swapStartDate, other.swapStartDate)
        .result();
  }

  //------------------------- AUTOGENERATED START -------------------------
  /**
   * The meta-bean for {@code SwaptionExerciseDate}.
   */
  private static final TypedMetaBean<SwaptionExerciseDate> META_BEAN =
      MinimalMetaBean.of(
          SwaptionExerciseDate.class,
          new String[] {
              "exerciseDate",
              "unadjustedExerciseDate",
              "swapStartDate"},
          () -> new SwaptionExerciseDate.Builder(),
          b -> b.getExerciseDate(),
          b -> b.getUnadjustedExerciseDate(),
          b -> b.getSwapStartDate());

  /**
   * The meta-bean for {@code SwaptionExerciseDate}.
   * @return the meta-bean, not null
   */
  public static TypedMetaBean<SwaptionExerciseDate> meta() {
    return META_BEAN;
  }

  static {
    MetaBean.register(META_BEAN);
  }

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

  /**
   * Obtains an instance.
   * @param exerciseDate  the value of the property, not null
   * @param unadjustedExerciseDate  the value of the property, not null
   * @param swapStartDate  the value of the property, not null
   * @return the instance
   */
  public static SwaptionExerciseDate of(
      LocalDate exerciseDate,
      LocalDate unadjustedExerciseDate,
      LocalDate swapStartDate) {
    return new SwaptionExerciseDate(
      exerciseDate,
      unadjustedExerciseDate,
      swapStartDate);
  }

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

  private SwaptionExerciseDate(
      LocalDate exerciseDate,
      LocalDate unadjustedExerciseDate,
      LocalDate swapStartDate) {
    JodaBeanUtils.notNull(exerciseDate, "exerciseDate");
    JodaBeanUtils.notNull(unadjustedExerciseDate, "unadjustedExerciseDate");
    JodaBeanUtils.notNull(swapStartDate, "swapStartDate");
    this.exerciseDate = exerciseDate;
    this.unadjustedExerciseDate = unadjustedExerciseDate;
    this.swapStartDate = swapStartDate;
  }

  @Override
  public TypedMetaBean<SwaptionExerciseDate> metaBean() {
    return META_BEAN;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the adjusted exercise date.
   * <p>
   * This date has been adjusted to be a business day.
   * @return the value of the property, not null
   */
  public LocalDate getExerciseDate() {
    return exerciseDate;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the unadjusted exercise date.
   * <p>
   * This date may be a non-business day.
   * @return the value of the property, not null
   */
  public LocalDate getUnadjustedExerciseDate() {
    return unadjustedExerciseDate;
  }

  //-----------------------------------------------------------------------
  /**
   * Gets the adjusted swap start date.
   * <p>
   * This date has been adjusted to be a business day.
   * @return the value of the property, not null
   */
  public LocalDate getSwapStartDate() {
    return swapStartDate;
  }

  //-----------------------------------------------------------------------
  /**
   * 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()) {
      SwaptionExerciseDate other = (SwaptionExerciseDate) obj;
      return JodaBeanUtils.equal(exerciseDate, other.exerciseDate) &&
          JodaBeanUtils.equal(unadjustedExerciseDate, other.unadjustedExerciseDate) &&
          JodaBeanUtils.equal(swapStartDate, other.swapStartDate);
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    hash = hash * 31 + JodaBeanUtils.hashCode(exerciseDate);
    hash = hash * 31 + JodaBeanUtils.hashCode(unadjustedExerciseDate);
    hash = hash * 31 + JodaBeanUtils.hashCode(swapStartDate);
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(128);
    buf.append("SwaptionExerciseDate{");
    buf.append("exerciseDate").append('=').append(JodaBeanUtils.toString(exerciseDate)).append(',').append(' ');
    buf.append("unadjustedExerciseDate").append('=').append(JodaBeanUtils.toString(unadjustedExerciseDate)).append(',').append(' ');
    buf.append("swapStartDate").append('=').append(JodaBeanUtils.toString(swapStartDate));
    buf.append('}');
    return buf.toString();
  }

  //-----------------------------------------------------------------------
  /**
   * The bean-builder for {@code SwaptionExerciseDate}.
   */
  public static final class Builder extends DirectFieldsBeanBuilder<SwaptionExerciseDate> {

    private LocalDate exerciseDate;
    private LocalDate unadjustedExerciseDate;
    private LocalDate swapStartDate;

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

    /**
     * Restricted copy constructor.
     * @param beanToCopy  the bean to copy from, not null
     */
    private Builder(SwaptionExerciseDate beanToCopy) {
      this.exerciseDate = beanToCopy.getExerciseDate();
      this.unadjustedExerciseDate = beanToCopy.getUnadjustedExerciseDate();
      this.swapStartDate = beanToCopy.getSwapStartDate();
    }

    //-----------------------------------------------------------------------
    @Override
    public Object get(String propertyName) {
      switch (propertyName.hashCode()) {
        case -466830938:  // exerciseDate
          return exerciseDate;
        case -2091106995:  // unadjustedExerciseDate
          return unadjustedExerciseDate;
        case -71742755:  // swapStartDate
          return swapStartDate;
        default:
          throw new NoSuchElementException("Unknown property: " + propertyName);
      }
    }

    @Override
    public Builder set(String propertyName, Object newValue) {
      switch (propertyName.hashCode()) {
        case -466830938:  // exerciseDate
          this.exerciseDate = (LocalDate) newValue;
          break;
        case -2091106995:  // unadjustedExerciseDate
          this.unadjustedExerciseDate = (LocalDate) newValue;
          break;
        case -71742755:  // swapStartDate
          this.swapStartDate = (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 SwaptionExerciseDate build() {
      return new SwaptionExerciseDate(
          exerciseDate,
          unadjustedExerciseDate,
          swapStartDate);
    }

    //-----------------------------------------------------------------------
    /**
     * Sets the adjusted exercise date.
     * <p>
     * This date has been adjusted to be a business day.
     * @param exerciseDate  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder exerciseDate(LocalDate exerciseDate) {
      JodaBeanUtils.notNull(exerciseDate, "exerciseDate");
      this.exerciseDate = exerciseDate;
      return this;
    }

    /**
     * Sets the unadjusted exercise date.
     * <p>
     * This date may be a non-business day.
     * @param unadjustedExerciseDate  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder unadjustedExerciseDate(LocalDate unadjustedExerciseDate) {
      JodaBeanUtils.notNull(unadjustedExerciseDate, "unadjustedExerciseDate");
      this.unadjustedExerciseDate = unadjustedExerciseDate;
      return this;
    }

    /**
     * Sets the adjusted swap start date.
     * <p>
     * This date has been adjusted to be a business day.
     * @param swapStartDate  the new value, not null
     * @return this, for chaining, not null
     */
    public Builder swapStartDate(LocalDate swapStartDate) {
      JodaBeanUtils.notNull(swapStartDate, "swapStartDate");
      this.swapStartDate = swapStartDate;
      return this;
    }

    //-----------------------------------------------------------------------
    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(128);
      buf.append("SwaptionExerciseDate.Builder{");
      buf.append("exerciseDate").append('=').append(JodaBeanUtils.toString(exerciseDate)).append(',').append(' ');
      buf.append("unadjustedExerciseDate").append('=').append(JodaBeanUtils.toString(unadjustedExerciseDate)).append(',').append(' ');
      buf.append("swapStartDate").append('=').append(JodaBeanUtils.toString(swapStartDate));
      buf.append('}');
      return buf.toString();
    }

  }

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