// -*- c++ -*-

/*
 * Jakelib2 - General purpose C++ library
 * Copyright (C) 2001 Florian Wolff  (florian@donuz.de)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: Calendar.h,v 1.7 2003/09/20 09:06:52 florian Exp $
 */

#ifndef INCLUDE_JAKELIB2_UTIL_CALENDAR_H
# define INCLUDE_JAKELIB2_UTIL_CALENDAR_H

namespace jakelib {
  namespace util {
    class Date;
    class Locale;
    class Locales;
    class TimeZone;

    /**
     *
     * @class Calendar
     * @version $Id: Calendar.h,v 1.7 2003/09/20 09:06:52 florian Exp $
     * @author Florian 'Overflo' Wolff (florian@donuz.de)
     */
    class Calendar : public jakelib::lang::Object
    {
    public:

      typedef enum {
        /** Constant representing the era time field. */
        ERA,

        /** Constant representing the year time field. */
        YEAR,

        /** Constant representing the month time field. This field
            should contain one of the JANUARY (= \c 0), ..., DECEMBER (= \c 11)
            constants below. */
        MONTH,

        /** Constant representing the week of the year field. */
        WEEK_OF_YEAR,

        /** Constant representing the week of the month time field. */
        WEEK_OF_MONTH,

        /** Constant representing the day time field, synonym for DAY_OF_MONTH. */
        DATE,

        /** Constant representing the day time field. */
        DAY_OF_MONTH = DATE,

        /** Constant representing the day of year time field. This is
            1 for the first day in the year. */
        DAY_OF_YEAR,

        /** Constant representing the day of week time field.  This field
            should contain one of the SUNDAY,...,SATURDAY constants below. */
        DAY_OF_WEEK,

        /** Constant representing the day-of-week-in-month field.  For
            instance this field contains 2 for the second thursday in a
            month.  If you give a negative number here, the day will count
            from the end of the month. */
        DAY_OF_WEEK_IN_MONTH,

        /** Constant representing the part of the day for 12-hour clock.  This
            should be one of AM or PM.  */
        AM_PM,

        /** Constant representing the hour time field for 12-hour clock. */
        HOUR,

        /** Constant representing the hour of day time field for 24-hour clock. */
        HOUR_OF_DAY,

        /** Constant representing the minute of hour time field. */
        MINUTE,

        /** Constant representing the second time field. */
        SECOND,

        /** Constant representing the millisecond time field. */
        MILLISECOND,

        /** Constant representing the time zone offset time field for the
            time given in the other fields.  It is measured in
            milliseconds.  The default is the offset of the time zone. */
        ZONE_OFFSET,

        /** Constant representing the daylight saving time offset in
            milliseconds.  The default is the value given by the time zone. */
        DST_OFFSET
      } Field;

      /**
       * Number of time fields.
       */
      static const jint FIELD_COUNT;

      typedef enum {
        /** Constant representing AM in \c AM_PM field. */
        AM = 0,

        /** Constant representing PM in \c AM_PM field. */
        PM = 1
      } AmPmType;

      typedef enum {
        /** Constant representing Sunday. */
        SUNDAY = 1,
        /** Constant representing Monday. */
        MONDAY = 2,
        /** Constant representing Tuesday. */
        TUESDAY = 3,
        /** Constant representing Wednesday. */
        WEDNESDAY = 4,
        /** Constant representing Thursday. */
        THURSDAY = 5,
        /** Constant representing Friday. */
        FRIDAY = 6,
        /** Constant representing Saturday. */
        SATURDAY = 7
      } DayOfWeek;

      typedef enum {
        /** Constant representing January.  */
        JANUARY,
        /** Constant representing February. */
        FEBRUARY,
        /** Constant representing March. */
        MARCH,
        /** Constant representing April. */
        APRIL,
        /** Constant representing May. */
        MAY,
        /** Constant representing June. */
        JUNE,
        /** Constant representing July. */
        JULY,
        /** Constant representing August. */
        AUGUST,
        /** Constant representing September. */
        SEPTEMBER,
        /** Constant representing October. */
        OCTOBER,
        /** Constant representing November. */
        NOVEMBER,
        /** Constant representing December. */
        DECEMBER
      } Month;

    public:
      /**
       * Creates a calendar representing the actual time, using the default
       * time zone and locale.
       */
      static Calendar* getInstance();

      /**
       * Gets the set of locales for which a Calendar is available.
       * @exception MissingResourceException if locale data couldn't be found.
       * @return the set of locales.
       */
      static jakelib::util::Locales* getAvailableLocales();

      /**
       * Converts the time represented by this object to a
       * <code>Date</code>-Object.
       * @return the Date.
       */
      jakelib::util::Date* getTime();

      /**
       * Sets this Calendar's time to the given Date.  All time fields
       * are invalidated by this method.
       */
      void setTime(jakelib::util::Date* date);

      /**
       * Returns the time represented by this Calendar.
       * @return the time in milliseconds since the epoch.
       */
      jlong getTimeInMillis();

      /**
       * Sets this Calendar's time to the given Time.  All time fields
       * are invalidated by this method.
       * @param time the time in milliseconds since the epoch
       */
      void setTimeInMillis(jlong time);

      /**
       * Gets the value of the specified field.  They are recomputed
       * if they are invalid.
       * @param field the time field. One of the time field constants.
       * @return the value of the specified field
       */
      jint get(Field field);

      /**
       * Gets the value of the specified field. This method doesn't 
       * recompute the fields, if they are invalid.
       * @param field the time field. One of the time field constants.
       * @return the value of the specified field, undefined if
       * <code>areFieldsSet</code> or <code>isSet[field]</code> is false.
       */
      jint internalGet(Field field);

      /**
       * Sets the time field with the given value. This does invalidate
       * the time in milliseconds.
       *
       * @param field the time field. One of the time field constants
       * @param value the value to be set.
       */
      void set(Field field, jint value);

      /**
       * Sets the fields for year, month, and date.
       * Previous values of other fields are not altered.
       *
       * @param year the year.
       * @param month the month, one of the constants \c JANUARY, .., \c DECEMBER.
       * @param date the day of the month starting with \c 1
       */
      void set(jint year, jint month, jint date);

      /**
       * Sets the fields for year, month, date, hour, and minute
       * Previous values of other fields are not altered.
       *
       * @param year the year.
       * @param month the month, one of the constants \c JANUARY .. \c DECEMBER.
       * @param date the day of the month
       * @param hour the hour of day.
       * @param minute the minute.
       */
      void set(jint year, jint month, jint date, jint hour, jint minute);

      /**
       * Sets the fields for year, month, date, hour, and minute
       * Previous values of other fields are not altered.
       *
       * @param year the year.
       * @param month the month, one of the constants \c JANUARY .. \c DECEMBER.
       * @param date the day of the month
       * @param hour the hour of day.
       * @param minute the minute.
       * @param second the second.
       */
      void set(jint year, jint month, jint date,
               jint hour, jint minute, jint second);

      /**
       * Clears the values of all the time fields.
       */
      void clear();

      /**
       * Clears the values of the specified time field.
       * @param field the time field. One of the time field constants.
       */
      void clear(Field field);

      /**
       * Determines if the specified field has a valid value.
       * @return true if the specified field has a value.
       */
      jboolean isFieldSet(Field Field);

      /**
       * Compares the given calendar with this.  
       * @param o the object to that we should compare.
       * @return true, if the given object is a calendar, that represents
       * the same time (but doesn't necessary have the same fields).
       */
      jboolean equals(jakelib::lang::Object* o);

      /**
       * Returns a hash code for this calendar.
       * @return a hash code, which fullfits the general contract of 
       * <code>hashCode()</code>
       */
      jint hashCode();

      /**
       * Compares the given calendar with this.  
       * @param o the object to that we should compare.
       * @return true, if the given object is a calendar, and this calendar
       * represents a smaller time than the calendar o.
       * @exception ClassCastException if o is not an calendar.
       */
      jboolean before(jakelib::lang::Object* o);

      /**
       * Compares the given calendar with this.  
       * @param o the object to that we should compare.
       * @return true, if the given object is a calendar, and this calendar
       * represents a bigger time than the calendar o.
       * @exception ClassCastException if o is not an calendar.
       */
      jboolean after(jakelib::lang::Object* o);

      /**
       * Adds the specified amount of time to the given time field.  The
       * amount may be negative to subtract the time.  If the field overflows
       * it does what you expect: Jan, 25 + 10 Days is Feb, 4.
       * @param field the time field. One of the time field constants.
       * @param amount the amount of time.
       */
      virtual void add(Field field, jint amount) = 0;

      /**
       * Rolls the specified time field up or down.  This means add one
       * to the specified field, but don't change the other fields.  If
       * the maximum for this field is reached, start over with the 
       * minimum value.  <br>
       *
       * <strong>Note:</strong> There may be situation, where the other
       * fields must be changed, e.g rolling the month on May, 31. 
       * The date June, 31 is automatically converted to July, 1.
       * @param field the time field. One of the time field constants.
       * @param up the direction, true for up, false for down.
       */
      virtual void roll(Field field, jboolean up) = 0;

      /**
       * Rolls up or down the specified time field by the given amount.
       * A negative amount rolls down.  The default implementation is
       * call <code>roll(int, boolean)</code> for the specified amount.
       *
       * Subclasses should override this method to do more intuitiv things.
       *
       * @param field the time field. One of the time field constants.
       * @param amount the amount to roll by, positive for rolling up,
       * negative for rolling down.  
       */
      void roll(Field field, jint amount);

      /**
       * Specifies if the date/time interpretation should be lenient.
       * If the flag is set, a date such as "February 30, 1996" will be
       * treated as the 29th day after the February 1.  If this flag
       * is false, such dates will cause an exception.
       * @param lenient true, if the date should be interpreted linient,
       * false if it should be interpreted strict.
       */
      void setLenient(jboolean lenient);

      /**
       * Tells if the date/time interpretation is lenient.
       * @return true, if the date should be interpreted linient,
       * false if it should be interpreted strict.
       */
      jboolean isLenient();

      /**
       * Sets what the first day of week is.  This is used for
       * WEEK_OF_MONTH and WEEK_OF_YEAR fields. 
       * @param value the first day of week.  One of SUNDAY to SATURDAY.
       */
      void setFirstDayOfWeek(jint value);

      /**
       * Gets what the first day of week is.  This is used for
       * WEEK_OF_MONTH and WEEK_OF_YEAR fields. 
       * @return the first day of week.  One of SUNDAY to SATURDAY.
       */
      jint getFirstDayOfWeek();

      /**
       * Sets how many days are required in the first week of the year.
       * If the first day of the year should be the first week you should
       * set this value to 1.  If the first week must be a full week, set
       * it to 7.
       * @param value the minimal days required in the first week.
       */
      void setMinimalDaysInFirstWeek(jint value);

      /**
       * Gets how many days are required in the first week of the year.
       * @return the minimal days required in the first week.
       * @see #setMinimalDaysInFirstWeek
       */
      jint getMinimalDaysInFirstWeek();

      /**
       * Gets the smallest value that is allowed for the specified field.
       * @param field the time field. One of the time field constants.
       * @return the smallest value.
       */
      virtual jint getMinimum(Field field) = 0;

      /**
       * Gets the biggest value that is allowed for the specified field.
       * @param field the time field. One of the time field constants.
       * @return the biggest value.
       */
      virtual jint getMaximum(Field field) = 0;


      /**
       * Gets the greatest minimum value that is allowed for the specified field.
       * @param field the time field. One of the time field constants.
       * @return the greatest minimum value.
       */
      virtual jint getGreatestMinimum(Field field) = 0;

      /**
       * Gets the smallest maximum value that is allowed for the
       * specified field.  For example this is 28 for DAY_OF_MONTH.
       * @param field the time field. One of the time field constants.
       * @return the least maximum value.  
       */
      virtual jint getLeastMaximum(Field field) = 0;

      /**
       * Gets the actual minimum value that is allowed for the specified field.
       * This value is dependent on the values of the other fields.
       * @param field the time field. One of the time field constants.
       * @return the actual minimum value.
       */
      virtual jint getActualMinimum(Field field) = 0;

      /**
       * Gets the actual maximum value that is allowed for the specified field.
       * This value is dependent on the values of the other fields.
       * @param field the time field. One of the time field constants.
       * @return the actual maximum value.  
       */
      virtual jint getActualMaximum(Field field) = 0;

      /**
       * Returns a string representation of this object.  It is mainly
       * for debugging purposes and its content is implementation
       * specific.
       */
      jakelib::lang::String* toString();

    protected:

      /**
       * Constructs a new Calendar.
       */
      Calendar();

      ~Calendar();

      /**
       * Fills any unset fields in the time field list
       * @return true if the specified field has a value.  
       */
      void complete();

      /**
       * Converts the time field values (<code>fields</code>) to
       * milliseconds since the epoch UTC (<code>time</code>).  Override
       * this method if you write your own Calendar.  */
      virtual void computeTime() = 0;

      /**
       * Converts the milliseconds since the epoch UTC
       * (<code>time</code>) to time fields
       * (<code>fields</code>). Override this method if you write your
       * own Calendar.  
       */
      virtual void computeFields() = 0;

      /**
       * The time fields.  The array is indexed by the constants YEAR to
       * DST_OFFSET.
       */
      jint* fields;

      /**
       * The flags which tell if the fields above have a value.
       */
      jboolean* fieldSet;

      /** The time in milliseconds since the epoch (Jan 1st, 1970). */
      jlong time;

      /**
       * Tells if the above field has a valid value.
       */
      jboolean isTimeSet;

      /** Tells if the fields have a valid value.  This superseeds the isSet
          array. */
      jboolean areFieldsSet;

    private:

      /**
       * Specifies if the date/time interpretation should be lenient.
       * If the flag is set, a date such as "February 30, 1996" will be
       * treated as the 29th day after the February 1.  If this flag
       * is false, such dates will cause an exception.
       */
      jboolean lenient;

      /**
       * Sets what the first day of week is.  This is used for
       * WEEK_OF_MONTH and WEEK_OF_YEAR fields. 
       */
      jint firstDayOfWeek;

      /**
       * Sets how many days are required in the first week of the year.
       * If the first day of the year should be the first week you should
       * set this value to 1.  If the first week must be a full week, set
       * it to 7.
       */
      jint minimalDaysInFirstWeek;

      void init();

      //jakelib::lang::Strings* fieldNames;


      JAKELIB_DECLARE_CLASS(Calendar);
    };
  }
}
#endif
