/*
    Copyright (c) 2003 Hans Petter Bieker <bieker@kde.org>
    Copyright 2007, 2009, 2010 John Layt <john@layt.net>
        Calendar conversion routines based on Hdate v6, by Amos
        Shapir 1978 (rev. 1985, 1992)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

// Derived hebrew kde calendar class

#include "kcalendarsystemhebrew_p.h"
#include "kcalendarsystemprivate_p.h"

#include "kdebug.h"
#include "klocale.h"

#include <QtCore/QDate>
#include <QtCore/QCharRef>

static int hebrewDaysElapsed(int y);

class h_date
{
public:
    int hd_day;
    int hd_mon;
    int hd_year;
    int hd_dw;
    int hd_flg;
};

/*
 * compute general date structure from hebrew date
 */
static class h_date * hebrewToGregorian(int y, int m, int d)
{
    static class h_date h;
    int s;

    y -= 3744;
    s = hebrewDaysElapsed(y);
    d += s;
    s = hebrewDaysElapsed(y + 1) - s;      /* length of year */

    if (s > 365 && m > 6) {
        --m;
        d += 30;
    }
    d += (59 * (m - 1) + 1) / 2;      /* regular months */
    /* special cases */
    if (s % 10 > 4 && m > 2) {    /* long Heshvan */
        d++;
    }
    if (s % 10 < 4 && m > 3) {    /* short Kislev */
        d--;
    }
    // ### HPB: Broken in leap years
    //if (s > 365 && m > 6)  /* leap year */
    //  d += 30;
    d -= 6002;

    y = (d + 36525) * 4 / 146097 - 1;
    d -= y / 4 * 146097 + (y % 4) * 36524;
    y *= 100;

    /* compute year */
    s = (d + 366) * 4 / 1461 - 1;
    d -= s / 4 * 1461 + (s % 4) * 365;
    y += s;
    /* compute month */
    m = (d + 245) * 12 / 367 - 7;
    d -= m * 367 / 12 - 30;
    if (++m >= 12) {
        m -= 12;
        y++;
    }
    h.hd_day = d;
    h.hd_mon = m;
    h.hd_year = y;
    return(&h);
}

/*
 * compute date structure from no. of days since 1 Tishrei 3744
 */
static class h_date * gregorianToHebrew(int y, int m, int d)
{
    static class h_date h;
    int s;

    if ((m -= 2) <= 0) {
        m += 12;
        y--;
    }
    /* no. of days, Julian calendar */
    d += 365 * y + y / 4 + 367 * m / 12 + 5968;
    /* Gregorian calendar */
    d -= y / 100 - y / 400 - 2;
    h.hd_dw = (d + 1) % 7;

    /* compute the year */
    y += 16;
    s = hebrewDaysElapsed(y);
    m = hebrewDaysElapsed(y + 1);
    while (d >= m) {   /* computed year was underestimated */
        s = m;
        y++;
        m = hebrewDaysElapsed(y + 1);
    }
    d -= s;
    s = m - s;  /* size of current year */
    y += 3744;

    h.hd_flg = s % 10 - 4;

    /* compute day and month */
    if (d >= s - 236) {    /* last 8 months are regular */
        d -= s - 236;
        m = d * 2 / 59;
        d -= (m * 59 + 1) / 2;
        m += 4;
        if (s > 365 && m <= 5) {    /* Adar of Meuberet */
            m += 8;
        }
    } else {
        /* first 4 months have 117-119 days */
        s = 114 + s % 10;
        m = d * 4 / s;
        d -= (m * s + 3) / 4;
    }

    h.hd_day = d;
    h.hd_mon = m;
    h.hd_year = y;
    return(&h);
}

/* constants, in 1/18th of minute */
static const int HOUR = 1080;
static const int DAY = 24 * HOUR;
static const int WEEK = 7 * DAY;
#define M(h,p) ((h)*HOUR+p)
#define MONTH (DAY+M(12,793))

/**
 * @internal
 * no. of days in y years
 */
static int hebrewDaysElapsed(int y)
{
    int m, nm, dw, s, l;

    l = y * 7 + 1;  // no. of leap months
    m = y * 12 + l / 19;  // total no. of months
    l %= 19;
    nm = m * MONTH + M(1 + 6, 779);   // molad new year 3744 (16BC) + 6 hours
    s = m * 28 + nm / DAY - 2;

    nm %= WEEK;
    dw = nm / DAY;
    nm %= DAY;

    // special cases of Molad Zaken
    if ((l < 12 && dw == 3 && nm >= M(9 + 6, 204)) ||
            (l < 7 && dw == 2 && nm >= M(15 + 6, 589))) {
        s++, dw++;
    }

    /* ADU */
    if (dw == 1 || dw == 4 || dw == 6) {
        s++;
    }
    return s;
}

/**
 * @internal
 * true if long Cheshvan
 */
static int long_cheshvan(int year)
{
    QDate first, last;
    class h_date *gd;

    gd = hebrewToGregorian(year, 1, 1);
    first.setYMD(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);

    gd = hebrewToGregorian(year + 1, 1, 1);
    last.setYMD(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);

    return (first.daysTo(last) % 10 == 5);
}

/**
 * @internal
 * true if short Kislev
 */
static int short_kislev(int year)
{
    QDate first, last;
    class h_date * gd;

    gd = hebrewToGregorian(year, 1, 1);
    first.setYMD(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);

    gd = hebrewToGregorian(year + 1, 1, 1);
    last.setYMD(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);

    return (first.daysTo(last) % 10 == 3);
}

// Ok
static class h_date *toHebrew(const QDate &date)
{
    class h_date *sd;

    sd = gregorianToHebrew(date.year(), date.month(), date.day());
    ++sd->hd_mon;
    ++sd->hd_day;

    return sd;
}

class KCalendarSystemHebrewPrivate : public KCalendarSystemPrivate
{
public:
    explicit KCalendarSystemHebrewPrivate(KCalendarSystemHebrew *q);

    virtual ~KCalendarSystemHebrewPrivate();

    // Virtual methods each calendar system must re-implement
    virtual KLocale::CalendarSystem calendarSystem() const;
    virtual void loadDefaultEraList();
    virtual int monthsInYear(int year) const;
    virtual int daysInMonth(int year, int month) const;
    virtual int daysInYear(int year) const;
    virtual int daysInWeek() const;
    virtual bool isLeapYear(int year) const;
    virtual bool hasLeapMonths() const;
    virtual bool hasYearZero() const;
    virtual int maxDaysInWeek() const;
    virtual int maxMonthsInYear() const;
    virtual int earliestValidYear() const;
    virtual int latestValidYear() const;
    virtual QString monthName(int month, int year, KLocale::DateTimeComponentFormat format, bool possessive) const;
    virtual QString weekDayName(int weekDay, KLocale::DateTimeComponentFormat format) const;

    virtual int integerFromString(const QString &string, int maxLength, int &readLength) const;
    virtual QString stringFromInteger(int number, int padWidth = 0, QChar padChar = QLatin1Char('0')) const;
    virtual QString stringFromInteger(int number, int padWidth, QChar padChar, KLocale::DigitSet digitSet) const;

    virtual int monthNumberToMonthIndex(int year, int month) const;
};

// Shared d pointer base class definitions

KCalendarSystemHebrewPrivate::KCalendarSystemHebrewPrivate(KCalendarSystemHebrew *q)
                            : KCalendarSystemPrivate(q)
{
}

KCalendarSystemHebrewPrivate::~KCalendarSystemHebrewPrivate()
{
}

KLocale::CalendarSystem KCalendarSystemHebrewPrivate::calendarSystem() const
{
    return KLocale::HebrewCalendar;
}

void KCalendarSystemHebrewPrivate::loadDefaultEraList()
{
    QString name, shortName, format;
    // Jewish Era, Anno Mundi, "Year of the World".
    name = i18nc("Calendar Era: Hebrew Era, years > 0, LongFormat", "Anno Mundi");
    shortName = i18nc("Calendar Era: Hebrew Era, years > 0, ShortFormat", "AM");
    format = i18nc("(kdedt-format) Hebrew, AM, full era year format used for %EY, e.g. 2000 AM", "%Ey %EC");
    addEra('+', 1, q->epoch(), 1, q->latestValidDate(), name, shortName, format);
}

int KCalendarSystemHebrewPrivate::monthsInYear(int year) const
{
    if (isLeapYear(year)) {
        return 13;
    } else {
        return 12;
    }
}

int KCalendarSystemHebrewPrivate::daysInMonth(int year, int month) const
{
    int mi = monthNumberToMonthIndex(year, month);

    if (mi == 2 && long_cheshvan(year)) {
        return 30;
    }

    if (mi == 3 && short_kislev(year)) {
        return 29;
    }

    if (mi % 2 == 0) {    // Even number months have 29 days
        return 29;
    } else {  // Odd number months have 30 days
        return 30;
    }
}

int KCalendarSystemHebrewPrivate::daysInYear(int year) const
{
    int days;

    // Get Regular year length
    if (isLeapYear(year)) {      // Has 13 months
        days = 384;
    } else {  // Has 12 months
        days = 354;
    }

    // Check if is Deficient or Abundant year
    if (short_kislev(year)) {     // Deficient
        days = days - 1;
    } else if (long_cheshvan(year)) {     // Abundant
        days = days + 1;
    }

    return days;
}

int KCalendarSystemHebrewPrivate::daysInWeek() const
{
    return 7;
}

bool KCalendarSystemHebrewPrivate::isLeapYear(int year) const
{
    return ((((7 * year) + 1) % 19) < 7);
}

bool KCalendarSystemHebrewPrivate::hasLeapMonths() const
{
    return true;
}

bool KCalendarSystemHebrewPrivate::hasYearZero() const
{
    return false;
}

int KCalendarSystemHebrewPrivate::maxDaysInWeek() const
{
    return 7;
}

int KCalendarSystemHebrewPrivate::maxMonthsInYear() const
{
    return 13;
}

int KCalendarSystemHebrewPrivate::earliestValidYear() const
{
    return 5344;
}

int KCalendarSystemHebrewPrivate::latestValidYear() const
{
    return 8119;
}

int KCalendarSystemHebrewPrivate::integerFromString(const QString &inputString, int maxLength, int &readLength) const
{
    if (locale()->language() == QLatin1String("he")) {

        // Hebrew numbers are composed of combinations of normal letters which have a numeric value.
        // This is a non-positional system, the numeric values are simply added together, however
        // convention is for a RTL highest to lowest value ordering. There is also a degree of
        // ambiguity due to the lack of a letter for 0, hence 5 and 5000 are written the same.
        // Hebrew numbers are only used in dates.
        // See http://www.i18nguy.com/unicode/hebrew-numbers.html for more explaination

        /*
        Ref table for numbers to Hebrew chars

        Value     1       2       3        4        5       6         7        8      9

        x 1    Alef א  Bet  ב  Gimel ג  Dalet ד  He   ה  Vav  ו    Zayen ז  Het  ח  Tet  ט
               0x05D0  0x05D1  0x05D2   0x05D3   0x05D4  0x05D5    0x05D6   0x05D7  0x05D8

        x 10   Yod  י  Kaf  כ  Lamed ל  Mem  מ   Nun  נ  Samekh ס  Ayin ע   Pe   פ  Tzadi צ
               0x05D9  0x05DB  0x05DC   0x05DE   0x05E0  0x05E1    0x05E2   0x05E4  0x05E6

        x 100  Qof  ק  Resh ר  Shin ש   Tav  ת
               0x05E7  0x05E8  0x05E9   0x05EA

        Note special cases 15 = 9 + 6 = 96 טו and 16 = 9 + 7 = 97 טז
        */

        int decadeValues[14] = {10, 20, 20, 30, 40, 40, 50, 50, 60, 70, 80, 80, 90, 90};

        QChar thisChar, nextChar;
        QString string = inputString;

        int stringLength = string.length();
        readLength = 0;
        int position = 0;
        int result = 0;
        int value = 0;

        for (; position < stringLength ; ++position) {

            thisChar = string[position];

            if (position + 1 < stringLength) {
                nextChar = string[position + 1];
                // Ignore any geresh or gershayim chars, we don't bother checking they are in the right place
                if (nextChar == QLatin1Char('\'') ||  nextChar == QChar(0x05F3) ||      // geresh
                        nextChar == QLatin1Char('\"') ||  nextChar == QChar(0x05F4)) {     // gershayim
                    string.remove(position + 1, 1);
                    stringLength = string.length();
                    if (position + 1 < stringLength) {
                        nextChar = string[position + 1];
                    } else {
                        nextChar = QChar();
                    }
                    readLength = readLength + 1;
                }
            } else {
                nextChar = QChar();
            }

            if (thisChar >= QChar(0x05D0) && thisChar <= QChar(0x05D7)) {

                // If this char Alef to Het, 1 to 8, א to ח

                // If next char is any valid digit char (Alef to Tav, 1 to 400, א to ת)
                // then this char is a thousands digit
                // else this char is a ones digit

                if (nextChar >= QChar(0x05D0) && nextChar <= QChar(0x05EA)) {
                    value = (thisChar.unicode() - 0x05D0 + 1) * 1000;
                } else {
                    value = thisChar.unicode() - 0x05D0 + 1;
                }

            } else if (thisChar == QChar(0x05D8)) {

                // If this char is Tet, 9, ט

                // If next char is any valid digit char (Alef to Tav, 1 to 400, א to ת)
                // and next char not 6 (Special case for 96 = 15)
                // and next char not 7 (Special case for 97 = 16)
                // then is a thousands digit else is 9

                if (nextChar >= QChar(0x05D0) && nextChar <= QChar(0x05EA) &&
                        nextChar != QChar(0x05D5) && nextChar != QChar(0x05D6)) {
                    value = 9000;
                } else {
                    value = 9;
                }

            } else if (thisChar >= QChar(0x05D9) && thisChar <= QChar(0x05E6)) {

                // If this char Yod to Tsadi, 10 to 90, י to צ

                // If next char is a tens or hundreds char then is an error
                // Else is a tens digit

                if (nextChar >= QChar(0x05D9)) {
                    return -1;
                } else {
                    value = decadeValues[thisChar.unicode() - 0x05D9];
                }

            } else if (thisChar >= QChar(0x05E7) && thisChar <= QChar(0x05EA)) {

                // If this char Qof to Tav, 100 to 400, ק to ת, then is hundreds digit

                value = (thisChar.unicode() - 0x05E7 + 1) * 100;

            } else {

                // If this char any non-digit char including whitespace or punctuation, we're done
                break;

            }

            result = result + value;

            value = 0;
        }

        readLength += position;

        return result;

    } else {
        return KCalendarSystemPrivate::integerFromString(inputString, maxLength, readLength);
    }
}

QString KCalendarSystemHebrewPrivate::stringFromInteger(int number, int padWidth, QChar padChar) const
{
    return KCalendarSystemPrivate::stringFromInteger(number, padWidth, padChar);
}

QString KCalendarSystemHebrewPrivate::stringFromInteger(int number, int padWidth, QChar padChar, KLocale::DigitSet digitSet) const
{
    if (locale()->language() == QLatin1String("he")) {

        // Hebrew numbers are composed of combinations of normal letters which have a numeric value.
        // This is a non-positional system, the numeric values are simply added together, however
        // convention is for a RTL highest to lowest value ordering. There is also a degree of
        // ambiguity due to the lack of a letter for 0, hence 5 and 5000 are written the same.
        // Hebrew numbers are only used in dates.
        // See http://www.i18nguy.com/unicode/hebrew-numbers.html for more explaination

        /*
        Ref table for numbers to Hebrew chars

        Value     1       2       3        4        5       6         7        8      9

        x 1    Alef א  Bet  ב  Gimel ג  Dalet ד  He   ה  Vav  ו    Zayen ז  Het  ח  Tet  ט
               0x05D0  0x05D1  0x05D2   0x05D3   0x05D4  0x05D5    0x05D6   0x05D7  0x05D8

        x 10   Yod  י  Kaf  כ  Lamed ל  Mem  מ   Nun  נ  Samekh ס  Ayin ע   Pe   פ  Tzadi צ
               0x05D9  0x05DB  0x05DC   0x05DE   0x05E0  0x05E1    0x05E2   0x05E4  0x05E6

        x 100  Qof  ק  Resh ר  Shin ש   Tav  ת
               0x05E7  0x05E8  0x05E9   0x05EA

        Note special cases 15 = 9 + 6 = 96 טו and 16 = 9 + 7 = 97 טז
        */

        const QChar decade[] = {
            //  Tet = ט,    Yod = י,    Kaf = כ,    Lamed = ל,  Mem = מ
            //  Nun = נ,    Samekh = ס, Ayin = ע,   Pe = פ,     Tsadi = צ
            0x05D8,     0x05D9,     0x05DB,     0x05DC,     0x05DE,
            0x05E0,     0x05E1,     0x05E2,     0x05E4,     0x05E6
        };

        QString result;

        // We have no rules for coping with numbers outside this range
        if (number < 1 || number > 9999) {
            return KCalendarSystemPrivate::stringFromInteger(number, padWidth, padChar, digitSet);
        }

        // Translate the thousands digit, just uses letter for number 1..9 ( א to ט, Alef to Tet )
        // Years 5001-5999 do not have the thousands by convention
        if (number >= 1000) {
            if (number <= 5000 || number >= 6000) {
                result += QChar(0x05D0 - 1 + number / 1000);    // Alef א to Tet ט
            }
            number %= 1000;
        }

        // Translate the hundreds digit
        // Use traditional method where we only have letters assigned values for 100, 200, 300 and 400
        // so may need to repeat 400 twice to make up the required number
        if (number >= 100) {
            while (number >= 500) {
                result += QChar(0x05EA);    // Tav = ת
                number -= 400;
            }
            result += QChar(0x05E7 - 1 + number / 100);   // Qof = ק to xxx
            number %= 100;
        }

        // Translate the tens digit
        // The numbers 15 and 16 translate to letters that spell out the name of God which is
        // forbidden, so require special treatment where 15 = 9 + 6 and 1 = 9 + 7.
        if (number >= 10) {
            if (number == 15 || number == 16)
                number -= 9;
            result += decade[number / 10];
            number %= 10;
        }

        // Translate the ones digit, uses letter for number 1..9 ( א to ט, Alef to Tet )
        if (number > 0) {
            result += QChar(0x05D0 - 1 + number);    // Alef = א to xxx
        }

        // When used in a string with mixed names and numbers the numbers need special chars to
        // distinguish them from words composed of the same letters.
        // Single digit numbers are followed by a geresh symbol ? (Unicode = 0x05F3), but we use
        // single quote for convenience.
        // Multiple digit numbers have a gershayim symbol ? (Unicode = 0x05F4) as second-to-last
        // char, but we use double quote for convenience.
        if (result.length() == 1) {
            result += QLatin1Char('\'');
        } else {
            result.insert(result.length() - 1, QLatin1Char('\"'));
        }

        return result;

    } else {
        return KCalendarSystemPrivate::stringFromInteger(number, padWidth, padChar, digitSet);
    }
}

int KCalendarSystemHebrewPrivate::monthNumberToMonthIndex(int year, int month) const
{
    if (isLeapYear(year)) {
        if (month == 6) {
            return 13;        // Adar I
        } else if (month == 7) {
            return 14;        // Adar II
        } else if (month > 7) {
            return month - 1; // Because of Adar II
        }
    }

    return month;
}

QString KCalendarSystemHebrewPrivate::monthName(int month, int year, KLocale::DateTimeComponentFormat format, bool possessive) const
{
    // We must map month number to month index
    int monthIndex = monthNumberToMonthIndex(year, month);

    if (format == KLocale::NarrowName) {
        switch (monthIndex) {
        case 1:
            return ki18nc("Hebrew month 1 - KLocale::NarrowName",  "T").toString(locale());
        case 2:
            return ki18nc("Hebrew month 2 - KLocale::NarrowName",  "H").toString(locale());
        case 3:
            return ki18nc("Hebrew month 3 - KLocale::NarrowName",  "K").toString(locale());
        case 4:
            return ki18nc("Hebrew month 4 - KLocale::NarrowName",  "T").toString(locale());
        case 5:
            return ki18nc("Hebrew month 5 - KLocale::NarrowName",  "S").toString(locale());
        case 6:
            return ki18nc("Hebrew month 6 - KLocale::NarrowName",  "A").toString(locale());
        case 7:
            return ki18nc("Hebrew month 7 - KLocale::NarrowName",  "N").toString(locale());
        case 8:
            return ki18nc("Hebrew month 8 - KLocale::NarrowName",  "I").toString(locale());
        case 9:
            return ki18nc("Hebrew month 9 - KLocale::NarrowName",  "S").toString(locale());
        case 10:
            return ki18nc("Hebrew month 10 - KLocale::NarrowName", "T").toString(locale());
        case 11:
            return ki18nc("Hebrew month 11 - KLocale::NarrowName", "A").toString(locale());
        case 12:
            return ki18nc("Hebrew month 12 - KLocale::NarrowName", "E").toString(locale());
        case 13:
            return ki18nc("Hebrew month 13 - KLocale::NarrowName", "A").toString(locale());
        case 14:
            return ki18nc("Hebrew month 14 - KLocale::NarrowName", "A").toString(locale());
        default:
            return QString();
        }
    }

    if (format == KLocale::ShortName && possessive) {
        switch (monthIndex) {
        case 1:
            return ki18nc("Hebrew month 1 - KLocale::ShortName Possessive",  "of Tis").toString(locale());
        case 2:
            return ki18nc("Hebrew month 2 - KLocale::ShortName Possessive",  "of Hes").toString(locale());
        case 3:
            return ki18nc("Hebrew month 3 - KLocale::ShortName Possessive",  "of Kis").toString(locale());
        case 4:
            return ki18nc("Hebrew month 4 - KLocale::ShortName Possessive",  "of Tev").toString(locale());
        case 5:
            return ki18nc("Hebrew month 5 - KLocale::ShortName Possessive",  "of Shv").toString(locale());
        case 6:
            return ki18nc("Hebrew month 6 - KLocale::ShortName Possessive",  "of Ada").toString(locale());
        case 7:
            return ki18nc("Hebrew month 7 - KLocale::ShortName Possessive",  "of Nis").toString(locale());
        case 8:
            return ki18nc("Hebrew month 8 - KLocale::ShortName Possessive",  "of Iya").toString(locale());
        case 9:
            return ki18nc("Hebrew month 9 - KLocale::ShortName Possessive",  "of Siv").toString(locale());
        case 10:
            return ki18nc("Hebrew month 10 - KLocale::ShortName Possessive", "of Tam").toString(locale());
        case 11:
            return ki18nc("Hebrew month 11 - KLocale::ShortName Possessive", "of Av").toString(locale());
        case 12:
            return ki18nc("Hebrew month 12 - KLocale::ShortName Possessive", "of Elu").toString(locale());
        case 13:
            return ki18nc("Hebrew month 13 - KLocale::ShortName Possessive", "of Ad1").toString(locale());
        case 14:
            return ki18nc("Hebrew month 14 - KLocale::ShortName Possessive", "of Ad2").toString(locale());
        default:
            return QString();
        }
    }

    if (format == KLocale::ShortName && !possessive) {
        switch (monthIndex) {
        case 1:
            return ki18nc("Hebrew month 1 - KLocale::ShortName",  "Tis").toString(locale());
        case 2:
            return ki18nc("Hebrew month 2 - KLocale::ShortName",  "Hes").toString(locale());
        case 3:
            return ki18nc("Hebrew month 3 - KLocale::ShortName",  "Kis").toString(locale());
        case 4:
            return ki18nc("Hebrew month 4 - KLocale::ShortName",  "Tev").toString(locale());
        case 5:
            return ki18nc("Hebrew month 5 - KLocale::ShortName",  "Shv").toString(locale());
        case 6:
            return ki18nc("Hebrew month 6 - KLocale::ShortName",  "Ada").toString(locale());
        case 7:
            return ki18nc("Hebrew month 7 - KLocale::ShortName",  "Nis").toString(locale());
        case 8:
            return ki18nc("Hebrew month 8 - KLocale::ShortName",  "Iya").toString(locale());
        case 9:
            return ki18nc("Hebrew month 9 - KLocale::ShortName",  "Siv").toString(locale());
        case 10:
            return ki18nc("Hebrew month 10 - KLocale::ShortName", "Tam").toString(locale());
        case 11:
            return ki18nc("Hebrew month 11 - KLocale::ShortName", "Av").toString(locale());
        case 12:
            return ki18nc("Hebrew month 12 - KLocale::ShortName", "Elu").toString(locale());
        case 13:
            return ki18nc("Hebrew month 13 - KLocale::ShortName", "Ad1").toString(locale());
        case 14:
            return ki18nc("Hebrew month 14 - KLocale::ShortName", "Ad2").toString(locale());
        default:
            return QString();
        }
    }

    if (format == KLocale::LongName && possessive) {
        switch (monthIndex) {
        case 1:
            return ki18nc("Hebrew month 1 - KLocale::LongName Possessive",  "of Tishrey").toString(locale());
        case 2:
            return ki18nc("Hebrew month 2 - KLocale::LongName Possessive",  "of Heshvan").toString(locale());
        case 3:
            return ki18nc("Hebrew month 3 - KLocale::LongName Possessive",  "of Kislev").toString(locale());
        case 4:
            return ki18nc("Hebrew month 4 - KLocale::LongName Possessive",  "of Tevet").toString(locale());
        case 5:
            return ki18nc("Hebrew month 5 - KLocale::LongName Possessive",  "of Shvat").toString(locale());
        case 6:
            return ki18nc("Hebrew month 6 - KLocale::LongName Possessive",  "of Adar").toString(locale());
        case 7:
            return ki18nc("Hebrew month 7 - KLocale::LongName Possessive",  "of Nisan").toString(locale());
        case 8:
            return ki18nc("Hebrew month 8 - KLocale::LongName Possessive",  "of Iyar").toString(locale());
        case 9:
            return ki18nc("Hebrew month 9 - KLocale::LongName Possessive",  "of Sivan").toString(locale());
        case 10:
            return ki18nc("Hebrew month 10 - KLocale::LongName Possessive", "of Tamuz").toString(locale());
        case 11:
            return ki18nc("Hebrew month 11 - KLocale::LongName Possessive", "of Av").toString(locale());
        case 12:
            return ki18nc("Hebrew month 12 - KLocale::LongName Possessive", "of Elul").toString(locale());
        case 13:
            return ki18nc("Hebrew month 13 - KLocale::LongName Possessive", "of Adar I").toString(locale());
        case 14:
            return ki18nc("Hebrew month 14 - KLocale::LongName Possessive", "of Adar II").toString(locale());
        default:
            return QString();
        }
    }

    // Default to LongName
    switch (monthIndex) {
    case 1:
        return ki18nc("Hebrew month 1 - KLocale::LongName",  "Tishrey").toString(locale());
    case 2:
        return ki18nc("Hebrew month 2 - KLocale::LongName",  "Heshvan").toString(locale());
    case 3:
        return ki18nc("Hebrew month 3 - KLocale::LongName",  "Kislev").toString(locale());
    case 4:
        return ki18nc("Hebrew month 4 - KLocale::LongName",  "Tevet").toString(locale());
    case 5:
        return ki18nc("Hebrew month 5 - KLocale::LongName",  "Shvat").toString(locale());
    case 6:
        return ki18nc("Hebrew month 6 - KLocale::LongName",  "Adar").toString(locale());
    case 7:
        return ki18nc("Hebrew month 7 - KLocale::LongName",  "Nisan").toString(locale());
    case 8:
        return ki18nc("Hebrew month 8 - KLocale::LongName",  "Iyar").toString(locale());
    case 9:
        return ki18nc("Hebrew month 9 - KLocale::LongName",  "Sivan").toString(locale());
    case 10:
        return ki18nc("Hebrew month 10 - KLocale::LongName", "Tamuz").toString(locale());
    case 11:
        return ki18nc("Hebrew month 11 - KLocale::LongName", "Av").toString(locale());
    case 12:
        return ki18nc("Hebrew month 12 - KLocale::LongName", "Elul").toString(locale());
    case 13:
        return ki18nc("Hebrew month 13 - KLocale::LongName", "Adar I").toString(locale());
    case 14:
        return ki18nc("Hebrew month 14 - KLocale::LongName", "Adar II").toString(locale());
    default:
        return QString();
    }
}

// Use Western day names for now as that's what the old version did,
// but wouldn't it be better to use the right Hebrew names like Shabbat?
// Could make it switchable by adding new enums to WeekDayFormat, e.g. ShortNameWestern?
QString KCalendarSystemHebrewPrivate::weekDayName(int weekDay, KLocale::DateTimeComponentFormat format) const
{
    if (format == KLocale::NarrowName) {
        switch (weekDay) {
        case 1:
            return ki18nc("Gregorian weekday 1 - KLocale::NarrowName ", "M").toString(locale());
        case 2:
            return ki18nc("Gregorian weekday 2 - KLocale::NarrowName ", "T").toString(locale());
        case 3:
            return ki18nc("Gregorian weekday 3 - KLocale::NarrowName ", "W").toString(locale());
        case 4:
            return ki18nc("Gregorian weekday 4 - KLocale::NarrowName ", "T").toString(locale());
        case 5:
            return ki18nc("Gregorian weekday 5 - KLocale::NarrowName ", "F").toString(locale());
        case 6:
            return ki18nc("Gregorian weekday 6 - KLocale::NarrowName ", "S").toString(locale());
        case 7:
            return ki18nc("Gregorian weekday 7 - KLocale::NarrowName ", "S").toString(locale());
        default:
            return QString();
        }
    }

    if (format == KLocale::ShortName  || format == KLocale:: ShortNumber) {
        switch (weekDay) {
        case 1:
            return ki18nc("Gregorian weekday 1 - KLocale::ShortName", "Mon").toString(locale());
        case 2:
            return ki18nc("Gregorian weekday 2 - KLocale::ShortName", "Tue").toString(locale());
        case 3:
            return ki18nc("Gregorian weekday 3 - KLocale::ShortName", "Wed").toString(locale());
        case 4:
            return ki18nc("Gregorian weekday 4 - KLocale::ShortName", "Thu").toString(locale());
        case 5:
            return ki18nc("Gregorian weekday 5 - KLocale::ShortName", "Fri").toString(locale());
        case 6:
            return ki18nc("Gregorian weekday 6 - KLocale::ShortName", "Sat").toString(locale());
        case 7:
            return ki18nc("Gregorian weekday 7 - KLocale::ShortName", "Sun").toString(locale());
        default: return QString();
        }
    }

    switch (weekDay) {
    case 1:
        return ki18nc("Gregorian weekday 1 - KLocale::LongName", "Monday").toString(locale());
    case 2:
        return ki18nc("Gregorian weekday 2 - KLocale::LongName", "Tuesday").toString(locale());
    case 3:
        return ki18nc("Gregorian weekday 3 - KLocale::LongName", "Wednesday").toString(locale());
    case 4:
        return ki18nc("Gregorian weekday 4 - KLocale::LongName", "Thursday").toString(locale());
    case 5:
        return ki18nc("Gregorian weekday 5 - KLocale::LongName", "Friday").toString(locale());
    case 6:
        return ki18nc("Gregorian weekday 6 - KLocale::LongName", "Saturday").toString(locale());
    case 7:
        return ki18nc("Gregorian weekday 7 - KLocale::LongName", "Sunday").toString(locale());
    default:
        return QString();
    }
}


KCalendarSystemHebrew::KCalendarSystemHebrew(const KLocale *locale)
                     : KCalendarSystem(*new KCalendarSystemHebrewPrivate(this), KSharedConfig::Ptr(), locale)
{
    d_ptr->loadConfig(calendarType());
}

KCalendarSystemHebrew::KCalendarSystemHebrew(const KSharedConfig::Ptr config, const KLocale *locale)
                     : KCalendarSystem(*new KCalendarSystemHebrewPrivate(this), config, locale)
{
    d_ptr->loadConfig(calendarType());
}

KCalendarSystemHebrew::KCalendarSystemHebrew(KCalendarSystemHebrewPrivate &dd,
                                             const KSharedConfig::Ptr config, const KLocale *locale)
                     : KCalendarSystem(dd, config, locale)
{
    d_ptr->loadConfig(calendarType());
}

KCalendarSystemHebrew::~KCalendarSystemHebrew()
{
}

QString KCalendarSystemHebrew::calendarType() const
{
    return QLatin1String("hebrew");
}

QDate KCalendarSystemHebrew::epoch() const
{
    // Hebrew 0001-01-01 (Gregorian -3760-09-07, Julian -3761-10-07)
    return QDate::fromJulianDay(347998);
}

QDate KCalendarSystemHebrew::earliestValidDate() const
{
    // Current formulas using direct Gregorian <-> Hebrew conversion using Qt
    // will return invalid results prior to the Gregorian switchover in 1582
    // Next valid Hebrew year starts 5344-01-01 (Gregorian 1583-09-17)
    return QDate::fromJulianDay(2299498);
}

QDate KCalendarSystemHebrew::latestValidDate() const
{
    // Testing shows current formulas only work up to 8119-13-29 (Gregorian 4359-10-07)
    return QDate::fromJulianDay(3313431);
}

bool KCalendarSystemHebrew::isValid(int year, int month, int day) const
{
    return KCalendarSystem::isValid(year, month, day);
}

bool KCalendarSystemHebrew::isValid(const QDate &date) const
{
    return KCalendarSystem::isValid(date);
}

int KCalendarSystemHebrew::dayOfWeek(const QDate &date) const
{
    class h_date * sd = toHebrew(date);
    if (sd->hd_dw == 0) {
        return 7;
    } else {
        return (sd->hd_dw);
    }
}

bool KCalendarSystemHebrew::isLeapYear(int year) const
{
    return KCalendarSystem::isLeapYear(year);
}

bool KCalendarSystemHebrew::isLeapYear(const QDate &date) const
{
    return KCalendarSystem::isLeapYear(date);
}

QString KCalendarSystemHebrew::monthName(int month, int year, MonthNameFormat format) const
{
    return KCalendarSystem::monthName(month, year, format);
}

QString KCalendarSystemHebrew::monthName(const QDate &date, MonthNameFormat format) const
{
    return KCalendarSystem::monthName(date, format);
}

QString KCalendarSystemHebrew::weekDayName(int weekDay, WeekDayNameFormat format) const
{
    return KCalendarSystem::weekDayName(weekDay, format);
}

QString KCalendarSystemHebrew::weekDayName(const QDate &date, WeekDayNameFormat format) const
{
    return KCalendarSystem::weekDayName(date, format);
}

int KCalendarSystemHebrew::yearStringToInteger(const QString &string, int &readLength) const
{
    int result = KCalendarSystem::yearStringToInteger(string, readLength);

    // Hebrew has no letter for 0, so 5 and 5000 are written the same
    // Assume if less than 10 then we are in an exact multiple of 1000
    if (result < 10) {
        result = result * 1000;
    }

    // Not good just assuming, make configurable
    if (result < 1000) {
        result += 5000; // assume we're in the 6th millenium (y6k bug)
    }

    return result;
}

int KCalendarSystemHebrew::weekDayOfPray() const
{
    return 6; // Saturday
}

bool KCalendarSystemHebrew::isLunar() const
{
    return false;
}

bool KCalendarSystemHebrew::isLunisolar() const
{
    return true;
}

bool KCalendarSystemHebrew::isSolar() const
{
    return false;
}

bool KCalendarSystemHebrew::isProleptic() const
{
    return false;
}

bool KCalendarSystemHebrew::julianDayToDate(int jd, int &year, int &month, int &day) const
{
    class h_date * sd = toHebrew(QDate::fromJulianDay(jd));

    year = sd->hd_year;

    month = sd->hd_mon;
    if (isLeapYear(sd->hd_year)) {
        if (month == 13 /*AdarI*/) {
            month = 6;
        } else if (month == 14 /*AdarII*/) {
            month = 7;
        } else if (month > 6 && month < 13) {
            ++month;
        }
    }

    day = sd->hd_day;

    return true;
}

bool KCalendarSystemHebrew::dateToJulianDay(int year, int month, int day, int &jd) const
{
    class h_date * gd = hebrewToGregorian(year, month, day);

    QDate tempDate(gd->hd_year, gd->hd_mon + 1, gd->hd_day + 1);

    jd = tempDate.toJulianDay();

    return true;
}
