// 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.
/*
var Calendar=new Class({options:{blocked:[],classes:[],days:["Domingo","Lunes","Martes","Miercoles","Jueves","Viernes","Sabado"],direction:0,draggable:true,months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],navigation:1,offset:0,onHideStart:Class.empty,onHideComplete:Class.empty,onShowStart:Class.empty,onShowComplete:Class.empty,pad:1,tweak:{x:0,y:0}},initialize:function(F,B){if(!F){return false}this.setOptions(B);var D=["calendar","prev","next","month","year","today","invalid","valid","inactive","active","hover","hilite"];var A=D.map(function(J,I){if(this.options.classes[I]){if(this.options.classes[I].length){J=this.options.classes[I]}}return J},this);this.classes=A.associate(D);this.calendar=new Element("div",{styles:{left:"-1000px",opacity:0,position:"absolute",top:"-1000px",zIndex:1000}}).addClass(this.classes.calendar).injectInside(document.body);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)"}this.fx=this.calendar.effect("opacity",{onStart:function(){if(this.calendar.getStyle("opacity")==0){if(window.ie6){this.iframe.setStyle("display","block")}this.calendar.setStyle("display","block");this.fireEvent("onShowStart",this.element)}else{this.fireEvent("onHideStart",this.element)}}.bind(this),onComplete:function(){if(this.calendar.getStyle("opacity")==0){this.calendar.setStyle("display","none");if(window.ie6){this.iframe.setStyle("display","none")}this.fireEvent("onHideComplete",this.element)}else{this.fireEvent("onShowComplete",this.element)}}.bind(this)});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)})}this.calendars=[];var H=0;var G=new Date();G.setDate(G.getDate()+this.options.direction.toInt());for(var C in F){var E={button:new Element("button",{type:"button"}),el:$(C),els:[],id:H++,month:G.getMonth(),visible:false,year:G.getFullYear()};if(!this.element(C,F[C],E)){continue}E.el.addClass(this.classes.calendar);E.button.addClass(this.classes.calendar).addEvent("click",function(I){this.toggle(I)}.pass(E,this)).injectAfter(E.el);E.val=this.read(E);$extend(E,this.bounds(E));$extend(E,this.values(E));this.rebuild(E);this.calendars.push(E)}},blocked:function(C){var A=[];var D=new Date(C.year,C.month,1).getDay();var B=new Date(C.year,C.month+1,0).getDate();this.options.blocked.each(function(I){var G=I.split(" ");for(var J=0;J<=3;J++){if(!G[J]){G[J]=(J==3)?"":"*"}G[J]=G[J].contains(",")?G[J].split(","):new Array(G[J]);var K=G[J].length-1;for(var H=K;H>=0;H--){if(G[J][H].contains("-")){var L=G[J][H].split("-");for(var F=L[0];F<=L[1];F++){if(!G[J].contains(F)){G[J].push(F+"")}}G[J].splice(H,1)}}}if(G[2].contains(C.year+"")||G[2].contains("*")){if(G[1].contains(C.month+1+"")||G[1].contains("*")){G[0].each(function(M){if(M>0){A.push(M.toInt())}});if(G[3]){for(var J=0;J<B;J++){var E=(J+D)%7;if(G[3].contains(E+"")){A.push(J+1)}}}}}},this);return A},bounds:function(C){var D=new Date(1000,0,1);var A=new Date(2999,11,31);var B=new Date().getDate()+this.options.direction.toInt();if(this.options.direction>0){D=new Date();D.setDate(B+this.options.pad*C.id)}if(this.options.direction<0){A=new Date();A.setDate(B-this.options.pad*(this.calendars.length-C.id-1))}C.els.each(function(F){if(F.getTag()=="select"){if(F.format.test("(y|Y)")){var E=[];F.getChildren().each(function(J){var I=this.unformat(J.value,F.format);if(!E.contains(I[0])){E.push(I[0])}},this);E.sort(this.sort);if(E[0]>D.getFullYear()){d=new Date(E[0],D.getMonth()+1,0);if(D.getDate()>d.getDate()){D.setDate(d.getDate())}D.setYear(E[0])}if(E.getLast()<A.getFullYear()){d=new Date(E.getLast(),A.getMonth()+1,0);if(A.getDate()>d.getDate()){A.setDate(d.getDate())}A.setYear(E.getLast())}}if(F.format.test("(F|m|M|n)")){var G=[];var H=[];F.getChildren().each(function(J){var I=this.unformat(J.value,F.format);if($type(I[0])!="number"||I[0]==E[0]){if(!G.contains(I[1])){G.push(I[1])}}if($type(I[0])!="number"||I[0]==E.getLast()){if(!H.contains(I[1])){H.push(I[1])}}},this);G.sort(this.sort);H.sort(this.sort);if(G[0]>D.getMonth()){d=new Date(D.getFullYear(),G[0]+1,0);if(D.getDate()>d.getDate()){D.setDate(d.getDate())}D.setMonth(G[0])}if(H.getLast()<A.getMonth()){d=new Date(D.getFullYear(),H.getLast()+1,0);if(A.getDate()>d.getDate()){A.setDate(d.getDate())}A.setMonth(H.getLast())}}}},this);return{start:D,end:A}},caption:function(G){var A={prev:{month:true,year:true},next:{month:true,year:true}};if(G.year==G.start.getFullYear()){A.prev.year=false;if(G.month==G.start.getMonth()&&this.options.navigation==1){A.prev.month=false}}if(G.year==G.end.getFullYear()){A.next.year=false;if(G.month==G.end.getMonth()&&this.options.navigation==1){A.next.month=false}}if($type(G.months)=="array"){if(G.months.length==1&&this.options.navigation==2){A.prev.month=A.next.month=false}}var B=new Element("caption");var E=new Element("a").addClass(this.classes.prev).appendText("\x3c");var D=new Element("a").addClass(this.classes.next).appendText("\x3e");if(this.options.navigation==2){var F=new Element("span").addClass(this.classes.month).injectInside(B);if(A.prev.month){E.clone().addEvent("click",function(H){this.navigate(H,"m",-1)}.pass(G,this)).injectInside(F)}F.adopt(new Element("span").appendText(this.options.months[G.month]));if(A.next.month){D.clone().addEvent("click",function(H){this.navigate(H,"m",1)}.pass(G,this)).injectInside(F)}var C=new Element("span").addClass(this.classes.year).injectInside(B);if(A.prev.year){E.clone().addEvent("click",function(H){this.navigate(H,"y",-1)}.pass(G,this)).injectInside(C)}C.adopt(new Element("span").appendText(G.year));if(A.next.year){D.clone().addEvent("click",function(H){this.navigate(H,"y",1)}.pass(G,this)).injectInside(C)}}else{if(A.prev.month&&this.options.navigation){E.clone().addEvent("click",function(H){this.navigate(H,"m",-1)}.pass(G,this)).injectInside(B)}B.adopt(new Element("span").addClass(this.classes.month).appendText(this.options.months[G.month]));B.adopt(new Element("span").addClass(this.classes.year).appendText(G.year));if(A.next.month&&this.options.navigation){D.clone().addEvent("click",function(H){this.navigate(H,"m",1)}.pass(G,this)).injectInside(B)}}return B},changed:function(A){A.val=this.read(A);$extend(A,this.values(A));this.rebuild(A);if(!A.val){return }if(A.val.getDate()<A.days[0]){A.val.setDate(A.days[0])}if(A.val.getDate()>A.days.getLast()){A.val.setDate(A.days.getLast())}A.els.each(function(B){B.value=this.format(A.val,B.format)},this);this.check(A);this.calendars.each(function(B){if(B.visible){this.display(B)}},this)},check:function(A){this.calendars.each(function(D,B){if(D.val){var E=false;if(B<A.id){var C=new Date(Date.parse(A.val));C.setDate(C.getDate()-(this.options.pad*(A.id-B)));if(C<D.val){E=true}}if(B>A.id){var C=new Date(Date.parse(A.val));C.setDate(C.getDate()+(this.options.pad*(B-A.id)));if(C>D.val){E=true}}if(E){if(D.start>C){C=D.start}if(D.end<C){C=D.end}D.month=C.getMonth();D.year=C.getFullYear();$extend(D,this.values(D));D.val=D.days.contains(C.getDate())?C:null;this.write(D);if(D.visible){this.display(D)}}}else{D.month=A.month;D.year=A.year}},this)},clicked:function(C,A,B){B.val=(this.value(B)==A)?null:new Date(B.year,B.month,A);this.write(B);if(!B.val){B.val=this.read(B)}if(B.val){this.check(B);this.toggle(B)}else{C.addClass(this.classes.valid);C.removeClass(this.classes.active)}},display:function(J){this.calendar.empty();this.calendar.className=this.classes.calendar+" "+this.options.months[J.month].toLowerCase();var K=new Element("div").injectInside(this.calendar);var R=new Element("table").injectInside(K).adopt(this.caption(J));var Q=new Element("thead").injectInside(R);var B=new Element("tr").injectInside(Q);for(var P=0;P<=6;P++){var E=this.options.days[(P+this.options.offset)%7];B.adopt(new Element("th",{title:E}).appendText(E.substr(0,1)))}var A=new Element("tbody").injectInside(R);var B=new Element("tr").injectInside(A);var T=new Date(J.year,J.month,1);var D=((T.getDay()-this.options.offset)+7)%7;var I=new Date(J.year,J.month+1,0).getDate();var L=new Date(J.year,J.month,0).getDate();var F=this.value(J);var N=J.days;var M=[];var G=[];this.calendars.each(function(X,W){if(X!=J&&X.val){if(J.year==X.val.getFullYear()&&J.month==X.val.getMonth()){M.push(X.val.getDate())}if(J.val){for(var V=1;V<=I;V++){T.setDate(V);if((W<J.id&&T>X.val&&T<J.val)||(W>J.id&&T>J.val&&T<X.val)){if(!G.contains(V)){G.push(V)}}}}}},this);var T=new Date();var S=new Date(T.getFullYear(),T.getMonth(),T.getDate()).getTime();for(var P=1;P<43;P++){if((P-1)%7==0){B=new Element("tr").injectInside(A)}var H=new Element("td").injectInside(B);var O=P-D;var U=new Date(J.year,J.month,O);var C="";if(O===F){C=this.classes.active}else{if(M.contains(O)){C=this.classes.inactive}else{if(N.contains(O)){C=this.classes.valid}else{if(O>=1&&O<=I){C=this.classes.invalid}}}}if(U.getTime()==S){C=C+" "+this.classes.today}if(G.contains(O)){C=C+" "+this.classes.hilite}H.addClass(C);if(N.contains(O)){H.setProperty("title",this.format(U,"D M jS Y"));H.addEvents({click:function(X,V,W){this.clicked(X,V,W)}.pass([H,O,J],this),mouseover:function(W,V){W.addClass(V)}.pass([H,this.classes.hover]),mouseout:function(W,V){W.removeClass(V)}.pass([H,this.classes.hover])})}if(O<1){O=L+O}else{if(O>I){O=O-I}}H.appendText(O)}},element:function(B,C,D){if($type(C)=="object"){for(var A in C){if(!this.element(A,C[A],D)){return false}}return true}B=$(B);if(!B){return false}B.format=C;if(B.getTag()=="select"){B.addEvent("change",function(E){this.changed(E)}.pass(D,this))}else{B.readOnly=true;B.addEvent("focus",function(E){this.toggle(E)}.pass(D,this))}D.els.push(B);return true},format:function(C,K){var I="";if(C){var E=C.getDate();var L=C.getDay();var D=this.options.days[L];var B=C.getMonth()+1;var H=this.options.months[B-1];var J=C.getFullYear()+"";for(var F=0,G=K.length;F<G;F++){var A=K.charAt(F);switch(A){case"y":J=J.substr(2);case"Y":I+=J;break;case"m":if(B<10){B="0"+B}case"n":I+=B;break;case"M":H=H.substr(0,3);case"F":I+=H;break;case"d":if(E<10){E="0"+E}case"j":I+=E;break;case"D":D=D.substr(0,3);case"l":I+=D;break;case"N":L+=1;case"w":I+=L;break;case"S":if(E%10==1&&E!="11"){I+="ero"}else{if(E%10==2&&E!="12"){I+="do"}else{if(E%10==3&&E!="13"){I+="ero"}else{I+="to"}}}break;default:I+=A}}}return I},navigate:function(C,B,D){switch(B){case"m":if($type(C.months)=="array"){var A=C.months.indexOf(C.month)+D;if(A<0||A==C.months.length){if(this.options.navigation==1){this.navigate(C,"y",D)}A=(A<0)?C.months.length-1:0}C.month=C.months[A]}else{var A=C.month+D;if(A<0||A==12){if(this.options.navigation==1){this.navigate(C,"y",D)}A=(A<0)?11:0}C.month=A}break;case"y":if($type(C.years)=="array"){var A=C.years.indexOf(C.year)+D;C.year=C.years[A]}else{C.year+=D}break}$extend(C,this.values(C));if($type(C.months)=="array"){var A=C.months.indexOf(C.month);if(A<0){C.month=C.months[0]}}this.display(C)},read:function(C){var A=[null,null,null];C.els.each(function(F){var E=this.unformat(F.value,F.format);E.each(function(H,G){if($type(H)=="number"){A[G]=H}})},this);if($type(A[0])=="number"){C.year=A[0]}if($type(A[1])=="number"){C.month=A[1]}var D=null;if(A.every(function(E){return $type(E)=="number"})){var B=new Date(A[0],A[1]+1,0).getDate();if(A[2]>B){A[2]=B}D=new Date(A[0],A[1],A[2])}return(C.val==D)?null:D},rebuild:function(A){A.els.each(function(B){if(B.getTag()=="select"&&B.format.test("^(d|j)$")){var C=this.value(A);if(!C){C=B.value.toInt()}B.empty();A.days.each(function(D){var E=new Element("option",{selected:(C==D),value:((B.format=="d"&&D<10)?"0"+D:D)}).appendText(D).injectInside(B)},this)}},this)},sort:function(B,A){return B-A},toggle:function(C){document.removeEvent("mousedown",this.fn);if(C.visible){C.visible=false;C.button.removeClass(this.classes.active);this.fx.start(1,0)}else{this.fn=function(I,H){var I=new Event(I);var G=I.target;var F=false;while(G!=document.body&&G.nodeType==1){if(G==this.calendar){F=true}this.calendars.each(function(J){if(J.button==G||J.els.contains(G)){F=true}});if(F){I.stop();return false}else{G=G.parentNode}}this.toggle(H)}.create({"arguments":C,bind:this,event:true});document.addEvent("mousedown",this.fn);this.calendars.each(function(F){if(F==C){F.visible=true;F.button.addClass(this.classes.active)}else{F.visible=false;F.button.removeClass(this.classes.active)}},this);var B=window.getSize().scrollSize;var E=C.button.getCoordinates();var A=E.right+this.options.tweak.x;var D=E.top+this.options.tweak.y;if(!this.calendar.coord){this.calendar.coord=this.calendar.getCoordinates()}if(A+this.calendar.coord.width>B.x){A-=(A+this.calendar.coord.width-B.x)}if(D+this.calendar.coord.height>B.y){D-=(D+this.calendar.coord.height-B.y)}this.calendar.setStyles({left:A+"px",top:D+"px"});if(window.ie6){this.iframe.setStyles({height:this.calendar.coord.height+"px",left:A+"px",top:D+"px",width:this.calendar.coord.width+"px"})}this.display(C);this.fx.start(0,1)}},unformat:function(B,G){G=G.escapeRegExp();var I={d:"([0-9]{2})",j:"([0-9]{1,2})",D:"("+this.options.days.map(function(J){return J.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(J){return J.substr(0,3)}).join("|")+")",n:"([0-9]{1,2})",Y:"([0-9]{4})",y:"([0-9]{2})"};var E=[];var F="";for(var C=0;C<G.length;C++){var H=G.charAt(C);if(I[H]){E.push(H);F+=I[H]}else{F+=H}}var D=B.match("^"+F+"$");var A=new Array(3);if(D){D=D.slice(1);E.each(function(K,J){J=D[J];switch(K){case"y":J="19"+J;case"Y":A[0]=J.toInt();break;case"F":J=J.substr(0,3);case"M":J=this.options.months.map(function(L){return L.substr(0,3)}).indexOf(J)+1;case"m":case"n":A[1]=J.toInt()-1;break;case"d":case"j":A[2]=J.toInt();break}},this)}return A},value:function(B){var A=null;if(B.val){if(B.year==B.val.getFullYear()&&B.month==B.val.getMonth()){A=B.val.getDate()}}return A},values:function(F){var D,A,H;F.els.each(function(I){if(I.getTag()=="select"){if(I.format.test("(y|Y)")){D=[];I.getChildren().each(function(K){var J=this.unformat(K.value,I.format);if(!D.contains(J[0])){D.push(J[0])}},this);D.sort(this.sort)}if(I.format.test("(F|m|M|n)")){A=[];I.getChildren().each(function(K){var J=this.unformat(K.value,I.format);if($type(J[0])!="number"||J[0]==F.year){if(!A.contains(J[1])){A.push(J[1])}}},this);A.sort(this.sort)}if(I.format.test("(d|j)")&&!I.format.test("^(d|j)$")){H=[];I.getChildren().each(function(K){var J=this.unformat(K.value,I.format);if(J[0]==F.year&&J[1]==F.month){if(!H.contains(J[2])){H.push(J[2])}}},this)}}},this);var G=1;var E=new Date(F.year,F.month+1,0).getDate();if(F.year==F.start.getFullYear()){if(A==null&&this.options.navigation==2){A=[];for(var C=0;C<12;C++){if(C>=F.start.getMonth()){A.push(C)}}}if(F.month==F.start.getMonth()){G=F.start.getDate()}}if(F.year==F.end.getFullYear()){if(A==null&&this.options.navigation==2){A=[];for(var C=0;C<12;C++){if(C<=F.end.getMonth()){A.push(C)}}}if(F.month==F.end.getMonth()){E=F.end.getDate()}}var B=this.blocked(F);if($type(H)=="array"){H=H.filter(function(I){if(I>=G&&I<=E&&!B.contains(I)){return I}})}else{H=[];for(var C=G;C<=E;C++){if(!B.contains(C)){H.push(C)}}}H.sort(this.sort);return{days:H,months:A,years:D}},write:function(A){this.rebuild(A);A.els.each(function(B){B.value=this.format(A.val,B.format)},this)}});Calendar.implement(new Events,new Options);
*/

// 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.
function var_dump(data,addwhitespace,safety,level) {
        var rtrn = '';
        var dt,it,spaces = '';
        if(!level) {level = 1;}
        for(var i=0; i<level; i++) {
           spaces += '   ';
        }//end for i<level
        if(typeof(data) != 'object') {
           dt = data;
           if(typeof(data) == 'string') {
              if(addwhitespace == 'html') {
                 dt = dt.replace(/&/g,'&amp;');
                 dt = dt.replace(/>/g,'&gt;');
                 dt = dt.replace(/</g,'&lt;');
              }//end if addwhitespace == html
              dt = dt.replace(/\"/g,'\"');
              dt = '"' + dt + '"';
           }//end if typeof == string
           if(typeof(data) == 'function' && addwhitespace) {
              dt = new String(dt).replace(/\n/g,"\n"+spaces);
              if(addwhitespace == 'html') {
                 dt = dt.replace(/&/g,'&amp;');
                 dt = dt.replace(/>/g,'&gt;');
                 dt = dt.replace(/</g,'&lt;');
              }//end if addwhitespace == html
           }//end if typeof == function
           if(typeof(data) == 'undefined') {
              dt = 'undefined';
           }//end if typeof == undefined
           if(addwhitespace == 'html') {
              if(typeof(dt) != 'string') {
                 dt = new String(dt);
              }//end typeof != string
              dt = dt.replace(/ /g,"&nbsp;").replace(/\n/g,"<br>");
           }//end if addwhitespace == html
           return dt;
        }//end if typeof != object && != array
        for (var x in data) {
           if(safety && (level > safety)) {
              dt = '*RECURSION*';
           } else {
              try {
                 dt = var_dump(data[x],addwhitespace,safety,level+1);
              } catch (e) {continue;}
           }//end if-else level > safety
           it = var_dump(x,addwhitespace,safety,level+1);
           rtrn += it + ':' + dt + ',';
           if(addwhitespace) {
              rtrn += '\n'+spaces;
           }//end if addwhitespace
        }//end for...in
        if(addwhitespace) {
           rtrn = '{\n' + spaces + rtrn.substr(0,rtrn.length-(2+(level*3))) + '\n' + spaces.substr(0,spaces.length-3) + '}';
        } else {
           rtrn = '{' + rtrn.substr(0,rtrn.length-1) + '}';
        }//end if-else addwhitespace
        if(addwhitespace == 'html') {
           rtrn = rtrn.replace(/ /g,"&nbsp;").replace(/\n/g,"<br>");
        }//end if addwhitespace == html
        return rtrn;
     }//end function var_dump

var Calendar = new Class({	

	options: {
		blocked: [], // blocked dates 
		classes: [], // ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite']
		days: ['Domingo', 'Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado'], // days of the week starting at sunday
		direction: 0, // -1 past, 0 past + future, 1 future
		draggable: true,
		months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Deciembre'],
		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
		//click: click || false
		click: false
	},

	// 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) {
		
		
		
			
			
			
			
		if (typeof (obj['CAMBIO'])!='undefined'){
			variable = "({"+options['maldito_id']+":'d/m/Y'})";
			obj = eval(variable);
			options = null;
		}
		

		// 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 = this.calendar.effect('opacity', { 
			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.getTag() == '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);
		}

		if (this.options.click) this.options.click();

	},
	

	// 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.getTag() == '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) {
		cal.els.each(function(el) {			
			/*
			if (el.getTag() == '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.getTag() == '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); 
	},


	// 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(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.getSize().scrollSize;
			
			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(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.getTag() == '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);

