// Variables globales
var aGmap;
var aGmap_zoom_min = 1;
var aGmap_zoom_max_view = 9;
var pointIcon;
var pointIconBold;
var pointIconUltraBold;
var locIcon;
var excIcon;
var tooltip;
var showSVC = false;
var geocoder;
var tabAccuracy = new Array(
    2,
    4,
    6,
    10,
    12,
    13,
    16,
    16,
    17,
    17
);

continents = {
    'w/0': {
	'lat': 46.9202,
	'lng': 10.8984,
	'zoom': 3
    },
    'w/1': {
	'lat': 50.2893,
	'lng': -99.1406,
	'zoom': 2
    },
    'w/2': {
	'lat': 20.5505,
	'lng': -75.3222,
	'zoom': 4
    },
    'w/3': {
	'lat': 2.4601,
	'lng': 15.8203,
	'zoom': 2
    },
    'w/4': {
	'lat': -27.8390,
	'lng': 145.0195,
	'zoom': 3
    },
    'w/5': {
	'lat': -18.3128,
	'lng': -70.3125,
	'zoom': 2
    },
    'w/6': {
	'lat': 1.4061,
	'lng': 73.3007,
	'zoom': 2
    },
    'w/7': {
	'lat': 32.9902,
	'lng': 33.0468,
	'zoom': 3
    },
    'w/8': {
	'lat': 8.9284,
	'lng': 110.0390,
	'zoom': 3
    },
    'w/9': {
	'lat': 33.7243,
	'lng': 97.0312,
	'zoom': 2
    },
    'w/10': {
	'lat': 42.5530,
	'lng': 14.0625,
	'zoom': 1
    },
    'w/11': {
	'lat': 39.5040,
	'lng': 43.1542,
	'zoom': 4
    }
}


// Chargement de la carte
googleLoad.include('aGmap');
google.load('maps', '2.184');//2.184

// Initialisation une fois la carte prête
google.setOnLoadCallback(googleInit);

// Initialisation
function aGmapInit()
{
    initIcons();

    // Création de la carte
    aGmap = new google.maps.Map2($('aGmap'), {
	draggableCursor: 'pointer'
    });

    geocoder = new GClientGeocoder();

    // Positionnement initial de la carte
    if ('es' == 'en') {
	var point = new google.maps.LatLng(20, 280);
    } else {
	var point = new google.maps.LatLng(20, 5);
    }
    aGmap.setCenter(point, aGmap_zoom_min);

    // Taille des controles à afficher
    if (aGmap_size == 'small' || aGmap_size == 'ultrasmall') {
	aGmap.addControl(new GSmallMapControl());
    } else {
	aGmap.addControl(new GLargeMapControl());
    }

    // Type de carte
    aGmap.setMapType(aGmap_type);

    // Permettre le changement de type de carte
    aGmap.addControl(new GMenuMapTypeControl());
    aGmap.addMapType(G_PHYSICAL_MAP);

    // Affichage de l'échelle en bas à droite de la carte
    aGmap.addControl(new GScaleControl(), new GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(5, 25)));

    // Préparation des tooltips
    tooltip = new Element('div', {
	'id' : 'tooltip',
	'style': 'position:relative;z-index:1;background-color:#FFFFFF;border:1px solid #000000;width:100px;font-size:10px;visibility:hidden;',
	'class': 'centrer'
    });
    tooltip.injectInside($('aGmap'));

    // Fonction à appeler en fonction du mode
    switch (aGmap_mode) {
	case 'search':
	    aGmapSearch();
	    break;
	case 'results':
	    aGmapResults();
	    break;
	case 'admin':
	    aGmapAdmin();
	    break;
	case 'view':
	    aGmapView();
	    break;
    }
}

function getLatLng() {
    var bounds = aGmap.getBounds();
    var southWest = bounds.getSouthWest();
    var northEast = bounds.getNorthEast();
    var lngSpan = northEast.lng() - southWest.lng();
    var latSpan = northEast.lat() - southWest.lat();
    var tabLatLng = new Array();
    tabLatLng.push(southWest.lat());
    tabLatLng.push(southWest.lat() + latSpan);
    tabLatLng.push(southWest.lng());
    tabLatLng.push(southWest.lng() + lngSpan);
    tabLatLng.push(aGmap.getZoom());

    /*if (tabLatLng[3] < tabLatLng[2]) {
	p('Longitude inversion !');
	var tmpLng = tabLatLng[2];
	tabLatLng[2] = tabLatLng[3];
	tabLatLng[3] = tmpLng;
    }*/

    return tabLatLng;
}

function getIcon(icon)
{
    switch (icon) {
	case 'pointIcon':
	    return pointIcon;
	    break;
	case 'pointIconBold':
	    return pointIconBold;
	    break;
	case 'pointIconUltraBold':
	    return pointIconUltraBold;
	    break;
	case 'excIcon':
	    return excIcon;
	    break;
	case 'locIcon':
	default:
	    return locIcon;
	    break;
    }
}

function initIcons()
{
    pointIcon = new GIcon(G_DEFAULT_ICON);
    pointIcon.image = baseUrl + '/images/gmap_light.png';
    pointIcon.iconSize = new GSize(3,3);
    pointIcon.shadowSize = new GSize(0,0);
    pointIcon.iconAnchor = new GPoint(0,0);
    
    pointIconBold = new GIcon(G_DEFAULT_ICON);
    pointIconBold.image = baseUrl + '/images/gmap_bold.png';
    pointIconBold.iconSize = new GSize(6,6);
    pointIconBold.shadowSize = new GSize(0,0);
    pointIconBold.iconAnchor = new GPoint(0,0);
    
    pointIconUltraBold = new GIcon(G_DEFAULT_ICON);
    pointIconUltraBold.image = baseUrl + '/images/gmap_ultrabold.png';
    pointIconUltraBold.iconSize = new GSize(8,8);
    pointIconUltraBold.shadowSize = new GSize(0,0);
    pointIconUltraBold.iconAnchor = new GPoint(0,0);
    
    locIcon = new GIcon(G_DEFAULT_ICON);
    locIcon.image = baseUrl + '/images/gmap_loc.png';
    locIcon.iconSize = new GSize(14,15);
    locIcon.shadowSize = new GSize(0,0);
    locIcon.iconAnchor = new GPoint(7,15);
    
    excIcon = new GIcon(G_DEFAULT_ICON);
    excIcon.image = baseUrl + '/images/gmap_loc.png';
    excIcon.iconSize = new GSize(14,15);
    excIcon.shadowSize = new GSize(0,0);
    excIcon.iconAnchor = new GPoint(7,15);
}

function posFromAddress(address, newMarker)
{
    if ($chk($('worldregion')) && $('worldregion').get('value') && $chk($('country')) && !$('country').get('value')) {
	posFromContinent();
    } else {
	address = address.replace(/\((.*)\)/g, '');
	if (aGmap_mode != 'admin' && $chk($('country'))) {
	    address += $('country').options[$('country').selectedIndex].text.replace(/\((.*)\)/g, '');
	}
	geocoder.getLocations(address, function(retdata)
	{
	    if (retdata.Status.code == 200) {
		aGmap.doMove = false;
		var place = retdata.Placemark[0];
		aGmap.setCenter(new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]), tabAccuracy[place.AddressDetails.Accuracy]);
		aGmap.doMove = true;
		if (newMarker) {
		    setMarker();
		}
	    } else {
		divAlert("No pudimos localizar su dirección. Puede desplazar el cursor 'casa' manualmente sobre el mapa");
	    }
	});
    }
}

function posFromContinent()
{
    if ($chk($('worldregion')) && $('worldregion').get('value')) {
	var continent = $('worldregion').get('value');
	aGmap.setCenter(new GLatLng(continents[continent].lat, continents[continent].lng));
	aGmap.setZoom(continents[continent].zoom);
    }
}

function showTooltip(marker, nb) {
    nb = (nb <= 100) ? nb : '100+';
    tooltip.innerHTML = nb + ' anuncios';
    var point = aGmap.getCurrentMapType().getProjection().fromLatLngToPixel(aGmap.getBounds().getSouthWest(), aGmap.getZoom());
    var offset = aGmap.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(), aGmap.getZoom());
    var anchor = marker.getIcon().iconAnchor;
    var width = marker.getIcon().iconSize.width;
    var pos = new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(offset.x - point.x - anchor.x + width,- offset.y + point.y +anchor.y + 5));
    pos.apply(tooltip);
    tooltip.setStyle('visibility', 'visible');
}

function infoBulle(gmark, marker_infos) {
    GEvent.addListener(gmark, 'click', function() {
        $clear(timer);
	requestBulle(gmark, marker_infos);
    });
    GEvent.addListener(gmark, 'mouseover', function() {
        $clear(timer);
	var delayedFunction = function() {requestBulle(gmark, marker_infos)};
	timer = delayedFunction.delay(1000);
    });
    GEvent.addListener(gmark, 'mouseout', function() {
	$clear(timer);
    });
}

function requestBulle(gmark, marker_infos) {
    coordsBeforeInfoWindow = aGmap.getBounds().getCenter();
    var jSonRequest = new Request.JSON({
	url: baseUrl + '/ajax/get_aGmap_infoBulle.php',
	onComplete: function(retdata) {
	    iW++;
	    aGmap.doMove = false;
	    gmark.openInfoWindowHtml(retdata);
	}
    }).post({'rep': marker_infos.rep, 'id': marker_infos.id});
    if (!gmark.iW) {
	gmark.iW = true;
	GEvent.addListener(gmark, 'infowindowclose', function() {
	    iW--;
	    aGmap.setCenter(coordsBeforeInfoWindow);
	    if (!iW) {
		aGmap.doMove = true;
	    }
	});
    }
}
// Variables globales
var timer;
var coordsBeforeInfoWindow;
var iW = 0;
var queries = 0;
var changedFields = new Array();

//Initialisation du mode recherche
function aGmapSearch()
{
    // On ajoute les évènements zoom et déplacement
    aGmap.doMove = true;
    GEvent.addListener(aGmap, 'moveend', function(oldZoom, newZoom) {
	if (aGmap.doMove) {
	    showSVC = true;
	    aGmapUpdateResults();
	}
    });

    // Et on lance la mise à jour de la carte
    if ($chk(mod)) {
	aGmapUpdateResults(null, 1);
    } else {
	aGmapUpdateResults(null, null, null, 1);
    }
}

// Ajoute une liste de marqueurs à la carte et la positionne au plus près
function aGmapDisplayMarkers(tab, centerBounds)
{
    var markers = tab.markers;
    var icon;
    var gmark;

    var latLngMinMax = getLatLng();
    var latMin = latLngMinMax[0];
    var latMax = latLngMinMax[1];
    var lngMin = latLngMinMax[2];
    var lngMax = latLngMinMax[3];
    var zoom = (centerBounds && tab.phpZoom) ? tab.phpZoom : latLngMinMax[4];

    aGmap.clearOverlays();

    markers.each(function(marker)
    {
	icon = getIcon(marker.icon);
	var point = new GLatLng(marker.lat, marker.lng);
	var gmark = new GMarker(point, { icon: icon });
	if (zoom > aGmap_zoom_min) {
	    infoBulle(gmark, marker);
	} else {
	    GEvent.addListener(gmark, 'mouseover', function() {
		showTooltip(gmark, marker.nb);
	    });        
	    GEvent.addListener(gmark, 'mouseout', function() {
		tooltip.setStyle('visibility', 'hidden');
	    });
	}
	gmark.iW = false;
	aGmap.addOverlay(gmark);
	if (centerBounds) {
	    aGmap.bounds.extend(point);
	}
    });

    if (centerBounds) {
	aGmap.doMove = false;
	aGmap.setCenter(aGmap.bounds.getCenter());
	//var newZoom = aGmap.getBoundsZoomLevel(aGmap.bounds);
	var newZoom = zoom;
	(newZoom >= aGmap_zoom_min) ? aGmap.setZoom(newZoom) : aGmap.setZoom(aGmap_zoom_min);
	aGmap.doMove = true;
    }
}

function aGmapUpdateResults(changed, centerBounds, address, cache)
{
    changedFields.combine([
	$('fdatedep'),
	$('fdatearr'),
	$('envsea_dist'),
	$('envmountain_dist'),
	$('envcity_dist')
    ]);

    if (changed) {
	var toInclude = false;
	switch (changed.get('tag')) {
	    case 'input':
		switch (changed.type) {
		    case 'checkbox':
			if (changed.checked) {
			    toInclude = true;
			}
			break;
		    case 'text':
		    case 'hidden':
			if (changed.value && changed.value != defaultText) {
			    toInclude = true;
			}
			break;
		    case 'radio':
			if (changed.checked) {
			    toInclude = true;
			}
			break;
		}
		break;
	    case 'select':
		if (changed.value || changed.getAttribute('multiple') != null) {
		    toInclude = true;
		}
		break;
	}

	if (changed.getParent().get('id') == '5levels_selector') {
	    toInclude = false;
	}

	if (toInclude && changed.get('name') != 'ski_resort') {
	    changedFields.include(changed);
	} else if (toInclude && changed.get('name') == 'ski_resort') {
	    return false;
	} else {
	    changedFields.erase(changed);
	}
    }

    var tempForm = new Element('form');
    changedFields.each(function(el)
    {
	if (el) {
	    if (el.hasAttribute && el.hasAttribute("multiple")) {
		var cloneEl = el.clone(true);
		var values = new Array();
		cloneEl.getChildren().each(function(subel) { 
		    values.push(subel.value);
		});
		var elHTML = '';
		values.each(function(val) {
		    elHTML += '<option value="' + val + '" selected="selected">';
		});
		$empty(cloneEl);
		cloneEl.set('html', elHTML);
		tempForm.adopt(cloneEl.clone(true));
	    } else {
		tempForm.adopt(el.clone(true));
		if ((tempForm.getChildren('input[type=checkbox]').length) && (Browser.Engine.trident4)) {
		    if (el.checked == true) {
			var elclone = tempForm.getChildren('input[type=checkbox]').getLast();
			elclone.set('checked', true);
		    }
		}
	    }
	}
    });

    if ($chk($('5levels_selector'))) {
	tempForm.adopt($('5levels_selector').clone(true));
    }

    if ($chk($('places-selection'))) {
	tempForm.adopt($('places-selection').clone(true));
    }

    var tabLatLng = getLatLng();
    var mapLatLng = new Element('input', {
	'type': 'hidden',
	'id': 'mapLatLng',
	'name': 'mapLatLng',
	'value' : tabLatLng[0] + ',' + tabLatLng[1] + ',' + tabLatLng[2] + ',' + tabLatLng[3] + ',' + aGmap.getZoom()
    }).injectInside(tempForm);

    if (!queries && $chk($('aGmap_loader'))) {
	$('aGmap_loader').loader = new Loader($('aGmap_loader'));
    }
    queries++;
    if (centerBounds) {
	aGmap.bounds = null;
	aGmap.bounds = new GLatLngBounds();
	var url = baseUrl + '/ajax/get_aGmap_markers.php?cb=1&rep=' + aGmap_rep + '&s=' + aGmap_size;
    } else {
	var url = baseUrl + '/ajax/get_aGmap_markers.php?rep=' + aGmap_rep;
    }
    if (cache) {
	url += '&cache=1';
    }
    url += '&l=es';

    if($chk($('searchFormResults')) && showSVC){
	if ($('searchFormResults').getStyle('display') == 'none') {
	    $('searchFormResults').setStyle('display', 'block');
	}
    }

    tempForm.set('send', {
	url: url,
	method: 'post',
	onComplete: function(retdata)
	{
	    queries--;
	    if (!queries && $chk($('aGmap_loader'))) {
		$('aGmap_loader').loader.destroy();
		$('aGmap_loader').loader = null;
	    }
	    var tab = JSON.decode(retdata);
	    if (tab && tab.nbResults > 0) {
		aGmapDisplayMarkers(tab, centerBounds);
	    } else {
		aGmap.clearOverlays();
		if (address) {
		    posFromAddress(address);
		} else {
		    //aGmap.doMove = false;
		    //aGmap.setZoom(aGmap_zoom_min);
		    aGmap.doMove = true;
		}
	    }
	    displayResults(tab);
	}
    });
    
    tempForm.send();

    tempForm.dispose();
    mapLatLng.dispose();
}


function displayResults(tab)
{
    var tabLatLng = getLatLng();
    var more = (loadedModule == 'biblio' || loadedModule == 'shareresults') ? "Haga zoom en el mapa para ver más resultados." : "Agrande en el mapa para ver más propiedades";
    if (aGmap_size == 'small') {
	$('aGmap_nbResults').set('text', limitResults(tab.nbResults, 'short'));
	 if (tabLatLng[4] <= 1) {
	    $('svc').set('html', more);
	} else if (tab.nbMarkersAll && tab.nbMarkers < tab.nbMarkersAll) {
	    $('svc').set('html', more);
	} else {
	    $('svc').set('html', '');
	}
    } else {
	$('aGmap_nbResults').set('text', limitResults(tab.nbResults, 'long'));

	$('latMin').set('value', '');
	$('latMax').set('value', '');
	$('lngMin').set('value', '');
	$('lngMax').set('value', '');

	if ($chk($('SVCMap'))) {
	    if (tabLatLng[4] <= 1) {
		$('SVCMap').set('html', more);
	    } else if (tab.nbMarkersAll) {
		var nb = tab.nbMarkersAll;
		if (nb == 1) {
		    if (loadedModule == 'biblio' || loadedModule == 'shareresults') {
			$('SVCMap').set('html', "En el perímetro del mapa:" + nb + " resultado encontrado.");
		    } else {
			$('SVCMap').set('html', "En el perímetro del mapa:" + nb + " anuncio encontrado.");
		    }
		} else {
		    if (loadedModule == 'biblio' || loadedModule == 'shareresults') {
			$('SVCMap').set('html', "En el perímetro del mapa:" + limitResults(nb) + " resultados encontrados.");
		    } else {
			$('SVCMap').set('html', "En el perímetro del mapa:" + limitResults(nb) + " anuncios encontrados.");
		    }
		}
		$('SVCMap').set('html', $('SVCMap').get('html') + '<br /><input type="submit" value="Ver" name="submit" class="gmapSubmit" />');
		if (tab.nbMarkers < tab.nbMarkersAll) {
		    if (loadedModule == 'biblio' || loadedModule == 'shareresults') {
			$('SVCMap').set('html', $('SVCMap').get('html') + "<br /><br /><span class=\"tips\">Haga zoom para ver más resultados.</span>");
		    } else {
			$('SVCMap').set('html', $('SVCMap').get('html') + "<br /><br /><span class=\"tips\">Todas no se indican, agrandar para ver más.</span>");
		    }
		}
		$('latMin').set('value', tabLatLng[0]);
		$('latMax').set('value', tabLatLng[1]);
		$('lngMin').set('value', tabLatLng[2]);
		$('lngMax').set('value', tabLatLng[3]);
	    } else {
		$('SVCMap').set('html', "Ningún resultado en el perímetro del mapa.");
	    }
	} else if (aGmap_size == 'large') {
	    $('latMin').set('value', tabLatLng[0]);
	    $('latMax').set('value', tabLatLng[1]);
	    $('lngMin').set('value', tabLatLng[2]);
	    $('lngMax').set('value', tabLatLng[3]);
	} else {
		if ((tab.nbMarkers && tab.nbMarkers < tab.nbMarkersAll)) {
		    $('svc').set('text', more);
		} else {
		    $('svc').set('html', "&nbsp;");
		}
	}
	$$('[class=gmapSubmit]').removeEvents('click');
	$$('[class=gmapSubmit]').addEvent('click', function (event) {
	    this.inject($('searchform'));
	    this.setStyle('display', 'none');
	});
    }
}

var offset = 1;
var navigation = 1;
var tweak = {x: 0, y: 0};
var cal;
var date = new Date();
var day = date.getDate();
var month = date.getMonth() + 1;
var year = date.getFullYear();
var classes = new Array('alternate');

window.addEvent('domready', function() {
    cal = new Calendar({ 
	fdatedep: { fdatedep: 'Y-m-d'}, 
	fdatearr: { fdatearr: 'Y-m-d'}
    }, {
	offset: offset,
	days: days,
	months: months,
	direction: 0,
	navigation: navigation,
	tweak: tweak,
	classes: classes,
	draggable: false
    });
    cal.addEvent('onHideComplete', function(el) {
	var date;
	if ($chk($('aGmap'))) {
	    if (!showSVC) {
		showSVC = true;
	    }
	    aGmapUpdateResults(null, true);
	}
	if ($chk($('sdatedep')) && $('fdatedep').get('value')) {
	    date = formatDateMysqlToUser($('fdatedep').get('value'), 'dateshort');
	    $('sdatedep').set('html', date);
	}
	if ($chk($('sdatearr')) && $('fdatearr').get('value')) {
	    date = formatDateMysqlToUser($('fdatearr').get('value'), 'dateshort');
	    $('sdatearr').set('html', date);
	}
    });
});
// Calendar: a Javascript class for Mootools that adds accessible and unobtrusive date pickers to your form elements <http://electricprism.com/aeron/calendar>
// Calendar RC4, Copyright (c) 2007 Aeron Glemann <http://electricprism.com/aeron>, MIT Style License.
// Mootools 1.2 compatibility by Davorin Å ego

var Calendar = new Class({	

Implements: [Options, Events],

	options: {
		blocked: [], // blocked dates 
		classes: [], // ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite']
		days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // days of the week starting at sunday
		direction: 0, // -1 past, 0 past + future, 1 future
		draggable: true,
		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
		navigation: 1, // 0 = no nav; 1 = single nav for month; 2 = dual nav for month and year
		offset: 0, // first day of the week: 0 = sunday, 1 = monday, etc..
		onHideStart: Class.empty,
		onHideComplete: Class.empty,
		onShowStart: Class.empty,
		onShowComplete: Class.empty,
		pad: 1, // padding between multiple calendars
		tweak: {x: 0, y: 0} // tweak calendar positioning
	},

	// initialize: calendar constructor
	// @param obj (obj) a js object containing the form elements and format strings { id: 'format', id: 'format' etc }
	// @param props (obj) optional properties

	initialize: function(obj, options) {
		// basic error checking
		if (!obj) { return false; }

		this.setOptions(options);

		// create our classes array
		var keys = ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite'];

		var values = keys.map(function(key, i) {
			if (this.options.classes[i]) {
				if (this.options.classes[i].length) { key = this.options.classes[i]; }
			}
			return key;
		}, this);

		this.classes = values.associate(keys);

		// create cal element with css styles required for proper cal functioning
		this.calendar = new Element('div', { 
			'styles': { left: '-1000px', opacity: 0, position: 'absolute', top: '-1000px', zIndex: 1000 }
		}).addClass(this.classes.calendar).injectInside(document.body);

		// iex 6 needs a transparent iframe underneath the calendar in order to not allow select elements to render through
		if (window.ie6) {
			this.iframe = new Element('iframe', { 
				'styles': { left: '-1000px', position: 'absolute', top: '-1000px', zIndex: 999 }
			}).injectInside(document.body);
			this.iframe.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
		}

		// initialize fade method
		this.fx = new Fx.Tween(this.calendar, {
			onStart: function() { 
				if (this.calendar.getStyle('opacity') == 0) { // show
					if (window.ie6) { this.iframe.setStyle('display', 'block'); }
					this.calendar.setStyle('display', 'block');
					this.fireEvent('onShowStart', this.element);
				}
				else { // hide
					this.fireEvent('onHideStart', this.element);
				}
			}.bind(this),
			onComplete: function() { 
				if (this.calendar.getStyle('opacity') == 0) { // hidden
					this.calendar.setStyle('display', 'none');
					if (window.ie6) { this.iframe.setStyle('display', 'none'); }
					this.fireEvent('onHideComplete', this.element);
				}
				else { // shown
					this.fireEvent('onShowComplete', this.element);
				}
			}.bind(this)
		});

		// initialize drag method
		if (window.Drag && this.options.draggable) {
			this.drag = new Drag.Move(this.calendar, { 
				onDrag: function() {
					if (window.ie6) { this.iframe.setStyles({ left: this.calendar.style.left, top: this.calendar.style.top }); } 
				}.bind(this) 
			}); 
		}
		
		// create calendars array
		this.calendars = [];

		var id = 0;
		var d = new Date(); // today

		d.setDate(d.getDate() + this.options.direction.toInt()); // correct today for directional offset

		for (var i in obj) {
			var cal = { 
				button: new Element('button', { 'type': 'button' }),
				el: $(i),
				els: [],
				id: id++,
				month: d.getMonth(),
				visible: false,
				year: d.getFullYear()
			};

			// fix for bad element (naughty, naughty element!)
			if (!this.element(i, obj[i], cal)) { continue; }
			
			cal.el.addClass(this.classes.calendar);

			// create cal button
			cal.button.addClass(this.classes.calendar).addEvent('click', function(cal) { this.toggle(cal); }.pass(cal, this)).injectAfter(cal.el);

			// read in default value
			cal.val = this.read(cal);

			$extend(cal, this.bounds(cal)); // abs bounds of calendar

			$extend(cal, this.values(cal)); // valid days, months, years

			this.rebuild(cal);

			this.calendars.push(cal); // add to cals array		
		}	
	},


	// blocked: returns an array of blocked days for the month / year
	// @param cal (obj)
	// @returns blocked days (array)

	blocked: function(cal) {
		var blocked = [];
		var offset = new Date(cal.year, cal.month, 1).getDay(); // day of the week (offset)
		var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month
		
		this.options.blocked.each(function(date){
			var values = date.split(' ');
			
			// preparation
			for (var i = 0; i <= 3; i++){ 
				if (!values[i]){ values[i] = (i == 3) ? '' : '*'; } // make sure blocked date contains values for at least d, m and y
				values[i] = values[i].contains(',') ? values[i].split(',') : new Array(values[i]); // split multiple values
				var count = values[i].length - 1;
				for (var j = count; j >= 0; j--){
					if (values[i][j].contains('-')){ // a range
						var val = values[i][j].split('-');
						for (var k = val[0]; k <= val[1]; k++){
							if (!values[i].contains(k)){ values[i].push(k + ''); }
						}
						values[i].splice(j, 1);
					}
				}
			}

			// execution
			if (values[2].contains(cal.year + '') || values[2].contains('*')){
				if (values[1].contains(cal.month + 1 + '') || values[1].contains('*')){
					values[0].each(function(val){ // if blocked value indicates this month / year
						if (val > 0){ blocked.push(val.toInt()); } // add date to blocked array
					});

					if (values[3]){ // optional value for day of week
						for (var i = 0; i < last; i++){
								var day = (i + offset) % 7;
	
								if (values[3].contains(day + '')){ 
									blocked.push(i + 1); // add every date that corresponds to the blocked day of the week to the blocked array
								}
						}
					}
				}
			}
		}, this);

		return blocked;
	},


	// bounds: returns the start / end bounds of the calendar
	// @param cal (obj)
	// @returns obj	

	bounds: function(cal) {
		// 1. first we assume the calendar has no bounds (or a thousand years in either direction)
		
		// by default the calendar will accept a millennium in either direction
		var start = new Date(1000, 0, 1); // jan 1, 1000
		var end = new Date(2999, 11, 31); // dec 31, 2999

		// 2. but if the cal is one directional we adjust accordingly
		var date = new Date().getDate() + this.options.direction.toInt();

		if (this.options.direction > 0) {
			start = new Date();
			start.setDate(date + this.options.pad * cal.id);
		}
		
		if (this.options.direction < 0) {
			end = new Date();
			end.setDate(date - this.options.pad * (this.calendars.length - cal.id - 1));
		}

		// 3. then we can further filter the limits by using the pre-existing values in the selects
		cal.els.each(function(el) {	
			if (el.get('tag') == 'select') {		
				if (el.format.test('(y|Y)')) { // search for a year select
					var years = [];

					el.getChildren().each(function(option) { // get options
						var values = this.unformat(option.value, el.format);
	
						if (!years.contains(values[0])) { years.push(values[0]); } // add to years array
					}, this);
	
					years.sort(this.sort);
			
					if (years[0] > start.getFullYear()) { 
						d = new Date(years[0], start.getMonth() + 1, 0); // last day of new month
					
						if (start.getDate() > d.getDate()) { start.setDate(d.getDate()); }
	
						start.setYear(years[0]); 
					}
					
					if (years.getLast() < end.getFullYear()) { 
						d = new Date(years.getLast(), end.getMonth() + 1, 0); // last day of new month
					
						if (end.getDate() > d.getDate()) { end.setDate(d.getDate()); }
	
						end.setYear(years.getLast());
					}		
				}
	
				if (el.format.test('(F|m|M|n)')) { // search for a month select
					var months_start = [];
					var months_end = [];

					el.getChildren().each(function(option) { // get options
						var values = this.unformat(option.value, el.format);
	
						if ($type(values[0]) != 'number' || values[0] == years[0]) { // if it's a year / month combo for curr year, or simply a month select
							if (!months_start.contains(values[1])) { months_start.push(values[1]); } // add to months array
						}
	
						if ($type(values[0]) != 'number' || values[0] == years.getLast()) { // if it's a year / month combo for curr year, or simply a month select
							if (!months_end.contains(values[1])) { months_end.push(values[1]); } // add to months array
						}
					}, this);
	
					months_start.sort(this.sort);
					months_end.sort(this.sort);
					
					if (months_start[0] > start.getMonth()) { 
						d = new Date(start.getFullYear(), months_start[0] + 1, 0); // last day of new month
					
						if (start.getDate() > d.getDate()) { start.setDate(d.getDate()); }
	
						start.setMonth(months_start[0]); 
					}
					
					if (months_end.getLast() < end.getMonth()) { 
						d = new Date(start.getFullYear(), months_end.getLast() + 1, 0); // last day of new month
					
						if (end.getDate() > d.getDate()) { end.setDate(d.getDate()); }
	
						end.setMonth(months_end.getLast());
					}		
				}
			}
		}, this);
		
		return { 'start': start, 'end': end };
	},


	// caption: returns the caption element with header and navigation
	// @param cal (obj)
	// @returns caption (element)

	caption: function(cal) {
		// start by assuming navigation is allowed
		var navigation = {
			prev: { 'month': true, 'year': true },
			next: { 'month': true, 'year': true }
		};
		
		// if we're in an out of bounds year
		if (cal.year == cal.start.getFullYear()) { 
			navigation.prev.year = false; 
			if (cal.month == cal.start.getMonth() && this.options.navigation == 1) { 
				navigation.prev.month = false;
			}		
		}		
		if (cal.year == cal.end.getFullYear()) { 
			navigation.next.year = false; 
			if (cal.month == cal.end.getMonth() && this.options.navigation == 1) { 
				navigation.next.month = false;
			}
		}

		// special case of improved navigation but months array with only 1 month we can disable all month navigation
		if ($type(cal.months) == 'array') {
			if (cal.months.length == 1 && this.options.navigation == 2) {
				navigation.prev.month = navigation.next.month = false;
			}
		}

		var caption = new Element('caption');

		var prev = new Element('a').addClass(this.classes.prev).appendText('\x3c'); // <		
		var next = new Element('a').addClass(this.classes.next).appendText('\x3e'); // >

		if (this.options.navigation == 2) {
			var month = new Element('span').addClass(this.classes.month).injectInside(caption);
			
			if (navigation.prev.month) { prev.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', -1); }.pass(cal, this)).injectInside(month); }
			
			month.adopt(new Element('span').appendText(this.options.months[cal.month]));

			if (navigation.next.month) { next.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', 1); }.pass(cal, this)).injectInside(month); }

			var year = new Element('span').addClass(this.classes.year).injectInside(caption);

			if (navigation.prev.year) { prev.clone().addEvent('click', function(cal) { this.navigate(cal, 'y', -1); }.pass(cal, this)).injectInside(year); }
			
			year.adopt(new Element('span').appendText(cal.year));

			if (navigation.next.year) { next.clone().addEvent('click', function(cal) { this.navigate(cal, 'y', 1); }.pass(cal, this)).injectInside(year); }
		}
		else { // 1 or 0
			if (navigation.prev.month && this.options.navigation) { prev.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', -1); }.pass(cal, this)).injectInside(caption); }

			caption.adopt(new Element('span').addClass(this.classes.month).appendText(this.options.months[cal.month]));
			
			caption.adopt(new Element('span').addClass(this.classes.year).appendText(cal.year));
			
			if (navigation.next.month && this.options.navigation) { next.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', 1); }.pass(cal, this)).injectInside(caption); }

		}

		return caption;
	},


	// changed: run when a select value is changed
	// @param cal (obj)

	changed: function(cal) {
		cal.val = this.read(cal); // update calendar val from inputs	

		$extend(cal, this.values(cal)); // update bounds - based on curr month

		this.rebuild(cal); // rebuild days select

		if (!cal.val) { return; } // in case the same date was clicked the cal has no set date we should exit		
		if (cal.val.getDate() < cal.days[0]) { cal.val.setDate(cal.days[0]); }
		if (cal.val.getDate() > cal.days.getLast()) { cal.val.setDate(cal.days.getLast()); }
		
		cal.els.each(function(el) {	// then we can set the value to the field
			el.value = this.format(cal.val, el.format); 		
		}, this);
		
		this.check(cal); // checks other cals

		this.calendars.each(function(kal) { // update cal graphic if visible
			if (kal.visible) { this.display(kal); }
		}, this);
	},


	// check: checks other calendars to make sure no overlapping values
	// @param cal (obj)

	check: function(cal) {
		this.calendars.each(function(kal, i) {
			if (kal.val) { // if calendar has value set
				var change = false;
			
				if (i < cal.id) { // preceding calendar
					var bound = new Date(Date.parse(cal.val));
					
					bound.setDate(bound.getDate() - (this.options.pad * (cal.id - i)));

					if (bound < kal.val) { change = true; }
				}
				if (i > cal.id) { // following calendar
					var bound = new Date(Date.parse(cal.val));
					
					bound.setDate(bound.getDate() + (this.options.pad * (i - cal.id)));
					
					if (bound > kal.val) { change = true; }
				}

				if (change) {
					if (kal.start > bound) { bound = kal.start; }
					if (kal.end < bound) { bound = kal.end; }

					kal.month = bound.getMonth();
					kal.year = bound.getFullYear();		

					$extend(kal, this.values(kal));			

					// TODO - IN THE CASE OF SELECT MOVE TO NEAREST VALID VALUE
					// IN THE CASE OF INPUT DISABLE

					// if new date is not valid better unset cal value
					// otherwise it would mean incrementally checking to find the nearest valid date which could be months / years away
					kal.val = kal.days.contains(bound.getDate()) ? bound : null;

					this.write(kal);

					if (kal.visible) { this.display(kal); } // update cal graphic if visible
				}
			}
			else {
				kal.month = cal.month;
				kal.year = cal.year;
			}
		}, this);
	},
	

	// clicked: run when a valid day is clicked in the calendar
	// @param cal (obj)

	clicked: function(td, day, cal) {
		cal.val = (this.value(cal) == day) ? null : new Date(cal.year, cal.month, day); // set new value - if same then disable

		this.write(cal); 

		// ok - in the special case that it's all selects and there's always a date no matter what (at least as far as the form is concerned)
		// we can't let the calendar undo a date selection - it's just not possible!!
		if (!cal.val) { cal.val = this.read(cal); }

		if (cal.val) {
			this.check(cal); // checks other cals						
			this.toggle(cal); // hide cal
		} 
		else { // remove active class and replace with valid
			td.addClass(this.classes.valid);
			td.removeClass(this.classes.active);
		}
	},
	

	// display: create calendar element
	// @param cal (obj)

	display: function(cal) {
		// 1. header and navigation
		this.calendar.empty(); // init div

		this.calendar.className = this.classes.calendar + ' ' + this.options.months[cal.month].toLowerCase();

		var div = new Element('div').injectInside(this.calendar); // a wrapper div to help correct browser css problems with the caption element

		var table = new Element('table').injectInside(div).adopt(this.caption(cal));
				
		// 2. day names		
		var thead = new Element('thead').injectInside(table);

		var tr = new Element('tr').injectInside(thead);
		
		for (var i = 0; i <= 6; i++) {
			var th = this.options.days[(i + this.options.offset) % 7];
			
			tr.adopt(new Element('th', { 'title': th }).appendText(th.substr(0, 1)));
		}

		// 3. day numbers
		var tbody = new Element('tbody').injectInside(table);
		var tr = new Element('tr').injectInside(tbody);

		var d = new Date(cal.year, cal.month, 1);
		var offset = ((d.getDay() - this.options.offset) + 7) % 7; // day of the week (offset)
		var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month
		var prev = new Date(cal.year, cal.month, 0).getDate(); // last day of previous month
		var active = this.value(cal); // active date (if set and within curr month)
		var valid = cal.days; // valid days for curr month
		var inactive = []; // active dates set by other calendars
		var hilited = [];
		this.calendars.each(function(kal, i) {
			if (kal != cal && kal.val) {
				if (cal.year == kal.val.getFullYear() && cal.month == kal.val.getMonth()) { inactive.push(kal.val.getDate()); }

				if (cal.val) {
					for (var day = 1; day <= last; day++) {
						d.setDate(day);
						
						if ((i < cal.id && d > kal.val && d < cal.val) || (i > cal.id && d > cal.val && d < kal.val)) { 
							if (!hilited.contains(day)) { hilited.push(day); }
						}
					}
				}
			}
		}, this);
		var d = new Date();
		var today = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); // today obv 
		
		for (var i = 1; i < 43; i++) { // 1 to 42 (6 x 7 or 6 weeks)
			if ((i - 1) % 7 == 0) { tr = new Element('tr').injectInside(tbody); } // each week is it's own table row

			var td = new Element('td').injectInside(tr);
						
			var day = i - offset;
			var date = new Date(cal.year, cal.month, day);
			
			var cls = '';
			
			if (day === active) { cls = this.classes.active; } // active
			else if (inactive.contains(day)) { cls = this.classes.inactive; } // inactive
			else if (valid.contains(day)) { cls = this.classes.valid; } // valid
			else if (day >= 1 && day <= last) { cls = this.classes.invalid; } // invalid

			if (date.getTime() == today) { cls = cls + ' ' + this.classes.today; } // adds class for today

			if (hilited.contains(day)) { cls = cls + ' ' + this.classes.hilite; } // adds class if hilited

			td.addClass(cls);

			if (valid.contains(day)) { // if it's a valid - clickable - day we add interaction
				td.setProperty('title', this.format(date, 'D M jS Y'));
				
				td.addEvents({
					'click': function(td, day, cal) { 
						this.clicked(td, day, cal); 
					}.pass([td, day, cal], this),
					'mouseover': function(td, cls) { 
						td.addClass(cls); 
					}.pass([td, this.classes.hover]),
					'mouseout': function(td, cls) { 
						td.removeClass(cls); 
					}.pass([td, this.classes.hover])
				});
			}

			// pad calendar with last days of prev month and first days of next month
			if (day < 1) { day = prev + day; }
			else if (day > last) { day = day - last; }

			td.appendText(day);
		}
	},


	// element: helper function
	// @param el (string) element id
	// @param f (string) format string
	// @param cal (obj)

	element: function(el, f, cal) {
		if ($type(f) == 'object') { // in the case of multiple inputs per calendar
			for (var i in f) { 
				if (!this.element(i, f[i], cal)) { return false; }		
			}
			
			return true;
		}

		el = $(el);

		if (!el) { return false; }
		
		el.format = f;
		
		if (el.get('tag') == 'select') { // select elements allow the user to manually set the date via select option
			el.addEvent('change', function(cal) { this.changed(cal); }.pass(cal, this));
		}
		else { // input (type text) elements restrict the user to only setting the date via the calendar
			el.readOnly = true;
			el.addEvent('focus', function(cal) { this.toggle(cal); }.pass(cal, this));
		}

		cal.els.push(el);

		return true;
	},


	// format: formats a date object according to passed in instructions
	// @param date (obj)
	// @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y
	// @returns string

	format: function(date, format) {
		var str = '';
		
		if (date) {
			var j = date.getDate(); // 1 - 31
      var w = date.getDay(); // 0 - 6
			var l = this.options.days[w]; // Sunday - Saturday
			var n = date.getMonth() + 1; // 1 - 12
			var f = this.options.months[n - 1]; // January - December
			var y = date.getFullYear() + ''; // 19xx - 20xx
			
			for (var i = 0, len = format.length; i < len; i++) {
				var cha = format.charAt(i); // format char
				
				switch(cha) {
					// year cases
					case 'y': // xx - xx
						y = y.substr(2);
					case 'Y': // 19xx - 20xx
						str += y;
						break;
	
					// month cases
					case 'm': // 01 - 12
						if (n < 10) { n = '0' + n; }
					case 'n': // 1 - 12
						str += n;
						break;
	
					case 'M': // Jan - Dec
						f = f.substr(0, 3);
					case 'F': // January - December
						str += f;
						break;
	
					// day cases
					case 'd': // 01 - 31
						if (j < 10) { j = '0' + j; }
					case 'j': // 1 - 31
						str += j;
						break;
	
					case 'D': // Sun - Sat
						l = l.substr(0, 3);
					case 'l': // Sunday - Saturday
						str += l;
						break;
	
					case 'N': // 1 - 7
						w += 1;
					case 'w': // 0 - 6
						str += w;
						break;

					case 'S': // st, nd, rd or th (works well with j)
						if (j % 10 == 1 && j != '11') { str += 'st'; }
						else if (j % 10 == 2 && j != '12') { str += 'nd'; }
						else if (j % 10 == 3 && j != '13') { str += 'rd'; }
						else { str += 'th'; }
						break;
	
					default:
						str += cha;
				}
			}
		}

	  return str; //  return format with values replaced
	},


	// navigate: calendar navigation
	// @param cal (obj)
	// @param type (str) m or y for month or year
	// @param n (int) + or - for next or prev

	navigate: function(cal, type, n) {
		switch (type) {
			case 'm': // month
					if ($type(cal.months) == 'array') {
						var i = cal.months.indexOf(cal.month) + n; // index of current month
						
						if (i < 0 || i == cal.months.length) { // out of range
							if (this.options.navigation == 1) { // if type 1 nav we'll need to increment the year
								this.navigate(cal, 'y', n);		
							}
		
							i = (i < 0) ? cal.months.length - 1 : 0;
						}

						cal.month = cal.months[i];
					}
					else { 
						var i = cal.month + n;
		
						if (i < 0 || i == 12) {
							if (this.options.navigation == 1) {
								this.navigate(cal, 'y', n);	
							}
		
							i = (i < 0) ? 11 : 0;
						}
						
						cal.month = i;
					}		
					break;

				case 'y': // year
					if ($type(cal.years) == 'array') {
						var i = cal.years.indexOf(cal.year) + n;

						cal.year = cal.years[i]; 
					}
					else { 
						cal.year += n;
					}						
					break;		
		}

		$extend(cal, this.values(cal));

		if ($type(cal.months) == 'array') { // if the calendar has a months select
			var i = cal.months.indexOf(cal.month); // and make sure the curr months exists for the new year

			if (i < 0) { cal.month = cal.months[0]; } // otherwise we'll reset the month
		}


		this.display(cal);
	},


	// read: compiles cal value based on array of inputs passed in
	// @param cal (obj)
	// @returns date (obj) or (null)

	read: function(cal) {
		var arr = [null, null, null];

		cal.els.each(function(el) {
			// returns an array which may contain empty values
			var values = this.unformat(el.value, el.format);
			
			values.each(function(val, i) { 
				if ($type(val) == 'number') { arr[i] = val; }
			}); 
		}, this);

		// we can update the cals month and year values
		if ($type(arr[0]) == 'number') { cal.year = arr[0]; }
		if ($type(arr[1]) == 'number') { cal.month = arr[1]; }

		var val = null;

		if (arr.every(function(i) { return $type(i) == 'number'; })) { // if valid date
			var last = new Date(arr[0], arr[1] + 1, 0).getDate(); // last day of month

			if (arr[2] > last) { arr[2] = last; } // make sure we stay within the month (ex in case default day of select is 31 and month is feb)
			
			val = new Date(arr[0], arr[1], arr[2]);
		}

		return (cal.val == val) ? null : val; // if new date matches old return null (same date clicked twice = disable)
	},

	
	// rebuild: rebuilds days + months selects
	// @param cal (obj)

	rebuild: function(cal) {

		//alert(':)');
		cal.els.each(function(el) {			
			/*
			if (el.get('tag') == 'select' && el.format.test('^(F|m|M|n)$')) { // special case for months-only select
				if (!cal.options) { cal.options = el.clone(); } // clone a copy of months select
			
				var val = (cal.val) ? cal.val.getMonth() : el.value.toInt();

				el.empty(); // initialize select

				cal.months.each(function(month) {
					// create an option element
					var option = new Element('option', {
						'selected': (val == month),
						'value': this.format(new Date(1, month, 1), el.format);
					}).appendText(day).injectInside(el);
				}, this);
			}
			*/

			if (el.get('tag') == 'select' && el.format.test('^(d|j)$')) { // special case for days-only select
				var d = this.value(cal);

				if (!d) { d = el.value.toInt(); } // if the calendar doesn't have a set value, try to use value from select

				el.empty(); // initialize select

				cal.days.each(function(day) {
					// create an option element
					var option = new Element('option', {
						'selected': (d == day),
						'value': ((el.format == 'd' && day < 10) ? '0' + day : day)
					}).appendText(day).injectInside(el);
				}, this);
			}
		}, this);
		//alert(':(');
	},


	// sort: helper function for numerical sorting

	sort: function(a, b) {
		return a - b;
	},


	// toggle: show / hide calendar 
	// @param cal (obj)

	toggle: function(cal) {
		document.removeEvent('mousedown', this.fn); // always remove the current mousedown script first
			
		if (cal.visible) { // simply hide curr cal						
			cal.visible = false;
			cal.button.removeClass(this.classes.active); // active
			
			this.fx.start('opacity', 1, 0);
		}
		else { // otherwise show (may have to hide others)
			// hide cal on out-of-bounds click
			this.fn = function(e, cal) { 
				var e = new Event(e);
			
				var el = e.target;

				var stop = false;
				
				while (el != document.body && el.nodeType == 1) {
					if (el == this.calendar) { stop = true; }
					this.calendars.each(function(kal) {
						if (kal.button == el || kal.els.contains(el)) { stop = true; }
					});

					if (stop) { 
						e.stop();
						return false;
					}
					else { el = el.parentNode; }
				}
				
				this.toggle(cal);
			}.create({ 'arguments': cal, 'bind': this, 'event': true });				

			document.addEvent('mousedown', this.fn);

			this.calendars.each(function(kal) {
				if (kal == cal) {
					kal.visible = true;
					kal.button.addClass(this.classes.active); // css c-icon-active
				}
				else {
					kal.visible = false;
					kal.button.removeClass(this.classes.active); // css c-icon-active
				}
			}, this);
			
			var size = window.getScrollSize();
			
			var coord = cal.button.getCoordinates();

			var x = coord.right + this.options.tweak.x;
			var y = coord.top + this.options.tweak.y;

			// make sure the calendar doesn't open off screen
			if (!this.calendar.coord) { this.calendar.coord = this.calendar.getCoordinates(); }

			if (x + this.calendar.coord.width > size.x) { x -= (x + this.calendar.coord.width - size.x); }
			if (y + this.calendar.coord.height > size.y) { y -= (y + this.calendar.coord.height - size.y); }
			
			this.calendar.setStyles({ left: x + 'px', top: y + 'px' });

			if (window.ie6) { 
				this.iframe.setStyles({ height: this.calendar.coord.height + 'px', left: x + 'px', top: y + 'px', width: this.calendar.coord.width + 'px' }); 
			}

			this.display(cal);
			
			this.fx.start('opacity', 0, 1);
		}
	},


	// unformat: takes a value from an input and parses the d, m and y elements
	// @param val (string)
	// @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y
	// @returns array
	
	unformat: function(val, f) {
		f = f.escapeRegExp();
		
		var re = {
			d: '([0-9]{2})',
			j: '([0-9]{1,2})',
			D: '(' + this.options.days.map(function(day) { return day.substr(0, 3); }).join('|') + ')',					
			l: '(' + this.options.days.join('|') + ')',
			S: '(st|nd|rd|th)',
			F: '(' + this.options.months.join('|') + ')',
			m: '([0-9]{2})',
			M: '(' + this.options.months.map(function(month) { return month.substr(0, 3); }).join('|') + ')',					
			n: '([0-9]{1,2})',
			Y: '([0-9]{4})',
			y: '([0-9]{2})'
		}

		var arr = []; // array of indexes

		var g = '';

		// convert our format string to regexp
		for (var i = 0; i < f.length; i++) {
			var c = f.charAt(i);
			
			if (re[c]) {
				arr.push(c);

				g += re[c];
			}
			else {
				g += c;
			}
		}

		// match against date
		var matches = val.match('^' + g + '$');
		
		var dates = new Array(3);

		if (matches) {
			matches = matches.slice(1); // remove first match which is the date

			arr.each(function(c, i) {
				i = matches[i];
				
				switch(c) {
					// year cases
					case 'y':
						i = '19' + i; // 2 digit year assumes 19th century (same as JS)
					case 'Y':
						dates[0] = i.toInt();
						break;

					// month cases
					case 'F':
						i = i.substr(0, 3);
					case 'M':
						i = this.options.months.map(function(month) { return month.substr(0, 3); }).indexOf(i) + 1;
					case 'm':
					case 'n':
						dates[1] = i.toInt() - 1;
						break;

					// day cases
					case 'd':
					case 'j':
						dates[2] = i.toInt();
						break;
				}
			}, this);
		}

		return dates;
	},


	// value: returns day value of calendar if set
	// @param cal (obj)
	// @returns day (int) or null

	value: function(cal) {
		var day = null;

		if (cal.val) {
			if (cal.year == cal.val.getFullYear() && cal.month == cal.val.getMonth()) { day = cal.val.getDate(); }
		}

		return day;
	},
	

	// values: returns the years, months (for curr year) and days (for curr month and year) for the calendar
	// @param cal (obj)
	// @returns obj	

	values: function(cal) {
		var years, months, days;

		cal.els.each(function(el) {	
			if (el.get('tag') == 'select') {		
				if (el.format.test('(y|Y)')) { // search for a year select
					years = [];

					el.getChildren().each(function(option) { // get options
						var values = this.unformat(option.value, el.format);
	
						if (!years.contains(values[0])) { years.push(values[0]); } // add to years array
					}, this);
	
					years.sort(this.sort);
				}
	
				if (el.format.test('(F|m|M|n)')) { // search for a month select
					months = []; // 0 - 11 should be

					el.getChildren().each(function(option) { // get options
						var values = this.unformat(option.value, el.format);
	
						if ($type(values[0]) != 'number' || values[0] == cal.year) { // if it's a year / month combo for curr year, or simply a month select
							if (!months.contains(values[1])) { months.push(values[1]); } // add to months array
						}
					}, this);
	
					months.sort(this.sort);
				}
				
				if (el.format.test('(d|j)') && !el.format.test('^(d|j)$')) { // search for a day select, but NOT a days only select
					days = []; // 1 - 31
					
					el.getChildren().each(function(option) { // get options
						var values = this.unformat(option.value, el.format);

						// in the special case of days we dont want the value if its a days only select
						// otherwise that will screw up the options rebuilding
						// we will take the values if they are exact dates though
						if (values[0] == cal.year && values[1] == cal.month) {
							if (!days.contains(values[2])) { days.push(values[2]); } // add to days array
						}
					}, this);
				}
			}
		}, this);
		
		// we start with what would be the first and last days were there no restrictions
		var first = 1;
		var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of the month
		
		// if we're in an out of bounds year
		if (cal.year == cal.start.getFullYear()) {
			// in the special case of improved navigation but no months array, we'll need to construct one
			if (months == null && this.options.navigation == 2) {
				months = [];
				
				for (var i = 0; i < 12; i ++) { 
					if (i >= cal.start.getMonth()) { months.push(i); } 
				}
			}
			
			// if we're in an out of bounds month
			if (cal.month == cal.start.getMonth()) { 
				first = cal.start.getDate(); // first day equals day of bound
			}
		}		
		if (cal.year == cal.end.getFullYear()) {
			// in the special case of improved navigation but no months array, we'll need to construct one
			if (months == null && this.options.navigation == 2) {
				months = [];
				
				for (var i = 0; i < 12; i ++) { 
					if (i <= cal.end.getMonth()) { months.push(i); } 
				}
			}

			if (cal.month == cal.end.getMonth()) { 
				last = cal.end.getDate(); // last day equals day of bound
			}
		}

		// let's get our invalid days
		var blocked = this.blocked(cal);

		// finally we can prepare all the valid days in a neat little array
		if ($type(days) == 'array') { // somewhere there was a days select
			days = days.filter(function(day) {
				if (day >= first && day <= last && !blocked.contains(day)) { return day; }
			});
		}
		else { // no days select we'll need to construct a valid days array
			days = [];
			
			for (var i = first; i <= last; i++) { 
				if (!blocked.contains(i)) { days.push(i); }
			}
		}		

		days.sort(this.sort); // sorting our days will give us first and last of month

		return { 'days': days, 'months': months, 'years': years };
	},


	// write: sets calendars value to form elements
	// @param cal (obj)

	write: function(cal) {
		this.rebuild(cal);	 // in the case of options, we'll need to make sure we have the correct number of days available
		
		cal.els.each(function(el) {	// then we can set the value to the field
			el.value = this.format(cal.val, el.format); 		
		}, this);
	}
});

//Calendar.implement(new Events, new Options);
/* Pushup
 * Copyright (c) 2008 Nick Stakenburg (www.nickstakenburg.com)
 *
 * License: MIT-style license.
 * Website: http://www.pushuptheweb.com
 *
 */

var Pushup = {
  Version: '1.0.3',
  options: {
    appearDelay: .5,
    fadeDelay: 6,
    images: '/images-pushup/',
    message: "An important update is available for your browser",
    reminder: {
      hours: 6,
      message: "Remind me in #{hours}"
    },
    skip: false
  },
  updateLinks: {
    IE: 'http://www.microsoft.com/windows/downloads/ie/',
    Firefox: 'http://www.getfirefox.com',
    Safari: 'http://www.apple.com/safari/download/',
    Opera: 'http://www.opera.com/download/'
  },
  Browser: {
    IE: !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Firefox: navigator.userAgent.indexOf('Firefox') > -1,
    Safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 &&
      /Apple/.test(navigator.vendor),
    Opera: navigator.userAgent.indexOf('Opera') > -1
  }
};

Pushup.conditions = {
  IE: (function(agent) {
    var version = /MSIE ([\d.]+)/.exec(agent);
    return version && parseFloat(version[1]) < 7;
  })(navigator.userAgent),
  Firefox: Pushup.Browser.Firefox &&
    parseFloat(navigator.userAgent.match(/Firefox[\/\s](\d+)/)[1]) < 3,
  Safari: Pushup.Browser.Safari &&
    parseFloat(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1]) < 500,
  Opera: Pushup.Browser.Opera && (!window.opera.version ||
    parseFloat(window.opera.version()) < 9.5)
};

(function() {
// find current browser and check if it needs an update
for (var browser in Pushup.Browser)
  if (Pushup.Browser[browser]) Pushup._browserUsed = browser;
Pushup._updateBrowser = Pushup.conditions[Pushup._browserUsed] &&
  Pushup._browserUsed;

// stop if no update is required and we want to skip build
if (!Pushup._updateBrowser && Pushup.options.skip) return;

function Extend(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
}

Extend(Pushup, {
  start: function() {
    // get the image directory
    if (/^(https?:\/\/|\/)/.test(this.options.images))
      this.images = this.options.images;
    else {
      var srcMatch = /pushup(?:-[\w\d.]+)?\.js(.*)/,
       scripts = document.getElementsByTagName('script');
      for (var i = 0, l = scripts.length; i < l; i++) {
        var s = scripts[i];
        if (s.src && s.src.match(srcMatch))
          this.images = s.src.replace(srcMatch, '') + this.options.images;
      }
    }
    if (Pushup._updateBrowser) this.show();
  },

  build: function() {
    this.pushup = document.createElement('div');
    Opacity.set(this.pushup, 0);
    this.pushup.id = 'pushup';

    this.messageLink = this.pushup.appendChild(document.createElement('a'));
    this.messageLink.className = 'pushup_messageLink';
    this.messageLink.target = '_blank';

    this.messageLink.appendChild(this.icon = document.createElement('div'));
    this.icon.className = 'pushup_icon';

    this.messageLink.appendChild(this.message = document.createElement('span'));
    this.message.className = 'pushup_message';
    this.message.innerHTML = this.options.message;

    // reminder message if cookies are enabled
    var hours = this.options.reminder.hours;
    if (hours && Pushup.cookiesEnabled) {
      this.pushup.appendChild(this.reminder = document.createElement('a'));
      this.reminder.href = '#';
      this.reminder.className = 'pushup_reminder';
      this.pushup.className = 'withReminder';
      //var H = hours + " hora" + (hours > 1 ? 's' : ''),
      var H = hours + " " + ((hours == 1) ? "hora" : "horas");
       message = this.options.reminder.message.replace('#{hours}', H);
      this.reminder.innerHTML = message;
    }

    // Older Opera doesn't handle float correctly
    if (Pushup.Browser.Opera &&
       (!window.opera.version || parseFloat(window.opera.version()) < 9.25)) {
      this.messageLink.style.cssFloat = 'none';
      this.reminder.style.cssFloat = 'none';
    }

    Pushup.setBrowser(Pushup._updateBrowser);
    document.body.appendChild(this.pushup);
    Pushup.addEvents();
  },

  addEvents: function() {
    if (this.reminder) {
      Event.add(this.reminder, 'click', function(event) {
        Event.stop(event);
        Pushup.setReminder(Pushup.options.reminder.hours);
        Pushup.fade();
      });
    }
    Event.add(this.pushup, 'mouseover', Pushup.clearFade);
    Event.add(this.pushup, 'mouseout', function() {
      Pushup.fade({ delay: Pushup.options.fadeDelay })
    });
  },

  setBrowser: function(browser) {
    browser = browser || 'IE';
    setPngBackground(this.icon, this.images + browser.toLowerCase() + '.png');
    this.messageLink.href = this.updateLinks[browser];
  },

  show: function() {
    // default to IE if no browser was detected
    var browser = typeof arguments[0] == 'string' ?
      arguments[0] : Pushup._browserUsed || 'IE',
     options = arguments[browser ? 1 : 0] || {};

    if (options.resetReminder) Pushup.resetReminder();

    // show if not blocked by cookie
    if (!options.ignoreReminder && Pushup.cookiesEnabled &&
      Cookie.get('_pushupBlocked')) return;

    if (!Pushup.pushup) Pushup.build();
    Opacity.set(Pushup.pushup, 0);
    Pushup.pushup.style.display = 'block';
    if (browser) Pushup.setBrowser(browser);
    this.appear({ fadeAfter: true, delay: Pushup.options.appearDelay });
  },

  appear: function(delay) {
    Pushup.clearFade();
    var options = arguments[0] || {};
    return window.setTimeout(function() {
      Appear(Pushup.pushup, { afterFinish: function() {
        if (options.fadeAfter)
          Pushup.fade({ delay: Pushup.options.fadeDelay });
      }});
    }, (options.delay || 0.01) * 1000);
  },

  clearFade: function() {
    if (Pushup._fadeTimer) {
      window.clearTimeout(Pushup._fadeTimer);
      Pushup._fadeTimer = null;
    }
  },

  fade: function() {
    var options = arguments[0] || {};
    Pushup._fadeTimer = window.setTimeout(function() {
      Fade(Pushup.pushup);
    }, (options.delay || 0.01) * 1000);
  },

  setReminder: function(hours) {
    Cookie.set('_pushupBlocked', 'blocked', { duration: 1 / 24 * hours })
  },

  resetReminder: function() { Cookie.remove('_pushupBlocked') }
});

// Opacity adapted from the Prototype JavaScript framework
// http://www.prototypejs.org
var Opacity = {
  set: function(element, value) {
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
  },

  get:  function(element) {
    var opacity = element.style.opacity;
    return opacity ? parseFloat(opacity) : 1.0;
  }
};

if (Pushup.Browser.IE) {
  Opacity.get = function(element) {
    var opacity = element.style.opacity;
    if (!opacity && element.currentStyle) opacity = element.currentStyle[opacity];

    if (opacity = (element.style.filter || '').match(/alpha\(opacity=(.*)\)/))
      if (opacity[1]) return parseFloat(opacity[1]) / 100;
    return 1.0;
  };

  Opacity.set = function(element, value) {
    function stripAlpha(filter) {
      return filter.replace(/alpha\([^\)]*\)/gi,'')
    }
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;
    var filter = element.style.filter,
     style = element.style;
    if (value == 1 || value === '') (filter = stripAlpha(filter)) ?
      style.filter = filter : style.filter = '';
    else style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
  };
}

function Appear(element) {
  var current = Opacity.get(element),
   options = arguments[1] || {};
  if (element.style.display != 'block')
    element.style.display = 'block';
  if (current < 1) {
    setTimeout(function() {
      Opacity.set(element, current += 0.05);
      Appear(element, options);
    }, 0.01);
  }
  else {
    if (Pushup.Browser.IE && element.style.filter)
      element.style.removeAttribute('filter');
    if (options.afterFinish) options.afterFinish.call();
  }
}

function Fade(element) {
  var current = Opacity.get(element),
   options = arguments[1] || {};
  if (current > 0) {
    setTimeout(function() {
      Opacity.set(element, current -= 0.05);
      Fade(element, options);
    }, 0.01);
  }
  else {
    element.style.display = 'none';
    if (options.afterFinish) options.afterFinish.call();
  }
}

function setPngBackground(element, url) {
  var options = Extend({
    align: 'top left',
    repeat: 'no-repeat',
    sizingMethod: 'crop',
    backgroundColor: ''
  }, arguments[2] || {});

  Extend(element.style, arguments.callee.IEBelow7 ? {
    filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' +
      url + '\'\', sizingMethod=\'' + options.sizingMethod + '\')'
  } : {
    background: options.backgroundColor + ' url(' + url + ') ' +
      options.align + ' ' + options.repeat
  });
}
setPngBackground.IEBelow7 = Pushup.Browser.IE &&
  parseFloat(/MSIE ([\d.]+)/.exec(navigator.userAgent)[1]) < 7;

// Based on the work of Peter-Paul Koch - http://www.quirksmode.org
var Cookie = {
  set: function(name, value) {
    var expires = '', options = arguments[2] || {};
    if (options.duration) {
      var date = new Date();
      date.setTime(date.getTime() + options.duration * 1000 * 60 * 60 * 24);
      value += '; expires=' + date.toGMTString();
    }
    document.cookie = name + "=" + value + expires + "; path=/";
  },

  remove: function(name) { this.set(name, '', -1) },

  get: function(name) {
    var cookies = document.cookie.split(';'), nameEQ = name + "=";
    for (var i = 0, l = cookies.length; i < l; i++) {
      var c = cookies[i];
      while (c.charAt(0) == ' ')
        c = c.substring(1,c.length);
      if (c.indexOf(nameEQ) == 0)
        return c.substring(nameEQ.length, c.length);
    }
    return null;
  }
};

// check if cookies are enabled
Pushup.cookiesEnabled = (function(test) {
  if (Cookie.get(test)) return true;
  Cookie.set(test, 'test', { duration: 15 });
  return Cookie.get(test);
})('_pushupCookiesEnabled');

var Event = {
  add: function(obj, type, fn) {
    if (obj.attachEvent) {
      obj['e' + type + fn] = fn;
      obj[type + fn] = function(){ obj['e' + type +fn](window.event) };
      obj.attachEvent('on' + type, obj[type + fn]);
    }
    else obj.addEventListener(type, fn, false);
  },

  stop: function(event) {
    if (Pushup.Browser.IE) {
      event.cancelBubble = true;
      event.returnValue = false;
    }
    else {
      event.preventDefault();
      event.stopPropagation();
    }
  }
};

Event.add(window, 'load', function() { Pushup.start() });
})();
