/***************************************************************************
 *   Copyright (C) 2003 by Jonathan Singer                                             *
 *   jsinger@leeta.net                                                                                *
 *   Calendar routines from Hebrew Calendar by Frank Yellin                     *
 *                                                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or      *
 *   (at your option) any later version.                                                       *
 ***************************************************************************/
#include "converter.h"
#include <klocale.h>

Converter::Converter()
{

}

Converter::~Converter()
{
}

long Converter::absolute_from_gregorian(int year, int month, int day)
{
  int xyear, day_number;

  xyear = year - 1;
  day_number = day + 31 * (month - 1);
  if (month > 2)
      {
        day_number -= (23 + (4 * month)) / 10;
        if (gregorian_leap_year_p(year))
          day_number++;
      }
  return day_number +     /* the day number within the current year */
    365L * xyear +        /* days in prior years */
    (xyear / 4) +         /* Julian leap years */
    (-(xyear / 100)) +    /* deduct century years */
    (xyear / 400);        /* add Gregorian leap years */
}

/* Given a Hebrew date, calculate the number of days since
 * January 0, 0001, Gregorian
 */
long Converter::absolute_from_hebrew(int year, int month, int day)
{
  long sum = day + hebrew_elapsed_days(year) - 1373429L;
  int i;

  if (month < 7)
      {
        int months = hebrew_months_in_year(year);

        for (i = 7; i <= months; ++i)
          sum += hebrew_month_length(year, i);
        for (i = 1; i < month; ++i)
          sum += hebrew_month_length(year, i);
      }
  else
      {
        for (i = 7; i < month; ++i)
          sum += hebrew_month_length(year, i);
      }
  return sum;
}

/* Given an absolute date, calculate the gregorian date  */
void
  Converter::gregorian_from_absolute(long date, int *yearp,
                                     int *monthp, int *dayp)
{
  int year, month, day;

  for (year = date / 366;
       date >= absolute_from_gregorian(year + 1, 1, 1); ++year) ;
  for (month = 1;
       (month <= 11)
       && (date >= absolute_from_gregorian(year, 1 + month, 1));
       ++month ) ;
  day = 1 + date - absolute_from_gregorian(year, month, 1);
  *yearp = year;
  *monthp = month;
  *dayp = day;
}

/* Given an absolute date, calculate the Hebrew date */
void
  Converter::hebrew_from_absolute(long date, int *yearp, int *monthp,
                                  int *dayp)
{
  int year, month, day, gyear, gmonth, gday, months;

  gregorian_from_absolute(date, &gyear, &gmonth, &gday);
  year = gyear + 3760;
  while (date >= absolute_from_hebrew(1 + year, 7, 1))
    year++;
  months = hebrew_months_in_year(year);
  for (month = 7;
       date > absolute_from_hebrew(year, month,
                                   hebrew_month_length(year, month));
       month = 1 + (month % months)) ;
  day = 1 + date - absolute_from_hebrew(year, month, 1);
  *yearp = year;
  *monthp = month;
  *dayp = day;
}

/* Number of months in a Hebrew year */
int Converter::hebrew_months_in_year(int year)
{
  if (hebrew_leap_year_p(year))
    return 13;
  else
    return 12;
}

enum
{ Nissan =
    1, Iyar, Sivan, Tamuz, Ab, Elul, Tishrei, Cheshvan, Kislev, Tevet,
  Shvat, Adar, AdarII, AdarI = 12
};

enum
{ January =
    1, February, March, April, May, June, July, August, September,
  October, November, December
};

/* Number of days in a Hebrew month */
int Converter::hebrew_month_length(int year, int month)
{
  switch (month)
      {
      case Tishrei:
      case Shvat:
      case Nissan:
      case Sivan:
      case Ab:
        return 30;

      case Tevet:
      case Iyar:
      case Tamuz:
      case Elul:
      case AdarII:
        return 29;

      case Cheshvan:
        // 29 days, unless it's a long year.
        if ((hebrew_year_length(year) % 10) == 5)
          return 30;
        else
          return 29;

      case Kislev:
        // 30 days, unless it's a short year.
        if ((hebrew_year_length(year) % 10) == 3)
          return 29;
        else
          return 30;

      case Adar:
        // Adar (non-leap year) has 29 days.  Adar I has 30 days.
        if (hebrew_leap_year_p(year))
          return 30;
        else
          return 29;

      default:
        return 0;
      }
}

/* Number of days in a Julian or gregorian month */
int
  Converter::secular_month_length(int year,
                                  int month /*, bool julianp */ )
{
  switch (month)
      {
      case January:
      case March:
      case May:
      case July:
      case August:
      case October:
      case December:
        return 31;
      case April:
      case June:
      case September:
      case November:
        return 30;
      case February:
        if (gregorian_leap_year_p(year))
          return 29;
        else
          return 28;
      default:
        return 0;
      }
}

/* Is it a Leap year in the gregorian calendar */
bool Converter::gregorian_leap_year_p(int year)
{
  if ((year % 4) != 0)
    return 0;
  if ((year % 400) == 0)
    return 1;
  if ((year % 100) == 0)
    return 0;
  return 1;
}

/* Is it a leap year in the Jewish Calendar */
bool Converter::hebrew_leap_year_p(int year)
{
  switch (year % 19)
      {
      case 0:
      case 3:
      case 6:
      case 8:
      case 11:
      case 14:
      case 17:
        return 1;
      default:
        return 0;
      }
}

/* Return the number of days from 1 Tishrei 0001 to the beginning of the given year.
 * Since this routine gets called frequently with the same year arguments, we cache
 * the most recent values.
 */
#define MEMORY 5
long Converter::hebrew_elapsed_days(int year)
{
  static int saved_year[MEMORY] = { -1, -1, -1, -1, -1 };
  static long saved_value[MEMORY];
  int i;

  for (i = 0; i < MEMORY; ++i)
    if (year == saved_year[i])
      return saved_value[i];
  for (i = 0; i < MEMORY-1; ++i) {
    saved_year[i] = saved_year[1 + i];
    saved_value[i] = saved_value[1 + i];
  }
  saved_year[MEMORY - 1] = year;
  saved_value[MEMORY - 1] = hebrew_elapsed_days2(year);
  return saved_value[MEMORY - 1];
}

long Converter::hebrew_elapsed_days2(int year)
{
  long prev_year = year - 1;
  long months_elapsed = 235L * (prev_year / 19)  /* months in complete cycles so far */
    + 12L * (prev_year % 19)                     /* regular months in this cycle */
    + (((prev_year % 19) * 7 + 1) / 19);         /* leap months this cycle */
  long parts_elapsed = 5604 + 13753 * months_elapsed;
  long day = 1 + 29 * months_elapsed + parts_elapsed / 25920;
  long parts = parts_elapsed % 25920;
  int weekday = (day % 7);
  long alt_day = ((parts >= 19440)
                  || (weekday == 2 && (parts >= 9924)
                      && !hebrew_leap_year_p(year)) || (weekday == 1
                                                        && (parts >=
                                                            16789)
                                                        &&
                                                        hebrew_leap_year_p
                                                        (prev_year)))
    ? day + 1 : day;
  switch (alt_day % 7)
      {
      case 0:
      case 3:
      case 5:
        return 1 + alt_day;
      default:
        return alt_day;
      }
}

/* Number of days in the given Hebrew year */
int Converter::hebrew_year_length(int year)
{
  return hebrew_elapsed_days(1 + year) - hebrew_elapsed_days(year);
}

/* Fill in the DateResult structure based on the given secular date */
void
  Converter::SecularToHebrewConversion(int syear, int smonth,
                                       int sday,
                                       struct DateResult *result)
{
  int hyear, hmonth, hday;
  long absolute;

  absolute = absolute_from_gregorian(syear, smonth, sday);

  hebrew_from_absolute(absolute, &hyear, &hmonth, &hday);

  result->year = hyear;
  result->month = hmonth;
  result->day = hday;
  finish_up(absolute, hyear, hmonth, syear, smonth, result);
}

/* Fill in the DateResult structure based on the given Hebrew date */
void
  Converter::HebrewToSecularConversion(int hyear, int hmonth,
                                       int hday,
                                       struct DateResult *result)
{
  int syear, smonth, sday;
  long absolute;

  absolute = absolute_from_hebrew(hyear, hmonth, hday);
  gregorian_from_absolute(absolute, &syear, &smonth, &sday);
  result->year = hyear;
  result->month = hmonth;
  result->day = hday;
  finish_up(absolute, hyear, hmonth, syear, smonth, result);
}

/* This is common code for filling up the DateResult structure */
void
  Converter::finish_up(long absolute, int hyear, int hmonth,
                       int syear, int smonth,
                       struct DateResult *result)
{
  result->hebrew_month_length = hebrew_month_length(hyear, hmonth);
  result->secular_month_length = secular_month_length(syear, smonth);
  result->hebrew_leap_year_p = hebrew_leap_year_p(hyear);
  result->secular_leap_year_p = gregorian_leap_year_p(syear);
  result->kvia = (hebrew_year_length(hyear) % 10) - 3;
  // absolute is -1 on 1/1/0001 Julian
  result->day_of_week = (7 + absolute) % 7;
  result->hebrew_day_number =
    absolute - absolute_from_hebrew(hyear, 7, 1) + 1;

}