// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
 *******************************************************************************
 * Copyright (C) 1996-2014, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */

package com.ibm.icu.util;

import java.util.Date;
import java.util.Locale;

import com.ibm.icu.util.ULocale.Category;

/**
 * <code>PersianCalendar</code> is a subclass of <code>Calendar</code> that
 * that implements the Persian calendar.  It is used as the main civil
 * calendar in Iran and Afghanistan, and by Iranians and Afghans worldwide.
 * <p>
 * The Persian calendar is solar, and is similar to the Gregorian calendar
 * in various ways, except its leap year rule, which is determined
 * astronomically.  The Persian year starts around the March equinox.
 * <p>
 * The modern Persian calendar (used in Iran since 1925 CE and in
 * Afghanistan since 1957 CE), has the lengths of the months fixed.  The
 * first six months are 31 days each, the next five months are 30 days each,
 * and the final month is 29 days in non-leap years and 30 days in leap
 * ones.  Historically, the lengths of the month differed in different
 * years, but they were finally fixed at the times mentioned above.  Partial
 * information is available about the historical lengths.
 * <p>
 * The official rule for determination of the beginning of the Persian year
 * is locale dependent, but at the same time, it has not specified a locale. 
 * Iranians around the world traditionally follow the calendar authorities
 * of Iran, which haven't officially specified the locale.  Some
 * calendarists use some point in Tehran as the locale, while others have
 * tried the more neutral 52.5 degrees east meridian.  It is not clear which
 * locale should be used for the Persian calendar of Afghanistan, but it is
 * expected that for about one year in every twenty-four years, the Afghan
 * calendar may become different from the Iranian one.
 * <p> 
 * The exact locale to be used for the Iranian calendar starts to make a
 * difference at around 2090 CE.  The specific arithmetic method implemented
 * here, commonly known as the 33-year cycle rule, matches the astronomical
 * calendar at least for the whole period that the calendar has been both
 * well-defined and official, from 1925 to around 2090 CE.  The other
 * commonly known algorithm, the 2820-year cycle, has been incorrectly
 * designed to follow the tropical year instead of the spring equinoctial
 * year, and fails to match the astronomical one as early as 2025 CE.
 * <p>
 * This class should not be subclassed.</p>
 * <p>
 * PersianCalendar usually should be instantiated using
 * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a
 * <code>ULocale</code> with the tag <code>"@calendar=persian"</code>.</p>
 *
 * @see com.ibm.icu.util.GregorianCalendar
 * @see com.ibm.icu.util.Calendar
 *
 * @author Roozbeh Pournader
 *
 * @internal
 * @deprecated This API is ICU internal only.
 */
@Deprecated
public class PersianCalendar extends Calendar {
    private static final long serialVersionUID = -6727306982975111643L;

    //-------------------------------------------------------------------------
    // Constants...
    //-------------------------------------------------------------------------
    
    private static final int[][] MONTH_COUNT = {
        //len len2   st
        {  31,  31,   0 }, // Farvardin
        {  31,  31,  31 }, // Ordibehesht
        {  31,  31,  62 }, // Khordad
        {  31,  31,  93 }, // Tir
        {  31,  31, 124 }, // Mordad
        {  31,  31, 155 }, // Shahrivar
        {  30,  30, 186 }, // Mehr
        {  30,  30, 216 }, // Aban
        {  30,  30, 246 }, // Azar
        {  30,  30, 276 }, // Dey
        {  30,  30, 306 }, // Bahman
        {  29,  30, 336 }  // Esfand
        // len  length of month
        // len2 length of month in a leap year
        // st   days in year before start of month
    };
    
    private static final int PERSIAN_EPOCH = 1948320;

    //-------------------------------------------------------------------------
    // Constructors...
    //-------------------------------------------------------------------------

    /**
     * Constructs a default <code>PersianCalendar</code> using the current time
     * in the default time zone with the default <code>FORMAT</code> locale.
     * @see Category#FORMAT
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar()
    {
        this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
    }

    /**
     * Constructs a <code>PersianCalendar</code> based on the current time
     * in the given time zone with the default <code>FORMAT</code> locale.
     * @param zone the given time zone.
     * @see Category#FORMAT
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(TimeZone zone)
    {
        this(zone, ULocale.getDefault(Category.FORMAT));
    }

    /**
     * Constructs a <code>PersianCalendar</code> based on the current time
     * in the default time zone with the given locale.
     *
     * @param aLocale the given locale.
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(Locale aLocale)
    {
        this(TimeZone.getDefault(), aLocale);
    }

    /**
     * Constructs a <code>PersianCalendar</code> based on the current time
     * in the default time zone with the given locale.
     *
     * @param locale the given ulocale.
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(ULocale locale)
    {
        this(TimeZone.getDefault(), locale);
    }

    /**
     * Constructs a <code>PersianCalendar</code> based on the current time
     * in the given time zone with the given locale.
     *
     * @param zone the given time zone.
     * @param aLocale the given locale.
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(TimeZone zone, Locale aLocale)
    {
        super(zone, aLocale);
        setTimeInMillis(System.currentTimeMillis());
    }

    /**
     * Constructs a <code>PersianCalendar</code> based on the current time
     * in the given time zone with the given locale.
     *
     * @param zone the given time zone.
     * @param locale the given ulocale.
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(TimeZone zone, ULocale locale)
    {
        super(zone, locale);
        setTimeInMillis(System.currentTimeMillis());
    }

    /**
     * Constructs a <code>PersianCalendar</code> with the given date set
     * in the default time zone with the default <code>FORMAT</code> locale.
     *
     * @param date      The date to which the new calendar is set.
     * @see Category#FORMAT
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(Date date) {
        super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
        this.setTime(date);
    }

    /**
     * Constructs a <code>PersianCalendar</code> with the given date set
     * in the default time zone with the default <code>FORMAT</code> locale.
     *
     * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
     * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
     *              Note that the month value is 0-based. e.g., 0 for Farvardin.
     * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
     * @see Category#FORMAT
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(int year, int month, int date)
    {
        super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
        this.set(Calendar.YEAR, year);
        this.set(Calendar.MONTH, month);
        this.set(Calendar.DATE, date);
    }

    /**
     * Constructs a <code>PersianCalendar</code> with the given date
     * and time set for the default time zone with the default <code>FORMAT</code> locale.
     *
     * @param year  the value used to set the {@link #YEAR YEAR} time field in the calendar.
     * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
     *              Note that the month value is 0-based. e.g., 0 for Farvardin.
     * @param date  the value used to set the {@link #DATE DATE} time field in the calendar.
     * @param hour  the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field
     *              in the calendar.
     * @param minute the value used to set the {@link #MINUTE MINUTE} time field
     *              in the calendar.
     * @param second the value used to set the {@link #SECOND SECOND} time field
     *              in the calendar.
     * @see Category#FORMAT
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public PersianCalendar(int year, int month, int date, int hour,
                           int minute, int second)
    {
        super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
        this.set(Calendar.YEAR, year);
        this.set(Calendar.MONTH, month);
        this.set(Calendar.DATE, date);
        this.set(Calendar.HOUR_OF_DAY, hour);
        this.set(Calendar.MINUTE, minute);
        this.set(Calendar.SECOND, second);
    }

    //-------------------------------------------------------------------------
    // Minimum / Maximum access functions
    //-------------------------------------------------------------------------

    private static final int LIMITS[][] = {
        // Minimum  Greatest     Least   Maximum
        //           Minimum   Maximum
        {        0,        0,        0,        0}, // ERA
        { -5000000, -5000000,  5000000,  5000000}, // YEAR
        {        0,        0,       11,       11}, // MONTH
        {        1,        1,       52,       53}, // WEEK_OF_YEAR
        {/*                                   */}, // WEEK_OF_MONTH
        {        1,        1,       29,       31}, // DAY_OF_MONTH
        {        1,        1,      365,      366}, // DAY_OF_YEAR
        {/*                                   */}, // DAY_OF_WEEK
        {       -1,       -1,        5,        5}, // DAY_OF_WEEK_IN_MONTH
        {/*                                   */}, // AM_PM
        {/*                                   */}, // HOUR
        {/*                                   */}, // HOUR_OF_DAY
        {/*                                   */}, // MINUTE
        {/*                                   */}, // SECOND
        {/*                                   */}, // MILLISECOND
        {/*                                   */}, // ZONE_OFFSET
        {/*                                   */}, // DST_OFFSET
        { -5000000, -5000000,  5000000,  5000000}, // YEAR_WOY
        {/*                                   */}, // DOW_LOCAL
        { -5000000, -5000000,  5000000,  5000000}, // EXTENDED_YEAR
        {/*                                   */}, // JULIAN_DAY
        {/*                                   */}, // MILLISECONDS_IN_DAY
    };

    /**
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    protected int handleGetLimit(int field, int limitType) {
        return LIMITS[field][limitType];
    }

    //-------------------------------------------------------------------------
    // Assorted calculation utilities
    //

    /**
     * Determine whether a year is a leap year in the Persian calendar
     */
    private final static boolean isLeapYear(int year)
    {
        int[] remainder = new int[1];
        floorDivide(25 * year + 11, 33, remainder);
        return remainder[0] < 8;
        
    }

    //----------------------------------------------------------------------
    // Calendar framework
    //----------------------------------------------------------------------

    /**
     * Return the length (in days) of the given month.
     *
     * @param extendedYear  The Persian year
     * @param month The Persian month, 0-based
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    protected int handleGetMonthLength(int extendedYear, int month) {
        // If the month is out of range, adjust it into range, and
        // modify the extended year value accordingly.
        if (month < 0 || month > 11) {
            int[] rem = new int[1];
            extendedYear += floorDivide(month, 12, rem);
            month = rem[0];
        }

        return MONTH_COUNT[month][isLeapYear(extendedYear)?1:0];
    }

    /**
     * Return the number of days in the given Persian year
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    protected int handleGetYearLength(int extendedYear) {
        return isLeapYear(extendedYear) ? 366 : 365;
    }
    
    //-------------------------------------------------------------------------
    // Functions for converting from field values to milliseconds....
    //-------------------------------------------------------------------------

    /**
     * Return JD of start of given month/year
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
        // If the month is out of range, adjust it into range, and
        // modify the extended year value accordingly.
        if (month < 0 || month > 11) {
            int[] rem = new int[1];
            eyear += floorDivide(month, 12, rem);
            month = rem[0];
        }

        int julianDay = PERSIAN_EPOCH - 1 + 365 * (eyear - 1) + floorDivide(8 * eyear + 21, 33);
        if (month != 0) {
            julianDay += MONTH_COUNT[month][2];
        }
        return julianDay;
    }    

    //-------------------------------------------------------------------------
    // Functions for converting from milliseconds to field values
    //-------------------------------------------------------------------------

    /**
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    protected int handleGetExtendedYear() {
        int year;
        if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
            year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
        } else {
            year = internalGet(YEAR, 1); // Default to year 1
        }
        return year;
    }

    /**
     * Override Calendar to compute several fields specific to the Persian
     * calendar system.  These are:
     *
     * <ul><li>ERA
     * <li>YEAR
     * <li>MONTH
     * <li>DAY_OF_MONTH
     * <li>DAY_OF_YEAR
     * <li>EXTENDED_YEAR</ul>
     * 
     * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
     * method is called.
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    protected void handleComputeFields(int julianDay) {
        int year, month, dayOfMonth, dayOfYear;

        long daysSinceEpoch = julianDay - PERSIAN_EPOCH;
        year = 1 + (int) floorDivide(33 * daysSinceEpoch + 3, 12053);

        long farvardin1 = 365L * (year - 1L) + floorDivide(8L * year + 21, 33L);
        dayOfYear = (int)(daysSinceEpoch - farvardin1); // 0-based
        if (dayOfYear < 216) { // Compute 0-based month
            month = dayOfYear / 31;
        } else {
            month = (dayOfYear - 6) / 30;
        }
        dayOfMonth = dayOfYear - MONTH_COUNT[month][2] + 1;
        ++dayOfYear; // Make it 1-based now
        
        internalSet(ERA, 0);
        internalSet(YEAR, year);
        internalSet(EXTENDED_YEAR, year);
        internalSet(MONTH, month);
        internalSet(DAY_OF_MONTH, dayOfMonth);
        internalSet(DAY_OF_YEAR, dayOfYear);       
    }    

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public String getType() {
        return "persian";
    }
}
