// wishlist: allow the grid to scroll if we try to drag above/below the limits

// todo: make home page show variations, replacements fortnightly and beginning events as such. (Ignore temporary or permanent cancellations.)
// todo: make similar changes to reminder emails

function readcss(theClass,element) {
    var cssRules;
    var value;
    for (var S = 0; S < document.styleSheets.length; S++){	
	if (document.styleSheets[S]['rules']) {
	    cssRules = 'rules';
	} else if (document.styleSheets[S]['cssRules']) {
	    cssRules = 'cssRules';
	} else {
	    //no rules found... browser unknown
	}
	for (var R = 0; R < document.styleSheets[S][cssRules].length; R++) {
// need to cast to lower case, because some browsers will give the selector text like DIV others div:
// we need to match either.
	    if (document.styleSheets[S][cssRules][R].selectorText && (document.styleSheets[S][cssRules][R].selectorText.toLowerCase() == theClass.toLowerCase())) {
		if(element) {
		    if(document.styleSheets[S][cssRules][R].style[element]){
			value=document.styleSheets[S][cssRules][R].style[element];
			break;
		    }
		} else {
		    value=document.styleSheets[S][cssRules][R].style;
		    break;
		}
	    }
	}
    }
    return value;
}
function changecss(theClass,element,value) {
    //Last Updated on June 23, 2009
    //documentation for this script at
    //http://www.shawnolson.net/a/503/altering-css-class-attributes-with-javascript.html
    var cssRules;
    
    var added = false;
    for (var S = 0; S < document.styleSheets.length; S++){
	
	if (document.styleSheets[S]['rules']) {
	    cssRules = 'rules';
	} else if (document.styleSheets[S]['cssRules']) {
	    cssRules = 'cssRules';
	} else {
	    //no rules found... browser unknown
	}
	
	for (var R = 0; R < document.styleSheets[S][cssRules].length; R++) {
	    if (document.styleSheets[S][cssRules][R].selectorText == theClass) {
		if(document.styleSheets[S][cssRules][R].style[element]){
		    document.styleSheets[S][cssRules][R].style[element] = value;
		    added=true;
		    break;
		}
	    }
	}
	if(!added){
	    if(document.styleSheets[S].insertRule){
		document.styleSheets[S].insertRule(theClass+' { '+element+': '+value+'; }',document.styleSheets[S][cssRules].length);
	    } else if (document.styleSheets[S].addRule) {
		document.styleSheets[S].addRule(theClass,element+': '+value+';');
	    }
	}
    }
}

function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[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;
}

// determine whether or not to show regular weekly events
function showreg() {
    if ( typeof showreg.visible == 'undefined' ) {
        // It has not... perform the initilization
        showreg.visible = false;
    }
    if(showreg.visible) {
	changecss('td div.weekly','display','none');
	document.getElementById("b_showregular").innerHTML="Show weekly events";
	showreg.visible=false;
    } else {
	changecss('td div.weekly','display','block');
	document.getElementById("b_showregular").innerHTML="Hide weekly events";
	showreg.visible=true;
    }
    createCookie('calshowreg',showreg.visible,0);
}

// get the showreg value from the cookie
function setshowreg() {
    var visible=readCookie('calshowreg');
    if(visible=="true") { 
	showreg(); 
    }
}

// make a day on the calendar 'active'
var activeElement=new Object;
activeElement.tgt=null;
function isdescendant(parent, child) {
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) return true;
         node = node.parentNode;
     }
     return false;
}
function parentcell(element) {
    while(element!=null && element.nodeName!="TD") element=element.parentNode;
    return element;
}
// linked to mouseover for calendar cells
function makeactive(e) {
    if(daygrid && daygrid.alreadyopen) return;
    e=e==null?window.event:e;
    var target=parentcell(e.target || e.srcElement);
    if(target==null || target==activeElement.tgt) return; 
    if(activeElement.tgt!=null) activeElement.tgt.setAttribute('class',activeElement.cls);
    activeElement.tgt=target;
    if(target.getAttribute('class')!=null) {
	activeElement.cls=target.getAttribute('class');
	target.setAttribute('class',activeElement.cls+" active");
    } else {
	activeElement.cls="";
	target.setAttribute('class',"active");
    }
    return stopDef(e);
}
// linked to the mouseout event for calendar cells
function makeinactive(e) {
    if(daygrid && daygrid.alreadyopen) return;
    e=e==null?window.event:e;
    var target=parentcell(e.target || e.srcElement);
    var relTarg = parentcell(e.relatedTarget || e.toElement);
    if(activeElement.tgt!=null && activeElement.tgt==relTarg) return; // moving to same cell: ignore
    if(activeElement.tgt==target ) {
	target.setAttribute('class',activeElement.cls);
	activeElement.tgt=null;
    }
    return stopDef(e);
}
function setCalendarMouseOvers(e) {
    var theTDs=document.getElementById('t_calendar').getElementsByTagName("TD");
    for(var n=0; n<theTDs.length; n++) {
	if(theTDs[n].getAttribute('class')!='empty') {
	    theTDs[n].onmouseover=makeactive;
	    theTDs[n].onmouseout=makeinactive;
	}
    }
}

window.onload=setCalendarMouseOvers;


var daygrid;

function dayclick(year, month, day) {
    // ignore if it's already open
    if(daygrid && daygrid.alreadyopen) return;
    var uri="/caldate?year="+year+"&month="+month+"&day="+day;
    if (window.XMLHttpRequest)
    {// code for IE7+, Firefox, Chrome, Opera, Safari
	var xmlhttp=new XMLHttpRequest();
    }
    else
    {// code for IE6, IE5
	var xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange=function() {
	if (xmlhttp.readyState==4 && xmlhttp.status==200) {
	    var daybox=document.getElementById("daybox");
	    daybox.onmousedown=div_dragstart;
	    daybox.innerHTML=xmlhttp.responseText;
	    
//	    window.onmousewheel = document.onmousewheel = mwHandler;
//	    if (window.addEventListener) {
//		window.addEventListener('DOMMouseScroll', mwHandler, true);
		// ignore mozilla's pixel scroll so it doesn't scroll the bg
//		window.addEventListener('MozMousePixelScroll', stopDef, true);
//	    }
	    daybox.style.visibility="visible";
	    daygrid=new DayGrid(year,month,day);
	    daygrid.alreadyopen=true;
//	    daygrid.paint();
	}
    }
    xmlhttp.open("GET",uri,true);
    xmlhttp.send();
}
function dayclose() {
    var daybox=document.getElementById("daybox");
//    window.onmousewheel = document.onmousewheel = "";
//    if (window.removeEventListener) {
//	window.removeEventListener('DOMMouseScroll', mwHandler, true);
//	window.removeEventListener('MozMousePixelScroll', stopDef, true);
//    }
    daybox.style.visibility="hidden";
    daygrid.alreadyopen=false;
    daygrid.top=-1;
}    

function unescape(etxt) {
    txt=new String(etxt);
    txt=txt.replace("[^\\]\\","#\\#\\#");
    txt=txt.replace("\\t","\t");
    txt=txt.replace("\\n","\n");
    txt=txt.replace("\\r","\r");
    txt=txt.replace("#\\#\\#","\\");
    return txt;
}
function urlencode(ptxt) {
    txt=new String(escape(ptxt));
    txt=txt.replace("+", "%2B");
    txt=txt.replace("/", "%2F");
    return txt;
}

function stopDef(e) {
    e=e==null?window.event:e;
    if (e.preventDefault) e.preventDefault();
    if (e.stopPropagation) e.stopPropagation();
    e.cancelBubble=true;
    e.returnValue = false;
    return false;
}
function Event(string)
{
  /* the parameter is a tab delimited record containing
| meeting_no       | int
| event_date       | sql date
| event_time       | decimal hour
| event_duration   | decimal hour
| event_title      | escaped string
| event_full_desc  | escaped string
| event_short_desc | escaped string
| category         | int 0=one-off; 1=regular; 2=semi-regular; 3=terminate; 4=suspend; 5=monthly
| nweekly          | int how many weeks between (semi-)regular events
| private_desc     | escaped string
| replaces         | int
| variation_category | int
   */
    if(string!=null) {
	var fields=string.split("\t");
	this.meeting_no=new Number(fields[0]);
// Firefox can't parse yyyy-mm-dd into a date
//	this.event_date=new Date(fields[1]);
	var ymd=fields[1].split('-');
	this.event_date=new Date(parseInt(ymd[0],10),parseInt(ymd[1],10)-1,parseInt(ymd[2],10),0,0,0,0);
	this.event_time=new Number(fields[2]);
	this.event_duration=new Number(fields[3]);
	this.event_title=unescape(fields[4]);
	this.event_full_desc=unescape(fields[5]);
	this.event_short_desc=unescape(fields[6]);
	this.category=new Number(fields[7]);
	this.nweekly=new Number(fields[8]);
	this.private_desc=unescape(fields[9]);
	this.replaces=new Number(fields[10]);
	this.isprivate=new Number(fields[11]);
	this.variation_category=new Number(fields[12]);
	this.event_desc=this.event_short_desc.length>0?this.event_short_desc:this.event_full_desc;
	this.column=1;
	this.columns=1;
    } else {
	this.meeting_no=new Number(-1);
	this.event_date=new Date();
	this.event_time=new Number();
	this.event_duration=new Number();
	this.event_title="";
	this.event_full_desc="";
	this.event_short_desc="";
	this.category=new Number(0);
	this.nweekly=new Number(0);
	this.private_desc="";
	this.replaces=new Number(0);
	this.isprivate=new Number(0);
	this.event_desc="";
	this.column=1;
	this.columns=1;
     }
}

Number.prototype.toOrdinal = function () {
    var i = this.toString();
    if ( i.match(/\D/) ) return i;
    if ( i == 3 || i.match(/[^1]3$/) ) return i + 'rd';
    if ( i == 2 || i.match(/[^1]2$/) ) return i + 'nd';
    if ( i == 1 || i.match(/[^1]1$/) ) return i + 'st';
    return i + 'th';
}

// open with a POST, passing params, call the callback with the response when it's done.
function postXMLDoc(uri, params, callback, context)
{
    if (window.XMLHttpRequest)
    {// code for IE7+, Firefox, Chrome, Opera, Safari
	xmlhttp=new XMLHttpRequest();
    }
    else
    {// code for IE6, IE5
	xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.context=context;
    xmlhttp.onreadystatechange=function()
    {
	if (xmlhttp.readyState==4 && xmlhttp.status==200)
	{
	    callback(xmlhttp.responseText, xmlhttp.context);
	}
    }
    xmlhttp.open("POST",uri,true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.setRequestHeader("Content-length", params.length);
    xmlhttp.setRequestHeader("Connection", "close");
    xmlhttp.send(params);
}

// decimal time <-> string representation
function ftime(dtime) {
    var hr=Math.floor(dtime);
    var min=Math.floor(60*(dtime-hr)+.1);
    var z=min<10?"0":"";
    var ap=hr<12?"am":"pm";
    if(hr<1) hr=12;
    if(hr>12) hr=hr-12;
    return ""+hr+":"+z+min+ap;
}
function ftime24(dtime) {
    var hr=Math.floor(dtime);
    var min=Math.floor(60*(dtime-hr)+.1);
    var z=min<10?"0":"";
    return ""+hr+":"+z+min;
}
function parsetime(stime){
    var time = stime.match(/(\d+)(?::(\d\d))?\s*(p?)/);
    var hours= parseInt(time[1],10) + (time[3] ? 12 : 0);
    if(hours==12 || hours==24) {
	hours-=12; 
    }
    hours+= (parseInt(time[2],10) || 0 )/60;
    return hours;
}
function sqldate(date){
    var yr=date.getFullYear();
    var mo=date.getMonth()+1;
    var dy=date.getDate();
    return yr+"-"+(mo<10?"0":"")+mo+"-"+(dy<10?"0":"")+dy;
}
// the editor object
function Editor(container)
{
    this.container=container;
    this.div=document.getElementById("editor");
    this.title=document.getElementById("intitle");
    this.desc=document.getElementById("indesc");
    this.priv=document.getElementById("inpriv");
    this.date=document.getElementById("indate");
    this.start=document.getElementById("instart");
    this.finish=document.getElementById("infinish");
    this.category=document.getElementById("incategory");
    this.nweekly=document.getElementById("innweekly");
    this.cat1=document.getElementById("cat1");
    this.cat5=document.getElementById("cat5");
    this.isprivate=document.getElementById("inisprivate");
    this.b_changeall=document.getElementById("inchangeall");
    this.b_changeone=document.getElementById("inchangeone");
    this.b_save=document.getElementById("insave");
    this.b_stopall=document.getElementById("instopall");
    this.b_stopone=document.getElementById("instopone");
    this.form=document.getElementById("editorform");
    this.savingsingle=false; // to flag that a save of a recurrent event changes only one instance

    this.div.onmousedown=div_dragstart;

    this.weekdays=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');

    this.show=function() {
	this.div.style.visibility="visible";
    }
    this.hide=function() {
	this.div.style.visibility="hidden";
    }
    this.cancel=function() {
	if(this.event.meeting_no<0) this.container.events.pop();
	this.hide();
    }
//	container.paint();
    this.form_is_modified=function()
    {
	var oForm=this.form;
	var el, opt, hasDefault, i = 0, j;
	while (el = oForm.elements[i++]) {
	    switch (el.type) {
	    case 'text' :
            case 'textarea' :
            case 'hidden' :
                if (!/^\s*$/.test(el.value) && el.value != el.defaultValue) return true;
                break;
            case 'checkbox' :
            case 'radio' :
                if (el.checked != el.defaultChecked) return true;
                break;
            case 'select-one' :
            case 'select-multiple' :
                j = 0, hasDefault = false;
                while (opt = el.options[j++])
                    if (opt.defaultSelected) hasDefault = true;
                j = hasDefault ? 0 : 1;
                while (opt = el.options[j++]) 
                    if (opt.selected != opt.defaultSelected) return true;
                break;
	    }
	}
	return false;
    }
    this.enable_saves=function() {
	flag=!AUTHENTICATED || !this.form_is_modified();
	this.b_save.disabled=flag;
	this.b_changeone.disabled=flag;
	this.b_changeall.disabled=flag;
    }
    this.startchanged=function() {
	// the start time was changed ... bump the end time to keep the duration.
	if(this.finishsmudged!=true) {
	    this.finish.value=ftime(parsetime(this.start.value)+this.event.event_duration);
	}
    }
    this.smudgefinish=function() {
	finishsmudged=true;
	this.enable_saves();
    }

    this.openclick=function(event) {
	event=event==null?window.event:event;
	var target=event.target || event.srcElement;
	while(target.nodeName!='P') target=target.parentNode;
	var key=parseInt(target.id.replace('dayevent',''),10);
// spy(event);
	if(target.id.match(/dayevent[0-9]+/)==null) return true;
	return daygrid.editor.open(key);
    }
    this.open=function(key){
	this.event=container.events[key];
	var event=this.event;
	var isnew=(event.meeting_no<0);
	this.title.defaultValue=event.event_title;
	if(event.event_short_desc.length>0) {
	    this.desc.defaultValue=event.event_short_desc;
	} else {
	    this.desc.defaultValue=event.event_full_desc;
	}
	this.priv.defaultValue=event.private_desc;
	this.date.innerHTML=event.event_date.toDateString();
	this.start.defaultValue=ftime(event.event_time);
	this.finish.defaultValue=ftime(event.event_time+event.event_duration);
	for(o=0; o<3; o++) this.category.options[o].defaultSelected=false;
	var selectedIndex=Array(0,1,1,1,1,2)[event.category];
	this.category.options[selectedIndex].defaultSelected=true;
//	this.category.selectedIndex=selectedIndex;
	for(o=0; o<4; o++) this.nweekly.options[o].defaultSelected=false;
	selectedIndex=Array(0,0,1,2,3)[event.nweekly];
	this.nweekly.options[selectedIndex].defaultSelected=true;
//	this.nweekly.selectedIndex=selectedIndex;

	var weekday=this.weekdays[event.event_date.getDay()];
	var weekno=Array('first','second','third','fourth','fifth')[Math.floor((event.event_date.getDate()-1)/7+0.01)];

// decide on whether the event date matches the d/m/y. For repeat events, editing the first event allows
// the whole series to be deleted or changed.
	if(AUTHENTICATED) {
	    if(event.event_date.getFullYear()==this.container.year &&
	       event.event_date.getMonth()+1==this.container.month &&
	       event.event_date.getDate()==this.container.day) { 
		this.b_changeall.style['display']="none";
		this.b_changeone.style['display']="none";
		this.b_stopone.style['display']=isnew?"none":"";
		this.b_stopall.style['display']="none";
		this.b_save.style['display']="";
		this.category.disabled=false;
		this.nweekly.disabled=false;
	    } else {
		this.b_changeall.style['display']="";
		this.b_changeone.style['display']="";
		this.b_stopone.style['display']="";
		this.b_stopall.style['display']="";
		this.b_save.style['display']="none";
		this.category.disabled=true;
		this.nweekly.disabled=true;
	    }
	    changecss('p.eprivate','display','inline');
	} else {
	    this.b_changeall.style['display']="none";
	    this.b_changeone.style['display']="none";
	    this.b_stopone.style['display']="none";
	    this.b_stopall.style['display']="none";
	    this.b_save.style['display']="none";
	    changecss('p.eprivate','display','none');
	}
	this.cat1.innerHTML="On "+weekday;
	this.cat5.innerHTML="On the "+weekno+" "+weekday+" of each month";

	this.isprivate.defaultChecked=(event.isprivate==1?true:false);
	
	this.form.reset();

	this.category.onchange=this.categorychange;
	this.categorychange();
	this.finishsmudged=false;

	this.enable_saves();
	this.show();
	if(isnew) {
	    this.title.focus();
	    this.title.select();
	}
    }

    this.categorychange=function() {
	daygrid.editor.nweekly.style.visibility=(daygrid.editor.category.selectedIndex==1)?"":"hidden";
	daygrid.editor.smudgefinish();
    }

    // encode an event ready to send
    this.urlencode_event=function(event){
	var param;
	param="record[meeting_no]="+event.meeting_no;
	param+="&record[event_date]="+sqldate(event.event_date)
	param+="&record[event_time]="+ftime24(event.event_time);
	param+="&record[event_minutes]="+(event.event_duration*60);
	param+="&record[event_title]="+urlencode(event.event_title);
	param+="&record[event_short_desc]="+urlencode(event.event_desc);
	param+="&record[private_desc]="+urlencode(event.private_desc);
	param+="&record[category]="+event.category;
	param+="&record[nweekly]="+event.nweekly;
	param+="&record[event_is_private]="+event.isprivate;
	return param;
    }

    // when the user clicks save or save all, and indirectly for save one.
    this.save=function() {
	var param;
	var event=this.event;
	var startTime=parsetime(this.start.value);
	var finishTime=parsetime(this.finish.value);
	var duration=(finishTime-startTime);
	if(duration<0) {
	    duration=1;
	}
	event.event_title=this.title.value;
	event.event_time=startTime;
	event.event_duration=duration;
	event.event_desc=this.desc.value;
	event.private_desc=this.priv.value;
	event.isprivate=(this.isprivate.checked?1:0);
	event.category=this.category.value;
	if(event.category==1 || event.category==2) {
	    event.nweekly=this.nweekly.value;
	} else {
	    event.nweekly=0;
	}

	param="year="+this.container.year;
	param+="&month="+this.container.month;
	param+="&day="+this.container.day;
	param+="&event="+event.meeting_no;
	param+="&"+this.urlencode_event(event);
//	if(event.category==0)
	if(event.event_date.getFullYear()==this.container.year &&
	   event.event_date.getMonth()+1==this.container.month &&
	   event.event_date.getDate()==this.container.day) { 
	    param+="&submit=Save";
	} else if(this.savingsingle) {
	    param+="&submit=Single";
	    this.savingsingle=false;
	} else {
	    param+="&submit=Supercede";
	}
	
	var uri="/calendar-updatedb";
	postXMLDoc(uri, param, this.finishsave, this);
    }
    this.finishsave=function(response, context) {
	if(context) context.hide();
	if(response.length>0) {
	    alert(response);
	}
	if(context) context.container.refreshcontent();
	else daygrid.refreshcontent();
    }

    this.changeall=this.save;
    this.changeone=function() {
	this.savingsingle=true;
	this.save();
    }

    this.confirm=function(question, notext, yestext, yesaction) {
	var thediv=document.getElementById('confirm');
	var theq=document.getElementById('question');
	theq.innerHTML=question;
	var theno=document.getElementById('confirmno');
	theno.setAttribute('value',notext);
	theno.onclick=function() { document.getElementById('confirm').style.visibility="hidden"; }
	var theyes=document.getElementById('confirmyes');
	theyes.setAttribute('value',yestext);
	theyes.onclick=yesaction;
	thediv.style.visibility="visible";
    }

    // when the user clicks delete
    this.stopone=function() {
	this.confirm("Confirm deletion of this event","Cancel","Confirm Deletion", function() {
	    document.getElementById('confirm').style.visibility="hidden";
	    var event=daygrid.editor.event;
	    var param;
	    param="year="+daygrid.year;
	    param+="&month="+daygrid.month;
	    param+="&day="+daygrid.day;
	    param+="&event="+event.meeting_no;
//	if(event.category==0)
	if(event.event_date.getFullYear()==daygrid.year &&
	   event.event_date.getMonth()+1==daygrid.month &&
	   event.event_date.getDate()==daygrid.day) { 
		param+="&delete=Delete";
	    } else {
		param+="&suspend=Suspend";
	    }
	    var uri="/calendar-updatedb";
	    postXMLDoc(uri, param, daygrid.editor.finishsave, daygrid.editor);
	});
    }
    // when the user clicks delete series
    this.stopall=function() {
	// TODO: show deletion/suspension/change events for series to allow them to be undone
	this.confirm("Confirming will remove this event from this date on.","Cancel","Confirm Deletion", function() {
	    document.getElementById('confirm').style.visibility="hidden";
	    var event=daygrid.editor.event;
	    var param;
	    param="year="+daygrid.year;
	    param+="&month="+daygrid.month;
	    param+="&day="+daygrid.day;
	    param+="&event="+event.meeting_no;
	    param+="&discontinue=Discontinue";
	    var uri="/calendar-updatedb";
	    postXMLDoc(uri, param, daygrid.editor.finishsave, daygrid.editor);
	});
    }
}

// object to track dragging
dragObj=new Object;
dragObj.dragging=false;

function DayGrid(year,month,day)
{
    this.alreadyopen=false;
    this.year=year;
    this.month=month;
    this.day=day;
    this.resolution=0.5; // half hour slots
    this.top=-1; 
    this.linetop=0; //pt
    this.lineheight=12; // pt
    this.totalheight=new Number(readcss('div.daybox','height').replace('pt',''))-20; // pt
    this.timestrip=32;
    this.scrollwidth=12;
    this.totalwidth=400;
    this.thegrid=document.getElementById("daygrid");
    this.selected=new Array();
    this.selecting=-1;
    this.events=new Array();
    this.editor=new Editor(this);
    function enhance_hyperlink(text) {
	return text.replace(/(http:\/\/[^ ]+[^ .,:;?!])/g, '<a href="\1">\1</a>');
    }
    function enhance_text_tags(text) {
	text=text.replace("\r\n","<br />");
	text=text.replace("\n","<br />");
	text=text.replace("\r","<br />");
	text=text.replace(/[*]([^*]*)[*]/g, "<strong>$1</strong>");
	text=text.replace(/[_]([^_]*)[_]/g, '<em>$1</em>');
	return text
    }
    function enhance_text(text)
    {
	return enhance_text_tags(enhance_hyperlink(text));
    }

    this.updatecalendar=function() {
	// first update the current day
	this.updatecalendar_day();
	var day, events;
	day=this.day+7;
	events=new Array();
	// now every subsequent week (even if we don't need to)
	while(day<=(new Date(this.year,this.month,0,0,0,0,0)).getDate()) {
	    if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
		var xmlhttp=new XMLHttpRequest();
	    } else {// code for IE6, IE5
		var xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	    }
	    var uri="/caldatexml?year="+this.year+"&month="+this.month+"&day="+day
	    xmlhttp.open("GET",uri,false);
	    xmlhttp.send();
	    var content=xmlhttp.responseText;
	    var records=content.split("\n");
	    events.length=0;
	    for (var key=0; key<records.length; key++)
		if(records[key].length>0) events.push(new Event(records[key]));
	    this.updatecalendar_day(events,day);
	    day+=7;
	}
    }
    this.updatecalendar_day=function(events, day) {
	events=events==null?this.events:events;
	day=day==null?this.day:day;
	var html='';
	for(key=0; key<events.length; key++) {
	    var event=events[key];
	    html+= '<div class="calcat'+event.category;
	    if((event.category==1 || event.category==2) && event.nweekly==1) html+=" weekly";
	    else html+=" notweekly";
	    html+='"><p>';
	    html+= "<b>"+ftime(event.event_time)+" "+enhance_text(event.event_title)+"</b> ";
	    if(DETAIL) {
		html+=enhance_text(event.event_desc);
		if(AUTHENTICATED) {
		    html+=' '+enhance_text(event.private_desc);
		}
	    }
	    html+="</p></div>\n";
	}
	document.getElementById('day'+day).innerHTML=html
    }

    // use the known width to get px to pt translation
    dragObj.pxPerPt=document.getElementById('daybox').offsetWidth/400.0;


    // fetch the content and paint it
    if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
	this.xmlhttp=new XMLHttpRequest();
    } else {// code for IE6, IE5
	this.xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    this.xmlhttp.container=this;
    this.xmlhttp.onreadystatechange=function() {
	if (this.readyState==4 && this.status==200) {
	    var content=this.responseText;
	    // do something with the content
	    var records=content.split("\n");
	    this.container.events.length=0;
	    // for each record,
	    for (key=0; key<records.length; key++) {
		if(records[key].length>0) {
		    this.container.events.push(new Event(records[key]));
		    if(this.container.top<0) {
			this.container.top=Math.floor(this.container.events[0].event_time/this.container.resolution)*this.container.resolution;
//			if(this.container.top+this.container.nlines()*this.container.resolution>=24) {
//			    this.container.top=24-this.container.nlines()*this.container.resolution;
//			}
		    }
		}
	    }
	    if(this.container.top<0) {
		this.container.top=10;
	    }
	    this.container.paint();
	    this.container.updatecalendar();
	}
    }

    this.refreshcontent=function() {
	var uri="/caldatexml?year="+this.year+"&month="+this.month+"&day="+this.day
	this.xmlhttp.open("GET",uri,true);
	this.xmlhttp.send();
    }

    // translate a decimal hour to a y coordinate on the screen
    this.timeToY=function(thetime) {
	return thetime*this.lineheight/this.resolution;
    }
    this.yToTime=function(y) {
	var time=y*this.resolution/this.lineheight;
//	var time=y*this.resolution/this.lineheight+this.top;
	var res;
	if(daygrid.resolution==1) res=4;
	if(daygrid.resolution==0.5) res=6;
	else res=12;
	time=Math.floor(time*res+0.5)/res;
	return time;
    }

    // calculate how many lines we fit
    this.nlines=function() {
	return 24/this.resolution;
    }
    // initialise all times as unselected.
    this.unselect=function() {
	for(var line=0; line<Math.floor(24/this.resolution+0.1); line++) 
	    this.selected[line]=false;
	if(document.getElementById("gridline0")!=null)
	   for(var line=0; line<this.nlines(); line++) 
	       document.getElementById("gridline"+line).className="";
    }

    // translate a line on the screen to a line in the selected array
    // now just one-to-one
    this.absoluteline=function(line) {
	return line;
    }

    // methods to scroll up and down
    this.scrolltimer=-1;
    this.dayup=function(once) {
	if(this.thegrid.scrollTop>this.lineheight) {
	    this.thegrid.scrollTop -= this.lineheight/5;
	    if(!once && this.scrolltimer==-1) {
		this.scrolltimer=setInterval("daygrid.dayup()",40);
	    }
	} else {
	    this.thegrid.scrollTop=0;
	}
	this.paint();
    }
    this.daydown=function(once) {
	if(this.thegrid.scrollTop<(this.nlines()-1)*this.lineheight) {
	    this.thegrid.scrollTop += this.lineheight/5;
	    if(!once && this.scrolltimer==-1) {
		this.scrolltimer=setInterval("daygrid.daydown()",40);
	    }
	} else {
	    this.top=(this.nlines()-1)*this.lineheight;
	}
	this.paint();
    }
    this.scrollselectup = function() {
	if(this.selecting!=-1) {
	    this.dayup();
	    this.mouseover_inner(0);
	    if(this.scrolltimer==-1) {
		this.scrolltimer=setInterval('daygrid.scrollselectup()',200);
	    }
	}
    }
    this.scrollselectdown = function() {
	if(this.selecting!=-1) {
	    this.daydown();
	    this.mouseover_inner(this.nlines()-1);
	    if(this.scrolltimer==-1) {
		this.scrolltimer=setInterval('daygrid.scrollselectdown()',200);
	    }
	}
    }
    this.stopscrollselect = function() {
	clearInterval(this.scrolltimer);
	this.scrolltimer=-1;
    }
    this.scrollstop=this.stopscrollselect;

    function adjustcolumns(key,events) {
	var prevcol=events[key].column-1;
	var prevkey=key-1;
	while(prevcol>0) {
	    events[prevkey].columns=events[key].columns;
	    if(events[prevkey].column==prevcol) {
		prevcol--;
	    }
	    prevkey--;
	}
    }
    function testoverlap(key,events,columns) {
	// the first event always goes in the first column
	if(key==0) return 1;
	// try each possible column
	for(var col=1; col<=columns+1; col++) {
	    // if we're up to columns+1 we make a new column ... it must fit.
	    if(col==columns+1) {
		events[key].column=col;
		events[key].columns=col;
		adjustcolumns(key,events);
		return col;
	    }
	    // find the latest event in that column
	    var prevkey=key-1;
	    while(events[prevkey].column!=col) {
		prevkey--;
	    }
	    // does the event overlap?
	    if(events[key].event_time >= events[prevkey].event_time+events[prevkey].event_duration) {
		// no overlap ... use this column
		events[key].column=col;
		events[key].columns=col;
		for(prevkey++;prevkey<key;prevkey++) {
		    if(events[prevkey].column>events[key].column
		      && events[prevkey].event_time+events[prevkey].event_duration>events[key].event_time) {
			events[key].columns=events[prevkey].column;
		    }
		}
		return events[key].columns;
	    }
	}
    }
    // paint the dategrid
    this.paint=function() {
	// first remove any child nodes
	if ( this.thegrid.hasChildNodes() ) {
	    while ( this.thegrid.childNodes.length >= 1 ) {
		this.thegrid.removeChild( this.thegrid.firstChild );       
	    } 
	}
	this.thegrid.style.height=this.totalheight+"pt";
	for(var line=0; line<=this.nlines(); line++) {
	    var top=line * this.lineheight;
	    var time=line*this.resolution;
	    var hour=Math.floor(time+.001);
	    var min=Math.floor(60*(time-hour)+.001);
	    var thisElt=document.createElement('p');
	    thisElt.style.top=top+"pt";
	    thisElt.style.width=this.totalwidth-this.scrollwidth+"pt";
	    thisElt.style.zIndex="1";
	    if(hour>=12) thisElt.style.color="red";
	    if(this.selected[this.absoluteline(line)]) {
		thisElt.setAttribute('class','selected');
	    }
	    thisElt.setAttribute('id','gridline'+line);
	    thisElt.onmousedown=daygrid.mousedown;
	    var html=(hour>12 ? hour-12: hour)+":"+min;
	    if(min<10) {
		html=html+"0";
	    }
	    thisElt.innerHTML=html;
	    this.thegrid.appendChild(thisElt);	    
	}
	// allow for overlapping events
	var columns=1;
	for(var key=0;key<this.events.length; key++) {
	    columns=testoverlap(key, this.events, columns);
	}
	var colwidth;
	// and paint the events
	for(var key=0;key<this.events.length; key++) {
	    if(this.events[key].isprivate==1 && AUTHENTICATED==0) break;
	    colwidth=(this.totalwidth-this.timestrip-this.scrollwidth)/this.events[key].columns;
	    var thisElt=document.createElement('p');
	    var eclass="calcat"+this.events[key].category+" dayevent";
	    
	    if(this.events[key].nweekly==1 && (this.events[key].category==1 || this.events[key].category==2)) {
		eclass+=" weekly";
	    } else {
		eclass+=" notweekly";
	    }
	    if(this.events[key].isprivate!=0) {
		eclass+=" private";
	    }
	    thisElt.setAttribute('class', eclass);
	    thisElt.setAttribute('id',"dayevent"+key);
	    var top=this.timeToY(this.events[key].event_time);
	    thisElt.style.top=top+"pt";
	    var bot=this.timeToY(this.events[key].event_time+this.events[key].event_duration);
	    thisElt.style.height=(bot-top)+"pt";
	    thisElt.onmousedown=daygrid.event_mousedown;
	    thisElt.style.left=(this.timestrip+(this.events[key].column-1)*colwidth)+"pt";
	    thisElt.style.width=(colwidth-4)+"pt";
	    thisElt.style.zIndex="4";
	    var html=this.events[key].event_desc;
	    if(AUTHENTICATED) html+=" "+this.events[key].private_desc;
	    html=enhance_text(html);
	    html="<span class=\"dgtitle\">"+enhance_text(this.events[key].event_title)+"</span> "+html;
	    thisElt.innerHTML=html;
	    if(top<bot) {
		this.thegrid.appendChild(thisElt);
	    }
	}
	// if a scrolltop position has been requested, set it then clear the request.
	if(this.top>0) this.thegrid.scrollTop=this.timeToY(this.top)*dragObj.pxPerPt;
	this.top=0;
    }

    // method for dragging time
    this.event_mousedown=function(event) {
	document.onmouseup=daygrid.dragstop;
	if(!AUTHENTICATED) return stopDef(event);
	event=event==null?window.event:event;
	var target=event.target || event.srcElement;
	// if we've hit some containing text or span or whatever, go up until we hit the parent Paragraph
	while(target.nodeName!='P') target=target.parentNode;
	dragObj.elNode=target;
	var key=parseInt(target.id.replace('dayevent',''),10);
	dragObj.daygrid=daygrid.thegrid;
	dragObj.event=daygrid.events[key];
	dragObj.dragging=false;
//	dragObj.elNode=document.getElementById('dayevent'+key);
	// only allow dragging if the event date matches the date we're showing ...
	// that is, not on repeat events unless it's the first of them.
// this.events[key] needs to be replaced with dragObj.event
//	alert("target.id="+target.id);
	if(dragObj.event.event_date.getFullYear()==daygrid.year &&
	   dragObj.event.event_date.getMonth()+1==daygrid.month &&
	   dragObj.event.event_date.getDate()==daygrid.day) {
	    if (event.pageX || event.pageY) {
		posx = event.pageX;
		posy = event.pageY;
	    }
	    else if (event.clientX || event.clientY) {
		posx = event.clientX + document.body.scrollLeft
		    + document.documentElement.scrollLeft;
		posy = event.clientY + document.body.scrollTop
		    + document.documentElement.scrollTop;
	    }
	    // posx and posy contain the mouse position relative to the document
	    dragObj.cursorStartX = posx+daygrid.thegrid.scrollLeft;
	    dragObj.cursorStartY = posy+daygrid.thegrid.scrollTop;
	    dragObj.elStartLeft  = parseInt(dragObj.elNode.style.left,10);
	    dragObj.elStartTop   = parseInt(dragObj.elNode.style.top,10);
	    dragObj.elHeight     = parseInt(dragObj.elNode.style.height,10);
	    dragObj.gridStartScrollTop=daygrid.thegrid.scrollTop;
	    dragObj.elNode.style.zindex="5";
	    document.onmousemove=daygrid.dragmove;
	}
    }
    this.dragstop=function(e){
	e = e==null?window.event:e;
	document.onmousemove="";
	document.onmouseup="";
	document.onmouseover="";
	daygrid.scrollstop();
	if(dragObj.dragging!=true) {
	    return daygrid.editor.openclick(e);
	}
	
	dragObj.dragging=false;
	// turn the height into time and update
	dragObj.event.event_time=daygrid.yToTime(parseInt(dragObj.elNode.style.top,10));
	var param;
	var event=dragObj.event;

	param="year="+daygrid.year;
	param+="&month="+daygrid.month;
	param+="&day="+daygrid.day;
	param+="&event="+event.meeting_no;

	// allow dragging exception events without turning them into replacement events.
	var category=event.category;
	event.category=event.variation_category;
	param+="&"+daygrid.editor.urlencode_event(event);
	event.category=category;
	if(event.variation_category!=event.category) param+="&submit=Single";
	else param+="&submit=Save";
	
	var uri="/calendar-updatedb";
	postXMLDoc(uri, param, daygrid.editor.finishsave, 0);
	return stopDef(e);
    }
    this.dragmove=function(e){
	e= e==null?window.event:e;

	if (e.pageX || e.pageY) {
	    posx = e.pageX+daygrid.thegrid.scrollLeft;
	    posy = e.pageY+daygrid.thegrid.scrollTop;
	}
	else if (e.clientX || e.clientY) {
	    posx = e.clientX + document.body.scrollLeft
		+ document.documentElement.scrollLeft+daygrid.thegrid.scrollLeft;
	    posy = e.clientY + document.body.scrollTop
		+ document.documentElement.scrollTop+daygrid.thegrid.scrollTop;
	}
	var deltaX, deltaY;
	deltaX=posx-dragObj.cursorStartX;
	deltaY=posy-dragObj.cursorStartY;

	if(!dragObj.dragging && deltaX*deltaX+deltaY*deltaY > 5) { 
	    // until we move 5 pixels it's a click, not a drag
	    dragObj.dragging=true;
	}
	if(dragObj.dragging) {
	    var dy=deltaY/dragObj.pxPerPt;
	    var top=(dragObj.elStartTop+dy);
	    var bot=top+dragObj.elHeight;
	    if((top-daygrid.lineheight)*dragObj.pxPerPt<dragObj.gridStartScrollTop) {
		daygrid.thegrid.scrollTop=(top-daygrid.lineheight)*dragObj.pxPerPt;
		dragObj.gridStartScrollTop=daygrid.thegrid.scrollTop;
	    }
	    else if((bot+daygrid.lineheight-daygrid.totalheight)*dragObj.pxPerPt>dragObj.gridStartScrollTop) {
		daygrid.thegrid.scrollTop=(bot+daygrid.lineheight-daygrid.totalheight)*dragObj.pxPerPt;
		dragObj.gridStartScrollTop=daygrid.thegrid.scrollTop;
	    }
	    else
		daygrid.thegrid.scrollTop=dragObj.gridStartScrollTop;
	    if(top<0) top=0;
	    if(bot>daygrid.timeToY(24)) top=daygrid.timeToY(24)-dragObj.elHeight;
	    
	    dragObj.elNode.style.top=top+"pt";
//	    console.log("new top="+dragObj.elNode.style.top+", scrolltop="+daygrid.thegrid.scrollTop/dragObj.pxPerPt+"pt");
	}
	return stopDef(e);
    }
    // methods for selecting a range of times ... used to enter new events
    this.mouseover=function(event) {
	event=event==null?window.event:event;
	var target=event.target || event.srcElement;
	while(target.parentNode && target.nodeName!='P') target=target.parentNode;
	if(target.id.match(/^gridline[0-9]+$/)!=null) {
	    var line=parseInt(target.id.replace('gridline',''),10);
	    daygrid.mouseover_inner(line);
	    return stopDef(event);
	} 
    }
    this.mouseover_inner=function(line) {
	var aline=daygrid.absoluteline(line);
	if(this.selecting!=-1) {
	    var start=this.selecting;
	    var end=this.selecting;
	    if(aline>start) {
		end=aline;
	    } else {
		start=aline;
	    }
	    var l,al;
	    for(l=0,al=this.absoluteline(0); l<this.nlines(); l++,al++) {
		if(al>=start && al<=end) {
		    this.selected[al]=true;
		    document.getElementById("gridline"+l).className="selected";
		} else {
		    this.selected[al]=false;
		    document.getElementById("gridline"+l).className="";
		}
	    }
	}
    } 
    this.mousedown=function(event) {
	if(!AUTHENTICATED) return false;
	document.onmouseup=daygrid.mouseup;
	document.onmouseover=daygrid.mouseover;

	event = event==null?window.event:event;
	var target=event.target || event.srcElement;
	var line=parseInt(target.id.replace('gridline',''),10);

	daygrid.selecting=daygrid.absoluteline(line);
	if(event.shiftKey) {
	    var oldselectinglast=-1;
	    var oldselectingfirst=-1;
	    for(var al=0; al<Math.floor(24/daygrid.resolution+0.1); al++) {
		if(daygrid.selected[al]) {
		    if(oldselectingfirst==-1) {
			oldselectingfirst=al;
		    }
		    oldselectinglast=al;
		}
	    }
	    if(oldselectingfirst>daygrid.selecting) {
		daygrid.selecting=oldselectinglast;
	    } else if(oldselectingfirst>-1) {
		daygrid.selecting=oldselectingfirst;
	    }
	}
	daygrid.mouseover_inner(line);
	return stopDef(event);
    } 
    this.mouseup=function(e) {
	daygrid.stopscrollselect();
	e = e==null?window.event:e;
	var target=e.target || e.srcElement;
	if(target.id.match(/gridline[0-9]+/)==null) {
	    daygrid.selecting=-1;
	    daygrid.unselect();
	    return stopDef(e);
	}
	// if we have something selected, pop up the editor for a new event
	if(daygrid.selecting!=-1) {
	    daygrid.selecting=-1;
	    var event=new Event();
	    var start, finish;
	    start=-1;
	    event.event_date=new Date(daygrid.year,daygrid.month-1,daygrid.day,0,0,0,0);
	    for(var al=0; al<Math.floor(24/daygrid.resolution+0.1); al++) {
		if(daygrid.selected[al]) {
		    if(start==-1) start=al;
		    finish=al;
		}
	    }
	    if(start!=-1) {
		event.event_time=start*daygrid.resolution;
		event.event_duration=(finish-start+1)*daygrid.resolution;
		event.event_title="New Event";
		daygrid.editor.open(daygrid.events.push(event)-1);
	    }
	    daygrid.unselect();
	}
	return stopDef(e);
    } 

    // method for changing the resolution
    this.dayres=function(newres) {
	this.top=this.yToTime(this.thegrid.scrollTop/dragObj.pxPerPt);
	this.unselect();
	this.resolution=newres;
	this.paint();
    }
    // set up the select array
    this.unselect();
    // fetch and paint content
    this.xmlhttp.container=this;
    this.refreshcontent();
}

// method for dragging divs
function div_dragstart(e) {
    e= e==null?window.event:e;
    var targ, posx, posy;
    if (e.target) targ = e.target;
    else if (e.srcElement) targ = e.srcElement;
    if (targ.nodeName == 'H1') targ=targ.parentNode;
    if (targ.nodeName != 'DIV') return;
    
    dragObj.elNode=targ;
    if (e.pageX || e.pageY) {
	posx = e.pageX;
	posy = e.pageY;
    }
    else if (e.clientX || e.clientY) {
	posx = e.clientX + document.body.scrollLeft
	    + document.documentElement.scrollLeft;
	posy = e.clientY + document.body.scrollTop
	    + document.documentElement.scrollTop;
    }
	// posx and posy contain the mouse position relative to the document
    dragObj.cursorStartX = posx;
    dragObj.cursorStartY = posy;
    dragObj.elStartLeft  = parseInt(dragObj.elNode.style.left,10);
    dragObj.elStartTop   = parseInt(dragObj.elNode.style.top,10);
    document.onmousemove=div_dragmove;
    document.onmouseup=div_dragstop;
//	alert("mousedown key="+key);
    return stopDef(e);
}
function div_dragstop(e){
    document.onmousemove="";
    document.onmouseup="";
    return stopDef(e);
}
function div_dragmove(e){
    e= e==null?window.event:e;
    if (e.pageX || e.pageY) {
	posx = e.pageX;
	posy = e.pageY;
    }
    else if (e.clientX || e.clientY) {
	posx = e.clientX + document.body.scrollLeft
	    + document.documentElement.scrollLeft;
	posy = e.clientY + document.body.scrollTop
	    + document.documentElement.scrollTop;
    }
    var deltaX, deltaY;
    deltaX=posx-dragObj.cursorStartX;
    deltaY=posy-dragObj.cursorStartY;
    
    var top=(dragObj.elStartTop+deltaY);
    var left=(dragObj.elStartLeft+deltaX);
    dragObj.elNode.style.top=top+"px";
    dragObj.elNode.style.left=left+"px";
    return stopDef(e);
}

function spy(event) {
    var prop, k, v;
    prop="";
    for(k in event) {
	prop+=k+"->"+eval("event."+k)+"\n";
    }
    document.body.innerHTML="<pre>"+prop+"</pre>";
}
