/*
 *  Copyright 2001-2005 Stephen Colebourne
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.joda.time.chrono;

import org.joda.time.Chronology;
import org.joda.time.DateTimeConstants;

/**
 * Abstract implementation of a calendar system based around fixed length months.
 * <p>
 * As the month length is fixed various calculations can be optimised.
 * This implementation assumes any additional days after twelve
 * months fall into a thirteenth month.
 * <p>
 * BasicFixedMonthChronology is thread-safe and immutable, and all
 * subclasses must be as well.
 *
 * @author Brian S O'Neill
 * @author Stephen Colebourne
 * @since 1.2, refactored from CopticChronology
 */
abstract class BasicFixedMonthChronology extends BasicChronology {

    /** Serialization lock */
    private static final long serialVersionUID = 261387371998L;

    /** The length of the month. */
    static final int MONTH_LENGTH = 30;

    /** The typical millis per year. */
    static final long MILLIS_PER_YEAR =
        (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY);

    /** The length of the month in millis. */
    static final long MILLIS_PER_MONTH = ((long) MONTH_LENGTH) * DateTimeConstants.MILLIS_PER_DAY;

    //-----------------------------------------------------------------------
    /**
     * Restricted constructor.
     *
     * @param base  the base chronology
     * @param param  the init parameter
     * @param minDaysInFirstWeek  the minimum days in the first week
     */
    BasicFixedMonthChronology(Chronology base, Object param, int minDaysInFirstWeek) {
        super(base, param, minDaysInFirstWeek);
    }

    //-----------------------------------------------------------------------
    long setYear(long instant, int year) {
        // optimsed implementation of set, due to fixed months
        int thisYear = getYear(instant);
        int dayOfYear = getDayOfYear(instant, thisYear);
        int millisOfDay = getMillisOfDay(instant);

        if (dayOfYear > 365) {
            // Current year is leap, and day is leap.
            if (!isLeapYear(year)) {
                // Moving to a non-leap year, leap day doesn't exist.
                dayOfYear--;
            }
        }

        instant = getYearMonthDayMillis(year, 1, dayOfYear);
        instant += millisOfDay;
        return instant;
    }

    //-----------------------------------------------------------------------
    long getYearDifference(long minuendInstant, long subtrahendInstant) {
        // optimsed implementation of getDifference, due to fixed months
        int minuendYear = getYear(minuendInstant);
        int subtrahendYear = getYear(subtrahendInstant);

        // Inlined remainder method to avoid duplicate calls to get.
        long minuendRem = minuendInstant - getYearMillis(minuendYear);
        long subtrahendRem = subtrahendInstant - getYearMillis(subtrahendYear);

        int difference = minuendYear - subtrahendYear;
        if (minuendRem < subtrahendRem) {
            difference--;
        }
        return difference;
    }

    //-----------------------------------------------------------------------
    long getTotalMillisByYearMonth(int year, int month) {
        return ((month - 1) * MILLIS_PER_MONTH);
    }

    //-----------------------------------------------------------------------
    int getDayOfMonth(long millis) {
        // optimised for fixed months
        return (getDayOfYear(millis) - 1) % MONTH_LENGTH + 1;
    }

    //-----------------------------------------------------------------------
    boolean isLeapYear(int year) {
        return (year & 3) == 3;
    }

    //-----------------------------------------------------------------------
    int getDaysInYearMonth(int year, int month) {
        return (month != 13) ? MONTH_LENGTH : (isLeapYear(year) ? 6 : 5);
    }

    //-----------------------------------------------------------------------
    int getDaysInMonthMax() {
        return MONTH_LENGTH;
    }

    //-----------------------------------------------------------------------
    int getDaysInMonthMax(int month) {
        return (month != 13 ? MONTH_LENGTH : 6);
    }

    //-----------------------------------------------------------------------
    int getMonthOfYear(long millis) {
        return (getDayOfYear(millis) - 1) / MONTH_LENGTH + 1;
    }

    //-----------------------------------------------------------------------
    int getMonthOfYear(long millis, int year) {
        long monthZeroBased = (millis - getYearMillis(year)) / MILLIS_PER_MONTH;
        return ((int) monthZeroBased) + 1;
    }

    //-----------------------------------------------------------------------
    int getMaxMonth() {
        return 13;
    }

    //-----------------------------------------------------------------------
    long getAverageMillisPerYear() {
        return MILLIS_PER_YEAR;
    }

    //-----------------------------------------------------------------------
    long getAverageMillisPerYearDividedByTwo() {
        return MILLIS_PER_YEAR / 2;
    }

    //-----------------------------------------------------------------------
    long getAverageMillisPerMonth() {
        return MILLIS_PER_MONTH;
    }

}
