/* Distributed as a part of CalendarX version > 0.4.5 */
/* minor typo fixed on line 52 (lenght => length) */
/* and modded for 0.6.2 and 0.6.3 for multimonth view */
/* and modded for 0.6.3 for fixCategories function (controls Categories checkbox behavior) */
/* and modded for 0.6.6: to read values for mouseover highlighting from CX_props_css) */

/* calendar functions */

var PREFIX = "cal";
var BGCOLOR_DEFAULT = new Array(new Array("event", "#FFFFFF"), new Array("noevent", "#FFFFFF"), new Array("outOfMonth", "#D1D1D1"));
var BGCOLOR_HIGHLIGHTED = "#FFFFFF";
var MAX_ID = 0;

var DATE_LABEL_HEIGHT = 19;
var BAR_HEIGHT = 16;
var BAR_SPACER = 1;

// Popup support
document.writeln('<scr' + 'ipt src="overlibmws/overlibmws.js">' + '<\/script>');
document.writeln('<scr' + 'ipt src="overlibmws/overlibmws_bubble.js">' + '<\/script>');

// Cross browser DOM utility methods
document.writeln('<scr' + 'ipt src="yui/yahoo/yahoo-min.js">' + '<\/script>');
document.writeln('<scr' + 'ipt src="yui/dom/dom-min.js">' + '<\/script>');

popupIdx = 0;

/**
* Functions called from pt. Calls highlightEventRange() and showCalPopup() or clearEventRange() and hideCalPopup().
*/
function mouseOverEvent(start, end, eventID) {
//highlightEventRange(start, end);
showCalPopup(eventID);
}
function mouseClickEvent(start, end, eventID) {
//highlightEventRange(start, end);
showCalPopup(eventID);
}
function mouseOutEvent(start, end, eventID) {
//clearEventRange(start, end);
hideCalPopup(eventID);
}
/**
* Added these functions to test array passing instead of a linear arrange.
* Need in multimonth views. But if they work well, I might use them everywhere.
*/
function mMmouseOverEvent(array, eventID) {
showCalPopup(eventID);
mmMhighlightEventRange(array);
}
function mMmouseOutEvent(array, eventID) {
hideCalPopup(eventID);
mmMclearEventRange(array);
}



/**
* Functions used for highlighting the time-range of an event in the current (month, week, week2, day) calendarview.
*/
function setMax(maxID) {
MAX_ID = maxID;
}
function highlightEventRange(start, end) {
for(i = start; i <= end; i++) {
if ((MAX_ID > 0) && (i > MAX_ID)) {
i = end;
}
else {
getElem("id", PREFIX + i, null).style.backgroundColor = BGCOLOR_HIGHLIGHTED;
}
}
}
function clearEventRange(start, end) {
for(i = start; i <= end; i++) {
if ((MAX_ID > 0) && (i > MAX_ID)) {
i = end;
}
else {
elem = getElem("id", PREFIX + i, null);
elemClass = getAttr("id", PREFIX + i, null, "class");
bgcolor = "";
for(j = 0; j < BGCOLOR_DEFAULT.length; j++) {
if (BGCOLOR_DEFAULT[j][0] == elemClass) {
bgcolor = BGCOLOR_DEFAULT[j][1];
j = BGCOLOR_DEFAULT.length;
}
}
getElem("id", PREFIX + i, null).style.backgroundColor = bgcolor;
}
}
}
function mMhighlightEventRange(array) {
start = 0;
end = array.length;
for(i = start; i < end ; i++) {
if ((MAX_ID > 0) && (i > MAX_ID)) {
i = end;
}
else {
getElem("id", PREFIX + array[i], null).style.backgroundColor = BGCOLOR_HIGHLIGHTED;
}
}
}
function mMclearEventRange(array) {
start = 0;
end = array.length;
for(i = start; i < end; i++) {
if ((MAX_ID > 0) && (i > MAX_ID)) {
i = end;
}
else {
elem = getElem("id", PREFIX + array[i], null);
elemClass = getAttr("id", PREFIX + array[i], null, "class");
bgcolor = "";
for(j = 0; j < BGCOLOR_DEFAULT.length; j++) {
if (BGCOLOR_DEFAULT[j][0] == elemClass) {
bgcolor = BGCOLOR_DEFAULT[j][1];
j = BGCOLOR_DEFAULT.length;
}
}
getElem("id", PREFIX + array[i], null).style.backgroundColor = bgcolor;
}
}
}
function mmMhighlightEventRange(array) {
//array is a list of pairs of start,end tags. loop through each pair as a range of integers
numpairs = array.length / 2;
for(h = 0; h < numpairs ; h++) {
start = array[2*h];
end = array[2*h+1];
for(i = start; i <= end ; i++) {
getElem("id", PREFIX + i, null).style.backgroundColor = BGCOLOR_HIGHLIGHTED;
}
}
}
function mmMclearEventRange(array) {
//array is a list of pairs of start,end tags. loop through each pair as a range of integers
numpairs = array.length / 2;
for(h = 0; h < numpairs ; h++) {
start = array[2*h];
end = array[2*h+1];
for(i = start; i <= end; i++) {
elem = getElem("id", PREFIX + i, null);
elemClass = getAttr("id", PREFIX + i, null, "class");
bgcolor = "";
for(j = 0; j < BGCOLOR_DEFAULT.length; j++) {
if (BGCOLOR_DEFAULT[j][0] == elemClass) {
bgcolor = BGCOLOR_DEFAULT[j][1];
j = BGCOLOR_DEFAULT.length;
}
}
getElem("id", PREFIX + i, null).style.backgroundColor = bgcolor;
}
}
}
/**
* function used to show/hide popup "window" (div-tag)
*/
function showCalPopup(tagID) {
OLregisterImage('gquotation',350,274,327,181,12,12,227,274)
popupIdx++;
setTimeout("if (popupIdx == "+popupIdx+") showBubblePopup('"+tagID+"')", 500)
}
function showBubblePopup(tagID) {
    overlib(getElem('id', tagID, null).innerHTML,
        BUBBLE, BUBBLETYPE, 'gquotation', STICKY, CLOSECLICK, CAPTION, ' ', 
        CGCOLOR, '#FFFFFF', CAPCOLOR, '#FFFFFF', CLOSETEXT, 
        '<img  src=\'exit.gif\' alt=\'Click to Close\' width=\'14\' height=\'13\' border=\'0\'>',
        TEXTSIZE, '100%');
}
function hideCalPopup(tagID) {
popupIdx++;
//nd(2000);
}


function fixCategories(param) {
// (1) checks/unchecks all as ViewAll is checked/unchecked // use param='ALL' in the form for the View All choice.
// (2) unchecks ViewAll if any others get checked. // use no param for all other choices in the xsub checkboxes

var catlen = document.subjectform.xsub.length;
args = fixCategories.arguments;
if (args.length == 1 && args[0] == 'ALL') { //then View All was clicked, either checked or not
if (document.subjectform.xsubALL.checked) {
for (i=0;i<catlen;i++) {
document.subjectform.xsub[i].checked = true ;
}
}
else {
for (i=0;i<catlen;i++) {
document.subjectform.xsub[i].checked = false ;
}
document.subjectform.xsubALL.checked = false ;
}
}
else {
var flag = false;
for (i=0;i<catlen;i++) {
flag = document.subjectform.xsub[i].checked ;
if (flag) break;
}
document.subjectform.xsubALL.checked = false ;
}
}



// Bar positioning Javascript below this point

var morelinks = new Array();
var timer;
var pageHasBars = false;
var calendarModel = null;
var cloneID = 0
var clones = new Array()
var cellRegions = new Array()
var isOpera = navigator.userAgent.indexOf('Opera') > -1;
var isIE = navigator.userAgent.indexOf('MSIE') > 1 && !isOpera;

function initializeModel()
{
    if (isNull(calendarModel)) {
        // initialize model object
        calendarModel = new CalendarModel(7);

        // add events to the model object
        var alldivs = document.getElementsByTagName('div');
        for (var i = 0; i < alldivs.length; i++) {
            var divid = alldivs[i].id;
            if (alldivs[i].style.display != 'none' && divid.substr(0,4) == 'evt:') {
                var openIdx = divid.indexOf(':',4);
                var dashIdx = divid.indexOf('-',openIdx);
                var closeIdx = divid.indexOf(':',dashIdx);
                var startCellNum = parseInt(divid.substring(openIdx+1,dashIdx))
                var endCellNum = parseInt(divid.substring(dashIdx+1,closeIdx))
                var startSubjects = closeIdx+1;        
                var subjects = divid.substr(startSubjects);

                // model cellNums are zero based, so subtract one from the num variables
                if (endCellNum > 0) // don't want events that end before this calendar
                {
                    if (startCellNum == 0) {// correct the "before this calendar" index
                        startCellNum = 1;
                    }
                    calendarModel.addEvent(divid, alldivs[i], subjects, startCellNum-1, endCellNum-1);
                }
            }
        }
    }
}

function redraw() {
    // IE can call this method multi-threaded--this isn't good, since the method uses global
    // resources and will mess up if called in parallel threads.  Therefore, the next section
    // uses the timer thread to collapse multi-threaded calls.  See
    // http://www.oreillynet.com/cs/user/view/cs_msg/81559 for a good explanation.
    clearTimeout(timer)
    timer=setTimeout("redrawImmediately()",1); 
}

function redrawImmediately() {
    initializeModel()
    applyFilters();
    saveFilterState();
    if (pageHasBars) {
        repositionBars();
    }
}

/* filter expand/colapse functions 
----------------------------------------------- */

function saveFilterState() {
	var btn = document.getElementById('filterkeysminimize');
	var filterState = btn.className;
	
	var allinputs = document.getElementById("filterkeys").getElementsByTagName("input");
	for(var i = 0; i < allinputs.length; i++) {
		var inputid = allinputs[i].id;
		if(inputid.substr(0,7) == 'filter:') {
			if(!allinputs[i].checked) {
				filterState += "|"+inputid.substr(7);
			}
		}
	}
	
	// cookie will expire in 30 days.  In other words, if the user doesn't hit the calendar
	// for 30 days, then we reset the filter.
	setCookie('event-calendar-filter', filterState, new Date(new Date().getTime() + (30*24*60*60*1000)), '/')
}

function loadFilterState() {
    var filterBtn = document.getElementById("filterkeysminimize");
	var filterStateCookie = getCookie('event-calendar-filter');
	if(filterStateCookie == null) {
		if(filterBtn.className != "expanded") setFiltersExpanded(filterBtn);
	}
	else {
		filterStateVals = filterStateCookie.split("|");
		var filterState = filterStateVals[0];

		var allinputs = document.getElementById("filterkeys").getElementsByTagName("input");
		for(var i = 0; i < allinputs.length; i++) {
			var inputid = allinputs[i].id;
			if(inputid.substr(0,7) == 'filter:') {
				if(filterStateCookie.indexOf("|" + inputid.substr(7)) >= 0) {
 					allinputs[i].checked = false;
					allinputs[i].parentNode.parentNode.className = "filter-key-hidden";
				}
				else {
 					allinputs[i].checked = true;
					allinputs[i].parentNode.parentNode.className = "filter-key-visible";
				}
			}
		}

		if(filterState == "collapsed") {
			if(filterBtn.className != "collapsed") setFiltersCollapsed(filterBtn);
		}
		else {
			if(filterBtn.className != "expanded") setFiltersExpanded(filterBtn);
		}
	}
}

filterList = new Array();
function toggleFilters() {
	var filterBtn = document.getElementById("filterkeysminimize");
	if(filterBtn.className == "collapsed") setFiltersExpanded(filterBtn);
	else setFiltersCollapsed(filterBtn);
	redraw();
}
function setFiltersExpanded(filterBtn) {
	for(var i = 0; i < filterList.length; i++) {
		var filterObj = document.getElementById(filterList[i]);
		filterObj.className = "expanded";
	}
	filterBtn.className = "expanded";
	filterBtn.innerHTML = "Minimize Filters";
}
function setFiltersCollapsed(filterBtn) {
	for(var i = 0; i < filterList.length; i++) {
		var filterObj = document.getElementById(filterList[i]);
		filterObj.className = "collapsed";
		removeLastFilterComma(filterObj);
	}
	filterBtn.className = "collapsed";
	filterBtn.innerHTML = "Maximize Filters";
}
function removeLastFilterComma(filterObj) {
	var divs = filterObj.getElementsByTagName("div");
	var lastVisibleItem = null;
	for(var i = divs.length-1; i >= 0; i--) {
		if(divs[i].id.indexOf("filter_visibility") >= 0) {
			var spans = divs[i].getElementsByTagName("span");
			var seperator = null;
			for(var j = 0; j < spans.length; j++) {
				if(spans[j].className == "filteritemseperator" || spans[j].className == "lastvisible") seperator = spans[j];
			}
			if(lastVisibleItem == null) {
				if(divs[i].className == "filter-key-visible") {
					lastVisibleItem = divs[i];
					if(seperator != null) seperator.className = "lastvisible";
				}
				else if(seperator != null) seperator.className = "filteritemseperator";
			}
			else if(seperator != null) seperator.className = "filteritemseperator";
		}
	}
}

function repositionBars() {
    //timeCheck('repositionBarsReal')
    clearCloneDivs();
    clearCellRegionCache();
    clearXMoreCounts();

    // determine cell height
    cell1Region = getCellRegion(1)
    var cellHeight = cell1Region.bottom - cell1Region.top;
    
    // how many bars will fit in space?
    var usableCellHeight = cellHeight - 
                           DATE_LABEL_HEIGHT - // offset for date label
                           (BAR_HEIGHT + BAR_SPACER); // offset for "+X More" link
    var barsPerCell = parseInt(usableCellHeight / (BAR_HEIGHT + BAR_SPACER));
    
    // place bars
    //timeCheck('place bars')
    var eventPositions = calendarModel.getEventPositions();
    var isfirst = true;
    for (var eventPositionIdx = 0; eventPositionIdx < eventPositions.length; eventPositionIdx++)
    {
        var eventPosition = eventPositions[eventPositionIdx];
        var div = eventPosition.eventInfo.eventObject;
        var eventBars = eventPosition.getEventBarPositions();
        //alert('eventBars: ' + eventBars)
        var originalDivPlaced = false;
        for (var eventBarIdx = 0; eventBarIdx < eventBars.length; eventBarIdx++)
        {
            var eventBar = eventBars[eventBarIdx];
            //alert('eventBar: '+eventBar.startCellNum+', '+eventBar.endCellNum+', '+eventBar.verticalPosition)
            if (eventBar != null)
            {
                //alert('verticalPosition: '+eventBar.verticalPosition+', barsPerCell: '+barsPerCell)
                var startCellNum = eventBar.startCellNum + 1;
                var endCellNum = eventBar.endCellNum + 1;
                if (eventBar.verticalPosition+1 > barsPerCell) // too many
                {
                    doesntFit(div, startCellNum, endCellNum);
                }
                else
                {
                    startRegion = getCellRegion(startCellNum);
                    endRegion = getCellRegion(endCellNum);
                    barRegion = startRegion.union(endRegion);
                    barRegion.top += DATE_LABEL_HEIGHT + (eventBar.verticalPosition * (BAR_HEIGHT + BAR_SPACER));
                    barRegion.bottom = barRegion.top + BAR_HEIGHT;

                    // clone if multiple bars for this event and original div has already been placed
                    if (originalDivPlaced)
                    {
                        var clone = div.cloneNode(true);
                        clone.id = 'clone'+(cloneID++);
                        div.parentNode.appendChild(clone);
                        clones.push(clone);
                        div = clone;
                    }

                    // set position
                    replaceClass(div, 'eventlisting-bar-filtered', 'eventlisting-bar-visible')    
                    replaceClass(div, 'eventlisting-bar-hidden', 'eventlisting-bar-visible')    
                    YAHOO.util.Dom.setXY(div, [barRegion.left, barRegion.top])
                    div.style.width = (barRegion.right - barRegion.left)+"px";
                    div.style.height = (barRegion.bottom - barRegion.top)+"px";
                    
                    isfirst = false;
                    originalDivPlaced = true;
                }
            }
        }
    }        
    //timeCheck('place bars done')
        
    // hide hidden events
    var hiddenEventInfos = calendarModel.getHiddenEventInfos();
    for (var eventInfoIdx = 0; eventInfoIdx < hiddenEventInfos.length; eventInfoIdx++)
    {
        var eventInfo = hiddenEventInfos[eventInfoIdx];
        div = eventInfo.eventObject;
        replaceClass(div, 'eventlisting-bar-hidden', 'eventlisting-bar-filtered')    
        replaceClass(div, 'eventlisting-bar-visible', 'eventlisting-bar-filtered')    
    }
    
    // make "+X more" divs visible when needed
    for (var cellnum = 1; cellnum <= 42; cellnum++)
    {
        var div = document.getElementById('more'+cellnum);
        if (!isNull(div)) 
        {
            var count = morelinks[cellnum];
            if (isNull(count)) 
            {
                replaceClass(div, 'event-more-link-visible', 'event-more-link-hidden')    
            } 
            else 
            {
                //alert(count+' more at '+cellnum)
                var cell = document.getElementById('cal'+cellnum);
                replaceClass(div, 'event-more-link-hidden', 'event-more-link-visible')    
                var link = document.getElementById('daylink'+cellnum).href;
                div.innerHTML = '<a href="'+link+'">+'+count+' More</a>';
                cellXYPos = YAHOO.util.Dom.getXY(cell);
                YAHOO.util.Dom.setXY(div, [cellXYPos[0], cellXYPos[1] + cell.offsetHeight - BAR_HEIGHT])
                div.style.width = cell.offsetWidth + "px";
                div.style.height = cell.offsetHeight + "px";
            }
        }
    }
    //timeCheck('repositionBarsReal done')
    //displayTimings();
}

function replaceClass(obj, oldclass, newclass) {
    // Replaced regex with old fashioned indexOf's, for performance
    //var regex = new RegExp(oldclass);
    //obj.className = obj.className.replace(regex, newclass);
    var cn = obj.className;
    var start = cn.indexOf(oldclass);
    if (start >= 0) {
        var end = start + oldclass.length;
        cn = cn.substring(0,start) + newclass + cn.substring(end);
        obj.className = cn;
    }
}

function doesntFit(div, startcellnum, endcellnum) {
    replaceClass(div, 'eventlisting-bar-visible', 'eventlisting-bar-hidden')    
//    div.style.visibility = 'hidden';
    for (var cellnum = startcellnum; cellnum <= endcellnum; cellnum++) {
        var count = morelinks[cellnum];
        if (count == null) {
            count = 1;
        } else {
            count++;
        }
        morelinks[cellnum] = count;
    }
}

function clearCloneDivs() {
    for (var cloneIdx = 0; cloneIdx < clones.length; cloneIdx++)
    {
        var clone = clones[cloneIdx];
        clone.parentNode.removeChild(clone);
    }
    clones = new Array()
    cloneIdx = 0;
}

prevTime = null
timeMsgs = new Array()
function timeCheck(msg)
{
    if (isNull(prevTime))
    {
        prevTime = new Date()
        timeMsgs.push(msg+': '+prevTime)
    }
    else
    {
        newTime = new Date();
        elapsed = newTime.getTime() - prevTime.getTime()
        timeMsgs.push(msg+': '+newTime+' ('+elapsed+'ms)')
        prevTime = newTime
    }
}

function displayTimings()
{
    alert(timeMsgs.join('\n'))
    timeMsgs = new Array()
    prevTime = null
}

function getCellRegion(cellNum)
{
    region = cellRegions[cellNum]
    if (isNull(region))
    {
        var cell = document.getElementById('cal'+cellNum)
        region = YAHOO.util.Dom.getRegion(cell);
        if (isIE || isOpera) {
            region.left++; // move off the left border
        } else { // Firefox
            region.right--; // move off the right border
        }
        cellRegions[cellNum] = region
        return region;
    }
    else
    {
        return region
    }
}

function clearCellRegionCache()
{
    cellRegions = new Array()
}

function clearXMoreCounts()
{
    morelinks = new Array();
}
      
function setPageHasBars(hasBars)
{
    pageHasBars = hasBars;
}

function show_advanced_event_search()
{
basic = getElem("id", "basic-event-search", null);
advanced = getElem("id", "advanced-event-search", null);
basic.style.display = "none";
advanced.style.display = "block"
if (pageHasBars == true) {
    repositionBars();
}
}

function show_basic_event_search()
{
basic = getElem("id", "basic-event-search", null);
advanced = getElem("id", "advanced-event-search", null);
basic.style.display = "block";
advanced.style.display = "none";
if (pageHasBars == true) {
    repositionBars();
}
}

function applyFilters()
{
    var allinputs = document.getElementsByTagName('input')
    for (var i = 0; i < allinputs.length; i++) {
        var inputid = allinputs[i].id;
        if (inputid.substr(0,7) == 'filter:') {
            lastColon = inputid.lastIndexOf(':')
            filterid = inputid.substr(lastColon+1)
            if (allinputs[i].checked)
            {
                calendarModel.showType(filterid);
                document.getElementById('filter_visibility:'+filterid).className = 'filter-key-visible';
            }
            else
            {
                calendarModel.hideType(filterid);
                document.getElementById('filter_visibility:'+filterid).className = 'filter-key-hidden';
            }
        }
    }
    if (!pageHasBars) {
        applyFiltersNoBars(calendarModel.hiddenTypes)
    }
}

function isHidden(eventTypes, hiddenTypes)
{
    // An event is hidden if every item in any one "type group" is a hidden type.
    // a type group is a filter category, such as region or solution.
    var typeGroups = eventTypes.split(":");
    var hidden = false;
    for (var typeGroupIdx = 0; typeGroupIdx < typeGroups.length; typeGroupIdx++) 
    {
        var typeGroup = typeGroups[typeGroupIdx];
        types = typeGroup.split("-")
        if (allInArray(hiddenTypes, types))
        {
            return true;
        }
    }
    return false;
}

function applyFiltersNoBars(hiddenTypes) 
{
    // this is a search results page--need to hide show for that here
    var alldivs = document.getElementsByTagName('div');
    for (var i = 0; i < alldivs.length; i++) 
    {
        var divid = alldivs[i].id;    
        if (divid.substr(0,4) == 'evt:') 
        {
            var openIdx = divid.indexOf(':',4);
            var dashIdx = divid.indexOf('-',openIdx);
            var closeIdx = divid.indexOf(':',dashIdx);
            var eventTypes = divid.substr(closeIdx+1);

            // An event is hidden if every item in any one "type group" is a hidden type.
            // a type group is a filter category, such as region or solution.
            if (isHidden(eventTypes, hiddenTypes))
            {
                alldivs[i].style.display = 'none';
            }
            else
            {
                alldivs[i].style.display = 'block';
            }
        }
    }
}

function EventPosition(calendarModel, eventInfo)
{
    this.calendarModel = calendarModel;
    this.eventInfo = eventInfo;
    this.eventBarPositions = new Array();
    
    this.addEventBarPosition = function(startCellNum, endCellNum, verticalPosition)
    {
        var startRow = parseInt(startCellNum / this.calendarModel.rowLength);
        var endRow = parseInt(endCellNum / this.calendarModel.rowLength);
        if (startRow != endRow)
        {
            throw("Logic Error: event bars may not span multiple rows")
        }
        //alert('adding bar at '+startCellNum+', '+endCellNum+', '+verticalPosition)
        this.eventBarPositions[startRow] = new EventBarPosition(startCellNum, endCellNum, verticalPosition);
    }

    this.getEventBarPositions = function()
    {
        return this.eventBarPositions
    }
    
    this.getVerticalPositionAtCell = function(cellNum)
    {
        var row = parseInt(cellNum / this.calendarModel.rowLength);
        var barPosition = this.eventBarPositions[row];
        if (barPosition != null)
        {
            return barPosition.verticalPosition;
        } 
        else
        {
            return -1;
        }
    }
}

function EventBarPosition(startCellNum, endCellNum, verticalPosition)
{
    this.startCellNum = startCellNum;
    this.endCellNum = endCellNum;
    this.verticalPosition = verticalPosition;
}

function EventInfo(eventName, eventObject, type, startCellNum, endCellNum)
{
    this.eventName = eventName;
    this.eventObject = eventObject;
    this.type = type;
    this.startCellNum = startCellNum;
    this.endCellNum = endCellNum;
}
    
function CalendarModel(rowLength)
{
    this.rowLength = rowLength;
    this.hiddenTypes = new Array();
    this.eventInfos = new Array();
    this.eventPositions = new Array();
    this.eventPositionsByCellNum = new Object();
    this.eventPositionsByEventName = new Object();
    this.hiddenEventInfos = new Array();
    this.stale = true;
    
    this.addEvent = function(eventName, eventObject, type, startCellNum, endCellNum)
    {
        //alert('adding event: '+startCellNum+', '+endCellNum)
        this.eventInfos.push(new EventInfo(eventName, eventObject, type, startCellNum, endCellNum));

        //alert('added event: '+this.eventInfos[this.eventInfos.length-1].startCellNum + " named " + eventName + " of type " + type)
        this.stale = true;
    }
    
    this.hideType = function(type)
    {
        if (!inArray(this.hiddenTypes, type))
        {
            this.hiddenTypes.push(type);
            this.stale = true;
        }
    }
    
    this.showType = function(type)
    {
        var index = indexOf(this.hiddenTypes, type);
        if (index >= 0)
        {
            this.hiddenTypes = 
                this.hiddenTypes.slice(0,index).concat(
                this.hiddenTypes.slice(index+1));
            this.stale = true;
        }    
    }
    
    this.calculatePositions = function()
    {
        if (this.stale)
        {
            //alert('recalculating positions')
            this.eventPositions = new Array()
            this.eventPositionsByCellNum = new Object()
            this.eventPositionsByEventName = new Object()
            this.hiddenEventInfos = new Array()
            for (var eventInfoIdx = 0; eventInfoIdx < this.eventInfos.length; eventInfoIdx++)
            {
                var eventInfo = this.eventInfos[eventInfoIdx];
                if (isHidden(eventInfo.type, this.hiddenTypes))
                {
                    this.hiddenEventInfos.push(eventInfo);
                }
                else
                {
                    //alert('startCellNum: '+eventInfo.startCellNum+', rowLength: '+this.rowLength)
                    var startRow = parseInt(eventInfo.startCellNum / this.rowLength);
                    var endRow = parseInt(eventInfo.endCellNum / this.rowLength);

                    // build position object
                    var eventPosition = new EventPosition(this, eventInfo);
                    //alert('startRow: '+startRow+', endRow: '+endRow)
                    for (var row = startRow; row <= endRow; row++)
                    {
                        var startCellNum = (row == startRow)?eventInfo.startCellNum:(rowLength * row)
                        var endCellNum = (row == endRow)?eventInfo.endCellNum:((rowLength * (row+1)) - 1);
                        var usedVerticalPositions = new Array()
                        var verticalPosition = 0;
                        for (var cellNum = startCellNum; cellNum <= endCellNum; cellNum++)
                        {
                            var positionsAtCell = this.getEventPositionsByCellNoRecalc(cellNum);
                            for (var usedPositionIdx = 0; usedPositionIdx < positionsAtCell.length; usedPositionIdx++)
                            {
                                var usedPosition = positionsAtCell[usedPositionIdx];
                                vPos = usedPosition.getVerticalPositionAtCell(cellNum);
                                usedVerticalPositions[vPos] = 1;
                            }
                        }
                        var nextAvailableVPosition = 0;
                        while (usedVerticalPositions[nextAvailableVPosition] == 1) nextAvailableVPosition++;
                        eventPosition.addEventBarPosition(startCellNum, endCellNum, nextAvailableVPosition);
                    }
                    
                    // insert it into the array
                    this.eventPositions.push(eventPosition);

                    // insert it into the "by cellnum" cache
                    for (var cellNum = eventInfo.startCellNum; cellNum <= eventInfo.endCellNum; cellNum++)
                    {
                        var positions = this.getEventPositionsByCellNoRecalc(cellNum);
                        positions.push(eventPosition);
                    }

                    // insert it into the "by name" cache
                    this.eventPositionsByEventName[eventInfo.eventName] = eventPosition;
                }
            }
            this.stale = false;
        }
    }
            
    this.getEventPositionsByCell = function(cellNum)
    {
        this.calculatePositions()
        return this.getEventPositionsByCellNoRecalc(cellNum);
    }
   
    this.getEventPositionsByCellNoRecalc = function(cellNum)
    {
        positionsAtCellNum = this.eventPositionsByCellNum[cellNum]
        if (!isArray(positionsAtCellNum))
        {
            positionsAtCellNum = new Array()
            this.eventPositionsByCellNum[cellNum] = positionsAtCellNum;
        }
        return positionsAtCellNum;
    }
    
    this.getEventPositions = function()
    {
        this.calculatePositions()
        return this.eventPositions
    }
    
    this.getHiddenEventInfos = function()
    {
        this.calculatePositions()
        return this.hiddenEventInfos;
    }
    
    this.getEventPosition = function(eventName)
    {
        this.calculatePositions()
        return this.eventPositionsByEventName[eventName]
    }
}

function indexOf(array, item)
{
    for (var index = 0; index < array.length; index++)
    {
        if (item == array[index])
        {
            return index;
        }
    }
    return -1;
}

function inArray(array, item)
{
    return indexOf(array, item) >= 0;
}

function allInArray(array, items)
{
    for (var itemIdx = 0; itemIdx < items.length; itemIdx++)
    {
        var item = items[itemIdx];
        if (!inArray(array, item))
        {
            return false
        }
    }
    return true
}

function isNull(val){return(val==null);}

function isArray(obj){return !isNull(obj) && typeof(obj.length)!="undefined";}

/**
 * Sets a Cookie with the given name and value.
 *
 * name       Name of the cookie
 * value      Value of the cookie
 * [expires]  Expiration date of the cookie (default: end of current session)
 * [path]     Path where the cookie is valid (default: path of calling document)
 * [domain]   Domain where the cookie is valid
 *              (default: domain of calling document)
 * [secure]   Boolean value indicating if the cookie transmission requires a
 *              secure transmission
 */
function setCookie(name, value, expires, path, domain, secure) {
    document.cookie= name + "=" + escape(value) +
        ((expires) ? "; expires=" + expires.toGMTString() : "") +
        ((path) ? "; path=" + path : "") +
        ((domain) ? "; domain=" + domain : "") +
        ((secure) ? "; secure" : "");
}

/**
 * Gets the value of the specified cookie.
 *
 * name  Name of the desired cookie.
 *
 * Returns a string containing value of specified cookie,
 *   or null if cookie does not exist.
 */
function getCookie(name) {
    var dc = document.cookie;
    var prefix = name + "=";
    var begin = dc.indexOf("; " + prefix);
    if (begin == -1) {
        begin = dc.indexOf(prefix);
        if (begin != 0) return null;
    } else {
        begin += 2;
    }
    var end = document.cookie.indexOf(";", begin);
    if (end == -1) {
        end = dc.length;
    }
    return unescape(dc.substring(begin + prefix.length, end));
}


