
/*
 * NAME				: Calendar.js
 * GOAL				: Generar un calendario con posibilidad de selección múltiple o sencilla, IU basada en CSS.
 * AUTHOR			: Diego Andrés Hernández Lievano
 * THE LAST UPDATE	: 09/02/2006
 */

// CONSTRUCTOR
function Calendar (idCalendar, idContainer) {

	/* Asigna el nombre de la instancia. */
	this.name = idCalendar;

	/* Asigna la fecha inicial del calendario. */
	this.date = new Date();
	
	/* Asigna la fecha actual */
	this.selection = new Array ();

	this.currentYear = this.date.getUTCFullYear ();
	this.currentMonth = this.date.getUTCMonth ();
	this.currentDate = this.date.getUTCDate ();
	
	/* Almacena los textos de los labels de los días */
	this.days = new Array();

	/* Almacena los textos de los labels de los meses */
	this.months = new Array();
	
	/* Establece la fecha mínima que puede ser visualizada */
	this.min = new Date ();
	this.min.setUTCFullYear (0, 0, 1);
	
	/* Establece la fecha máxima que puede ser visualizada */
	this.max = new Date ();
	this.max.setUTCFullYear (9999, 0, 1);

	/* Establece el tipo de selección */
	this.multipleSelection = false;

	/* Almacena una función que actuará como evento */
	this.eventFunction = new Function ();

	/* Almacena una referencia al elemento contenedor */
	this.container = document.getElementById (idContainer);
	
	/* Escribe el código HTML de la tabla del calendario dentro del contenedor */
	this.container.innerHTML = HTMLGenerator (this);
	
	/* Renderiza el calendario */
	renderYear (this, 0);
}

function setDate(calendar, date) {

	/* Asigna la fecha inicial del calendario. */
/*
	calendar.date = new Date();
	calendar.date.setFullYear(2010,1,14);
*/
	calendar.date = date;
	
	calendar.currentYear = calendar.date.getUTCFullYear ();
	calendar.currentMonth = calendar.date.getUTCMonth ();
	calendar.currentDate = calendar.date.getUTCDate ();
	
	/* Renderiza el calendario */
	renderYear (calendar, 0);
}

// SET

/* Permite asignar una función como evento del calendario */
function setEventFunction (calendar, evt) {
	calendar.eventFunction = evt;	
}

/* Permite modificar los nombres de los días la semana */
function setDays (calendar, days) {
	var cDay = null;
	var i = 0;

	calendar.days = days.split(",");
	for (i = 0; i < calendar.days.length; i++) {
		cDay = document.getElementById ("cDay_" + i);
		cDay.innerHTML = calendar.days[i];
	}
}

/* Permite modificar los nombres de los meses del calendario */
function setMonths (calendar, months) {
	calendar.months = months.split(",");
	renderYear (calendar, 0);
}

/* Establece si el calendario permite múltiple selección */
function setMultipleSelection (calendar) {
	calendar.multipleSelection = !calendar.multipleSelection;
}

/* Permite establecer el rango de fecha que puede seleccionar el usuario */
function setRange (calendar, min, max) {
	calendar.min = min;
	calendar.max = max;
	renderYear (calendar, 0);
}

/* Cambia el estilo del botón de cerrar */
function setStateButton (buttonClose) {
	buttonClose.className = buttonClose.className == "buttonClose" ? "buttonCloseOver" : "buttonClose";	
}

/* Permite ocultar el contenedor */
function setVisible (calendar) {
	calendar.container.style["display"] = calendar.container.style["display"] == "none" ? "block" : "none";
}

// GET

/* 
 * Retorna el arreglo de fechas, ajustando el problema de incompatibilidad
 * entre el objeto Date de Javascript y el formato  * "xxxx/xx/xx".
 * Es decir, para el objeto Date los meses se enumeran desde cero (enero = 0, febrero = 1, ..., diciembre = 11)
 * pero al utilizar el constructor Date con una formato de fecha como "2006/0/1", no lo interpreta como 
 * 01 de Enero de 2006, sino como 01 de Diciembre de 2005.
 */
function getSelectionDate (calendar, position) {
	var i = 0;
	var date = new Array();
	var selection = null;

	if (position < 0) {
		selection = new Array();
		for (; i < calendar.selection.length; i++) {
			date = calendar.selection[i].split("-");
			date[1] = new Number (date[1]) + 1;
			selection.push(date[0] + "-" + date[1] + "-" + date[2]);
		}
	} else {
		date = calendar.selection[position].split("-");
		selection = date[0] + "-" + new Number(date[1]) + 1 + "-" + date[2]
	}
	
	return selection;
}

// MÉTODOS

/* Permite establecer las fechas seleccionadas */
function addSelectDate (calendar, date) {
	var add = false;
	
	if (!calendar.multipleSelection) {
		calendar.selection = new Array (date);
		add = true;
	} else if (searchDate(calendar, date) == -1) {
		calendar.selection.push(date);
		add = true;
	}
	
	if (add) {
		renderYear (calendar, 0);
	}
	
	return add;
}

/*
 * Genera el código HTML de la tabla que contiene los valores del
 * calendario.
 */
function HTMLGenerator (calendar) {
	var i = 0, j = 0, k = 0;
	var table = "";
	
	table = "<table class='calendarTable' id='calendar_" + calendar.name + "'>";
		
	/* Fila de Mes y Año */
	table += "<tr class='header'>";

	table += "<td class='yearButtonEnable' id='cHeaderBack'>";
	table += "<center onClick='renderYear(" + calendar.name + ", -1);'>&lsaquo;&lsaquo;</center>";
	table += "</td>";
	
	table += "<td class='yearButtonEnable'><center id='cHeaderYear'></center></td>";
	
	table += "<td class='yearButtonEnable' id='cHeaderNext'>";
	table += "<center onClick='renderYear(" + calendar.name + ", 1);'>&rsaquo;&rsaquo;</center>";
	table += "</td>";
	
	table += "<td colspan='3' class='yearButtonEnable'><center id='cHeaderMonth'></center></td>";

	table += "<td class='buttonClose' onClick='setVisible(" + calendar.name + ")'";
	table += " onMouseOver='setStateButton(this)' onMouseOut='setStateButton(this)'>";
	table += "<center>X</center></td>";
	
	table += "</tr>";
	
	/* Fila de días */
	table += "<tr class='header'>";
	for (i = 0; i < 7; i++) {
		table += "<td class='yearButtonEnable'><center id='cDay_" + i + "'";
		table += " onClick='selectColumn(" + calendar.name + "," + i + ")'>";
		table += "</center></td>";
	}
	table += "</tr>";
	
	/* Matriz de fechas */
	for (i = 0, k = 1; i < 6; i++) {
		table += "<tr>";
		for (j = 0; j < 7; j++, k++) {
			table += "<td class='" + ((i % 2) == 0 ? "rowPar" : "rowImpar") + "'>";
			table += "<center id='cDate_" + k + "' ";
//			table += "onClick='renderSelectedDate(" + calendar.name + ",\"cDate_" + k + "\")'></center></td>";
			table += "onClick='renderSelectedDate(" + calendar.name + ",this)'></center></td>";
		}
		table += "</tr>";
	}
	table += "</table>";
	return table;
}

/* Permite borrar una fecha del arreglo de fechas seleccionadas del calendario */
function quitSelectDate (calendar, date) {
	var i = searchDate(calendar, date);
	var quit = false;
	
	if (i != -1) {
		if (i == (calendar.selection.length - 1)) {
			calendar.selection.pop();	
		} else {
			for (; i < calendar.selection.length - 1; i++) {
				calendar.selection[i] = calendar.selection[i + 1];
			}
			calendar.selection.pop();
		}
		quit = true;
		renderYear (calendar, 0);
	}

	return quit;
}

/* Renderiza el año del calendario */
function renderYear (calendar, value) {
	var year = calendar.currentYear + value;

	if (year >= calendar.min.getUTCFullYear() && year <= calendar.max.getUTCFullYear()) {
		calendar.date.setUTCFullYear (year, calendar.currentMonth, calendar.currentDate);
		calendar.currentYear = calendar.date.getUTCFullYear();
		document.getElementById ("cHeaderYear").innerHTML = calendar.date.getUTCFullYear ();
		document.getElementById ("cHeaderBack").className = "yearButtonEnable";
		document.getElementById ("cHeaderNext").className = "yearButtonEnable";
	}
	
	if (year == calendar.min.getUTCFullYear() && year == calendar.max.getUTCFullYear()) {
		document.getElementById ("cHeaderBack").className = "yearButtonDisable";
		document.getElementById ("cHeaderNext").className = "yearButtonDisable";
	} else if (year == calendar.min.getUTCFullYear()) {
		calendar.date.setUTCFullYear (
				calendar.min.getUTCFullYear(),
				calendar.currentMonth < calendar.min.getUTCMonth() ? calendar.min.getUTCMonth() : calendar.currentMonth,
				calendar.currentDate < calendar.min.getUTCDate() ? calendar.min.getUTCDate() : calendar.currentDate
			);
		calendar.currentYear = calendar.date.getUTCFullYear ();
		calendar.currentMonth = calendar.date.getUTCMonth ();
		calendar.currentDate = calendar.date.getUTCDate ();
		document.getElementById ("cHeaderBack").className = "yearButtonDisable";
		document.getElementById ("cHeaderNext").className = "yearButtonEnable";
	} else if (year == calendar.max.getUTCFullYear()) {
		calendar.date.setUTCFullYear (
				calendar.max.getUTCFullYear(), 
				calendar.currentMonth > calendar.max.getUTCMonth() ? calendar.max.getUTCMonth() : calendar.currentMonth,
				calendar.currentDate > calendar.max.getUTCDate() ? calendar.max.getUTCDate() : calendar.currentDate
			);
		calendar.currentYear = calendar.date.getUTCFullYear ();
		calendar.currentMonth = calendar.date.getUTCMonth ();
		calendar.currentDate = calendar.date.getUTCDate () - 1;
		document.getElementById ("cHeaderBack").className = "yearButtonEnable";
		document.getElementById ("cHeaderNext").className = "yearButtonDisable";
	}
	
	renderMonth (calendar);
}

/* Renderiza los meses del calendario */
function renderMonth (calendar) {
	var i = 0;
	var select = "";
	var date = new Date ();
	var cMin = true, cMax = true;
	
	select = "<select class='selectMonths' id='cmbMonths' onChange='renderDates(" + calendar.name + ", this.options[this.selectedIndex].value)'>";
	for (i = 0; i < calendar.months.length; i++) {
		date.setUTCFullYear (calendar.currentYear, i, calendar.currentDate);
		
		cMin = date.getUTCFullYear() == calendar.min.getUTCFullYear() ? date.getUTCMonth() >= calendar.min.getUTCMonth() : true;
		cMax = date.getUTCFullYear() == calendar.max.getUTCFullYear() ? date.getUTCMonth() <= calendar.max.getUTCMonth() : true;
		
		if (cMin && cMax) {
			select += "<option value='" + i + "'";
			select += i == calendar.currentMonth ? " selected>" : ">";
			select += calendar.months[i] + "</option>";
		}
	}
	select += "</select>";
	
	document.getElementById ("cHeaderMonth").innerHTML = select;
	renderDates (calendar, calendar.currentMonth);
}

/* Renderiza las fechas del mes*/
function renderDates (calendar, month) {
	var cDate = null;
	var i = 1, days = 7;
	var cMin = true, cMax = true;

	do {
		cDate =	document.getElementById ("cDate_" + i);
		cDate.innerHTML = "";
		cDate.className = "noSelectedDate";
		i++;
	} while (document.getElementById ("cDate_" + i) != null);

	calendar.currentMonth = month;
	calendar.date.setUTCMonth (calendar.currentMonth);
	calendar.date.setUTCDate (1);
	days = calendar.date.getUTCDay ();
	while (calendar.date.getUTCDate () <= 31 && calendar.date.getUTCMonth () == calendar.currentMonth) {
		cDate = document.getElementById ("cDate_" + (calendar.date.getUTCDate () + days));
		
		calendar.date.setUTCHours (calendar.min.getUTCHours());
		calendar.date.setUTCMinutes (calendar.min.getUTCMinutes());
		calendar.date.setUTCSeconds (calendar.min.getUTCSeconds());
		calendar.date.setUTCMilliseconds (calendar.min.getUTCMilliseconds());
		
		if (calendar.date >= calendar.min && calendar.date <= calendar.max) {
			cDate.innerHTML = calendar.date.getUTCDate ();
			cDate.className = searchDate(
									 calendar, 
									 calendar.currentYear + "-" + calendar.currentMonth + "-" + cDate.innerHTML
								) != -1 ? "selectedDate" : "noSelectedDate";
		}
		calendar.date.setUTCDate (calendar.date.getUTCDate () + 1);
	}
}

/* Actualiza la fecha al ser seleccionada por el usuario */
function renderSelectedDate (calendar, td) {
	var date = calendar.currentYear + "-" + calendar.currentMonth + "-" + td.innerHTML;
	
	if (addSelectDate(calendar, date)) {
		calendar.date.setUTCDate (td.innerHTML);
		calendar.currentDate = td.innerHTML;
		calendar.eventFunction ();
	} else {
		quitSelectDate(calendar, date);
	}
}

/* Busca una fecha en el arreglo de elementos seleccionados */
function searchDate (calendar, date) {
	var i = 0, found = -1;
	
	if (calendar.multipleSelection) {
		for (; i < calendar.selection.length; i++) {
			if (date == calendar.selection[i]) {
				found = i;
				i = calendar.selection.length;
			}
		}
	} else {
		found = date == calendar.selection[0] ? 0 : -1;	
	}
	
	return found;
}

/* Permite seleccionar una columna completa */
function selectColumn (calendar, day) {
	if (calendar.multipleSelection) {
		var date = new Date(calendar.currentYear + "-" + (calendar.currentMonth + 1) + "-1");

		while (date.getUTCDay() != day) {
			date.setUTCDate(date.getUTCDate() + 1);	
		}

		while (date.getUTCMonth() == calendar.currentMonth) {
			if (!addSelectDate (calendar, calendar.currentYear + "-" + calendar.currentMonth + "-" + date.getUTCDate())) {
				quitSelectDate (calendar, calendar.currentYear + "-" + calendar.currentMonth + "-" + date.getUTCDate());
			}
			date.setUTCDate(date.getUTCDate() + 7);
		}
		
		renderDates (calendar, calendar.currentMonth);
	}
}
