/* 
	Oxxigeno JS Calendar Library
	- require commons.js
	- require dates_i18n_js.xhtml to store texts translation/definition
	Version: 1.0. Date: 2/Dec/2008
*/
// CSS CLASSES NAMES: can be redefined according to specific styles definition
var SELECTED_CLASS_NAME = 'seleccionado';
var FIRST_SELECTED_CLASS_NAME = 'primerseleccionado';
var AVAILABLE_CLASS_NAME = '';
var NO_CLICKABLE_CLASS_NAME = 'noclickable';
var NOT_AVAILABLE_CLASS_NAME = 'disabled';
var CLOSE_LINK_CLASS_NAME = 'lnk_cerrar';
var PREVIOUS_LINK_CLASS_NAME = 'lnk_anterior';
var NEXT_LINK_CLASS_NAME = 'lnk_siguiente';
var CALENDAR_LEFT_CLASS_NAME = 'calendar';
var CALENDAR_RIGHT_CLASS_NAME = 'calendar ultimo';
var BUTTON_CLASS_NAME = 'btn';
// CONSTANTS DEFINITION
var DAYS_OF_THE_WEEK = 7;
// commands
var CMD_PREV_MONTH = -1;var CMD_NEXT_MONTH = 1;
// modes
var SIMPLE_MODE = '1';var DOUBLE_MODE = '2';
// positions
var LEFT_POSITION = 0;var RIGHT_POSITION = 1;
// selectors
var TO_CALENDAR_SELECTOR = 'to';var FROM_CALENDAR_SELECTOR = 'from';
// states
var SELECT_ARRIVAL_DATE_STATE = 0;var SELECT_DEPARTURE_DATE_STATE = 1;var READY_DATE_STATE = 2;
// availability options
var NO_AVAILABILITY = 'none';var ALL_AVAILABILITY = 'all';var LABOUR_AVAILABILITY = 'labour';var WEEKEND_AVAILABILITY = 'weekend';
// syntax constants
var FIELD_SEPARATOR = '|';
var COMMON_CONFIGURATION_ID_PREFIX = 'common';
var HIDDEN_SELECTOR_PREFIX = 'hidden' + FIELD_SEPARATOR;
var INFO_SELECTOR_PREFIX = 'info' + FIELD_SEPARATOR;
var NIGHTS_SELECTOR_PREFIX = 'nights';
var LAYER_SELECTOR_PREFIX = 'layer';
var IFRAME_SELECTOR_PREFIX = 'iframe';
var DATE_SELECTION_SELECTOR_PREFIX = 'dateSelectionMode';
var DATE_SHOW_SELECTOR_PREFIX = 'dateShowMode';
var BEGIN_DATE_SELECTOR_PREFIX = 'beginDate';
var END_DATE_SELECTOR_PREFIX = 'endDate';
var AVAILABILITY_SCOPE_SELECTOR_PREFIX = 'availabilityScope';
var MIN_DAYS_SELECTOR_PREFIX = 'minDays';
var IFRAME_ELEMENT_ID = 'calendar_in_here';
var SPAN_RANGES_ELEMENT_ID = 'date_ranges';
// GLOBAL VARS
var openedCalendars = new Object();

// PUBLIC FUNCTIONS
function smartShowCalendar(calendarId, selector, element) {
	element.blur();
	showCalendar(calendarId, selector);
}
function showCalendar(calendarId, selector) {
	var calendar = new Calendar(calendarId, selector);
	addCalendar(calendar);
	if (calendar.dateSelectionMode == SIMPLE_MODE) {
		calendar.deselectDate(selector);
	}
	calendar.close(); // in case of sparate from/to calendar elements is useful to close both elements
	calendar.drawCalendar();
}
function selectDate(calendarId, selector, date, resetSelection) {
	var calendar = getCalendar(calendarId);
	var jsDate = parseDate(date);
	if (resetSelection == null) resetSelection = false;
	
	if (calendar.dateSelectionMode == SIMPLE_MODE) { // Simple date selection
		if (selector == FROM_CALENDAR_SELECTOR) calendar.realFromDate = jsDate;
		if (selector == TO_CALENDAR_SELECTOR) calendar.realToDate = jsDate;
		// if user is selecting arrival date, and departure date is unsetted, pre-set it to the right following day
		if (selector == FROM_CALENDAR_SELECTOR && (calendar.realToDate == null || calendar.realToDate <= calendar.realFromDate || resetSelection)) { 
			calendar.realToDate = copyDate(jsDate, calendar.minDays);
		} 
		// if user is selecting departure date, and arrival date is unsetted, pre-set it to the right previous day
		if (selector == TO_CALENDAR_SELECTOR && (calendar.realFromDate == null || calendar.realToDate <= calendar.realFromDate || resetSelection)) { 
			calendar.realFromDate = copyDate(jsDate, (-1 * calendar.minDays));
		}
	} else { // Double date selection
		if (calendar.state == SELECT_ARRIVAL_DATE_STATE) {
			calendar.state = SELECT_DEPARTURE_DATE_STATE;
			calendar.realFromDate = jsDate;
			if (calendar.realToDate != null && calendar.realToDate <= calendar.realFromDate || resetSelection) 
				calendar.realToDate = copyDate(jsDate, calendar.minDays);
		} else if (calendar.state == SELECT_DEPARTURE_DATE_STATE) {
			calendar.state = READY_DATE_STATE;
			calendar.realToDate = jsDate;
			if (calendar.realToDate <= calendar.realFromDate || resetSelection) {
				calendar.realFromDate = copyDate(jsDate, (-1 * calendar.minDays));
			}
		}
	}
	calendar.updateInfo();
	calendar.highlightDays();
	if (calendar.dateSelectionMode == SIMPLE_MODE || calendar.state == READY_DATE_STATE) 
		closeCalendar(calendarId);
}
function deselectDate(calendarId, selector) {
	var calendar = getCalendar(calendarId);
	calendar.deselectDate(selector);
	calendar.updateInfo();
	calendar.highlightDays();
}
function closeCalendar(calendarId) {
	getCalendar(calendarId).close();
	removeCalendar(calendarId);
}
function reset(calendarId) {
	var calendar = getCalendar(calendarId);
	calendar.realToDate = null;
	calendar.realFromDate = null;
	calendar.updateInfo();
	calendar.close();	
	removeCalendar(calendarId);
}
function move(calendarId, command) {
	var calendar = getCalendar(calendarId);
	// This only works in case of month-type movement commands 
	calendar.move(command);
	calendar.drawCalendar();
}
// PRIVATE FUNCTIONS
// calendar data management functions
function addCalendar(calendar) {
	var calendarId = calendar.calendarId;
	openedCalendars[calendarId] = calendar;
}
function getCalendar(calendarId) {
	return openedCalendars[calendarId];
}
function removeCalendar(calendarId) {
	openedCalendars[calendarId] = null;
}
// cell clickability functions
function makeClickable(element, calendarId, selector, formattedDate, resetSelection) {
	element.onclick = function() {selectDate(calendarId, selector, formattedDate, resetSelection);};
}
function makeNotClickable(element) {
	element.onclick = null;
}
// HTML support functions
function correctJSFunctionCall(onclickCode, documentReference) {
	return (documentReference == null)?onclickCode:('parent.' + onclickCode);
}
function createLink(txt, onclickCode, cssClassName) {
	var classAttribute = (cssClassName == null)?'':'class="' + cssClassName + '"';
	var onclickAttribute = (onclickCode == null)?'':'onclick="' + onclickCode + '"';
	var closeLink = '<a ' + classAttribute + ' href="javascript:;" ' + onclickAttribute + '>' + txt + '</a>';
	return closeLink;
}
function createCloseElement() {
	return '<div class="clear">&nbsp;</div>';
}
// Objects definition
// CRange object keep one date range with availability scope.
function CRange(begin, end, avScope) {
	this.begin = begin;
	this.end = end;
	this.avScope = avScope;
	
	this.initFromString = function initFromString(stringValue) {
		var values = stringValue.split(FIELD_SEPARATOR)
		this.begin = (values[0] != null && values[0] != "")?parseDate(values[0]):getToday();
		this.end = parseDate(values[1]);
		this.avScope = (values.length == 3) ? values[2] : NO_AVAILABILITY;
	}
	this.contains = function contains(day, toDateSelection) {
		return (this.begin.getTime() <= day.getTime() &&  day.getTime() <= this.end.getTime());
	}
	this.matchesSelectability = function matchesSelectability(day) {
		var dayAvScope = (isWeekEnd(day)) ? WEEKEND_AVAILABILITY : LABOUR_AVAILABILITY;
		return (this.avScope == ALL_AVAILABILITY || 
				(this.avScope == WEEKEND_AVAILABILITY && isWeekEnd(day)) ||
				(this.avScope == LABOUR_AVAILABILITY && isLabour(day)));
	}
}
// Calendar object keep calendar configuration and state, gathered from document form inputs
// this object initialization can be fed in case of new initialization/configuration mecanisms are required
function Calendar(id, selector) {
	// all calendar configuration elements, must follow specific naming, according to calendarId
	// 1. Calendar basic initialization
	this.calendarId = id; // calendar id [calendar elements id prefix]
	this.selector = selector; // this is no calendar configuration value, but the from/to date selection switch
	// 2. Calendar values initialization
	// From Date
	this.fromDate = initField(id,FROM_CALENDAR_SELECTOR);
	this.realFromDate = initDateField(id,HIDDEN_SELECTOR_PREFIX + FROM_CALENDAR_SELECTOR);
	// To Date
	this.toDate = initField(id, TO_CALENDAR_SELECTOR);
	this.realToDate = initDateField(id,HIDDEN_SELECTOR_PREFIX + TO_CALENDAR_SELECTOR);
	// calendar state
	this.state = (this.realFromDate == null)?SELECT_ARRIVAL_DATE_STATE:(this.realToDate == null)?SELECT_DEPARTURE_DATE_STATE:(this.selector == FROM_CALENDAR_SELECTOR)?SELECT_ARRIVAL_DATE_STATE:SELECT_DEPARTURE_DATE_STATE;
	// N� Nights
	this.nights = initField(id, NIGHTS_SELECTOR_PREFIX);
	// 3. Calendar layer initialization
	// element to show/hide when calendar open/close
	this.overLayers = new Object();
	initMultipleField(this.overLayers, id, LAYER_SELECTOR_PREFIX);		
	// element where the calendar is created within
	this.layers = new Object();
	initMultipleField(this.layers, id, LAYER_SELECTOR_PREFIX);		
	// iframe support calendar location
	this.documentReferences = new Object();
	initIframeMultipleField(this.documentReferences, id, IFRAME_SELECTOR_PREFIX);
	for (key in this.documentReferences) {
		var documentReference = this.documentReferences[key];
		if (documentReference != null) this.layers[key] = get(IFRAME_ELEMENT_ID, documentReference);
	}	
	// 4. Calendar configuration
	// calendar modes
	var dateSelectionMode = initField(id, DATE_SELECTION_SELECTOR_PREFIX);
	var dateShowMode = initField(id, DATE_SHOW_SELECTOR_PREFIX);
	this.dateSelectionMode = (dateSelectionMode == null || isEmpty(dateSelectionMode.value))?SIMPLE_MODE:dateSelectionMode.value;
	this.dateShowMode = (dateShowMode == null || isEmpty(dateShowMode.value))?DOUBLE_MODE:dateShowMode.value;
	// 5. Dates range
	this.initDateRanges = function initDateRanges(id) {
		var rangesParent = initField(id,SPAN_RANGES_ELEMENT_ID);
		var dateRanges = new Array();
		if (rangesParent == null) { // ranges element doesn't exist
			// default 'calendarBeginning' value is today [read-only]
			this.calendarBeginning = initDateField(id, BEGIN_DATE_SELECTOR_PREFIX, getToday());
			// default 'calendarEnd' value is null (no-limit) [read-only]
			this.calendarEnd = initDateField(id, END_DATE_SELECTOR_PREFIX, null);
			// The first dateRange is beginningDate to endDate with all days actives.	
			dateRanges.push(new CRange(this.calendarBeginning, this.calendarEnd, ALL_AVAILABILITY));
		} else {
			if (rangesParent == null) return dateRanges;	
			var childs = rangesParent.childNodes;
			for (var i = 0; i < childs.length; i++) {
				if (!isInput(childs[i])) continue;
				var range = new CRange(); range.initFromString(childs[i].value);
				dateRanges.push(range);		
			}

			this.calendarBeginning = (dateRanges[0].begin.getTime() < getToday()) ? getToday() : dateRanges[0].begin; 
			this.calendarEnd = dateRanges[0].end;
		}
		return dateRanges;
	}
	this.dateRanges = this.initDateRanges(id);
	
	// 6. Calendar data initialization
	var today = getToday();
	this.calendarCurrentDate = (this.calendarBeginning != null && this.calendarBeginning.getTime() > today.getTime())?copyDate(this.calendarBeginning):today;
	var minDays = initField(id, MIN_DAYS_SELECTOR_PREFIX);
	this.minDays = (minDays == null || isEmpty(minDays.value))?1:parseInt(minDays.value, 10);
	if (this.selector == TO_CALENDAR_SELECTOR && this.realToDate != null) this.calendarCurrentDate = copyDate(this.realToDate);
	if (this.selector == FROM_CALENDAR_SELECTOR && this.realFromDate != null) this.calendarCurrentDate = copyDate(this.realFromDate);
	this.calendarCurrentDate.setDate(1); // current date aplies just to months and years, so using first day of month cause no effect but increase security with date operations.
	this.months = new Array();
	this.toDateSelection = function toDateSelection() {
		return this.selector == TO_CALENDAR_SELECTOR || this.state >= SELECT_DEPARTURE_DATE_STATE;
	}
	// calendar visual date update functions
	this.deselectDate = function deselectDate(selector) {
		if (selector == FROM_CALENDAR_SELECTOR) {
			this.state = SELECT_ARRIVAL_DATE_STATE;
		} else if (selector == TO_CALENDAR_SELECTOR) {
			this.state = SELECT_DEPARTURE_DATE_STATE;
		}
	}
	this.updateInfo = function updateInfo() {
		this.updateSelectorInfo(FROM_CALENDAR_SELECTOR, this.realFromDate);
		this.updateSelectorInfo(TO_CALENDAR_SELECTOR, this.realToDate);
		if (this.nights == null) return;
		if(this.realFromDate != null && this.realToDate != null) { // number of nights
			var nnights = getNumberOfNights(this.realFromDate, this.realToDate);
			this.nights.innerHTML = '<input class="nights" value="' + (nnights + ' ' + ((nnights == 1)?NIGHT_TEXT:NIGHTS_TEXT)) +'" id="nights" type="text" readonly/>';
		} else this.nights.innerHTML = '';
	}
	this.updateSelectorInfo = function updateSelectorInfo(selector, jsDate) {
		var formElement = get(getCalendarElementId(this.calendarId, selector));
		if (formElement == null) return;
		var formHiddenElement = get(getCalendarElementId(this.calendarId, HIDDEN_SELECTOR_PREFIX + selector))
		var calendarInfoElement = this.smartGet(getCalendarElementId(this.calendarId, INFO_SELECTOR_PREFIX + selector))
		if (jsDate != null) {
			formElement.value = calendarGoodLookingFormat(jsDate);
			formHiddenElement.value = formatDate(jsDate);
			calendarInfoElement.innerHTML = calendarGoodLookingFormat(jsDate);
		} else {
			formElement.value = ALL_DATES;
			formHiddenElement.value = '';
			calendarInfoElement.innerHTML = NO_DATE;
		}
	}
	// CALENDAR DRAWING MANAGEMENT FUNCTIONS
	// draw/re-draw a calendar
	this.drawCalendar = function drawCalendar() {
		var layer = this.layers[this.selector];
		var documentReference = this.documentReferences[this.selector];
		// reset calendar layer
		layer.innerHTML = "";
		// 0. title
		layer.innerHTML+= '<p class="der">' + createLink(CLOSE_LINK_TEXT, correctJSFunctionCall("closeCalendar('" + this.calendarId + "')", documentReference), CLOSE_LINK_CLASS_NAME) + '</p>';
		layer.innerHTML+= createCloseElement();
		// 2. month/s
		this.months = new Array();
		// 2.1 first month
		var monthDate = copyDate(this.calendarCurrentDate);
		var movementLinks = new Array();
		movementLinks[LEFT_POSITION] = this.monthMovementIsPosible(monthDate, LEFT_POSITION);
		if (this.dateShowMode == SIMPLE_MODE) {
			var nextMonthDate = copyDate(this.calendarCurrentDate, null, 0);
			movementLinks[RIGHT_POSITION] = this.monthMovementIsPosible(nextMonthDate, RIGHT_POSITION);
			layer.style.width = "255px";
		}
		layer.innerHTML+= this.drawMonth(monthDate, CALENDAR_LEFT_CLASS_NAME, movementLinks);
		// 2.2 second month if double show mode is selected
		if (this.dateShowMode == DOUBLE_MODE) {
			layer.innerHTML+= '<br class="hide"/>';
			monthDate = copyDate(this.calendarCurrentDate, null, 1);
			movementLinks = new Array();
			movementLinks[RIGHT_POSITION] = this.monthMovementIsPosible(monthDate, RIGHT_POSITION);
			layer.innerHTML+= this.drawMonth(monthDate, CALENDAR_RIGHT_CLASS_NAME, movementLinks);
		}
		layer.innerHTML+= createCloseElement();
		// 3. info
		var info = '<div class="left">';
		var id = '';
		if (this.fromDate !=null) {
			id = getCalendarElementId(this.calendarId, INFO_SELECTOR_PREFIX + FROM_CALENDAR_SELECTOR);
			info+= '<p class="textsmall"><strong>' + ARRIVAL_DATE_TXT + '</strong><span id="' + id + '">' + showCalendarDateIfExists(this.realFromDate) + '</span>';
			if (this.dateSelectionMode == DOUBLE_MODE)
				info+= createLink(DELETE_LINK_TEXT, correctJSFunctionCall("deselectDate('" + this.calendarId + "', '" + FROM_CALENDAR_SELECTOR + "')", documentReference));
			info+= '</p>';
		}
		if (this.toDate !=null) {
			id = getCalendarElementId(this.calendarId, INFO_SELECTOR_PREFIX + TO_CALENDAR_SELECTOR);
			info+= '<p class="textsmall"><strong>' + DEPARTURE_DATE_TXT + '</strong><span id="' + id + '">' + showCalendarDateIfExists(this.realToDate) + '</span>';
			if (this.dateSelectionMode == DOUBLE_MODE)
				info+= createLink(DELETE_LINK_TEXT, correctJSFunctionCall("deselectDate('" + this.calendarId + "', '" + TO_CALENDAR_SELECTOR + "')", documentReference));
			info+= '</p>';
		}
		info += '</div>'
		layer.innerHTML+= info;
		// 4. 'Delete' button
		if (selector != '') {
			var deleteButton = '<div class="right">';
			deleteButton += '<input onclick="'+correctJSFunctionCall("reset('" + this.calendarId + "')", documentReference)+'" class="'+BUTTON_CLASS_NAME+'" value="'+DELETE_LINK_TEXT+'" type="button" />';
			deleteButton += '</div>';
			layer.innerHTML+= deleteButton;
			layer.innerHTML+= createCloseElement();
		}
		// 5. show calendar
		this.overLayers[this.selector].style.display = "block";
		addToolTip(this.overLayers[this.selector]);
		// 6. highlight days
		this.highlightDays();
	}
	// draw a month specified by monthDate, according to calendar object configuration, including navigation links specified
	this.drawMonth = function drawMonth(monthDate, calendarClass, movementLinks) {
		var monthToDraw = monthDate.getMonth();
		var monthInnerHTML = "";
		var calendarMonthId = getCalendarElementId(this.calendarId, formatDate(monthDate));
		// 0. open
		monthInnerHTML+= '<table id="' + calendarMonthId + '" class="' + calendarClass + '" cellspacing="0" cellpadding="0" border="0">';
		// 1. header: month title
		monthInnerHTML+= '<caption>';
		if (movementLinks[LEFT_POSITION] != null) monthInnerHTML+= movementLinks[LEFT_POSITION];
		monthInnerHTML+= '<strong>' + monthsName[monthToDraw] + ' ' + monthDate.getFullYear() + '</strong>';
		if (movementLinks[RIGHT_POSITION] != null) monthInnerHTML+= movementLinks[RIGHT_POSITION];
		monthInnerHTML+= '</caption>';
		// 2. header: days of the week
		var row = '';
		for (var day = 0; day < daysOfTheWeek.length; day++) row+= '<th class="title">' + daysOfTheWeek[day] + '</th>';
		monthInnerHTML+= '<tr>' + row + '</tr>';
		// 3. content: days of the month
		monthInnerHTML+= this.drawMonthDays(monthDate);
		// 4. close
		monthInnerHTML+= '</table>';
		this.months.push(calendarMonthId);
		return monthInnerHTML;
	}
	// draw months days in a week-organized structure. Generated cells are unstyled.
	this.drawMonthDays = function drawMonthDays(date) {
		var monthDays = '';
		// set first day of the month (begining of month number dates)
		date.setDate(1);
		var monthToDraw = date.getMonth();
		// fill first gap
		var monthDays = '<tr>';
		var dayOfTheWeekIndex = 1;
		// no month days
		for(; dayOfTheWeekIndex < getComfortableDayOfTheWeekIndex(date.getDay()); dayOfTheWeekIndex++) monthDays+= '<td>&nbsp;</td>';
		if (dayOfTheWeekIndex > DAYS_OF_THE_WEEK) monthDays+= '</tr>';
		while(monthToDraw == date.getMonth()) {
			if (dayOfTheWeekIndex > DAYS_OF_THE_WEEK) {monthDays+= '<tr>'; dayOfTheWeekIndex = 1;}
			monthDays+= '<td>' + createLink(date.getDate())  + '</td>';
			date.setDate((date.getDate() + 1));
			dayOfTheWeekIndex++;
			if (dayOfTheWeekIndex > DAYS_OF_THE_WEEK) monthDays+= '</tr>';
		}
		return monthDays;
	}
	// assing css class and capabilities to every day-cell of drawed calendar month/s
	this.highlightDays = function highlightDays() {
		var allDays = new Array();
		// 1. get months days (cells)
		for (var i = 0; i < this.months.length; i++) { // process each month
			var monthCells = this.getCellsFromCalendarMonth(this.months[i]);
			allDays = allDays.concat(monthCells);
		}
		// 2. need previous/next days to apply availability, and discard days, correctly
		allDays = this.addExtraMonthDays(allDays);
		// 3. apply ranges
		var selectedRangeBorderIndex = this.applyRangesAvailability(allDays);
		// 4. Assign availability group to avoid selections out of selectable days intervals.
		this.groupIdAssignment(allDays);
		// 5. Process to eliminate extra days, due to defined minDays.
		this.discardDays(allDays);
		// 6. Re-Assign availability group to avoid selections out of selectable days intervals.
		var selectedGroupId = this.groupIdAssignment(allDays);

      for (var i = 0; i < allDays.length; i++) {
            var day = allDays[i];
            if (day.element == null) continue;
            day.element.className = NOT_AVAILABLE_CLASS_NAME;
            if (day.isEnabled()) {
                day.element.className = AVAILABLE_CLASS_NAME;
                var reset = (selectedGroupId != day.group);
                if (selectedRangeBorderIndex != null && (Math.abs(selectedRangeBorderIndex - i) < this.minDays)) 
                    reset = true;
                makeClickable(day.element, this.calendarId, this.selector, formatDate(day.date), reset);
            }
            if (day.selected) day.element.className += " " + SELECTED_CLASS_NAME;
        } 
		
		this.days = allDays;
	}
	// enable/disable days, according to ranges definition. Also, returns a reference to the selected range side: Left side, for to date selection, right side for from date selection
	this.applyRangesAvailability = function applyRangesAvailability(days) {
		var selectedRangeBorderIndex = null; // stores left or right selected border, depending on the selection state [from/to date selection]
		for (var i = 0; i < days.length; i++) {
			var day = days[i];
			var selectable = false;
			// date ranges are processed in appearance order, so priority can be defined.
			for (var ii = 0; ii < this.dateRanges.length; ii++) {
				var dateRange = this.dateRanges[ii];
				if (!dateRange.contains(day.date)) { selectable = selectable; continue; }
				// day in range
				selectable = (dateRange.matchesSelectability(day.date));
				if (this.realFromDate != null && this.realFromDate.getTime() == day.date.getTime() || 
					(this.realToDate != null && (this.realFromDate.getTime() <= day.date.getTime() && day.date.getTime() <= this.realToDate.getTime()))
				) { 
					day.selected = true;
					if (!this.toDateSelection()) selectedRangeBorderIndex = i;
					else if (selectedRangeBorderIndex == null) selectedRangeBorderIndex = i;
				}
			}
			day.setEnabled(selectable);			
		}
		return selectedRangeBorderIndex;
	}
	// disable days, according to min days definition
	this.discardDays = function discardDays(days) {
		var max = (days.length - 1);

		// extra day for ToDate selection
		if (this.toDateSelection()) {
			var previousGroup = days[0].group;
			for (var i = 0; i <= max ;i++) {
				if (days[i].group == 0 && previousGroup > 0) { 
					days[i].group = previousGroup; previousGroup = 0;
				} else previousGroup = days[i].group;
			}
		}
		
		for (var index = 0; index <= max ;index++) {
			var i = (this.toDateSelection()) ? (max - index) : index;
			var indexToCompare = (this.toDateSelection()) ? (i- this.minDays) : (i+ this.minDays-1);
			if (indexToCompare < 0 || max < indexToCompare) continue;
			var referenceDate = copyDate(this.calendarBeginning, this.minDays);
			var isInvalidDay = (this.toDateSelection()) ? days[i].date.getTime() < referenceDate : days[i].date.getTime() < this.calendarBeginning.getTime();
			if (days[indexToCompare].group != days[i].group || isInvalidDay) {
				days[i].disable();
			}
		}
	}
	// disable days, according to min days definition
	this.groupIdAssignment = function groupIdAssignment(days) {
		var groupId = 1; // initial group id
		var selectedGroupId = null;
		for (var i = 0; i < days.length; i++) {
			var day = days[i];			
			if (day.isEnabled()) day.group = groupId;
			else groupId++;
			if (day.selected) {
				if (this.toDateSelection()) selectedGroupId = day.group;
				else if (selectedGroupId == null) selectedGroupId = day.group;
			}
		}
		return selectedGroupId;
	}
	// gathers every calendar cell from a specified calendar month element id
	this.getCellsFromCalendarMonth = function getCellsFromCalendarMonth(id) {
		var calendarMonth = this.smartGet(id);
		var monthDate = parseDate(getSelectorFromId(id));
		var dayCells = new Array();	var day; var cells = calendarMonth.getElementsByTagName('A');
		for (var ii = 0; ii < cells.length; ii++) {
			day = parseInt(cells[ii].innerHTML);
			if (isNaN(day)) continue;
			monthDate.setDate(day);
			dayCells.push(new DayCell(copyDate(monthDate), cells[ii]));
		}
		return dayCells;
	}
	// add previous and following days, to complete days list (as many as minDays?)
	this.addExtraMonthDays = function addExtraMonthDays(days) {
		var previousDay = new Array();
		var referenceDate = copyDate(days[0].date, (-1 * this.minDays));
		for (var i = 0; i < this.minDays; i++) {
			previousDay.push(new DayCell(copyDate(referenceDate)));
			referenceDate.setDate(referenceDate.getDate() + 1);
		}
		var followingDay = new Array();
		var referenceDate = copyDate(days[days.length - 1].date, 1);
		for (var i = 0; i < this.minDays; i++) {
			followingDay.push(new DayCell(copyDate(referenceDate)));
			referenceDate.setDate(referenceDate.getDate() + 1);
		}
		return previousDay.concat(previousDay, days, followingDay);
	}
	// determine if calendar month movement is possible. In such case, draws the link.
	this.monthMovementIsPosible = function monthMovementIsPosible(monthDate, direction) {
		var documentReference = this.documentReferences[this.selector];
		var link = null; var isPosible = false; var date = copyDate(monthDate);
		date.setDate(1); // for security reasons, month operation begins with first day of the month assignment. 
		if (direction == LEFT_POSITION) {		
			if (this.calendarBeginning == null) isPosible = true;
			else {
				date.setMonth(date.getMonth() - 1); // set previous month
				date.setDate(this.calendarBeginning.getDate()); // same begining day
				isPosible =  (date.getTime() >= this.calendarBeginning.getTime());
			}
			if (isPosible) 
				link = createLink(PREVIOUS_MONTH_TEXT, correctJSFunctionCall("move('" + this.calendarId + "', " + CMD_PREV_MONTH + ")", documentReference), PREVIOUS_LINK_CLASS_NAME);
		} else {
			if (this.calendarEnd == null) isPosible = true;
			else {
				date.setMonth(date.getMonth() + 1); // set next month
				date.setDate(this.calendarEnd.getDate()); // same end day
				isPosible =  (date.getTime() <= this.calendarEnd.getTime());
			}			
			if (isPosible) 
				link = createLink(NEXT_MONTH_TEXT, correctJSFunctionCall("move('" + this.calendarId + "', " + CMD_NEXT_MONTH + ")", documentReference), NEXT_LINK_CLASS_NAME);
		}
		return link;
	}
	this.move = function move(command) { 
		// This is secure operation because calendarCurrentDate always refers to month/year, day is always first day in month.
		this.calendarCurrentDate.setMonth(this.calendarCurrentDate.getMonth() + command);
	}
	this.close = function close() {
		for (key in this.layers) {
			if (this.layers[key] != null) {
				this.layers[key].innerHTML = "";
				this.overLayers[key].style.display = "none";
			}
		}
	}
	this.smartGet = function smartGet(id) {
		return get(id, this.documentReferences[this.selector]);
	}
}
// Utility object to store both date and DOM element reference
function DayCell(date, element) {
	this.date = date;
	this.element = element;	
	this.selected = false;
	this.group = 0;
	this.enable = function enable() { this.group = 1; /* generic value */ };
	this.disable = function disable() { this.group = 0; /* no availability group means no availability */ };
	this.isEnabled = function isEnabled() { return this.group > 0; /* belongs to any availability group */};
	this.setEnabled = function setEnabled(selectable) {this.group = (selectable) ? 1 : 0; }
}
// Utility Functions
function initMultipleField(vector, id, fieldId, dateMode) {
	vector[FROM_CALENDAR_SELECTOR] = null; 	vector[TO_CALENDAR_SELECTOR] = null;
	var initFunc = (dateMode == null) ? initField : initDateField;
	for (key in vector) {
		var value = initFunc(id, fieldId + FIELD_SEPARATOR + key);
		if (value != null) vector[key] = value;
		else vector[key] = initFunc(id, fieldId);
	}
}
function initIframeMultipleField(vector, id, fieldId) {
	vector[FROM_CALENDAR_SELECTOR] = null; 
	vector[TO_CALENDAR_SELECTOR] = null;
	for (key in vector) {
	 	var documentReference = frames[id + FIELD_SEPARATOR + IFRAME_SELECTOR_PREFIX + FIELD_SEPARATOR + key];
	 	if (documentReference == null) documentReference = frames[id + FIELD_SEPARATOR + IFRAME_SELECTOR_PREFIX];
	 	vector[key] = documentReference;
	}
}	
function initField(id, fieldId) {
	var htmlField = get(id + "|" + fieldId);
	if (htmlField == null) htmlField = get(COMMON_CONFIGURATION_ID_PREFIX + FIELD_SEPARATOR + fieldId);
	return htmlField;
}
function initDateField(id, fieldId, defaultValue) {
	var htmlField = get(id + "|" + fieldId);
	if (htmlField == null) htmlField = get(COMMON_CONFIGURATION_ID_PREFIX + FIELD_SEPARATOR + fieldId);
	var date = (htmlField == null)?defaultValue:(isEmpty(htmlField.value))?null:parseDate(htmlField.value);
	return date;
}
function getCalendarElementId(calendar, selector) {
	return calendar + FIELD_SEPARATOR + selector;
}
function getSelectorFromId(id) {
	var data = id.split(FIELD_SEPARATOR);
	return data[1];
}
function calendarGoodLookingFormat(date) {
	if (date == null) return "Cualquiera:)";
	return date.getDate() + DATE_SEPARATOR + months[date.getMonth()] + DATE_SEPARATOR + date.getFullYear();
}
function showCalendarDateIfExists(date) {
	return ((date == null)?NO_DATE:calendarGoodLookingFormat(date));
}
function isLabour(day) {
	var weekDayIndex = day.getDay();
	return (weekDayIndex != 6 && weekDayIndex != 0);
}
function isWeekEnd(day) {
	var weekDayIndex = day.getDay();
	return (weekDayIndex == 5 || weekDayIndex == 6 || weekDayIndex == 0);
}