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

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

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

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

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


package malik.emulator.time;

import java.util.*;

public final class GregorianCalendar extends Calendar
{
	private static final byte[][] DAYS;
	private static final DateTimeFormat DEFAULT_FORMAT;
	private static final DateTimeFormat STANDARD_FORMAT;

	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
				}
		};
		DEFAULT_FORMAT = new DefaultFormat();
		STANDARD_FORMAT = new StandardFormat();
	}

	public static boolean isLeapYear(int year)
	{
		return year % (year % 100 == 0 ? 400 : 4) == 0;
	}

	public static int getDayOfWeek(int year, int month, int day)
	{
		return (int) ((((computeTime(year, month, day, 0, 0, 0, 0, 0) + 0x00003883122cd800L) /
				86400000L) + 1) % 7);
	}

	public static int getNumOfDays(int year, int month)
	{
		return DAYS[isLeapYear(year) ? 1 : 0][month - 1];
	}

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

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

	public static long computeTime(int year, int month, int day,
			int hour, int minute, int second, int millis, int offset)
	{
		int i;
		int leap = isLeapYear(year--) ? 1 : 0;
		long result = -0x00003883122cd800L;
		byte[] months;
		result += 12622780800000L * (long) (year / 400);
		year %= 400;
		result += 3155673600000L * (long) (year / 100);
		year %= 100;
		result += 126230400000L * (long) (year / 4);
		year %= 4;
		result += 31536000000L * (long) year;
		months = DAYS[leap];
		for(i = month - 1; 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 static DateTimeFormat getDefaultFormat()
	{
		return DEFAULT_FORMAT;
	}

	public static DateTimeFormat getStandardFormat()
	{
		return STANDARD_FORMAT;
	}


	public GregorianCalendar()
	{
	}

	public String toString()
	{
		return toString(DEFAULT_FORMAT);
	}

	public String toString(DateTimeFormat format)
	{
		int[] f;
		getTimeInMillis();
		computeFields();
		return format.represent(1, (f = fields)[YEAR], f[MONTH], f[DAY_OF_MONTH], f[DAY_OF_WEEK],
				f[HOUR_OF_DAY], f[MINUTE], f[SECOND], f[MILLISECOND], getTimeZone());
	}

	protected void computeFields()
	{
		int dur;
		int h;
		int j;
		long i;
		long time;
		byte[] months;
		int[] f;
		i = (time = this.time +
				((long) getTimeZone().getRawOffset()) + 0x00003883122cd800L) % 86400000L;
		h = (f = fields)[HOUR_OF_DAY] = (int) (i / 3600000L);
		f[AM_PM] = h < 12 ? AM : PM;
		f[HOUR] = (h %= 12) == 0 ? 12 : h;
		i %= 3600000L;
		f[MINUTE] = (int) (i / 60000L);
		i %= 60000L;
		f[SECOND] = (int) (i / 1000L);
		i %= 1000L;
		f[MILLISECOND] = (int) i;
		i = time / 86400000L;
		j = (int) ((i / 146097L) * 400L);
		i %= 146097L;
		if(i == 146096L)
		{
			j += 399;
			i = 365L;
		} else
		{
			j += (int) ((i / 36524L) * 100L);
			i %= 36524L;
			j += (int) ((i / 1461L) * 4L);
			i %= 1461L;
		    if(i == 1460)
		    {
		        j += 3;
		        i = 365L;
		    } else
		    {
				j += (int) (i / 365L);
				i %= 365L;
		    }
		}
		f[YEAR] = ++j;
		months = DAYS[isLeapYear(j) ? 1 : 0];
		for(j = 0; i >= (long) (dur = months[j]); j++)
		{
			i -= (long) dur;
		}
		f[MONTH] = j;
		f[DAY_OF_MONTH] = ((int) i) + 1;
		f[DAY_OF_WEEK] = ((((int) ((time / 86400000L) % 7L)) + 1) % 7) + 1;
	}

	protected void computeTime()
	{
		int[] f = fields;
		correctTime();
		time = computeTime(f[YEAR], f[MONTH] + 1, f[DAY_OF_MONTH],
				f[HOUR_OF_DAY], f[MINUTE], f[SECOND], f[MILLISECOND],
				getTimeZone().getRawOffset());
	}

	private void correctTime()
	{
		int hour;
		int ampm;
		boolean[] sets = isSet;
		int[] f = fields;
		if(sets[HOUR_OF_DAY])
		{
			f[HOUR_OF_DAY] = hour = Math.abs(f[HOUR_OF_DAY] % 24);
			f[AM_PM] = hour < 12 ? AM : PM;
			sets[HOUR_OF_DAY] = false;
			return;
		}
		if(sets[AM_PM])
		{
			if((ampm = f[AM_PM]) != AM && ampm != PM)
			{
				f[AM_PM] = f[HOUR_OF_DAY] < 12 ? AM : PM;
			}
			sets[AM_PM] = false;
		}
		if(sets[HOUR])
		{
			if((hour = f[HOUR]) > 12)
			{
				f[HOUR_OF_DAY] = (hour % 12) + 12;
				f[HOUR] = hour % 12;
				f[AM_PM] = PM;
			} else
			{
				f[HOUR_OF_DAY] = f[AM_PM] == PM ? hour + 12 : hour;
			}
			sets[HOUR] = false;
		}
	}
}
