/*!
 * ${copyright}
 */

// Provides class sap.ui.core.date.UniversalDate
sap.ui.define(['sap/ui/base/Object', 'sap/ui/core/LocaleData', './_Calendars'],
	function(BaseObject, LocaleData, _Calendars) {
	"use strict";


	/**
	 * Constructor for UniversalDate.
	 *
	 * @class
	 * The UniversalDate is the base class of calendar date instances. It contains the static methods to create calendar
	 * specific instances.
	 *
	 * The member variable <code>this.oDate</code> contains the JS Date object, which is the source value of the date information.
	 * The prototype is containing getters and setters of the JS Date and is delegating them to the internal date object.
	 * Implementations for specific calendars may override methods needed for their specific calendar (e.g. getYear
	 * and getEra for Japanese emperor calendar);
	 *
	 * @private
	 * @ui5-restricted
	 * @alias sap.ui.core.date.UniversalDate
	 */
	var UniversalDate = BaseObject.extend("sap.ui.core.date.UniversalDate", /** @lends sap.ui.core.date.UniversalDate.prototype */ {
		constructor: function() {
			var clDate = UniversalDate.getClass();
			return this.createDate(clDate, arguments);
		}
	});

	UniversalDate.UTC = function() {
		var clDate = UniversalDate.getClass();
		return clDate.UTC.apply(clDate, arguments);
	};

	UniversalDate.now = function() {
		return Date.now();
	};

	UniversalDate.prototype.createDate = function(clDate, aArgs) {
		switch (aArgs.length) {
			case 0: return new clDate();
			// new Date(new Date()) is officially not supported
			// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
			// IE 11 loses milliseconds when using new Date(new Date())
			// var oDateWithMs = new Date(1985, 9, 9, 13, 37, 13, 370);
			// new Date(oDateWithMs).getMilliseconds();
			// ie11: 0
			// chrome/edge/safari/ff: 370
			// -> Date#getTime to circumvent this problem
			// this is e.g. executed when calling <code>new UniversalDate(oDateWithMs)</code>
			case 1: return new clDate(aArgs[0] instanceof Date ? aArgs[0].getTime() : aArgs[0]);
			case 2: return new clDate(aArgs[0], aArgs[1]);
			case 3: return new clDate(aArgs[0], aArgs[1], aArgs[2]);
			case 4: return new clDate(aArgs[0], aArgs[1], aArgs[2], aArgs[3]);
			case 5: return new clDate(aArgs[0], aArgs[1], aArgs[2], aArgs[3], aArgs[4]);
			case 6: return new clDate(aArgs[0], aArgs[1], aArgs[2], aArgs[3], aArgs[4], aArgs[5]);
			case 7: return new clDate(aArgs[0], aArgs[1], aArgs[2], aArgs[3], aArgs[4], aArgs[5], aArgs[6]);
		}
	};

	/**
	 * Returns an instance of Date, based on the calendar type from the configuration, or as explicitly
	 * defined by parameter. The object provides all methods also known on the JavaScript Date object.
	 *
	 * Note: Prefer this method over calling <code>new UniversalDate</code> with an instance of <code>Date</code>
	 *
	 * @param {Date|sap.ui.core.date.UniversalDate} [oDate] JavaScript date object, defaults to <code>new Date()</code>
	 * @param {sap.ui.core.CalendarType} [sCalendarType] The calendar type, defaults to <code>sap.ui.getCore().getConfiguration().getCalendarType()</code>
	 * @returns {sap.ui.core.date.UniversalDate} The date instance
	 * @public
	 */
	UniversalDate.getInstance = function(oDate, sCalendarType) {
		var clDate, oInstance;
		if (oDate instanceof UniversalDate) {
			oDate = oDate.getJSDate();
		} else if (!oDate) {
			oDate = new Date();
		}
		if (!sCalendarType) {
			sCalendarType = sap.ui.getCore().getConfiguration().getCalendarType();
		}
		clDate = UniversalDate.getClass(sCalendarType);
		oInstance = Object.create(clDate.prototype);
		oInstance.oDate = oDate;
		oInstance.sCalendarType = sCalendarType;
		return oInstance;
	};

	/**
	 * Returns a specific Date class, based on the calendar type from the configuration, or as explicitly
	 * defined by parameter. The object provides all methods also known on the JavaScript Date object.
	 *
	 * @param {sap.ui.core.CalendarType} sCalendarType the type of the used calendar
	 * @public
	 */
	UniversalDate.getClass = function(sCalendarType) {
		if (!sCalendarType) {
			sCalendarType = sap.ui.getCore().getConfiguration().getCalendarType();
		}
		return _Calendars.get(sCalendarType);
	};

	/*
	 * Loop through the Date class and create delegates of all Date API methods
	 */
	[
		"getDate", "getMonth", "getFullYear", "getYear", "getDay", "getHours", "getMinutes", "getSeconds", "getMilliseconds",
		"getUTCDate", "getUTCMonth", "getUTCFullYear", "getUTCDay", "getUTCHours", "getUTCMinutes", "getUTCSeconds", "getUTCMilliseconds",
		"getTime", "valueOf", "getTimezoneOffset", "toString", "toDateString",
		"setDate", "setFullYear", "setYear", "setMonth", "setHours", "setMinutes", "setSeconds", "setMilliseconds",
		"setUTCDate", "setUTCFullYear", "setUTCMonth", "setUTCHours", "setUTCMinutes", "setUTCSeconds", "setUTCMilliseconds"
	].forEach(function(sName) {
		UniversalDate.prototype[sName] = function() {
			return this.oDate[sName].apply(this.oDate, arguments);
		};
	});

	/**
	 * Returns the JS date object representing the current calendar date value.
	 *
	 * @returns {Date} The JS date object representing the current calendar date value
	 * @public
	 */
	UniversalDate.prototype.getJSDate = function() {
		return this.oDate;
	};

	/**
	 * Returns the calendar type of the current instance of a UniversalDate.
	 *
	 * @returns {sap.ui.core.CalendarType} The calendar type of the date
	 */
	UniversalDate.prototype.getCalendarType = function() {
		return this.sCalendarType;
	};

	/*
	 * Provide additional getters/setters, not yet covered by the JS Date
	 */
	UniversalDate.prototype.getEra = function() {
		return UniversalDate.getEraByDate(this.sCalendarType, this.oDate.getFullYear(), this.oDate.getMonth(), this.oDate.getDate());
	};
	UniversalDate.prototype.setEra = function(iEra) {
		// The default implementation does not support setting the era
	};
	UniversalDate.prototype.getUTCEra = function() {
		return UniversalDate.getEraByDate(this.sCalendarType, this.oDate.getUTCFullYear(), this.oDate.getUTCMonth(), this.oDate.getUTCDate());
	};
	UniversalDate.prototype.setUTCEra = function(iEra) {
		// The default implementation does not support setting the era
	};
	UniversalDate.prototype.getWeek = function() {
		return UniversalDate.getWeekByDate(this.sCalendarType, this.getFullYear(), this.getMonth(), this.getDate());
	};
	UniversalDate.prototype.setWeek = function(oWeek) {
		var oDate = UniversalDate.getFirstDateOfWeek(this.sCalendarType, oWeek.year || this.getFullYear(), oWeek.week);
		this.setFullYear(oDate.year, oDate.month, oDate.day);
	};
	UniversalDate.prototype.getUTCWeek = function() {
		return UniversalDate.getWeekByDate(this.sCalendarType, this.getUTCFullYear(), this.getUTCMonth(), this.getUTCDate());
	};
	UniversalDate.prototype.setUTCWeek = function(oWeek) {
		var oDate = UniversalDate.getFirstDateOfWeek(this.sCalendarType, oWeek.year || this.getFullYear(), oWeek.week);
		this.setUTCFullYear(oDate.year, oDate.month, oDate.day);
	};
	UniversalDate.prototype.getQuarter = function() {
		return Math.floor((this.getMonth() / 3));
	};
	UniversalDate.prototype.getUTCQuarter = function() {
		return Math.floor((this.getUTCMonth() / 3));
	};
	UniversalDate.prototype.getDayPeriod = function() {
		if (this.getHours() < 12) {
			return 0;
		} else {
			return 1;
		}
	};
	UniversalDate.prototype.getUTCDayPeriod = function() {
		if (this.getUTCHours() < 12) {
			return 0;
		} else {
			return 1;
		}
	};


	// TODO: These are currently needed for the DateFormat test, as the date used in the test
	// has been enhanced with these methods. Should be implemented using CLDR data.
	UniversalDate.prototype.getTimezoneShort = function() {
		if (this.oDate.getTimezoneShort) {
			return this.oDate.getTimezoneShort();
		}
	};
	UniversalDate.prototype.getTimezoneLong = function() {
		if (this.oDate.getTimezoneLong) {
			return this.oDate.getTimezoneLong();
		}
	};

	/*
	 * Helper methods for week calculations
	 */
	var iMillisecondsInWeek = 7 * 24 * 60 * 60 * 1000;

	UniversalDate.getWeekByDate = function(sCalendarType, iYear, iMonth, iDay) {
		var oLocale = sap.ui.getCore().getConfiguration().getFormatSettings().getFormatLocale(),
			clDate = this.getClass(sCalendarType),
			oFirstDay = getFirstDayOfFirstWeek(clDate, iYear),
			oDate = new clDate(clDate.UTC(iYear, iMonth, iDay)),
			iWeek, iLastYear, iNextYear, oLastFirstDay, oNextFirstDay;
		// If region is US, always calculate the week for the current year, otherwise
		// the week might be the last week of the previous year or first week of next year
		if (oLocale.getLanguage() === "en" && (oLocale.getRegion() === "US" || !oLocale.getRegion())) {
			iWeek = calculateWeeks(oFirstDay, oDate);
		} else {
			iLastYear = iYear - 1;
			iNextYear = iYear + 1;
			oLastFirstDay = getFirstDayOfFirstWeek(clDate, iLastYear);
			oNextFirstDay = getFirstDayOfFirstWeek(clDate, iNextYear);
			if (oDate >= oNextFirstDay) {
				iYear = iNextYear;
				iWeek = 0;
			} else if (oDate < oFirstDay) {
				iYear = iLastYear;
				iWeek = calculateWeeks(oLastFirstDay, oDate);
			} else {
				iWeek = calculateWeeks(oFirstDay, oDate);
			}
		}
		return {
			year: iYear,
			week: iWeek
		};
	};

	UniversalDate.getFirstDateOfWeek = function(sCalendarType, iYear, iWeek) {
		var oLocale = sap.ui.getCore().getConfiguration().getFormatSettings().getFormatLocale(),
			clDate = this.getClass(sCalendarType),
			oFirstDay = getFirstDayOfFirstWeek(clDate, iYear),
			oDate = new clDate(oFirstDay.valueOf() + iWeek * iMillisecondsInWeek),
			bIsRegionUS = oLocale.getLanguage() === "en" && (oLocale.getRegion() === "US" || !oLocale.getRegion());
		//If first day of week is in last year and region is US, return the
		//1st of January instead for symmetric behaviour

		if (bIsRegionUS && iWeek === 0 && oFirstDay.getUTCFullYear() < iYear) {
			return {
				year: iYear,
				month: 0,
				day: 1
			};
		}
		return {
			year: oDate.getUTCFullYear(),
			month: oDate.getUTCMonth(),
			day: oDate.getUTCDate()
		};
	};

	function getFirstDayOfFirstWeek(clDate, iYear) {
		var oLocale = sap.ui.getCore().getConfiguration().getFormatSettings().getFormatLocale(),
			oLocaleData = LocaleData.getInstance(oLocale),
			iMinDays = oLocaleData.getMinimalDaysInFirstWeek(),
			iFirstDayOfWeek = oLocaleData.getFirstDayOfWeek(),
			oFirstDay = new clDate(clDate.UTC(iYear, 0, 1)),
			iDayCount = 7;
		// Find the first day of the first week of the year
		while (oFirstDay.getUTCDay() !== iFirstDayOfWeek) {
			oFirstDay.setUTCDate(oFirstDay.getUTCDate() - 1);
			iDayCount--;
		}
		// If less than min days are left, first week is one week later
		if (iDayCount < iMinDays) {
			oFirstDay.setUTCDate(oFirstDay.getUTCDate() + 7);
		}
		return oFirstDay;
	}

	function calculateWeeks(oFromDate, oToDate) {
		return Math.floor((oToDate.valueOf() - oFromDate.valueOf()) / iMillisecondsInWeek);
	}

	/*
	 * Helper methods for era calculations
	 */
	var mEras = {};

	UniversalDate.getEraByDate = function(sCalendarType, iYear, iMonth, iDay) {
		var aEras = getEras(sCalendarType),
			iTimestamp = new Date(0).setUTCFullYear(iYear, iMonth, iDay),
			oEra;
		for (var i = aEras.length - 1; i >= 0; i--) {
			oEra = aEras[i];
			if (!oEra) {
				continue;
			}
			if (oEra._start && iTimestamp >= oEra._startInfo.timestamp) {
				return i;
			}
			if (oEra._end && iTimestamp < oEra._endInfo.timestamp) {
				return i;
			}
		}
	};

	UniversalDate.getCurrentEra = function(sCalendarType) {
		var oNow = new Date();
		return this.getEraByDate(sCalendarType, oNow.getFullYear(), oNow.getMonth(), oNow.getDate());
	};

	UniversalDate.getEraStartDate = function(sCalendarType, iEra) {
		var aEras = getEras(sCalendarType),
			oEra = aEras[iEra] || aEras[0];
		if (oEra._start) {
			return oEra._startInfo;
		}
	};

	function getEras(sCalendarType) {
		var oLocale = sap.ui.getCore().getConfiguration().getFormatSettings().getFormatLocale(),
			oLocaleData = LocaleData.getInstance(oLocale),
			aEras = mEras[sCalendarType];
		if (!aEras) {
			// Get eras from localedata, parse it and add it to the array
			var aEras = oLocaleData.getEraDates(sCalendarType);
			if (!aEras[0]) {
				aEras[0] = {_start: "1-1-1"};
			}
			for (var i = 0; i < aEras.length; i++) {
				var oEra = aEras[i];
				if (!oEra) {
					continue;
				}
				if (oEra._start) {
					oEra._startInfo = parseDateString(oEra._start);
				}
				if (oEra._end) {
					oEra._endInfo = parseDateString(oEra._end);
				}
			}
			mEras[sCalendarType] = aEras;
		}
		return aEras;
	}

	function parseDateString(sDateString) {
		var aParts = sDateString.split("-"),
			iYear, iMonth, iDay;
		if (aParts[0] == "") {
			// negative year
			iYear = -parseInt(aParts[1]);
			iMonth = parseInt(aParts[2]) - 1;
			iDay = parseInt(aParts[3]);
		} else {
			iYear = parseInt(aParts[0]);
			iMonth = parseInt(aParts[1]) - 1;
			iDay = parseInt(aParts[2]);
		}
		return {
			timestamp: new Date(0).setUTCFullYear(iYear, iMonth, iDay),
			year: iYear,
			month: iMonth,
			day: iDay
		};
	}

	return UniversalDate;

});
