/*
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2000 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id$ Date Author Changes 12/05/2000 Arnaud Blandin Added the support for
 * NotSupportedOperationException 11/07/2000 Arnaud Blandin Added a new constructor and setValues
 * method 11/02/2000 Arnaud Blandin Changed the constructor 26/10/2000 Arnaud Blandin Created
 */
package org.exolab.castor.types;

import org.exolab.castor.types.TimeDuration;
import org.exolab.castor.xml.ValidationException;

import java.text.ParseException;

import javax.naming.OperationNotSupportedException;

/**
 * The base class for recurring Duration types.
 * <p>
 * This base class contains all the time fields (including the time zone ones) and also the facets
 * period and duration
 * <p>
 * The validation of the time fields is done in the set methods and follows
 * <a href="http://www.iso.ch/markete/8601.pdf">the ISO8601 Date and Time Format</a>
 *
 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
 * @see RecurringDuration
 * @see Time
 * @see TimeInstant
 * @see TimePeriod
 * @deprecated since Castor 1.0.6 since type derived from this base class are not in any
 *             recommendation.
 */
public abstract class RecurringDurationBase implements java.io.Serializable {

  /** the period facet of this recurringDuration. */
  private TimeDuration _period = null;
  /** the duration facet of this recurringDuration. */
  private TimeDuration _duration = null;
  /**
   * the hour field of this recurringDuration. -1 means that the field has been omitted (cf section
   * 4.5 of ISO 8601)
   */
  private short _hour = 0;
  /**
   * the minute field of this recurringDuration. -1 means that the field has been omitted (cf
   * section 4.5 of ISO 8601)
   */
  private short _minute = 0;
  /**
   * the second field of this recurringDuration. -1 means that the field has been omitted (cf
   * section 4.5 of ISO 8601)
   */
  private short _second = 0;
  /** the millsecond field of this recurringDuration. */
  private short _millsecond = 0;
  /** the time zone hour field of this recurringDuration. */
  private short _zoneHour = 0;
  /** the time zone minute field of this recurringDuration. */
  private short _zoneMinute = 0;
  /** true if this recurringDuration is UTC related. */
  private boolean _utc = false;
  /** true if the time zone is negative. */
  private boolean _zoneNegative = false;
  /** true if the recurring duration is negative. */
  private boolean _isNegative = false;

  /**
   * No-arg constructor.
   */
  protected RecurringDurationBase() {
    // Nothing to do
  }

  /**
   * Returns a recurringDurationBase with the facets duration and period set.
   *
   * @param duration the TimeDuration representing the duration facet
   * @param period the TimeDuration reprensenting the period facet
   */
  protected RecurringDurationBase(TimeDuration duration, TimeDuration period) {
    try {
      this.setDuration(duration);
      this.setPeriodInternal(period);
    } catch (UnsupportedOperationException e) {
      String err = "Recurring Duration: " + e;
      throw new IllegalArgumentException(err);
    }
  }

  /**
   * returns a recurringDurationBase with the facets duration and period set up
   *
   * @param duration the String representing the duration facet
   * @param period the String reprensenting the period facet
   * @throws IllegalArgumentException this exception is thrown when the parameter strings are not
   *         corresponding to valid TimeDuration
   */
  protected RecurringDurationBase(String duration, String period) throws IllegalArgumentException {
    try {
      this.setDuration(TimeDuration.parseTimeDuration(duration));
      this.setPeriodInternal(TimeDuration.parseTimeDuration(period));
    } catch (Exception e) {
      String err = "In RecurringDurationBase: " + e;
      throw new IllegalArgumentException(err);
    }
  }

  /**
   * returns a recurringDurationBase with the facets duration and period set up but also the fields
   *
   * @param duration the String representing the duration facet
   * @param period the String reprensenting the period facet
   * @param values an array of shorts which contains the values of the fields
   * @throws IllegalArgumentException this exception is thrown when the values array is not of
   *         length 6.
   * @see #setValues
   */
  protected RecurringDurationBase(String duration, String period, short[] values)
      throws OperationNotSupportedException {
    new RecurringDuration(duration, period);
    if (values.length != 6) {
      throw new IllegalArgumentException("Wrong numbers of values");
    }
    this.setValues(values);
  }

  /**
   * set the period facet this recurringDuration this method is used to avoid calling the setPeriod
   * method of subclasses
   *
   * @param period the period to set
   */
  private void setPeriodInternal(TimeDuration period) {
    _period = period;
  }

  /**
   * set the period facet for this recurringDuration
   *
   * @param period the period to set
   * @throws OperationNotSupportedException this exception is thrown when changing the value of the
   *         period facet is not allowed
   */
  public void setPeriod(TimeDuration period) throws UnsupportedOperationException {
    setPeriodInternal(period);
  }

  /**
   * set the period facet for this recurringDuration
   *
   * @param period the period to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         period facet is not allowed
   */
  public void setPeriod(String period) throws UnsupportedOperationException {
    try {
      setPeriodInternal(TimeDuration.parseTimeDuration(period));
    } catch (ParseException e) {
      String err = "RecurringDuration, setPeriod: " + e;
      throw new IllegalArgumentException(err);
    }
  }

  /**
   * set the duration facet for this recurringDuration
   *
   * @param duration the period to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         duration facet is not allowed
   */
  public void setDuration(TimeDuration duration) throws UnsupportedOperationException {
    _duration = duration;
  }

  /**
   * set the duration facet for this recurringDuration
   *
   * @param duration the period to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         duration facet is not allowed
   */
  public void setDuration(String duration) throws UnsupportedOperationException {
    try {
      _duration = TimeDuration.parseTimeDuration(duration);
    } catch (ParseException e) {
      String err = "RecurringDuration, setDuration: " + e;
      throw new IllegalArgumentException(err);
    }
  }

  /**
   * set the hour field for this recurringDuration
   *
   * @param hour the hour to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         hour field is not allowed
   */
  public void setHour(short hour) throws UnsupportedOperationException {
    String err = "";
    if (hour > 23) {
      err = "the hour field (" + hour + ")must be strictly lower than 24";
      throw new IllegalArgumentException(err);
    }
    _hour = hour;
  }

  /**
   * set the minute field for this recurringDuration
   *
   * @param minute the minute to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         minute field is not allowed
   */
  public void setMinute(short minute) throws UnsupportedOperationException {
    String err = "";
    if (minute == -1 && _hour != -1) {
      err = "minute cannot be omitted if the previous field is not omitted.";
      throw new IllegalArgumentException(err);
    } else if (minute > 59) {
      err = "the minute field (" + minute + ") must be lower than 59";
      throw new IllegalArgumentException(err);
    }
    _minute = minute;
  }

  /**
   * set the second field for this recurringDuration
   *
   * @param second the second to set
   * @param millsecond the millisecond to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         second field is not allowed
   */
  public void setSecond(short second, short millsecond) throws UnsupportedOperationException {
    String err = "";
    if ((second == -1) && (_minute != -1)) {
      err = "second cannot be omitted if the previous field is not omitted.";
      throw new IllegalArgumentException(err);
    } else if (second > 60) {
      err = "the second field (" + second + ")must be lower than 60";
      throw new IllegalArgumentException(err);
    }
    _second = second;
    _millsecond = millsecond;
  }

  /**
   * set the time zone fields for this recurringDuration
   *
   * @param hour the time zone hour to set
   * @param minute the time zone minute to set
   * @throws UnsupportedOperationException this exception is thrown when changing the value of the
   *         time zone fields is not allowed
   */
  public void setZone(short hour, short minute) throws UnsupportedOperationException {
    String err = "";
    if (hour > 23) {
      err = "the zone hour field (" + hour + ")must be strictly lower than 24";
      throw new IllegalArgumentException(err);
    }
    _zoneHour = hour;

    if (minute > 59) {
      err = "the minute field (" + minute + ")must be lower than 59";
      throw new IllegalArgumentException(err);
    }
    _zoneMinute = minute;
  }

  /**
   * set all the fields by reading the values in an array
   *
   * @param values an array of shorts with the values the array is supposed to be of length 6 and
   *        ordered like that:
   *        <ul>
   *        <li>hour</li>
   *        <li>minute</li>
   *        <li>second</li>
   *        <li>millisecond</li>
   *        <li>zoneHour</li>
   *        <li>zoneMinute</li>
   *        </ul>
   * @throws UnsupportedOperationException this exception is thrown when changing the value of a
   *         time related field is not allowed
   */
  public void setValues(short[] values) throws UnsupportedOperationException {
    this.setHour(values[0]);
    this.setMinute(values[1]);
    this.setSecond(values[2], values[3]);
    this.setZone(values[4], values[5]);
  }

  /**
   * set the negative field to true
   */
  public void setNegative() {
    _isNegative = true;
  }

  /**
   * set the time zone negative field to true
   *
   * @throws UnsupportedOperationException this exception is thrown when changing the time zone
   *         fields is not allowed
   */
  public void setZoneNegative() throws UnsupportedOperationException {
    _zoneNegative = true;
  }

  /**
   * set the UTC field to true
   */
  public void setUTC() {
    _utc = true;
  }

  // Get methods
  public TimeDuration getPeriod() {
    return _period;
  }

  public TimeDuration getDuration() {
    return _duration;
  }

  public short getHour() {
    return _hour;
  }

  public short getMinute() {
    return _minute;
  }

  public short getSeconds() {
    return _second;
  }

  public short getMilli() {
    return _millsecond;
  }

  public short getZoneHour() {
    return _zoneHour;
  }

  public short getZoneMinute() {
    return _zoneMinute;
  }

  /**
   * returns an array of short with all the fields which describe a RecurringDurationBase
   *
   * @return an array of short with all the fields which describe a RecurringDurationBase
   */
  public short[] getValues() {
    short[] result = new short[6];
    result[0] = this.getHour();
    result[1] = this.getMinute();
    result[2] = this.getSeconds();
    result[3] = this.getMilli();
    result[4] = this.getZoneHour();
    result[5] = this.getZoneMinute();
    return result;
  } // getValues

  /**
   * return true if this recurring Duration type is UTC i.e if there is no time zone.
   *
   * @return true if this recurringDuration type is UTC else false.
   */
  public boolean isUTC() {
    _utc = _zoneHour == 0 && _zoneMinute == 0;
    return _utc;
  }

  public boolean isNegative() {
    return _isNegative;
  }

  public boolean isZoneNegative() {
    return _zoneNegative;
  }

  /**
   * {@inheritDoc} Override the java.lang.equals method
   * 
   * @see #equal
   */
  public boolean equals(Object object) {
    if (object instanceof RecurringDurationBase) {
      try {
        return equal((RecurringDurationBase) object);
      } catch (ValidationException e) {
        e.printStackTrace();
        return false;
      }
    }
    return false;
  }

  /**
   * Returns true if the present instance of Recurring Duration Base is equal to the parameter.
   * <p>
   * The equals relation is the following :
   * <tt>rd1 equals rd2 iff each field of rd1 is equal to the corresponding field of rd2 </tt>
   *
   * @param reccD the recurring duration to compare with the present instance
   * @return true if the present instance is equal to the parameter false if not
   */
  public boolean equal(RecurringDurationBase reccD) throws ValidationException {
    boolean result = false;
    if (!(this.getPeriod().equals(reccD.getPeriod()))
        || !(this.getDuration().equals(reccD.getDuration()))) {
      String err = "Recurring Duration which have different values "
          + "for the duration and period can not be compared";
      throw new ValidationException(err);
    }

    result = (this.getHour() == reccD.getHour());
    result = result && (this.getMinute() == reccD.getMinute());
    result = result && (this.getSeconds() == reccD.getSeconds());
    result = result && (this.getMilli() == reccD.getMilli());
    result = result && (this.isNegative() == this.isNegative());
    if (!reccD.isUTC()) {
      result = result && (!this.isUTC());
      result = result && (this.getZoneHour() == reccD.getZoneHour());
      result = result && (this.getZoneMinute() == reccD.getZoneMinute());
    }
    return result;
  }// equals

  /**
   * Returns true if the present instance of RecurringDurationBase is greater than the parameter
   * <p>
   * Note : the order relation follows the W3C XML Schema draft i.e <tt>rd1 < rd2 iff rd2-rd1>0</tt>
   *
   * @param reccD the recurring duration base to compare with the present instance
   * @return true if the present instance is the greatest, false if not
   */
  public boolean isGreater(RecurringDurationBase reccD) throws ValidationException {
    if (!(this.getPeriod().equals(reccD.getPeriod()))
        || !(this.getDuration().equals(reccD.getDuration()))) {
      String err = "Recurring Duration which have different values "
          + "for the duration and period can not be compared";
      throw new ValidationException(err);
    }

    boolean result = false;
    short[] val_this = this.getValues();
    short[] val_reccD = reccD.getValues();
    for (int i = 0; result != true || i < val_this.length; i++) {
      result = val_this[i] > val_reccD[i];
      if (val_this[i] < val_reccD[i]) {
        return false;
      }
    }
    return result;
  } // isGreater

}// -- RecurringDurationBase
