/*
    Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
    и других спецификаций для функционирования компактных приложений на языке
    Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package malik.emulator.time;

public abstract class CalendarSystem extends Object
{
    public static final int MONDAY    = 1;
    public static final int TUESDAY   = 2;
    public static final int WEDNESDAY = 3;
    public static final int THURSDAY  = 4;
    public static final int FRIDAY    = 5;
    public static final int SATURDAY  = 6;
    public static final int SUNDAY    = 0;
    public static final int JANUARY   =  1;
    public static final int FEBRUARY  =  2;
    public static final int MARCH     =  3;
    public static final int APRIL     =  4;
    public static final int MAY       =  5;
    public static final int JUNE      =  6;
    public static final int JULY      =  7;
    public static final int AUGUST    =  8;
    public static final int SEPTEMBER =  9;
    public static final int OCTOBER   = 10;
    public static final int NOVEMBER  = 11;
    public static final int DECEMBER  = 12;

    static final byte[][] DAYS;
    public static final CalendarSystem gregorian;
    public static final CalendarSystem julian;

    static {
        DAYS = new byte[][] { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } };
        gregorian = new GregorianCalendarSystem();
        julian = new JulianCalendarSystem();
    }

    protected CalendarSystem() {
    }

    public abstract boolean isLeapYear(int year);

    public abstract int computeNumberOfDays(int year, int month);

    public abstract int computeDayOfWeek(int year, int month, int day);

    public abstract long computeFields(long time, int offset);

    public abstract long computeTime(int year, int month, int day, int hour, int minute, int second, int millis, int offset);

    public abstract long epochStart();

    public int getYear(long fields) {
        return (int) (fields >>> 48);
    }

    public int getMonth(long fields) {
        return (int) (fields >>> 40) & 0xff;
    }

    public int getDay(long fields) {
        return (int) (fields >>> 32) & 0xff;
    }

    public int getHour(long fields) {
        return (int) (fields >>> 24) & 0xff;
    }

    public int getMinute(long fields) {
        return (int) (fields >>> 16) & 0xff;
    }

    public int getSecond(long fields) {
        return ((int) fields & 0xffff) / 1000;
    }

    public int getMillis(long fields) {
        return ((int) fields & 0xffff) % 1000;
    }

    public long toFields(int year, int month, int day, int hour, int minute, int second, int millis) {
        return (long) year << 48 | (long) (month & 0xff) << 40 | (long) (day & 0xff) << 32 | (long) (hour & 0xff) << 24 | (long) (minute & 0xff) << 16 | (long) ((second * 1000 + millis) & 0xffff);
    }

    public long computeTime(long fields, int offset) {
        int millis = (int) fields & 0xffff;
        return computeTime(
            (int) (fields >>> 48), (int) (fields >> 40) & 0xff, (int) (fields >> 32) & 0xff, (int) (fields >> 24) & 0xff, (int) (fields >> 16) & 0xff, millis / 1000, millis % 1000, offset
        );
    }

    public long setYear(long fields, int year) {
        return fields & ~(0xffffL << 48) | (long) year << 48;
    }

    public long setMonth(long fields, int month) {
        return fields & ~(0xffL << 40) | (long) (month & 0xff) << 40;
    }

    public long setDay(long fields, int day) {
        return fields & ~(0xffL << 32) | (long) (day & 0xff) << 32;
    }

    public long setHour(long fields, int hour) {
        return fields & ~(0xffL << 24) | (long) (hour & 0xff) << 24;
    }

    public long setMinute(long fields, int minute) {
        return fields & ~(0xffL << 16) | (long) (minute & 0xff) << 16;
    }

    public long setSecond(long fields, int second) {
        int millis = ((int) fields & 0xffff) % 1000;
        return fields & ~0xffffL | (long) ((second * 1000 + millis) & 0xffff);
    }

    public long setMillis(long fields, int millis) {
        int second = ((int) fields & 0xffff) / 1000;
        return fields & ~0xffffL | (long) ((second * 1000 + millis) & 0xffff);
    }
}

final class GregorianCalendarSystem extends CalendarSystem
{
    private static final long EPOCH_START = 0x00003883122cd800L;

    public GregorianCalendarSystem() {
    }

    public boolean isLeapYear(int year) {
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.isLeapYear: аргумент year может быть только положительным.");
        }
        return year % (year % 100 == 0 ? 400 : 4) == 0;
    }

    public int computeNumberOfDays(int year, int month) {
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.computeNumberOfDays: аргумент year может быть только положительным.");
        }
        if(month < 1 || month-- > 12)
        {
            throw new IllegalArgumentException("CalendarSystem.computeNumberOfDays: аргумент month может быть только от 1 до 12.");
        }
        return DAYS[isLeapYear(year) ? 1 : 0][month];
    }

    public int computeDayOfWeek(int year, int month, int day) {
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.computeDayOfWeek: аргумент year может быть только положительным.");
        }
        if(month < 1 || month > 12)
        {
            throw new IllegalArgumentException("CalendarSystem.computeDayOfWeek: аргумент month может быть только от 1 до 12.");
        }
        return (int) (((computeTime(year, month, day, 0, 0, 0, 0, 0) + EPOCH_START) / 86400000L) + 1L) % 7;
    }

    public long computeFields(long time, int offset) {
        int year;
        int month;
        int day;
        int hour;
        int minute;
        int millis;
        int daysPerMonth;
        long rem;
        byte[] months;
        hour = (int) ((rem = (time += EPOCH_START + (long) offset) % 86400000L) / 3600000L);
        minute = (int) ((rem %= 3600000L) / 60000L);
        millis = (int) (rem % 60000L);
        year = (int) (((rem = time / 86400000L) / 146097L) * 400L);
        if((rem %= 146097L) >= 146096L)
        {
            year += 399;
            rem = 365L;
        } else
        {
            year += (int) ((rem / 36524L) * 100L) + (int) (((rem %= 36524L) / 1461L) * 4L);
            if((rem %= 1461L) >= 1460L)
            {
                year += 3;
                rem = 365L;
            } else
            {
                year += (int) (rem / 365L);
                rem %= 365L;
            }
        }
        months = DAYS[isLeapYear(++year) ? 1 : 0];
        for(month = 0; rem >= (long) (daysPerMonth = months[month]); rem -= (long) daysPerMonth, month++);
        month++;
        day = (int) rem + 1;
        return (long) year << 48 | (long) month << 40 | (long) day << 32 | (long) hour << 24 | (long) minute << 16 | (long) millis;
    }

    public long computeTime(int year, int month, int day, int hour, int minute, int second, int millis, int offset) {
        int leap;
        long result;
        byte[] months;
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.computeTime: поле year может быть только положительным.");
        }
        if(month < 1 || month-- > 12)
        {
            throw new IllegalArgumentException("CalendarSystem.computeTime: поле month может быть только от 1 до 12.");
        }
        leap = isLeapYear(year--) ? 1 : 0;
        result = -EPOCH_START + 12622780800000L * (long) (year / 400) + 3155673600000L * (long) ((year %= 400) / 100) + 126230400000L * (long) ((year %= 100) / 4) + 31536000000L * (long) (year % 4);
        months = DAYS[leap];
        for(int i = month; i-- > 0; result += 86400000L * (long) months[i]);
        return result + 86400000L * (long) (day - 1) + 3600000L * (long) hour + 60000L * (long) minute + 1000L * (long) second + (long) millis - (long) offset;
    }

    public long epochStart() {
        return EPOCH_START;
    }
}

final class JulianCalendarSystem extends CalendarSystem
{
    private static final long EPOCH_START = 0x000038831c799000L;

    public JulianCalendarSystem() {
    }

    public boolean isLeapYear(int year) {
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.isLeapYear: аргумент year может быть только положительным.");
        }
        return year % 4 == 0;
    }

    public int computeNumberOfDays(int year, int month) {
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.computeNumberOfDays: аргумент year может быть только положительным.");
        }
        if(month < 1 || month-- > 12)
        {
            throw new IllegalArgumentException("CalendarSystem.computeNumberOfDays: аргумент month может быть только от 1 до 12.");
        }
        return DAYS[isLeapYear(year) ? 1 : 0][month];
    }

    public int computeDayOfWeek(int year, int month, int day) {
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.computeDayOfWeek: аргумент year может быть только положительным.");
        }
        if(month < 1 || month > 12)
        {
            throw new IllegalArgumentException("CalendarSystem.computeDayOfWeek: аргумент month может быть только от 1 до 12.");
        }
        return (int) (((computeTime(year, month, day, 0, 0, 0, 0, 0) + EPOCH_START) / 86400000L) + 6L) % 7;
    }

    public long computeFields(long time, int offset) {
        int year;
        int month;
        int day;
        int hour;
        int minute;
        int millis;
        int daysPerMonth;
        long rem;
        byte[] months;
        hour = (int) ((rem = (time += EPOCH_START + (long) offset) % 86400000L) / 3600000L);
        minute = (int) ((rem %= 3600000L) / 60000L);
        millis = (int) (rem % 60000L);
        year = (int) (((rem = time / 86400000L) / 1461L) * 4L);
        if((rem %= 1461L) >= 1460L)
        {
            year += 3;
            rem = 365L;
        } else
        {
            year += (int) (rem / 365L);
            rem %= 365L;
        }
        months = DAYS[isLeapYear(++year) ? 1 : 0];
        for(month = 0; rem >= (long) (daysPerMonth = months[month]); rem -= (long) daysPerMonth, month++);
        month++;
        day = (int) rem + 1;
        return (long) year << 48 | (long) month << 40 | (long) day << 32 | (long) hour << 24 | (long) minute << 16 | (long) millis;
    }

    public long computeTime(int year, int month, int day, int hour, int minute, int second, int millis, int offset) {
        int leap;
        long result;
        byte[] months;
        if(year <= 0)
        {
            throw new IllegalArgumentException("CalendarSystem.computeTime: поле year может быть только положительным.");
        }
        if(month < 1 || month-- > 12)
        {
            throw new IllegalArgumentException("CalendarSystem.computeTime: поле month может быть только от 1 до 12.");
        }
        leap = isLeapYear(year--) ? 1 : 0;
        result = -EPOCH_START + 126230400000L * (long) (year / 4) + 31536000000L * (long) (year % 4);
        months = DAYS[leap];
        for(int i = month; i-- > 0; result += 86400000L * (long) months[i]);
        return result + 86400000L * (long) (day - 1) + 3600000L * (long) hour + 60000L * (long) minute + 1000L * (long) second + (long) millis - (long) offset;
    }

    public long epochStart() {
        return EPOCH_START;
    }
}
