/* This implements a simplified calendar, for day-to-day
   uses. GDate compatibility is preserved as much as possible, 
   
   The code will not be stand-alone, and for some calendars (e.g. Islamic)
   will require an external table loaded at runtime.
*/   


#ifndef __G_INTL_DATE_H__
#define __G_INTL_DATE_H__

#include <glib/gdate.h>

/* make struct tm known without having to include time.h */
struct tm;

typedef guint32 GCalendarVersion;

typedef struct _GCalendar GCalendar;

typedef enum
{
  G_CALENDAR_GREGORIAN = 0,
  G_CALENDAR_BUDDHIST,
  G_CALENDAR_CHINESE,
  G_CALENDAR_COPTIC,
  G_CALENDAR_ETHIOPIC,
  G_CALENDAR_HEBREW,
  G_CALENDAR_ISLAMIC, /* The observational/astronomical lunar calendar */
  G_CALENDAR_ISLAMIC_CIVIL, /* The arithmetic lunar calendar */
  G_CALENDAR_JAPANESE, /* Not the lunisolar calendar, but gregorian with emperor eras */
  G_CALENDAR_PERSIAN,
} GCalendarType;

struct _GCalendar
{
  GCalendarType calendar_type;
  const gchar  *territory; /* ISO 8601 or UN M.49 code, possibly appended by subterritory codes */
  gpointer      priv; /* for keeping other differences like the islamic calendar's location */
};


GCalendar*       g_calendar_new           (void);
/* Territory will be set to the calendar type's default territory */
GCalendar*       g_calendar_new_type      (GCalendarType     calendar_type);
/* Type will be set to territory's default calendar type, territory will be normalized, g_strdup-ed if not changing */
GCalendar*       g_calendar_new_territory (const gchar      *territory);
GCalendar*       g_calendar_new_full      (GCalendarType     calendar_type,
                                           const gchar      *territory);
void             g_calendar_free          (GCalendar        *calendar);

void             g_calendar_clear         (GCalendar        *calendar,
                                           guint             n_calendars);

void             g_calendar_set_type      (GCalendar        *calendar,
                                           GCalendarType     calendar_type);
/* territory will be normalized, g_strdup-ed if not changing */
void             g_calendar_set_territory (GCalendar        *calendar,
                                           const gchar      *territory);
void             g_calendar_set_full      (GCalendar        *calendar,
                                           GCalendarType     calendar_type,
                                           const gchar      *territory);

/* The information set by this will be used only if the calendar is localation-dependant (like Islamic) and the
 * territory does not have a national observance practice. If not set, the territory's
 * default location or its most populus location is used.
 *  
 *   latitude: angular distance from equator, in degrees. positive for northern hemisphere.
 *   longiture: angular distance from the Greenwich meridian, in degrees. positive for eastern hemisphere.
 *   elevation: elevation above sea level, in meters.
 *   timezone:  Olson timezone ID, like "Asia/Tokyo". Will be g_strdup-ed. Can be NULL.
 *
 * We should also probably define a constant for converstion between meters and feet.
 */
void             g_calendar_set_location  (GCalendar       *calendar,
                                           gfloat           latitude,
                                           gfloat           longitude,
                                           gfloat           elevation,
                                           const gchar     *timezone);
void             g_calendar_set_latitude  (GCalendar       *calendar,
                                           gfloat           latitude);
void             g_calendar_set_longitude (GCalendar       *calendar,
                                           gfloat           longitude);
void             g_calendar_set_elevation (GCalendar       *calendar,
                                           gfloat           elevation);
void             g_calendar_set_timezone  (GCalendar       *calendar,
                                           const gchar     *timezone);

#define G_METER_PER_FOOT 3.280839895013123359580052493438

gboolean         g_calendar_valid_type    (GCalendarType    calendar_type) G_GNUC_CONST;

GCalendarType    g_calendar_get_type      (const GCalendar *calendar);
const gchar*     g_calendar_get_territory (const GCalendar *calendar);
/* The output of these four functions is undefined if the value is not explicitly set.
   Different values from those set may be returned if the values are not used */
gfloat           g_calendar_get_latitude  (const GCalendar *calendar);
gfloat           g_calendar_get_longitude (const GCalendar *calendar);
gfloat           g_calendar_get_elevation (const GCalendar *calendar);
const gchar*     g_calendar_get_timezone  (const GCalendar *timezone);

/* Localized name of calendar */
const gchar*     g_calendar_get_name      (GCalendarType    calendar_type);

/* The version of algorithm or data file used to compute dates
 * in the calendar. Will have the same format as the DNS records names,
 * e.g. 2001020304 for the fourth update in the day 3 February 2001.
 *
 * The update number is necessary for the islamic calendar, when news
 * of observance of new moons of Ramadan and Shawwal arrive from different
 * territories and the data file is updated (possibly hourly) from a server.
 */
GCalendarVersion g_calendar_get_version   (const GCalendar *calendar);

/* The smallest date and latest the calendar supports (version-dependent).
   Note that some values, while in the range, may still return UNKNOWN fields. */
guint32          g_calendar_get_min_day   (const GCalendar *calendar);
guint32          g_calendar_get_max_day   (const GCalendar *calendar);


typedef guint8  GIntlDateDay;
typedef guint8  GIntlDateMonth;
typedef guint16 GIntlDateYear;
typedef guint8  GIntlDateEra;

typedef GDateWeekDay GIntlDateWeekday;
#define G_INTL_DATE_BAD_WEEKDAY G_DATE_BAD_WEEKDAY
#define G_INTL_DATE_MONDAY      G_DATE_MONDAY
#define G_INTL_DATE_TUESDAY     G_DATE_TUESDAY
#define G_INTL_DATE_WEDNESDAY   G_DATE_WEDNESDAY
#define G_INTL_DATE_THURSDAY    G_DATE_THURSDAY
#define G_INTL_DATE_FRIDAY      G_DATE_FRIDAY
#define G_INTL_DATE_SATURDAY    G_DATE_SATURDAY
#define G_INTL_DATE_SUNDAY      G_DATE_SUNDAY

#define G_INTL_DATE_BAD_JULIAN 0U
#define G_INTL_DATE_BAD_DAY    0U
#define G_INTL_DATE_BAD_MONTH  0U
#define G_INTL_DATE_BAD_YEAR   0U
/* Because era numbers can be (and usually are) zero */
#define G_INTL_DATE_BAD_ERA    G_MAXUINT8

#define G_INTL_DATE_UNKNOWN_JULIAN G_MAXUINT32
#define G_INTL_DATE_UNKNOWN_DAY    G_MAXUINT8
#define G_INTL_DATE_UNKNOWN_MONTH  G_MAXUINT8
#define G_INTL_DATE_UNKNOWN_YEAR   G_MAXUINT16
#define G_INTL_DATE_UNKNOWN_ERA   (G_MAXUINT8-1)

typedef struct _GIntlDate GIntlDate;

struct _GIntlDate
{
  const GCalendar *calendar;
  guint32    julian_days;
  
  gboolean julian; /* julian is valid */
  gboolean dmy;    /* dmye is valid */
  
  GIntlDateDay   day;
  GIntlDateMonth month;
  GIntlDateYear  year;
  GIntlDateEra   era;
};

GIntlDate*   g_intl_date_new              (const GCalendar *calendar);
/* We call this 'dmy' instead of 'dmye' to make migration from GDate a little easier */
GIntlDate*   g_intl_date_new_dmy          (const GCalendar *calendar,
                                           GIntlDateDay     day,
                                           GIntlDateMonth   month,
                                           GIntlDateYear    year,
                                           GIntlDateEra     era);
GIntlDate*   g_intl_date_new_julian       (const GCalendar *calendar,
                                           guint32          julian_day);
void         g_intl_date_free             (GIntlDate       *date);

gboolean     g_intl_date_valid            (const GIntlDate *date);
gboolean     g_intl_date_valid_day        (const GCalendar *calendar,
                                           GIntlDateDay     day);
gboolean     g_intl_date_valid_month      (const GCalendar *calendar,
                                           GIntlDateMonth   month);
gboolean     g_intl_date_valid_year       (const GCalendar *calendar,
                                           GIntlDateYear    year);
gboolean     g_intl_date_valid_era        (const GCalendar *calendar,
                                           GIntlDateEra     era);
#define      g_intl_date_valid_weekday(cal, wd) \
                                           g_date_valid_weekday (wd)
gboolean     g_intl_date_valid_julian     (const GCalendar *calendar,
                                           guint32          julian_date);
gboolean     g_intl_date_valid_dmy        (const GCalendar *calendar,
                                           GIntlDateDay     day,
                                           GIntlDateMonth   month,
                                           GIntlDateYear    year,
                                           GIntlDateEra     era);

GIntlDateWeekday g_intl_date_get_weekday           (const GIntlDate *date);
GIntlDateDay     g_intl_date_get_day               (const GIntlDate *date);
GIntlDateMonth   g_intl_date_get_month             (const GIntlDate *date);
GIntlDateYear    g_intl_date_get_year              (const GIntlDate *date);
GIntlDateEra     g_intl_date_get_era               (const GIntlDate *date);
guint32          g_intl_date_get_julian            (const GIntlDate *date);
guint            g_intl_date_get_day_of_year       (const GIntlDate *date);

/* We may also have a function that allocates the GDate */
void             g_intl_date_get_gdate             (const GIntlDate *date,
                                                    GDate           *gdate);

/* GDate's get_week_of_year functions are not supported with GIntlDate, as they don't necessarily
   make sense for non-Gregorian calendars */

void         g_intl_date_clear            (GIntlDate     *date,
                                           guint          n_dates);


void         g_intl_date_set_calendar     (GIntlDate       *date,
                                           const GCalendar *calendar);
/* Before calling these functions, g_intl_date_set_calendar must be called */
void         g_intl_date_set_parse        (GIntlDate       *date,
                                           const gchar     *str);
void         g_intl_date_set_time         (GIntlDate       *date,
                                           GTime            time_);
void         g_intl_date_set_gdate        (GIntlDate       *date,
                                           const GDate     *gdate);
void         g_intl_date_set_day          (GIntlDate       *date,
                                           GIntlDateDay     day);
void         g_intl_date_set_month        (GIntlDate       *date,
                                           GIntlDateMonth   month);
void         g_intl_date_set_year         (GIntlDate       *date,
                                           GIntlDateYear    year);
void         g_intl_date_set_era          (GIntlDate       *date,
                                           GIntlDateEra     era);
void         g_intl_date_set_dmy          (GIntlDate       *date,
                                           GIntlDateDay     day,
                                           GIntlDateMonth   month,
                                           GIntlDateYear    year,
                                           GIntlDateEra     era);
void         g_intl_date_set_julian       (GIntlDate       *date,
                                           guint32          julian_date);

/* Perhaps not very useful, here just for compatibility with GDate */
gboolean     g_intl_date_is_first_of_month     (const GIntlDate *date);
gboolean     g_intl_date_is_last_of_month      (const GIntlDate *date);


void         g_intl_date_add_days              (GIntlDate   *date,
                                                guint        n_days);
void         g_intl_date_subtract_days         (GIntlDate   *date,
                                                guint        n_days);
/* The day may change if the same day number does not exist in the new month */
void         g_intl_date_add_months            (GIntlDate   *date,
                                                guint        n_months);
void         g_intl_date_subtract_months       (GIntlDate   *date,
                                                guint        n_months);
/* The day and/or month may change if the same day and/or month do not exist in the new year */
void         g_intl_date_add_years             (GIntlDate   *date,
                                                guint        n_years);
void         g_intl_date_subtract_years        (GIntlDate   *date,
                                                guint        n_years);

gboolean     g_intl_date_is_leap_year          (const GCalendar *calendar,
                                                GIntlDateYear    year);
guint8       g_intl_date_get_days_in_month     (const GCalendar *calendar,
                                                GIntlDateMonth   month,
                                                GIntlDateYear    year);

gint         g_intl_date_days_between          (const GIntlDate *date1,
                                        					   const GIntlDate *date2);

/* qsort-friendly (with a cast...) */
gint         g_intl_date_compare               (const GIntlDate *lhs,
                                                const GIntlDate *rhs);

void         g_intl_date_clamp                 (GIntlDate *date,
                                        					   const GIntlDate *min_date,
                                        					   const GIntlDate *max_date);
void         g_intl_date_order                 (GIntlDate *date1, GIntlDate *date2);


void         g_intl_date_to_struct_tm     (const GIntlDate *date,
                                           struct tm   *tm);

/* Like g_date_strftime but using CLDR format instead of strftime format */
gsize        g_intl_date_format           (gchar       *s,
                                           gsize        slen,
                                           const gchar *format,
                                           const GIntlDate *date);

#endif /* __G_INTL_DATE_H__ */
