//Inicialización de la aplicación

/*fecha es el id del input de la fecha a rellenar en el formulario*/
/*fechasalida es el id del input de la fecha de salida a rellenar en el formulario*/
var calendar;
var calendarsalida;
window.onload = function () {
	calendar = new Epoch('epoch_popup','popup',document.getElementById('fecha'));
	calendarsalida = new Epoch('epoch_popup2','popup',document.getElementById('fechasalida'));	
};

/*****************************************************************************************/
/**********************************CALENDARIO JAVASCRIPT**********************************/
/*****************************************************************************************/

/*Función principal*/
function Epoch(name,mode,targetelement,multiselect) {
	var self = this; 
	
	/*Variables de definición y personalización de la agenda */
	function calConfig() {
		self.versionNumber = '2.0.2'; // Versión de la aplicación
		self.displayYearInitial = self.curDate.getFullYear(); //La fecha inicial en la carga
		self.displayMonthInitial = self.curDate.getMonth(); //Mes inicial en la carga
		self.displayYear = self.displayYearInitial;
		self.displayMonth = self.displayMonthInitial;
		self.minDate = new Date(2006,0,1); // Límite fecha inicial
		self.maxDate = new Date(2012,11,31); // Límite fecha final
		self.startDay = 1; // Día de inicio de la semana (1 -> Lunes)
		self.showWeeks = true; //Si se van a mostrar las semanas
		self.selCurMonthOnly = true; //Booleano que habilita la selección de la fecha por el usuario
		self.dateformat = 'd-m-Y';
	}
	//-----------------------------------------------------------------------------
	/**
	* Definiciones de los idiomas de forma personalizable.
	*/
	function setLang() {
		self.daylist = new Array('D','L','M','X','J','V','S','D','L','M','X','J','V','S');
		self.months_sh = new Array('Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic');
		self.monthup_title = 'Ir al mes siguiente';
		self.monthdn_title = 'Ir al mes anterior';
		self.clearbtn_caption = 'Limpiar';
		self.clearbtn_title = 'Elimina la fecha del calendario';
		self.maxrange_caption = 'Éste es el rango máximo';
		self.closebtn_caption = 'Cerrar';
		self.closebtn_title = 'Cerrar el calendario';
	}
	//-----------------------------------------------------------------------------
	/**
	* Parámetros del calendario gregoriano (definición de las excepciones; 30 de Febrero, etc.)
	*/
	function setDays() {
		self.daynames = new Array();
		var j=0;
		for(var i=self.startDay;i<self.startDay + 7;i++) {
			self.daynames[j++] = self.daylist[i];
		}
		self.monthDayCount = new Array(31,((self.curDate.getFullYear() - 2000) % 4 ? 28 : 29),31,30,31,30,31,31,30,31,30,31);
	}
	//-----------------------------------------------------------------------------
	/**
	* Implementación de la estructura DOM del calendario
	*/
	function createCalendar() {
		var tbody, tr, td;
		self.calendar = document.createElement('table');
		self.calendar.setAttribute('id',self.name+'_calendar');
		setClass(self.calendar,'calendar');
		self.calendar.style.display = 'none'; // La capa de visualización del calendario está oculta por defecto  
		//Control de errores para IE cuando se selecciona una fecha del calendario
		addEventHandler(self.calendar,'selectstart', function() {return false;});
		addEventHandler(self.calendar,'drag', function() {return false;});
		tbody = document.createElement('tbody');

		//Cabecera principal del calendario
		tr = document.createElement('tr');
		td = document.createElement('td');
		td.appendChild(createMainHeading());
		tr.appendChild(td);
		tbody.appendChild(tr);

		//Creación de los días de la cabecera del calendario y las celdas de los días del calendario
		tr = document.createElement('tr');
		td = document.createElement('td');
		self.calendar.celltable = document.createElement('table');
		setClass(self.calendar.celltable,'cells');
		self.calendar.celltable.appendChild(createDayHeading());
		self.calendar.celltable.appendChild(createCalCells());
		td.appendChild(self.calendar.celltable);
		tr.appendChild(td);
		tbody.appendChild(tr);

		//Pie del calendario
		tr = document.createElement('tr');
		td = document.createElement('td');
		td.appendChild(createFooter());
		tr.appendChild(td);
		tbody.appendChild(tr);

		//Creamos la etiqueta 'tbody' para la tabla principal del calendario
		self.calendar.appendChild(tbody);

		//Eventos 'mouseover' y 'mouseout' para el calendario
		addEventHandler(self.calendar,'mouseover',cal_onmouseover);
		addEventHandler(self.calendar,'mouseout',cal_onmouseout);
	}
	//-----------------------------------------------------------------------------

	// Creación del encabezado principal del calendario, con los meses y años
	function createMainHeading() {
		
		//Creación de la capa contenedora
		var container = document.createElement('div');
		setClass(container,'mainheading');
		//elementos hijos y otras variables
		self.monthSelect = document.createElement('select');
		self.yearSelect = document.createElement('select');
		var monthDn = document.createElement('input'), monthUp = document.createElement('input');
		var opt, i;
		//rellenado del mes seleccionado
		for(i=0;i<12;i++) {
			opt = document.createElement('option');
			opt.setAttribute('value',i);
			if(self.displayMonth == i) {
				opt.setAttribute('selected','selected');
			}
			opt.appendChild(document.createTextNode(self.months_sh[i]));
			self.monthSelect.appendChild(opt);
		}
		//fin del rellenado del mes seleccionado
		var yrMax = self.maxDate.getFullYear(), yrMin = self.minDate.getFullYear();
		for(i=yrMin;i<=yrMax;i++) {
			opt = document.createElement('option');
			opt.setAttribute('value',i);
			if(self.displayYear == i) {
				opt.setAttribute('selected','selected');
			}
			opt.appendChild(document.createTextNode(i));
			self.yearSelect.appendChild(opt);
		}
		//añadimos el hijo apropiado para los botones de los meses
		monthUp.setAttribute('type','button');
		monthUp.setAttribute('value','>');
		monthUp.setAttribute('title',self.monthup_title);
		monthDn.setAttribute('type','button');
		monthDn.setAttribute('value','<');
		monthDn.setAttribute('title',self.monthdn_title);
		self.monthSelect.owner = self.yearSelect.owner = monthUp.owner = monthDn.owner = self;  //hack to allow us to access self calendar in the events (<fix>??)

		//asignamos los eventos correspondientes a todos los controles
		function selectonchange()	{
			if(self.goToMonth(self.yearSelect.value,self.monthSelect.value)) {
				self.displayMonth = self.monthSelect.value;
				self.displayYear = self.yearSelect.value;
			}
			else {
				self.monthSelect.value = self.displayMonth;
				self.yearSelect.value = self.displayYear;
			}
		}
		addEventHandler(monthUp,'click',function(){self.nextMonth();});
		addEventHandler(monthDn,'click',function(){self.prevMonth();});
		addEventHandler(self.monthSelect,'change',selectonchange);
		addEventHandler(self.yearSelect,'change',selectonchange);

		//y finalmente añadimos los elementos a la capa contenedora
		container.appendChild(monthDn);
		container.appendChild(self.monthSelect);
		container.appendChild(self.yearSelect);
		container.appendChild(monthUp);
		return container;
	}
	//-----------------------------------------------------------------------------

	// Creación del pie del calendario - irá despues de las celdas del calendario
	function createFooter() {
		var container = document.createElement('div');
		var clearSelected = document.createElement('input');
		clearSelected.setAttribute('type','button');
		clearSelected.setAttribute('value',self.clearbtn_caption);
		clearSelected.setAttribute('title',self.clearbtn_title);
		clearSelected.owner = self;
		addEventHandler(clearSelected,'click',function() {self.resetSelections(false);});
		container.appendChild(clearSelected);
		if(self.mode == 'popup') {
			var closeBtn = document.createElement('input');
			closeBtn.setAttribute('type','button');
			closeBtn.setAttribute('value',self.closebtn_caption);
			closeBtn.setAttribute('title',self.closebtn_title);
			addEventHandler(closeBtn,'click',function(){self.hide();});
			setClass(closeBtn,'closeBtn');
			container.appendChild(closeBtn);
		}
		return container;
	}
	//-----------------------------------------------------------------------------

	// Creación del encabezado contenedor de los nombres de los días
	function createDayHeading() {
		//Creación de la tabla
		self.calHeading = document.createElement('thead');
		setClass(self.calHeading,'caldayheading');
		var tr = document.createElement('tr'), th;
		self.cols = new Array(false,false,false,false,false,false,false);

		//Si mostramos los encabezados de las semanas, creamos una celda vacía
		if(self.showWeeks) {
			th = document.createElement('th');
			setClass(th,'wkhead');
			tr.appendChild(th);
		}
		//Mostramos los títulos de los días
		for(var dow=0;dow<7;dow++) {
			th = document.createElement('th');
			th.appendChild(document.createTextNode(self.daynames[dow]));
			//Si se selecciona las entradas múltiples, asignamos la celda a una celda de encabezado para manejar todos los eventos
			if(self.selectMultiple) { 
				th.headObj = new CalHeading(self,th,(dow + self.startDay < 7 ? dow + self.startDay : dow + self.startDay - 7));
			}
			tr.appendChild(th);
		}
		self.calHeading.appendChild(tr);
		return self.calHeading;
	}
	//-----------------------------------------------------------------------------

	// Creación de la tabla que contendrá las celdas de los días del calendario
	function createCalCells() {
		self.rows = new Array(false,false,false,false,false,false);
		self.cells = new Array();
		var row = -1, totalCells = (self.showWeeks ? 48 : 42);
		var beginDate = new Date(self.displayYear,self.displayMonth,1);
		var endDate = new Date(self.displayYear,self.displayMonth,self.monthDayCount[self.displayMonth]);
		var sdt = new Date(beginDate);
		sdt.setDate(sdt.getDate() + (self.startDay - beginDate.getDay()) - (self.startDay - beginDate.getDay() > 0 ? 7 : 0) );

		//Creación de la tabla que contendrá las celdas
		self.calCells = document.createElement('tbody');
		var tr,td;
		var cellIdx = 0, cell, week, dayval;

		for(var i=0;i<totalCells;i++) {
			if(self.showWeeks) { //Si mostramos los encabezados de las semanas
				if(i % 8 == 0) {
					row++;
					week = sdt.getWeek(self.startDay);
					tr = document.createElement('tr');
					td = document.createElement('td');
					//Si selectMultiple está habilitado, creamos los objetos weekObj asociados
					if(self.selectMultiple) { 
						td.weekObj = new WeekHeading(self,td,week,row)
					}
					else { // Si no, colocamos la clase para adoptarle un aspecto adecuado
						setClass(td,'wkhead');
					}
					td.appendChild(document.createTextNode(week));
					tr.appendChild(td);
					i++;
				}
			}
			else if(i % 7 == 0) { // Si no, nueva celda cada 7 días
				row++;
				week = sdt.getWeek(self.startDay);
				tr = document.createElement('tr');
			}
			//Creamos las celdas de los días
			dayval = sdt.getDate();
			td = document.createElement('td');
			td.appendChild(document.createTextNode(dayval));
			cell = new CalCell(self,td,sdt,row,week);//,'normal',sdt.getTime() >= self.minDate.getTime() && sdt.getTime() <= self.maxDate.getTime());
			self.cells[cellIdx] = cell;
			td.cellObj = cell;
			tr.appendChild(td);
			self.calCells.appendChild(tr);
			self.reDraw(cellIdx++); // y pintamos la celda acorde a sus propiedades
			sdt.setDate(dayval + 1); //incrementamos el día
		}
		return self.calCells;
	}
	//-----------------------------------------------------------------------------

	//Ejecutamos todas las operaciones necesarias para cambiar el modo del calendario
	// @param HTMLInputElement targetelement

	function setMode(targetelement)	{
		//Asignamos el posicionamiento absoluto para la capa calendario
		if(self.mode == 'popup') { 
			self.calendar.style.position = 'absolute';
		}
		//Si se ha definido el texto explicativo, lo asignamos al calendario
		if(targetelement) {
			switch(self.mode) {
				case 'flat':
					self.tgt = targetelement;
					self.tgt.appendChild(self.calendar);
					self.visible = true;
					break;
				case 'popup':
					self.calendar.style.position = 'absolute';
					document.body.appendChild(self.calendar);
					self.setTarget(targetelement,false);
					break;
			}
		}
		else { //En otro caso, añadimos el calendario a la etiqueta body (afecta si el elemento no está definido antes de que el calendario esté inicializado)
			document.body.appendChild(self.calendar);
			self.visible = false;
		}
	}
	//-----------------------------------------------------------------------------

	// Elimina las celdas de la tabla calendario del DOM (pero no elimina los objetos de las celdas asociados)
	function deleteCells() {
		self.calendar.celltable.removeChild(self.calendar.celltable.childNodes[1]); //remove the tbody element from the cell table
	}
	//-----------------------------------------------------------------------------
	
	// Asocia la clase de hoja de estilos la clase del elemento CSS, W3C e IE
	function setClass(element,className) {
		element.setAttribute('class',className);
		element.setAttribute('className',className); 
	}

	// Actualizamos el dato de la celda, incluido la clase css y la propiedad de selección 
	function setCellProperties(cellindex) {
		var cell = self.cells[cellindex];
		var date;
		idx = self.dateInArray(self.dates,cell.date);
		if(idx > -1) {
			date = self.dates[idx]; //reduce la dirección
			cell.date.selected = date.selected || false;
			cell.date.type = date.type;
			cell.date.canSelect = date.canSelect;
			cell.setTitle(date.title);
			cell.setURL(date.href);
			cell.setHTML(date.cellHTML);
		}
		else {
			cell.date.selected = false; //si la fecha de la celda no está en las fechas del vector, ponemos su valor a falso
		}
		//recortamos fechas a sus límites mínimo y máximo
		if(cell.date.getTime() < self.minDate.getTime() || cell.date.getTime() > self.maxDate.getTime()) {
			cell.date.canSelect = false;
		}
		cell.setClass();
	}
	//-----------------------------------------------------------------------------
	function cal_onmouseover() {
		self.mousein = true;
	}
	//-----------------------------------------------------------------------------
	function cal_onmouseout()	{
		self.mousein = false;
	}
	//-----------------------------------------------------------------------------

	// Actualizamos los punteros del array
	function updateSelectedDates() {
		var idx = 0;
		self.selectedDates = new Array();
		for(i=0;i<self.dates.length;i++) {
			if(self.dates[i].selected) {
				self.selectedDates[idx++] = self.dates[i];
			}
		}
	}
	
	//MÉTODOS PÚBLICOS

	//-----------------------------------------------------------------------------
	// Encuentra la fecha del array, devolviendo su índice si lo encuentra y -1 si no
	self.dateInArray = function(arr,searchVal,startIndex) {
		startIndex = (startIndex != null ? startIndex : 0); //startIndex es 0 por defecto, si no está definido
		for(var i=startIndex;i<arr.length;i++) {
			if(searchVal.getUeDay() == arr[i].getUeDay()) {
				return i;
			}
		}
		return -1;
	};

	//-----------------------------------------------------------------------------
	// Cambia el tipo del elemento a otro input

	self.setTarget = function (targetelement, focus)
	{
		//si es un calendario popup
		if(self.mode == 'popup') {
			//declara el manejador de eventos para el tipo del elemento
			function popupFocus() {
				self.show();
			}
			function popupBlur() {
				if(!self.mousein){
					self.hide();
				}
			}
			function popupKeyDown() {
				self.hide();
			}
			//desactiva el tipo de manejador de eventos (si no está hecho ya)
			if(self.tgt) {
				removeEventHandler(self.tgt,'focus',popupFocus);
				removeEventHandler(self.tgt,'blur',popupBlur);
				removeEventHandler(self.tgt,'keydown',popupKeyDown);
			}
			//asigna el nuevo tipo de elemento
			self.tgt = targetelement;
			//Creación del puntero a la fecha del objeto INPUT e inicializa un nuevo array
			var dto = self.tgt.dateObj,pdateArr = new Array;
			//si la fecha está activa para el tipo del elemento
			if(dto) {
				if(self.tgt.value.length) { //Lo cargamos dentro del calendario...
					pdateArr[0] = dto;
				}
				self.goToMonth(dto.getFullYear(),dto.getMonth()); //... y nos vamos a la etiqueta mes/año
			}
			self.selectDates(pdateArr,true,true,true);

			self.topOffset = self.tgt.offsetHeight; // la distancia vertical (en píxeles) a mostrar el calendario de arriba de su elemento de entrada
			self.leftOffset = 0; 					// la distancia horizontal (en píxeles) desde la izquierda a mostrar del calendario
			self.updatePos(self.tgt);
			// añadimos el manejador de eventos para el nuevo elemento
			//addEventHandler(self.tgt,'focus',popupFocus);
			addEventHandler(self.tgt,'blur',popupBlur);
			addEventHandler(self.tgt,'keydown',popupKeyDown);
			if(focus !== false) { // Poscionamos el foco en el elemento inmediatamente, a no ser que se especifique de otro modo
				popupFocus();
			}
		}
		else { // si es un calendario plano o inline
			// Si la etiqueta está activa, eliminamos su representación DOM
			if(self.tgt) {
				self.tgt.removeChild(self.calendar);
			}
			//ahora, activamos el elemento del calendario a el nuevo elemento y mostramos el calendario
			self.tgt = targetelement;
			self.tgt.appendChild(self.calendar);
			self.show();
		}
	};
	//-----------------------------------------------------------------------------

	// Vamos al mes siguienteGo to the next month. Si es Diciembre, vamos a Enero del siguiente año
	self.nextMonth = function () {
		var month = self.displayMonth;
		var year = self.displayYear;
		//incrementamos los valores de los meses y los años, con cuidado de no pasarnos de los valores límites
		if(self.displayMonth < 11) { //por ejemplo, si es el año actual
			month++;
		}
		else if(self.yearSelect.value < self.maxDate.getFullYear()) { //Si no, incrementamos también el año
			month = 0;
			year++;
		}
		return self.goToMonth(year,month);
	};
	//-----------------------------------------------------------------------------

	//Vamos al mes anterior. Si es enero, vamos al més de Diciembre del año anterior
	self.prevMonth = function () {
		var month = self.displayMonth;
		var year = self.displayYear;
		// Decrementamos los valores del mes y del año, cuidando de no traspasar los límites fijados
		if(self.displayMonth > 0) { //por ejemplo, si no es el año acual
			month--;
		}
		else { //si no, decrementamos también el año
			month = 11;
			year--;
		}
		return self.goToMonth(year,month);
	};
	//-----------------------------------------------------------------------------

	// Activamos el calendario para mostrar el mes/año seleccionado, devolviendo true si la fecha está entre los valores mínimo y máximo permitidos
	self.goToMonth = function (year,month) {
		var testdatemin = new Date(year, month, 31);
		var testdatemax = new Date(year, month, 1);
		if(testdatemin >= self.minDate && testdatemax <= self.maxDate) {
			self.monthSelect.value = self.displayMonth = month;
			self.yearSelect.value = self.displayYear = year;
			// recreamos el calendario para el nuevo mes
			createCalCells();
			deleteCells();
			self.calendar.celltable.appendChild(self.calCells);
			return true;
		}
		else {
			alert(self.maxrange_caption);
			return false;
		}
	};
	//-----------------------------------------------------------------------------

	// Movemos el calendario a la ubicación de el elemento (sólo movemos la capa) 
	self.updatePos = function (target) {
		if(self.mode == 'popup') {
			self.calendar.style.top = getTop(target) + self.topOffset + 'px';
			self.calendar.style.left = getLeft(target) + self.leftOffset + 'px';
		}
	};
	//-----------------------------------------------------------------------------
	// Visualizamos la capa del calendario
	self.show = function ()	{
		self.updatePos(self.tgt); //Actualizamos la posición del calendario, en caso de que la posicicón de la página haya cambiado desde que se cargó
		self.calendar.style.display = 'block'; //'table'; //<iehack> 'table' es la recomendada por la W3C, opinión que no comparte el navegador IE
		self.visible = true;
	};
	//-----------------------------------------------------------------------------

	// 	Oculta el calendario
	self.hide = function () {
		self.calendar.style.display = 'none';
		self.visible = false;
	};
	//-----------------------------------------------------------------------------

	// Alterna la visualización del calendario dependiendo de su estado actual (muestra/oculta)
	self.toggle = function () {
		self.visible ? self.hide() : self.show();
	};
	//-----------------------------------------------------------------------------

	// Añade el array 'fechas' al el array de fechas del calendario, eliminando fechas duplicadas
	self.addDates = function (dates,redraw) {
		var i;
		for(i=0;i<dates.length;i++) {
			if(self.dateInArray(self.dates,dates[i]) == -1) { //si la fecha no está preparada en el array, la añadimos
				self.dates[self.dates.length] = dates[i];
			}
		}
		//ahora reconstruimos el puntero del array de las fechas seleccionadas del array
		updateSelectedDates();
		if(redraw != false) { //redibujamos el calendario si la variable esta a falso o no está definida
			self.reDraw();
		}
	};
	//-----------------------------------------------------------------------------
	
	// Eliminamos las fechas de las fechas del calendario y lo redibujamos
	self.removeDates = function (dates,redraw) {
		var idx;
		for(var i=0;i<dates.length;i++) {
			idx = self.dateInArray(self.dates,dates[i]);
			if(idx != -1) { //buscamos las fechas del array, eliminándolas si coinciden con las fechas
				self.dates.splice(idx,1);
			}
		}
		updateSelectedDates();
		if(redraw != false) { //redibujamos el calendario si la variables está activa o indefinida
			self.reDraw();
		}
	};
	//-----------------------------------------------------------------------------

	// Selecciona o elimina una array de fechas
	self.selectDates = function (inpdates,selectVal,redraw,removeothers) {
		var i, idx;
		if(removeothers == true) {
			for(i=0;i<self.dates.length;i++) {
				self.dates[i].selected = false;
			}
		}
		for(i=0;i<inpdates.length;i++) {
			idx = self.dateInArray(self.dates,inpdates[i]);
			if(selectVal == true) {
				inpdates[i].selected = true;
				if(idx == -1) { // si la fecha no existe en las fechas del calendario, la añadimos
					self.dates[self.dates.length] = inpdates[i];
				}
				else { // si no, lo seleccionamos
					self.dates[idx].selected = true;
				}
			}
			else { //si no seleccionamos...
				if(idx > -1) { //si hemos encontrado la fecha, deseleccionamos y la eliminamos de la fecha del calendario
					self.dates[idx].selected = inpdates[i].selected = false;
					if(self.dates[idx].type == 'normal') { //eliminamos las fechas 'normales' del array de fechas, ya que no son útiles hasta que no se seleccionan
						self.dates.splice(idx,1);
					}
				}
			}
		}
		// ahora reconstruimos el puntero del array selectedDates
		updateSelectedDates();
		if(redraw != false) { // redibujamos el calendario si "redraw" es false o no está definida
			self.reDraw();
		}
	};
	//-----------------------------------------------------------------------------

	// Añade las fechas como ocultas en el form "form
	self.sendForm = function(form,inputname) {
		var inpname = inputname || 'epochdates', f, inp;
		f = (typeof(form) == 'string' ? document.getElementById(form) : form);
		if(!f) {
			alert('ERROR: Invalid form input');
			return false;
		}
		for(var i=0;i<self.dates.length;i++) {
			inp = document.createElement('input');
			inp.setAttribute('type','hidden');
			inp.setAttribute('name',inpname + '['+i+']');
			inp.setAttribute('value',encodeURIComponent(self.dates[i].dateFormat('d-m-Y')));  //Valor por defencto para el formato de fecha ISO
			f.appendChild(inp);
		}
		return true;
	};
	//-----------------------------------------------------------------------------

	// Borra las fechas del array y resetea la selección de las fechas del calendario a sus valores por defecto
	// Si retMonth está activo, el calendario devolverá el mes/año definido por defecto 
	self.resetSelections = function (retMonth) {
		var dateArray = new Array();
		var dt = self.dates;
		for(var i=0;i<dt.length;i++) {
			if(dt[i].selected) {
				dateArray[dateArray.length] = dt[i];
			}
		}
		self.selectDates(dateArray,false,false);
		self.rows = new Array(false,false,false,false,false,false,false);
		self.cols = new Array(false,false,false,false,false,false,false);
		if(self.mode == 'popup') { // Oculta el calendario y limpia el valor de la caja de texto de la fecha
			self.tgt.value = '';
			self.hide();
		}
		retMonth == true ? self.goToMonth(self.displayYearInitial,self.displayMonthInitial) : self.reDraw();
	};
	//-----------------------------------------------------------------------------


	// Reasigna todas las clases CSS a las celdas del calendario 
	// Si el índice está definido, sólo redibujará la celda
	self.reDraw = function (index) {
		self.state = 1;
		var len = index ? index + 1 : self.cells.length;
		for(var i = index || 0;i<len;i++) {
			setCellProperties(i);
		}
		self.state = 2;
	};
	//-----------------------------------------------------------------------------


	// Devuelve el índice de la celda que coincida con "date", o -1 si no lo encuentra
	self.getCellIndex = function(date) {
		for(var i=0;i<self.cells.length;i++) {
			if(self.cells[i].date.getUeDay() == date.getUeDay()) {
				return i;
			}
		}
		return -1;
	};
	//-----------------------------------------------------------------------------
	//comienza el código para el constructor:

	//VARIABLES PÚBLICAS
	self.state = 0;
	self.name = name;
	self.curDate = new Date();
	self.mode = mode;
	self.selectMultiple = (multiselect == true); //'false' o true si no está todo activo
	//variables del calendario
	self.dates = new Array();
	self.selectedDates = new Array();

	self.calendar;
	self.calHeading;
	self.calCells;
	self.rows;
	self.cols;
	self.cells = new Array();
	//Los controles
	self.monthSelect;
	self.yearSelect;
	self.mousein = false;

	//Inicializa el calendario y sus variables llamando a las funciones correspondientes anteriormente definidas
	calConfig();
	setLang();
	setDays();
	createCalendar(); //creamos los elementos DOM del calendario, así como sus elemento hijos y sus objetos relacionados
	targetelement = typeof(targetelement) == 'string' ? document.getElementById(targetelement) : targetelement;
	setMode(targetelement);
	self.state = 2; //Valores: 0 -> inicializando, 1 -> redibujamdo, 2 -> ¡acabado!
	self.visible ? self.show() : self.hide();
}
//-----------------------------------------------------------------------------
/*****************************************************************************/

// Objeto que contiene los métodos y las propiedades del las cabeceras de los días del calendario
function CalHeading(owner,tableCell,dayOfWeek) {
	//-----------------------------------------------------------------------------
	function DayHeadingonclick() {//selecciona/deselecciona los días para los días de la semana
		var sdates = owner.dates;
		var cells = owner.cells;
		var dateArray = new Array();
		owner.cols[dayOfWeek] = !owner.cols[dayOfWeek];
		for(var i=0;i<cells.length;i++) { //bucle por todas las celdas del calendario, seleccionando todas aquellas con el mismo día de la semana y su encabezado
			if(cells[i].dayOfWeek == dayOfWeek && cells[i].date.canSelect && (!owner.selCurMonthOnly || cells[i].date.getMonth() == owner.displayMonth && cells[i].date.getFullYear() == owner.displayYear)) { //si la celda no coincide, con otras condiciones 
				dateArray[dateArray.length] = cells[i].date;
			}
		}
		owner.selectDates(dateArray,owner.cols[dayOfWeek],true);
	}
	//-----------------------------------------------------------------------------
	var self = this;
	self.dayOfWeek = dayOfWeek;
	addEventHandler(tableCell,'mouseup',DayHeadingonclick);
}
/*****************************************************************************/

// Objeto que contiene los métodos y las porpiedades de los en cabezados de la semana del calendario
function WeekHeading(owner,tableCell,week,tableRow) {
	//-----------------------------------------------------------------------------
	function weekHeadingonclick() {
		var cells = owner.cells;
		var sdates = owner.dates;
		var dateArray = new Array();
		owner.rows[tableRow] = !owner.rows[tableRow];
		for(var i=0;i<cells.length;i++) {
			if(cells[i].tableRow == tableRow && cells[i].date.canSelect && (!owner.selCurMonthOnly || cells[i].date.getMonth() == owner.displayMonth && cells[i].date.getFullYear() == owner.displayYear)) { //si la celda no coincide, con otras condiciones 
				dateArray[dateArray.length] = cells[i].date;
			}
		}
		owner.selectDates(dateArray,owner.rows[tableRow],true);
	}
	//-----------------------------------------------------------------------------
	var self = this;
	self.week = week;
	tableCell.setAttribute('class','wkhead');
	tableCell.setAttribute('className','wkhead'); //<iehack>
	addEventHandler(tableCell,'mouseup',weekHeadingonclick);
}
/*****************************************************************************/

// Objeto que contiene todos los datos y código relacionado con la celda del calendario
function CalCell(owner,tableCell,dateObj,row,week) {
	var self = this;
	//-----------------------------------------------------------------------------
	function calCellonclick() {
		if(self.date.canSelect) {
			if(owner.selectMultiple == true) { // Si podemos seleccionar varias celdas simultáneamente, añadimos la fecha actual a la fecha del array
				owner.selectDates(new Array(self.date),!self.date.selected,false);
				self.setClass(); //Actualizamos el estilo de la celda actual, no es necesario realizar un redibujado general
			}
			else { //si no podemos seleccionar varias fechas a la vez
				owner.selectDates(new Array(self.date),true,false,true);
				if(owner.mode == 'popup') { //actualizamos el valor del elemento y ocultamos el calendario si está en el modo adecuado
					owner.tgt.value = self.date.dateFormat(); //Usamos valores por defecto asignados a las fechas
					owner.tgt.dateObj = new Date(self.date); //Añadimos un objeto fecha al elemento por referencia
					owner.hide();
				}
				owner.reDraw(); //redibujamos todas las celdas del calendario
			}
		}
	}
	//-----------------------------------------------------------------------------

	// Replicamos la etiqueta :hover del CSS para navegadores incompatibles
	function calCellonmouseover() {
		if(self.date.canSelect) {
			tableCell.setAttribute('class',self.cellClass + ' hover');
			tableCell.setAttribute('className',self.cellClass + ' hover');
		}
	}
	//-----------------------------------------------------------------------------

	// Replicamos la etiqueta :hover del CSS para navegadores incompatibles
	function calCellonmouseout() {
		self.setClass();
	}
	//-----------------------------------------------------------------------------

	// Marcamos la clase CSS basados en el criterio corresondiente
	self.setClass = function ()
	{
		if(self.date.canSelect !== false) {
			if(self.date.selected) {
				self.cellClass = 'cell_selected';
			}
			else if(owner.displayMonth != self.date.getMonth() ) {
				self.cellClass = 'notmnth';
			}
			else if(self.date.type == 'holiday') {
				self.cellClass = 'hlday';
			}
			else if(self.dayOfWeek > 0 && self.dayOfWeek < 6) {
				self.cellClass = 'wkday';
			}
			else {
				self.cellClass = 'wkend';
			}
		}
		else {
			self.cellClass = 'noselect';
		}
		//Marcamos el día actual
		if(self.date.getUeDay() == owner.curDate.getUeDay()) {
			self.cellClass = self.cellClass + ' curdate';
		}
		tableCell.setAttribute('class',self.cellClass);
		tableCell.setAttribute('className',self.cellClass); //<iehack>
	};
	//-----------------------------------------------------------------------------

	// Activamos el hiperlink de la celda, si está declarado
	self.setURL = function(href,type) {
		if(href) {
			if(type == 'js') { //Creamos una celda vacía
				addEventHandler(self.tableCell,'mousedown',function(){window.location.href = href;});
			}
			else { //hacemos vinculable sólo el número de la celda
				var url = document.createElement('a');
				url.setAttribute('href',href);
				url.appendChild(document.createTextNode(self.date.getDate()));
				self.tableCell.replaceChild(url,self.tableCell.firstChild); // se asume que el primer hijo del nodo de la estructura DOM es de tipo texto
			}
		}
	};
	//-----------------------------------------------------------------------------

	// Activamos el título que aparece cuando un usuario se posiciona encima de una celda
	self.setTitle = function(titleStr) {
		if(titleStr && titleStr.length > 0) {
			self.title = titleStr;
			self.tableCell.setAttribute('title',titleStr);
		}
	};
	//-----------------------------------------------------------------------------

	// Activamos la celda html interna, usando una cadena que contiene el código HTML
	self.setHTML = function(html) {
		if(html && html.length > 0) {
			if(self.tableCell.childNodes[1]) {
				self.tableCell.childNodes[1].innerHTML = html;
			}
			else {
				var htmlCont = document.createElement('div');
				htmlCont.innerHTML = html;
				self.tableCell.appendChild(htmlCont);
			}
		}
	};
	//-----------------------------------------------------------------------------
	self.cellClass;			// la clase CSS para la celda
	self.tableRow = row;
	self.tableCell = tableCell;
	self.date = new Date(dateObj);
	self.date.canSelect = true; 
	self.date.type = 'normal';  
	self.date.selected = false;	
	self.date.cellHTML = '';
	self.dayOfWeek = self.date.getDay();
	self.week = week;
	//assign the event handlers for the table cell element
	addEventHandler(tableCell,'click', calCellonclick);
	addEventHandler(tableCell,'mouseover', calCellonmouseover);
	addEventHandler(tableCell,'mouseout', calCellonmouseout);
	self.setClass();
}
/*****************************************************************************/
Date.prototype.getDayOfYear = function () // devuelve el día y el año de su fecha
{
	return parseInt((this.getTime() - new Date(this.getFullYear(),0,1).getTime())/86400000 + 1);
};
//-----------------------------------------------------------------------------


// Devuelve el número de la semana de esta fecha
Date.prototype.getWeek = function (dowOffset) {
	dowOffset = typeof(dowOffset) == 'int' ? dowOffset : 0; //dowOffset a 0 por defecto
	var newYear = new Date(this.getFullYear(),0,1);
	var day = newYear.getDay() - dowOffset; //el día de la semana en el que empieza
	day = (day >= 0 ? day : day + 7);
	var weeknum, daynum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1;
	//si el año comienza después de la mitad de la semana
	if(day < 4) {
		weeknum = Math.floor((daynum+day-1)/7) + 1;
		if(weeknum > 52) {
			nYear = new Date(this.getFullYear() + 1,0,1);
			nday = nYear.getDay() - dowOffset;
			nday = nday >= 0 ? nday : nday + 7;
			weeknum = nday < 4 ? 1 : 53; //si el siguiente año comenza despues de la mitad de semana, es la semana #1 del año
		}
	}
	else {
		weeknum = Math.floor((daynum+day-1)/7);
	}
	return weeknum;
};
//-----------------------------------------------------------------------------
Date.prototype.getUeDay = function () //devuelve el numero de dias desde la época LINUX - buen ejemplo para comparar la porción de la fecha
{
	return parseInt(Math.floor((this.getTime() - this.getTimezoneOffset() * 60000)/86400000)); //debe tener encuenta la hora local
};
//-----------------------------------------------------------------------------
Date.prototype.dateFormat = function(format)
{
	if(!format) { // el formato de fecha por defecto a usar
		format = 'd/m/Y';
	}
	LZ = function(x) {return(x < 0 || x > 9 ? '' : '0') + x};
	var MONTH_NAMES = new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
	var DAY_NAMES = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=this.getFullYear().toString();
	var M=this.getMonth()+1;
	var d=this.getDate();
	var E=this.getDay();
	var H=this.getHours();
	var m=this.getMinutes();
	var s=this.getSeconds();
	value = {
		Y: y.toString(),
		y: y.substring(2),
		n: M,
		m: LZ(M),
		F: MONTH_NAMES[M-1],
		M: MONTH_NAMES[M+11],
		j: d,
		d: LZ(d),
		D: DAY_NAMES[E+7],
		l: DAY_NAMES[E],
		G: H,
		H: LZ(H)
	};
	if (H==0) {value['g']=12;}
	else if (H>12){value['g']=H-12;}
	else {value['g']=H;}
	value['h']=LZ(value['g']);
	if (H > 11) {value['a']='pm'; value['A'] = 'PM';}
	else { value['a']='am'; value['A'] = 'AM';}
	value['i']=LZ(m);
	value['s']=LZ(s);
	// contruimos la cadena resultante
	while (i_format < format.length) {
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
		}
		if (value[token] != null) { result=result + value[token]; }
		else { result=result + token; }
	}
	return result;
};
/*****************************************************************************/
//-----------------------------------------------------------------------------
function addEventHandler(element, type, func) { //desafortunado hack para la horrible estructuración del DOM desde IE
	if(element.addEventListener) {
		element.addEventListener(type,func,false);
	}
	else if (element.attachEvent) {
		element.attachEvent('on'+type,func);
	}
}
//-----------------------------------------------------------------------------
function removeEventHandler(element, type, func) { //desafortunado hack para la horrible estructuración del DOM desde IE
	if(element.removeEventListener) {
		element.removeEventListener(type,func,false);
	}
	else if (element.attachEvent) {
		element.detachEvent('on'+type,func);
	}
}
//-----------------------------------------------------------------------------
function getTop(element) { // posición absoluta de arriba, en píxeles
	var oNode = element;
	var iTop = 0;

	while(oNode.tagName != 'HTML') {
		iTop += oNode.offsetTop || 0;
		if(oNode.offsetParent) { //por ejemplo, el padre del elemento hijo
			oNode = oNode.offsetParent;
		}
		else {
			break;
		}
	}
	return iTop;
}
//-----------------------------------------------------------------------------
function getLeft(element) { //devuelve el valor absoluto del parámetro de la izquierda, en píxeles
	var oNode = element;
	var iLeft = 0;
	while(oNode.tagName != 'HTML') {
		iLeft += oNode.offsetLeft || 0;
		if(oNode.offsetParent) { //por ejemplo, el padre del elemento hijo
			oNode = oNode.offsetParent;
		}
		else {
			break;
		}
	}
	return iLeft;
}
//-----------------------------------------------------------------------------

/*****************************************************************************************/
/******************************FIN CALENDARIO JAVASCRIPT**********************************/
/*****************************************************************************************/
