/*!
 * datepair.js v0.4.16 - A javascript plugin for intelligently selecting date and time ranges inspired by Google Calendar.
 * Copyright (c) 2018 Jon Thornton - http://jonthornton.github.com/Datepair.js
 * License: MIT
 */

(function(window, document) {

	'use strict';

	var _ONE_DAY = 86400000;
	var jq = window.Zepto || window.jQuery;

	function simpleExtend(obj1, obj2) {
		var out = obj2 || {};

		for (var i in obj1) {
			if (!(i in out)) {
				out[i] = obj1[i];
			}
		}

		return out;
	}

	// IE's custom event support is totally borked.
	// Use jQuery if possible
	function triggerSimpleCustomEvent(el, eventName) {
		if (jq) {
			jq(el).trigger(eventName);
		} else {
			var event = document.createEvent('CustomEvent');
			event.initCustomEvent(eventName, true, true, {});
			el.dispatchEvent(event);
		}
	}

	// el.classList not supported by < IE10
	// use jQuery if available
	function hasClass(el, className) {
		if (jq) {
			return jq(el).hasClass(className);
		} else {
			return el.classList.contains(className);
		}
	}

	function Datepair(container, options) {
		this.dateDelta = null;
		this.timeDelta = null;
		this._defaults =	{
			startClass: 'start',
			endClass: 'end',
			timeClass: 'time',
			dateClass: 'date',
			defaultDateDelta: 0,
			defaultTimeDelta: 3600000,
			anchor: 'start',

			// defaults for jquery-timepicker; override when using other input widgets
			parseTime: function(input){
				return jq(input).timepicker('getTime');
			},
			updateTime: function(input, dateObj){
				jq(input).timepicker('setTime', dateObj);
			},
			setMinTime: function(input, dateObj){
				jq(input).timepicker('option', 'minTime', dateObj);
			},

			// defaults for bootstrap datepicker; override when using other input widgets
			parseDate: function(input){
				return input.value && jq(input).datepicker('getDate');
			},
			updateDate: function(input, dateObj){
				jq(input).datepicker('update', dateObj);
			}
		};

		this.container = container;
		this.settings = simpleExtend(this._defaults, options);

		this.startDateInput = this.container.querySelector('.'+this.settings.startClass+'.'+this.settings.dateClass);
		this.endDateInput = this.container.querySelector('.'+this.settings.endClass+'.'+this.settings.dateClass);
		this.startTimeInput = this.container.querySelector('.'+this.settings.startClass+'.'+this.settings.timeClass);
		this.endTimeInput = this.container.querySelector('.'+this.settings.endClass+'.'+this.settings.timeClass);

		// initialize date and time deltas
		this.refresh();

		// init starts here
		this._bindChangeHandler();
	}

	Datepair.prototype = {
		constructor: Datepair,

		option: function(key, value)
		{
			if (typeof key == 'object') {
				this.settings = simpleExtend(this.settings, key);

			} else if (typeof key == 'string' && typeof value != 'undefined') {
				this.settings[key] = value;

			} else if (typeof key == 'string') {
				return this.settings[key];
			}

			this._updateEndMintime();
		},

		getTimeDiff: function()
		{
			// due to the fact that times can wrap around, timeDiff for any
			// time-only pair will always be >= 0
			var delta = this.dateDelta + this.timeDelta;
			if (delta < 0 && (!this.startDateInput || !this.endDateInput) ) {
				delta += _ONE_DAY;
			}

			return delta;
		},

		refresh: function()
		{
			if (this.startDateInput && this.startDateInput.value && this.endDateInput && this.endDateInput.value) {
				var startDate = this.settings.parseDate(this.startDateInput);
				var endDate = this.settings.parseDate(this.endDateInput);
				if (startDate && endDate) {
					this.dateDelta = endDate.getTime() - startDate.getTime();
				}
			}
			if (this.startTimeInput && this.startTimeInput.value && this.endTimeInput && this.endTimeInput.value) {
				var startTime = this.settings.parseTime(this.startTimeInput);
				var endTime = this.settings.parseTime(this.endTimeInput);
				if (startTime && endTime) {
					this.timeDelta = endTime.getTime() - startTime.getTime();
					this._updateEndMintime();
				}
			}
		},

		remove: function()
		{
			this._unbindChangeHandler();
		},

		_bindChangeHandler: function(){
			// addEventListener doesn't work with synthetic "change" events
			// fired by jQuery's trigger() functioin. If jQuery is present,
			// use that for event binding
			if (jq) {
				jq(this.container).on('change.datepair', jq.proxy(this.handleEvent, this));
			} else {
				this.container.addEventListener('change', this, false);
			}
		},

		_unbindChangeHandler: function(){
			if (jq) {
				jq(this.container).off('change.datepair');
			} else {
				this.container.removeEventListener('change', this, false);
			}
		},

		// This function will be called when passing 'this' to addEventListener
		handleEvent: function(e){
			// temporarily unbind the change handler to prevent triggering this
			// if we update other inputs
			this._unbindChangeHandler();

			if (hasClass(e.target, this.settings.dateClass)) {
				if (e.target.value != '') {
					this._dateChanged(e.target);
					this._timeChanged(e.target);
				} else {
					this.dateDelta = null;
				}

			} else if (hasClass(e.target, this.settings.timeClass)) {
				if (e.target.value != '') {
					this._timeChanged(e.target);
				} else {
					this.timeDelta = null;
				}
			}

			this._validateRanges();
			this._updateEndMintime();
			this._bindChangeHandler();
		},

		_dateChanged: function(target){
			if (!this.startDateInput || !this.endDateInput) {
				return;
			}

			var startDate = this.settings.parseDate(this.startDateInput);
			var endDate = this.settings.parseDate(this.endDateInput);

			if (!startDate || !endDate) {
				if (this.settings.defaultDateDelta !== null) {
					if (startDate) {
						var newEnd = new Date(startDate.getTime() + this.settings.defaultDateDelta * _ONE_DAY);
						this.settings.updateDate(this.endDateInput, newEnd);

					} else if (endDate) {
						var newStart = new Date(endDate.getTime() - this.settings.defaultDateDelta * _ONE_DAY);
						this.settings.updateDate(this.startDateInput, newStart);
					}

					this.dateDelta = this.settings.defaultDateDelta * _ONE_DAY;
				} else {
					this.dateDelta = null;
				}

				return;
			}

			if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) {
				var newDate = new Date(startDate.getTime() + this.dateDelta);
				this.settings.updateDate(this.endDateInput, newDate);
			} else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) {
				var newDate = new Date(endDate.getTime() - this.dateDelta);
				this.settings.updateDate(this.startDateInput, newDate);
			} else {
				if (endDate < startDate) {
					var otherInput = hasClass(target, this.settings.startClass) ? this.endDateInput : this.startDateInput;
					var selectedDate = this.settings.parseDate(target);
					this.dateDelta = 0;
					this.settings.updateDate(otherInput, selectedDate);
				} else {
					this.dateDelta = endDate.getTime() - startDate.getTime();
				}
			}
		},

		_timeChanged: function(target){
			if (!this.startTimeInput || !this.endTimeInput) {
				return;
			}

			var startTime = this.settings.parseTime(this.startTimeInput);
			var endTime = this.settings.parseTime(this.endTimeInput);

			if (!startTime || !endTime) {
				if (this.settings.defaultTimeDelta !== null) {
					this.timeDelta = this.settings.defaultTimeDelta;

					if (startTime) {
						endTime = this._setTimeAndReturn(this.endTimeInput, new Date(startTime.getTime() + this.settings.defaultTimeDelta));
						this._doMidnightRollover(startTime, endTime);
					} else if (endTime) {
						startTime = this._setTimeAndReturn(this.startTimeInput, new Date(endTime.getTime() - this.settings.defaultTimeDelta));
						this._doMidnightRollover(startTime, endTime);
					}
				} else {
					this.timeDelta = null;
				}

				return;
			}

			if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) {
				endTime = this._setTimeAndReturn(this.endTimeInput, new Date(startTime.getTime() + this.timeDelta));
				this._doMidnightRollover(startTime, endTime);
			} else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) {
				startTime = this._setTimeAndReturn(this.startTimeInput, new Date(endTime.getTime() - this.timeDelta));
				this._doMidnightRollover(startTime, endTime);
			} else {
				this._doMidnightRollover(startTime, endTime);

				var startDate, endDate;
				if (this.startDateInput && this.endDateInput) {
					startDate = this.settings.parseDate(this.startDateInput);
					endDate = this.settings.parseDate(this.endDateInput);
				}

				if ((+startDate == +endDate) && (endTime < startTime)) {
					var thisInput  = hasClass(target, this.settings.endClass) ? this.endTimeInput : this.startTimeInput;
					var otherInput = hasClass(target, this.settings.startClass) ? this.endTimeInput : this.startTimeInput;
					var selectedTime = this.settings.parseTime(thisInput);
					this.timeDelta = 0;
					this.settings.updateTime(otherInput, selectedTime);
				} else {
					this.timeDelta = endTime.getTime() - startTime.getTime();
				}
			}


		},

		_setTimeAndReturn: function(input, newTime) {
			this.settings.updateTime(input, newTime);
			return this.settings.parseTime(input);
		},

		_doMidnightRollover: function(startTime, endTime) {
			if (!this.startDateInput || !this.endDateInput) {
				return;
			}

			var endDate = this.settings.parseDate(this.endDateInput);
			var startDate = this.settings.parseDate(this.startDateInput);
			var newDelta = endTime.getTime() - startTime.getTime();
			var offset = (endTime < startTime) ? _ONE_DAY : -1 * _ONE_DAY;

			if (this.dateDelta !== null
					&& this.dateDelta + this.timeDelta <= _ONE_DAY
					&& this.dateDelta + newDelta != 0
					&& (offset > 0 || this.dateDelta != 0)
					&& ((newDelta >= 0 && this.timeDelta < 0) || (newDelta < 0 && this.timeDelta >= 0))) {

				if (this.settings.anchor == 'start') {
					this.settings.updateDate(this.endDateInput, new Date(endDate.getTime() + offset));
					this._dateChanged(this.endDateInput);
				} else if (this.settings.anchor == 'end') {
					this.settings.updateDate(this.startDateInput, new Date(startDate.getTime() - offset));
					this._dateChanged(this.startDateInput);
				}
			}
			this.timeDelta = newDelta;
		},

		_updateEndMintime: function(){
			if (typeof this.settings.setMinTime != 'function') return;

			var baseTime = null;
			if (this.settings.anchor == 'start' && (!this.dateDelta || this.dateDelta < _ONE_DAY || (this.timeDelta && this.dateDelta + this.timeDelta < _ONE_DAY))) {
				baseTime = this.settings.parseTime(this.startTimeInput);
			}

			this.settings.setMinTime(this.endTimeInput, baseTime);
		},

		_validateRanges: function(){
			if (this.startTimeInput && this.endTimeInput && this.timeDelta === null) {
				triggerSimpleCustomEvent(this.container, 'rangeIncomplete');
				return;
			}

			if (this.startDateInput && this.endDateInput && this.dateDelta === null) {
				triggerSimpleCustomEvent(this.container, 'rangeIncomplete');
				return;
			}

			// due to the fact that times can wrap around, any time-only pair will be considered valid
			if (!this.startDateInput || !this.endDateInput || this.dateDelta + this.timeDelta >= 0) {
				triggerSimpleCustomEvent(this.container, 'rangeSelected');
			} else {
				triggerSimpleCustomEvent(this.container, 'rangeError');
			}
		}
	};

	window.Datepair = Datepair;

}(window, document));
