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) {
				if (startTime) {
					var newEnd = new Date(startTime.getTime() + this.settings.defaultTimeDelta);
					this.settings.updateTime(this.endTimeInput, newEnd);
				} else if (endTime) {
					var newStart = new Date(endTime.getTime() - this.settings.defaultTimeDelta);
					this.settings.updateTime(this.startTimeInput, newStart);
				}

				this.timeDelta = this.settings.defaultTimeDelta;
			} else {
				this.timeDelta = null;
			}

			return;
		}

		if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) {
			var newTime = new Date(startTime.getTime() + this.timeDelta);
			this.settings.updateTime(this.endTimeInput, newTime);
			endTime = this.settings.parseTime(this.endTimeInput);

			this._doMidnightRollover(startTime, endTime);
		} else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) {
			var newTime = new Date(endTime.getTime() - this.timeDelta);
			this.settings.updateTime(this.startTimeInput, newTime);
			startTime = this.settings.parseTime(this.startTimeInput);

			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();
			}
		}


	},

	_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');
		}
	}
};
