// requires Prototype
// created: 2005-09-23

// 2006-01-20 renamed after_change to after_date_change -- possible incompatibility
// 2006-03-20 parseDate introduced

/*
Event.observe(window, 'load', function () {
		var labels = document.getElementsByTagName('LABEL')
		for (var i = 0; i < labels.length; ++i) {
			var label = labels[i]
			
			Event.observe(label, 'click', function (event) {
					if (!event) event = window.event
					
					Event.stop(event)

					alert('clicked')
					var inputs = label.getElementsByTagName('INPUT')
					if (inputs) {
						inputs[0].click()
					}
			}, false)
		}
})
*/

// takes too long on large pages

if ('manualEventHandlers' in window && manualEventHandlers) {
	// no automatic adding of event handlers, as this can take ages on a long page
} else {
	Event.observe(window, 'load', function () {
		addEventHandlers(document);
	})
}

function addEventHandlers(container) {
	var elements, i, element;
	
	elements = document.getElementsByClassName('reportButton');
	for (i = 0; i < elements.length; ++i) {
		element = elements[i];

		addClassNameOnHover(element, 'reportButtonOver');
	}
	
	elements = document.getElementsByClassName('reportButtonSmall');
	for (i = 0; i < elements.length; ++i) {
		element = elements[i];

		addClassNameOnHover(element, 'reportButtonOverSmall');
	}
	
	elements = document.getElementsByClassName('narrowButton');
	for (i = 0; i < elements.length; ++i) {
		element = elements[i];

		addClassNameOnHover(element, 'narrowButtonOver');
	}

	elements = document.getElementsByTagName('input');
	for (i = 0; i < elements.length; ++i) {
		element = elements[i];
		if (Element.hasClassName(element, 'image-button')) {
			addClassNameOnHover(element, 'image-button-over');
		}
	}

	elements = document.getElementsByTagName('a');
	for (i = 0; i < elements.length; ++i) {
		if (Element.hasClassName(element, 'image-button')) {
			addClassNameOnHover(element, 'image-button-over');
		}
	}

	addCalendarButtons(container);
}

function addCalendarButtons(container) {
	container = $(container);
	var inputs = container.getElementsByTagName('input');
	for (var i = 0; i < inputs.length; ++i) {
		var input = inputs[i];
		
		if (Element.hasClassName(input, 'date')) {
			add_calendar_button(input);

			// input.onchange = date_changed
			Event.observe(input, 'change', date_changed);
		}
	}
}


// Prototype's Form.focusFirstElement used to do this, but now it only works on forms

function focusFirstFormControl(container) {
	container = $(container);
	var elements = container.getElementsByTagName('*');
	var elementCount = elements.length;
	for (var i = 0; i < elementCount; i++) {
		var element = elements[i];
		var tagName = element.tagName.toLowerCase();
		var rightType = (tagName == 'input' && element.type != 'hidden') || tagName == 'textarea' || tagName == 'select';
		
		if (rightType && !element.disabled) {
			element.focus();
			
			var isEditable = (tagName == 'input' && element.type == 'text') || tagName == 'textarea';
			if (isEditable) element.select();
			
			break;
		}
	}
}

function addClassNameOnHover(element, className) {
	Event.observe(element, 'mouseover', function () { Element.addClassName(element, className); });
	Event.observe(element, 'mouseout', function () { Element.removeClassName(element, className); });
}

function containsSubstring(haystack, needle) {
	return haystack.indexOf(needle) != -1;
}

function add_calendar_button(dateInput) {
	// Add a "button" (image) next to the given input that pops up a calendar when clicked.
	
	var button;
	
	if (dateInput.calendarButton) {
		button = dateInput.calendarButton;
	} else {
		var useRealButton = true;
		
		var image = document.createElement('IMG');
		var urlPrefix = (containsSubstring(location.pathname, '/mercury/') ? '/mercury' : '');
		image.src = urlPrefix + '/lib/popcalendar/tigracal.gif';
		
		if (useRealButton) {
			button = document.createElement('BUTTON');
			button.setAttribute('type', 'button');
			button.style.border = 'none';
			button.style.background = 'none';
			button.appendChild(image);
		} else {
			button = image;
		}
	
		button.className = 'popcalendar-open-button';
		
		dateInput.parentNode.insertBefore(button, dateInput.nextSibling);
	}
	
	dateInput.calendarButton = button;
	
	// set the click handler regardless: clones won't have it
	dateInput.calendarButton.onclick = function () {
		if (dateInput.disabled) return;
		dateInput.allowPastDates = ! Element.hasClassName(dateInput, 'not-past');
		if (Element.hasClassName(dateInput, 'birthdate')) dateInput.baseYear = 1900;
		showCalendar(dateInput, dateInput, 'dd/mm/yyyy', 'en', dateInput.allowPastDates);
	}
}


function zero_fill(number, length) {
	var numeral = number.toString()
	
	while (numeral.length < length) {
		numeral = '0' + numeral
	}
	
	return numeral
}

function ifNull(value, defaultValue) {
	// based on SQL's IFNULL

	if (value === null) {
		return defaultValue;
	}
	return value;
}

function between_inc(value, min, max) {
	// Return true iff value is between min and max, inclusive.
	
	return value >= min && value <= max
}

function plural(count, singular) {
	return count + ' ' + singular + (count == 1 ? '' : 's');
}


function dmy_from_date(date) {
	if (!date) return 'dd/mm/yyyy'
	//var dmy = date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear()
	//return dmy
	
	var zf = zero_fill
	//function zf(n, d) { return n }
	
	// calendar uses this format:
	return zf(date.getDate(), 2) + '/' + zf(date.getMonth() + 1, 2) + '/' + zf(date.getFullYear(), 4)
}

var monthShortNames = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(/ /);

function formatDate(date, format) {
	if (!date || isNaN(date.getMonth())) return "";
	
	format = format || 'dd/mm/yyyy';
	return monthShortNames[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
}	

function date_from_dmy(dmy) {
	var pieces = dmy.split('/')
	return new Date(pieces[2], pieces[1] - 1, pieces[0])
}

function ensureDate(value) {
	if (value instanceof Date) return value;
	
	return date_from_iso(value);
}

function parseDate(string, relation_to_base_date, base_date, baseYear) {
	// Parse a date string and return a Date object, or else null.
	
	var date;
	
// @@ needs proper parsing
//	date = new Date(string);
//	if (isNaN(date)) date = null;
//	return date;
	
	// =============
	

	baseYear = baseYear || 2000;

    if (relation_to_base_date === undefined) {
        relation_to_base_date = 'after';
        base_date = new Date(); // today
    }
    // assert(relation_to_base_date == 'after'); // @@ nothing else yet supported

    // separate date string into numeric pieces d, m and y: optional separators are hyphen, space, dot and slash; month and year are optional
	var dmy = string;
	var pieces = dmy.match(/^(\d{1,2})(?:[- .\/]?(\d{1,2})(?:[- .\/]?((?:19|20)\d{2}|\d{1,2}))?)?$/);

	if (pieces === null) {
		// not a format we recognise; let's see if the browser can figure it out
		date = new Date(string);
		if (isNaN(date)) date = null;
		return date;
	}

	// IE6's groups are empty strings if not matched, not undefined

    var day = parseInt(pieces[1], 10);
    if (!between_inc(day, 1, 31)) return null;

    var month;
    if (!pieces[2]) {
        month = base_date.getMonth() + 1;
        if (day <= base_date.getDate()) {
            month += 1;
        }
    } else {
        month = parseInt(pieces[2], 10);
        if (!between_inc(month, 1, 12)) return null;
    }

    var year;
    if (!pieces[3]) {
        year = base_date.getFullYear();
        if (month < base_date.getMonth() + 1) {
            year += 1;
        }
    } else {
        year = parseInt(pieces[3], 10);
        if (!between_inc(year, 0, 99) && !between_inc(year, 1900, 2099)) return null;

        if (between_inc(year, 0, 99)) year += baseYear;
    }

    // print('"' + dmy + '", day: ' + day + ', month: ' + month + ', year: ' + year);
    date = new Date(year, month - 1, day); // normalize

	return date;

//    day = date.getDate();
//    month = date.getMonth() + 1;
//    year = date.getFullYear();
//
    // print('"' + dmy + '", day: ' + day + ', month: ' + month + ', year: ' + year);
}

// test data
//parseDate('01022006');
//parseDate('010206');
//parseDate('1/2/6');
//parseDate('080808'); // not octal
//parseDate('1 2 3');
//parseDate('1');
//parseDate('0304');
//parseDate('123');
//parseDate('1', 'after', new Date(2006, 3 - 1, 20));
//parseDate('1 2', 'after', new Date(2006, 3 - 1, 20));
//parseDate('1 2 3', 'after', new Date(2006, 3 - 1, 20));
//parseDate('1', 'after', new Date(2006, 12 - 1, 31));



// Custom events to allow multiple things to happen when a date input is changed
// It'd be nice to use the standard DOM interfaces to do this, but IE6 doesn't support them.
// They could be monkeypatched in if it supported Node.prototype... but it doesn't.
// (Adding a behaviour is too cumbersome.)

// Modeled after Prototype's Event.

var CustomEvent = {
	observe: function (target, type, listener) {
		target = $(target);
		
		if (!('customEventListeners' in target)) {
			target.customEventListeners = {};
		}
		
		// create an array to hold the listeners for an event, if it doesn't already exist
		if (!(type in target.customEventListeners)) {
			target.customEventListeners[type] = [];
		}
		
		// add the new listener only if it's not already there
		if (!(target.customEventListeners[type].include(listener))) {
			target.customEventListeners[type].push(listener);
		}
	},
	
	dispatch: function (target, evt) {
		target = $(target);
		
		// no listeners? do nothing and return
		if (!('customEventListeners' in target)) return;
		if (!(evt.type in target.customEventListeners)) return;
		
		// call each listener with the event
		evt.target = target;
		
		var listeners = target.customEventListeners[evt.type];
		for (var i = 0; i < listeners.length; i++) {
			listeners[i](evt);
		}
	},
	
	stopObserving: function (target, type, listener) {
		target = $(target);
		
		if (!('customEventListeners' in target)) return;
		
		// shorter name
		var listeners = target.customEventListeners;
		
		if (!(type in listeners)) return;
		
		listeners[type].each(function (current_listener, i) {
			if (current_listener == listener) {
				delete listeners[type][i];
				return;
			}
		});
	}
}




function date_changed(event) {
	// A date input has changed: parse and validate its new value.
	
	event = event || window.event;
	
	var input = Event.element(event);
	
	var dateText = input.value;
	
//	var day, month, year
//	var today = new Date()
	
//	// var pieces = dmy.split(/[-+.\/]/);
//	// var pieces = dmy.match(/(\d{1,2})(?:[- .\/]?(\d{1,2})(?:[- .\/]?(\d{1,2}|(19|20)\d{2})?)?/);
//	
//	day = parseInt(pieces[0], 10)
//	if (!between_inc(day, 1, 31)) {
//		//alert('Please give the day as a number from 1 to 31')
//		//Effect.Highlight(input);
//		return;
//	}
//	
//	if (pieces.length >= 2) {
//		month = parseInt(pieces[1], 10)
//		if (!between_inc(month, 1, 12)) {
//			//alert('Please give the month as a number from 1 to 12')
//			return
//		}
//	} else {
//		month = today.getMonth() + 1 // counted from 0
//	}
//
//	if (pieces.length == 3) {
//		year = parseInt(pieces[2], 10)
//		if (!between_inc(year, 1, 2100)) {
//			//alert('Please give the year as a two-digit or four-digit number')
//			return
//		}
//	} else {
//		year = today.getFullYear()
//	}
//	
//	// convert one/two-digit years
//	if (year < 100) {
//		year += 2000
//	}
//	
//	var date = new Date(year, month - 1, day) // months counted from 0
	
	var isBirthdate = Element.hasClassName(input, 'birthdate');
	
	var baseDate;
	if ('baseDate' in input) {
		baseDate = input.baseDate;
	} else {
		baseDate = new Date(); // today
	}
	var date = parseDate(dmy, 'after', baseDate, isBirthdate ? 1900 : 2000);
	//var date = new Date(dateText); // let the browser parse American dates
	if (isNaN(date)) date = new Date(dateText + ', ' + baseDate.getFullYear()); // maybe it's missing a year

	input.value = formatDate(date, 'dd/mm/yyyy');
	

	// now call anything that depends on the new date
	CustomEvent.dispatch(input, {type: 'DateChange', newDate: date});
}

function set_visibility(elem, visible) {
	elem.style.visibility = (visible ? 'visible' : 'hidden')
}

function select_first_control(container) {
	focus_first_control(container, true)
}

function focus_first_control(container, select) {
	elems = container.getElementsByTagName('*')
	for (var i = 0; i < elems.length; ++i) {
		var elem = elems[i]
		if (elem.tagName == 'INPUT' || elem.tagName == 'SELECT' || elem.tagName == 'TEXTAREA') {
			if (elem.tagName == 'INPUT' && elem.type == 'hidden') {
				continue
			}
			
			// found one
			elem.focus()
			if ('select' in elem && select) {
				elem.select()
			}
			return
		}
	}
}

// @@ better names needed

function days_between(start_date, end_date) {
	if (!start_date || !end_date) return null;
	
	var MAX_DAYS = 400;
	
	for (var days = 0; days <= MAX_DAYS; ++days) {
		var possible_end_day = start_date.getDate() + days
		var possible_end_date = new Date(start_date.getFullYear(), start_date.getMonth(), possible_end_day)
		if (equal_dates(possible_end_date, end_date)) {
			return days
		}
	}
	
	return null
}

function date_add(start_date, days) {
	if (typeof days != 'number') {
		days = parseInt(days);
	}
	
	if (isNaN(days)) {
		return null;
	}
	
	var new_date = new Date(start_date.getFullYear(), start_date.getMonth(), start_date.getDate() + parseInt(days))
	return new_date
}

function equal_dates(first_date, second_date) {
	return (first_date.getFullYear() == second_date.getFullYear()
		&& first_date.getMonth() == second_date.getMonth()
		&& first_date.getDate() == second_date.getDate()
	)
}

function linkDateIntervalInputs(start_date_elem, end_date_elem, duration_elem) {
	// Connect three inputs so that a change made to one of them updates the other two.
	
	// when the start date changes:
	CustomEvent.observe(start_date_elem, 'DateChange', function (event) {
		if (!('source' in event)) {
			set_end_date_from_start_date_and_duration(end_date_elem, start_date_elem, duration_elem)
			//set_duration_from_dates(duration_elem, start_date_elem, end_date_elem)
		}
	});
	
	// when the end date changes:
	CustomEvent.observe(end_date_elem, 'DateChange', function (event) {
		if (!('source' in event)) {
			set_duration_from_dates(duration_elem, start_date_elem, end_date_elem)
		}
	});
	
	// when the duration changes:
	Event.observe(duration_elem, 'change', function () {
		set_end_date_from_start_date_and_duration(end_date_elem, start_date_elem, duration_elem)
	})
}
var link_date_interval_inputs = linkDateIntervalInputs; // old name

function set_duration_from_dates(duration_elem, start_date_elem, end_date_elem) {
	var start_date = parseDate($(start_date_elem).value)
	var end_date = parseDate($(end_date_elem).value)
	$(duration_elem).value = ifNull(days_between(start_date, end_date), '???');
}

function set_end_date_from_start_date_and_duration(end_date_elem, start_date_elem, duration_elem) {
	end_date_elem = $(end_date_elem);
	start_date_elem = $(start_date_elem);
	duration_elem = $(duration_elem);
	
	var start_date = parseDate(start_date_elem.value);
	var end_date = date_add(start_date, duration_elem.value);
	
	var end_date_string = (end_date ? formatDate(end_date) : 'unknown');
	
	if (['input', 'select'].include(end_date_elem.tagName.toLowerCase())) {
		end_date_elem.value = end_date_string;
	} else {
		// not a control: let's change its content instead
		end_date_elem.innerHTML = end_date_string; // does Moz support innerText?
	}
	
	CustomEvent.dispatch(end_date_elem, {type: 'DateChange', newDate: end_date, source: start_date_elem});
}
