/*
** Copyright (C) 2001-2026 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/


var CLNDR = null,
	calendar = function (timeobject, trigger_elmnt, date_time_format) {
		if (!this.checkOuterObj(timeobject)) {
			throw 'Calendar: constructor expects second parameter to be input form field DOM node.';
		}

		this.id = jQuery(trigger_elmnt).attr('id');
		this.trigger_elmnt = trigger_elmnt;
		this.date_time_format = date_time_format;
		this.sdt = new CDate();
	};

function toggleCalendar(trigger_elmnt, time_input, date_time_format) {
	if (CLNDR && jQuery(trigger_elmnt).is(CLNDR.trigger_elmnt) && CLNDR.is_visible) {
		CLNDR.clndrhide();
	}
	else {
		CLNDR && CLNDR.clndrhide();
		CLNDR = new calendar(time_input, trigger_elmnt, date_time_format);
		CLNDR.clndrshow();
	}
}

calendar.prototype = {
	id: null,					// Calendar ID. Should be unique on page.
	sdt: null,					// Date object of a selected date.
	month: 0,					// Represents currently opened month number.
	year: 2008,					// Represents currently opened year.
	day: 1,						// Represents currently opened day.
	clndr_calendar: null,		// html obj of calendar
	clndr_month_div: null,		// html obj
	clndr_year_div: null,		// html obj
	clndr_days: null,			// html obj
	clndr_month: null,			// html obj
	clndr_year: null,			// html obj
	clndr_year_wrap: null,		// html obj
	clndr_month_wrap: null,		// html obj
	clndr_monthup: null,		// html bttn obj
	clndr_monthdown: null,		// html bttn obj
	clndr_yearup: null,			// html bttn obj
	clndr_yeardown: null,		// html bttn obj
	timeobject: null,			// Input field with selected time. Source and destination of selected date.
	is_visible: false,			// State of calendar visibility.
	has_user_time: false,		// Confirms, if time was selected from input field.
	hl_month: null,				// highlighted month number
	hl_year: null,				// highlighted year number
	hl_day: null,				// highlighted days number
	active_section: null,		// Active calendar section. See 'sections' array. Default value set in method clndrshow.
	monthname: new Array(t('S_JANUARY'), t('S_FEBRUARY'), t('S_MARCH'), t('S_APRIL'), t('S_MAY'), t('S_JUNE'),
		t('S_JULY'), t('S_AUGUST'), t('S_SEPTEMBER'), t('S_OCTOBER'), t('S_NOVEMBER'), t('S_DECEMBER')
	),
	dayname: new Array(t('S_SUNDAY'), t('S_MONDAY'), t('S_TUESDAY'), t('S_WEDNESDAY'), t('S_THURSDAY'), t('S_FRIDAY'),
		t('S_SATURDAY')
	),
	sections: new Array('.calendar-year', '.calendar-month', '.calendar-date'),
	date_time_format: PHP_ZBX_FULL_DATE_TIME,
	trigger_elmnt: null,		// Calendar visibility trigger element.

	ondateselected: function() {
		this.setDateToOuterObj();
		this.clndrhide();
	},

	clndrhide: function() {
		if (this.is_visible) {
			this.calendardelete();
			this.is_visible = false;

			jQuery(window).off('resize', this.calendarPositionHandler);

			jQuery(document)
				.off('click', this.calendarDocumentClickHandler)
				.off('keydown', this.calendarKeyDownHandler)
				.off('keyup', this.calendarKeyUpHandler);

			removeFromOverlaysStack(this.id);
		}
	},

	clndrshow: function() {
		this.calendarcreate();
		this.setSDateFromOuterObj();
		this.syncBSDateBySDT();
		this.syncHlDate();
		this.setCDate();

		this.calendarPositionHandler();
		this.clndr_calendar.style.display = (this.clndr_calendar.tagName === 'span') ? 'inline' : 'block';
		this.is_visible = true;

		jQuery(window).on('resize', jQuery.proxy(this.calendarPositionHandler, this));
		jQuery(this.trigger_elmnt).on('remove', jQuery.proxy(this.clndrhide, this));

		jQuery(document)
			.on('keydown', jQuery.proxy(this.calendarKeyDownHandler, this))
			.on('keyup', jQuery.proxy(this.calendarKeyUpHandler, this))
			.on('click', jQuery.proxy(this.calendarDocumentClickHandler, this));

		addToOverlaysStack(this.id, this.trigger_elmnt, 'clndr');

		this.active_section = this.sections.indexOf('.calendar-date');
		this.focusSection();
	},

	setPosition: function(top, left) {
		jQuery(this.clndr_calendar).css({
			top: Math.min(top, jQuery(window).height() - jQuery(this.clndr_calendar).outerHeight()) + 'px',
			left: Math.min(left, jQuery(window).width() - jQuery(this.clndr_calendar).outerWidth()) + 'px'
		});
	},

	calendarDocumentClickHandler: function(e) {
		var $target = jQuery(e.target);

		if (!$target.is(this.trigger_elmnt) && !$target.closest('.overlay-dialogue.calendar').length) {
			this.clndrhide();
		}
	},

	calendarPositionHandler: function () {
		var $anchor = jQuery(this.trigger_elmnt),
			offset = $anchor.offset();

		this.setPosition(offset.top + $anchor.height(), offset.left + $anchor.width());
	},

	/**
	 * This function is workaround for Firefox bug.
	 *
	 * When triggering keydown event on [space] button, event is called for both, the actual element as well as calendar
	 * icon element, so the calendar is first closed (by handler of actually focused element) and immediately opened
	 * again (by calendar icon element's handler).
	 *
	 * Workaround works as follow - it separates [enter] and [space] button in 2 handlers with similar functionality
	 * (since pressing [space] and [enter] does the same thing in calendar). Keyup handles the [space] click event,
	 * while other keyboard events are handled by keydown event.
	 */
	calendarKeyUpHandler: function(event) {
		if (event.which == 32) { // Space
			// Enter has special meaning for each Calendar section.
			var active_section = this.sections[this.active_section];
			if (active_section === '.calendar-year' ||  active_section === '.calendar-month') {
				this.active_section++;
				this.focusSection();
			}
			else if (active_section === '.calendar-date') {
				this.setday(event, this.hl_day, this.hl_month, this.hl_year);
			}

			return false; // Prevent page scrolling when pressing Space.
		}
	},

	calendarKeyDownHandler: function(event) {
		var hl_date;

		if (this.active_section < 0 || this.active_section > this.sections.length) {
			this.active_section = 0;
		}

		switch (event.which) {
			case 37: // arrow left
			case 38: // arrow up
			case 39: // arrow right
			case 40: // arrow down
				switch (this.sections[this.active_section]) {
					case '.calendar-date':
						hl_date = new Date(this.hl_year, this.hl_month, this.hl_day, 0, 0, 0, 0);

						switch (event.which) {
							case 37: // arrow left
								hl_date.setDate(hl_date.getDate() - 1);
								break;

							case 38: // arrow up
								hl_date.setDate(hl_date.getDate() - 7);
								break;

							case 39: // arrow right
								hl_date.setDate(hl_date.getDate() + 1);
								break;

							case 40: // arrow down
								hl_date.setDate(hl_date.getDate() + 7);
								break;
						}

						this.hl_year = hl_date.getFullYear();
						this.hl_month = hl_date.getMonth();
						this.hl_day = hl_date.getDate();

						jQuery('td.highlighted', this.clndr_calendar)
							.removeClass('highlighted')
							.attr('tabindex', '-1');

						if (this.hl_year != this.year || this.hl_month != this.month) {
							this.year = this.hl_year;
							this.month = this.hl_month;
							this.day = this.hl_day;
							this.setCDate();
						}

						jQuery('td[data-date='+this.hl_day+']', this.clndr_calendar)
							.addClass('highlighted')
							.attr('tabindex', '0')
							.focus();
						break;

					case '.calendar-year':
						// Arrow left or arrow down.
						if (event.which == 37 || event.which == 40) {
							this.yeardown();
						}
						// Arrow right or arrow up.
						else if (event.which == 38 || event.which == 39) {
							this.yearup();
						}
						break;

					case '.calendar-month':
						// Arrow left or arrow down.
						if (event.which == 37 || event.which == 40) {
							this.monthdown();
						}
						// Arrow right or arrow up.
						else if (event.which == 38 || event.which == 39) {
							this.monthup();
						}
						break;
				}

				// Prevent page scrolling.
				event.preventDefault();

				break;

			case 9: // Tab
				event.preventDefault();

				if (event.shiftKey) {
					this.active_section--;
					if (this.active_section < 0) {
						this.active_section = this.sections.length - 1;
					}
				}
				else {
					this.active_section++;
					if (this.active_section >= this.sections.length) {
						this.active_section = 0;
					}
				}

				this.focusSection();
				break;

			case 13: // Enter
				// Enter has special meaning for each Calendar section.
				var active_section = this.sections[this.active_section];
				if (active_section === '.calendar-year' ||  active_section === '.calendar-month') {
					this.active_section++;
					this.focusSection();
				}
				else if (active_section === '.calendar-date') {
					this.setday(event, this.hl_day, this.hl_month, this.hl_year);
				}

				return false;

			case 32: // Prevent page scrolling when pressing Space.
				return false;
		}
	},

	focusSection: function() {
		var section_to_focus = this.sections[this.active_section];

		jQuery('.highlighted', this.clndr_calendar).removeClass('highlighted').blur();
		if (section_to_focus === '.calendar-year' ||  section_to_focus === '.calendar-month') {
			jQuery(section_to_focus, this.clndr_calendar).addClass('highlighted').focus();
		}
		else if (section_to_focus === '.calendar-date') {
			/**
			 * Switching between months and years, date picker will highlight previously selected date. If
			 * selected date is in different year or month, the first date of displayed year is highlighted.
			 * Same happens also if the number of dates in selected month is smaller than selected date in different
			 * month.
			 */
			if (this.hl_year != this.year || this.hl_month != this.month
					|| new Date(this.year, this.month + 1, 0).getDate() < this.hl_day) {
				this.hl_day = 1;
			}

			jQuery('td[data-date='+this.hl_day+']', this.clndr_calendar)
				.addClass('highlighted')
				.attr('tabindex', '0')
				.focus();
		}
	},

	checkOuterObj: function(timeobject) {
		if (typeof(timeobject) === 'undefined' || empty(timeobject)) {
			return false;
		}

		this.timeobject = document.getElementById(timeobject);

		if (this.timeobject === null || this.timeobject.tagName.toLowerCase() !== 'input') {
			return false;
		}

		return true;
	},

	setSDateFromOuterObj: function() {
		var val = this.timeobject.value,
			/**
			 * Date and time format separators must be synced with ZBX_FULL_DATE_TIME, ZBX_DATE_TIME and ZBX_DATE in
			 * defines.inc.php.
			 */
			datetime = val.split(' '),
			date = datetime[0].split('-'),
			time = (datetime.length > 1) ? datetime[1].split(':') : new Array();

		// Open calendar with current time by default.
		this.sdt = new CDate();
		this.has_user_time = false;

		if (date.length === 3 && this.setSDateDMY(date[2], date[1], date[0])) {
			if (!time.length) {
				return;
			}

			this.sdt.setTimeObject(null, null, null, 0, 0, 0);

			// Set time to calendar, so time doesn't change when selecting different date.
			if ((time.length === 2 || time.length === 3)
					&& time[0] > -1 && time[0] < 24
					&& time[1] > -1 && time[1] < 60) {
				this.has_user_time = true;
				this.sdt.setHours(time[0]);
				this.sdt.setMinutes(time[1]);

				if (time.length === 3 && time[2] > -1 && time[2] < 60) {
					this.sdt.setSeconds(time[2]);
				}
			}
		}
	},

	setSDateDMY: function(d, m, y) {
		var dateHolder = new Date(y, m - 1, d, 0, 0, 0);

		if (y >= 1970 && dateHolder.getFullYear() == y && dateHolder.getMonth() == m - 1 && dateHolder.getDate() == d) {
			this.sdt.setTimeObject(y, m - 1, d);
			return true;
		}

		return false;
	},

	setDateToOuterObj: function() {
		var $input = jQuery(this.timeobject),
			new_val = this.sdt.format(this.date_time_format);

		if ($input.val() != new_val) {
			$input.val(new_val).trigger('change');
		}
	},

	setday: function(e, day, month, year) {
		this.day = day;
		this.month = month;
		this.year = year;
		this.syncSDT();
		this.syncBSDateBySDT();
		this.ondateselected();
	},

	monthup: function() {
		this.month++;

		if (this.month > 11) {
			// prevent months from running in loop in year 2038
			if (this.year < 2038) {
				this.month = 0;
				this.yearup();
			}
			else {
				this.month = 11;
			}
		}
		else {
			this.setCDate();
		}

		this.hl_month = this.month;
		this.hl_year = this.year;
	},

	monthdown: function() {
		this.month--;

		if (this.month < 0) {
			// prevent months from running in loop in year 1970
			if (this.year > 1970) {
				this.month = 11;
				this.yeardown();
			}
			else {
				this.month = 0;
			}
		}
		else {
			this.setCDate();
		}

		this.hl_month = this.month;
		this.hl_year = this.year;
	},

	yearup: function() {
		if (this.year >= 2038) {
			return ;
		}
		this.year++;
		this.setCDate();
		this.hl_year = this.year;
	},

	yeardown: function() {
		if (this.year <= 1970) {
			return ;
		}
		this.year--;
		this.setCDate();
		this.hl_year = this.year;
	},

	syncBSDateBySDT: function() {
		this.day = this.sdt.getDate();
		this.month = this.sdt.getMonth();
		this.year = this.sdt.getFullYear();
	},

	syncHlDate: function() {
		this.hl_day = this.day;
		this.hl_month = this.month;
		this.hl_year = this.year;
	},

	syncSDT: function() {
		var hours = 0,
			minutes = 0,
			seconds = 0;

		if (this.has_user_time) {
			// If time was present in input field, use it.
			hours = this.sdt.getHours();
			minutes = this.sdt.getMinutes();
			seconds = this.sdt.getSeconds();
		}
		else {
			var cdt = new CDate();

			// If today was selected, use current time. Otherwise use 00:00:00.
			if (cdt.getFullYear() === this.year && cdt.getMonth() === this.month && cdt.getDate() === this.day) {
				hours = cdt.getHours();
				minutes = cdt.getMinutes();
				seconds = cdt.getSeconds();
			}
		}

		this.sdt.setTimeObject(this.year, this.month, this.day, hours, minutes, seconds);
	},

	setCDate: function() {
		this.clndr_month.textContent = this.monthname[this.month];
		this.clndr_year.textContent = this.year;
		this.createDaysTab();
	},

	createDaysTab: function() {
		var tbody = this.clndr_days;
		tbody.innerHTML = '';

		var cdt = new CDate();

		// Start drawing days from first week of the month.
		cdt.setTimeObject(this.year, this.month, 1);

		// make 0 - Monday, not Sunday (as default)
		var prev_days = cdt.getDay() - 1;
		if (prev_days < 0) {
			prev_days = 6;
		}

		// Set to first day of the week.
		if (prev_days > 0) {
			cdt.setTime(cdt.getTime() - prev_days * 86400000);
		}

		for (var y = 0; y < 6; y++) {
			var tr = document.createElement('tr');
			tr.setAttribute('role', 'presentation');

			tbody.appendChild(tr);
			for (var x = 0; x < 7; x++) {
				var td = document.createElement('td');
				tr.appendChild(td);

				if (this.month != cdt.getMonth()) {
					$(td).addClass('grey');
				}
				else {
					td.setAttribute('data-date', cdt.getDate());
				}

				if (this.sdt.getFullYear() == cdt.getFullYear()
						&& this.sdt.getMonth() == cdt.getMonth()
						&& this.sdt.getDate() == cdt.getDate()) {
					$(td).addClass('selected');
				}

				td.setAttribute('aria-label', this.calendarGetReadableDate(cdt));
				td.setAttribute('tabindex', '-1');
				td.setAttribute('role', 'button');

				var span = document.createElement('span');
				span.setAttribute('aria-hidden', 'true');
				span.appendChild(document.createTextNode(cdt.getDate()));
				td.appendChild(span);

				addListener(td, 'click', this.setday.bindAsEventListener(
					this, cdt.getDate(), cdt.getMonth(), cdt.getFullYear()
				));

				cdt.setTime(cdt.getTime() + 86400000); // + 1day
			}
		}
	},

	calendarGetReadableDate: function(cdt) {
		return cdt.getDate() + ', ' + this.dayname[cdt.getDay()] + ' ' + this.monthname[cdt.getMonth()] + ' ' +
				cdt.getFullYear();
	},
	/**
	 * Create and append calendar DOM element to body.
	 */
	calendarcreate: function() {
		this.clndr_calendar = document.createElement('div');
		this.clndr_calendar.className = 'overlay-dialogue calendar';
		this.clndr_calendar.setAttribute('aria-label', t('S_CALENDAR'));
		this.clndr_calendar.setAttribute('role', 'application');
		this.clndr_calendar.setAttribute('tabindex', '0');
		this.clndr_calendar.style.display = 'none';

		document.body.appendChild(this.clndr_calendar);

		/*
		 * Calendar header
		 */
		var header = document.createElement('div');
		this.clndr_calendar.appendChild(header);
		header.className = 'calendar-header';

		//  year
		this.clndr_year_div = document.createElement('div');
		this.clndr_year_div.setAttribute('role', 'presentation');
		this.clndr_year_div.className = 'calendar-year';
		header.appendChild(this.clndr_year_div);

		var arrow_left = document.createElement('span');
		arrow_left.className = 'arrow-left';
		var arrow_right = document.createElement('span');
		arrow_right.className = 'arrow-right';

		this.clndr_yeardown = document.createElement('button');
		this.clndr_yeardown.setAttribute('type', 'button');
		this.clndr_yeardown.setAttribute('tabindex', '-1');
		this.clndr_yeardown.className = 'btn-grey';
		this.clndr_yeardown.appendChild(arrow_left);
		this.clndr_year_div.appendChild(this.clndr_yeardown);

		this.clndr_year = document.createTextNode('');

		this.clndr_year_wrap = document.createElement('span');
		this.clndr_year_wrap.appendChild(this.clndr_year);
		this.clndr_year_wrap.setAttribute('aria-live', 'assertive');
		this.clndr_year_wrap.setAttribute('id', 'current-year'+this.id);
		this.clndr_year_wrap.setAttribute('aria-atomic', 'true');
		this.clndr_year_div.appendChild(this.clndr_year_wrap);
		this.clndr_year_div.setAttribute('aria-labelledby', this.clndr_year_wrap.id);

		this.clndr_yearup = document.createElement('button');
		this.clndr_yearup.setAttribute('type', 'button');
		this.clndr_yearup.setAttribute('tabindex', '-1');
		this.clndr_yearup.className = 'btn-grey';
		this.clndr_yearup.appendChild(arrow_right);
		this.clndr_year_div.appendChild(this.clndr_yearup);

		// month
		this.clndr_month_div = document.createElement('div');
		this.clndr_month_div.className = 'calendar-month';
		this.clndr_month_div.setAttribute('role', 'presentation');
		header.appendChild(this.clndr_month_div);

		var arrow_left = document.createElement('span');
		arrow_left.className = 'arrow-left';
		var arrow_right = document.createElement('span');
		arrow_right.className = 'arrow-right';

		this.clndr_monthdown = document.createElement('button');
		this.clndr_monthdown.setAttribute('type', 'button');
		this.clndr_monthdown.setAttribute('tabindex', '-1');
		this.clndr_monthdown.className = 'btn-grey';
		this.clndr_monthdown.appendChild(arrow_left);
		this.clndr_month_div.appendChild(this.clndr_monthdown);

		this.clndr_month = document.createTextNode('');
		this.clndr_month_wrap = document.createElement('span');
		this.clndr_month_wrap.setAttribute('aria-live', 'assertive');
		this.clndr_month_wrap.setAttribute('aria-atomic', 'true');
		this.clndr_month_wrap.setAttribute('id', 'current-month'+this.id);
		this.clndr_month_wrap.appendChild(this.clndr_month);
		this.clndr_month_div.appendChild(this.clndr_month_wrap);
		this.clndr_month_div.setAttribute('aria-labelledby', this.clndr_month_wrap.id);

		this.clndr_monthup = document.createElement('button');
		this.clndr_monthup.setAttribute('type', 'button');
		this.clndr_monthup.setAttribute('tabindex', '-1');
		this.clndr_monthup.className = 'btn-grey';
		this.clndr_monthup.appendChild(arrow_right);
		this.clndr_month_div.appendChild(this.clndr_monthup);

		// days heading
		var table = document.createElement('table');
		this.clndr_calendar.appendChild(table);

		var thead = document.createElement('thead');
		thead.setAttribute('role', 'presentation');
		table.appendChild(thead);

		var tr = document.createElement('tr');
		thead.appendChild(tr);

		[
			t('S_MONDAY_SHORT_BIG'),
			t('S_TUESDAY_SHORT_BIG'),
			t('S_WEDNESDAY_SHORT_BIG'),
			t('S_THURSDAY_SHORT_BIG'),
			t('S_FRIDAY_SHORT_BIG'),
			t('S_SATURDAY_SHORT_BIG'),
			t('S_SUNDAY_SHORT_BIG')
		].forEach(function(str) {
			var td = document.createElement('th');
			td.appendChild(document.createTextNode(str));
			tr.appendChild(td);
		});

		/*
		 * Days calendar
		 */
		this.clndr_days = document.createElement('tbody');
		this.clndr_days.setAttribute('class', 'calendar-date');
		table.appendChild(this.clndr_days);

		addListener(this.clndr_monthdown, 'click', this.monthdown.bindAsEventListener(this));
		addListener(this.clndr_monthup, 'click', this.monthup.bindAsEventListener(this));
		addListener(this.clndr_yeardown, 'click', this.yeardown.bindAsEventListener(this));
		addListener(this.clndr_yearup, 'click', this.yearup.bindAsEventListener(this));

		// Active section setter.
		var cal_obj = this;
		jQuery(this.sections).each(function(index, item) {
			jQuery(item, cal_obj.clndr_calendar)
				.attr({'tabindex': '0'})
				.on('click', function() {
					cal_obj.active_section = index;
					cal_obj.focusSection();
				});
		});
	},
	/**
	 * Remove calendar DOM element. Detach event listeners.
	 */
	calendardelete: function() {
		removeListener(this.clndr_monthdown, 'click', this.monthdown);
		removeListener(this.clndr_monthup, 'click', this.monthup);
		removeListener(this.clndr_yeardown, 'click', this.yeardown);
		removeListener(this.clndr_yearup, 'click', this.yearup);
		jQuery(this.clndr_calendar).remove();
	}
};
