/*!
 * FullCalendar v1.4.3
 * http://arshaw.com/fullcalendar/
 *
 * Use fullcalendar.css for basic styling.
 * For event drag & drop, required jQuery UI draggable.
 * For event resizing, requires jQuery UI resizable.
 *
 * Copyright (c) 2009 Adam Shaw
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Date: Tue Dec 22 00:41:38 2009 -0800
 *
 */
 
(function($) {


    var fc = $.fullCalendar = {};
    var views = fc.views = {};


    /* Defaults
    -----------------------------------------------------------------------------*/

    var defaults = {

        // display
        defaultView: 'month',
        aspectRatio: 1.35,
        header: {
            left: 'title',
            center: '',
            right: 'today prev,next'
        },
        weekends: true,

        // editing
        //editable: false,
        //disableDragging: false,
        //disableResizing: false,

        allDayDefault: true,

        // event ajax
        startParam: 'start',
        endParam: 'end',

        // time formats
        titleFormat: {
            month: 'MMMM yyyy',
            week: "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}",
            day: 'dddd, MMM d, yyyy'
        },
        columnFormat: {
            month: 'ddd',
            week: 'ddd M/d',
            day: 'dddd M/d'
        },
        timeFormat: { // for event elements
            '': 'h(:mm)t' // default
        },

        // locale
        isRTL: false,
        firstDay: 0,
        monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        buttonText: {
            prev: '&nbsp;&#9668;&nbsp;',
            next: '&nbsp;&#9658;&nbsp;',
            prevYear: '&nbsp;&lt;&lt;&nbsp;',
            nextYear: '&nbsp;&gt;&gt;&nbsp;',
            today: 'today',
            month: 'month',
            week: 'week',
            day: 'day'
        },

        // jquery-ui theming
        theme: false,
        buttonIcons: {
            prev: 'circle-triangle-w',
            next: 'circle-triangle-e'
        }

    };

    // right-to-left defaults
    var rtlDefaults = {
        header: {
            left: 'next,prev today',
            center: '',
            right: 'title'
        },
        buttonText: {
            prev: '&nbsp;&#9658;&nbsp;',
            next: '&nbsp;&#9668;&nbsp;',
            prevYear: '&nbsp;&gt;&gt;&nbsp;',
            nextYear: '&nbsp;&lt;&lt;&nbsp;'
        },
        buttonIcons: {
            prev: 'circle-triangle-e',
            next: 'circle-triangle-w'
        }
    };

    // function for adding/overriding defaults
    var setDefaults = fc.setDefaults = function(d) {
        $.extend(true, defaults, d);
    }



    /* .fullCalendar jQuery function
    -----------------------------------------------------------------------------*/

    $.fn.fullCalendar = function(options) {

        // method calling
        if (typeof options == 'string') {
            var args = Array.prototype.slice.call(arguments, 1),
			res;
            this.each(function() {
                var r = $.data(this, 'fullCalendar')[options].apply(this, args);
                if (res == undefined) {
                    res = r;
                }
            });
            if (res != undefined) {
                return res;
            }
            return this;
        }

        // pluck the 'events' and 'eventSources' options
        var eventSources = options.eventSources || [];
        delete options.eventSources;
        if (options.events) {
            eventSources.push(options.events);
            delete options.events;
        }

        // first event source reserved for 'sticky' events
        eventSources.unshift([]);

        // initialize options
        options = $.extend(true, {},
		defaults,
		(options.isRTL || options.isRTL == undefined && defaults.isRTL) ? rtlDefaults : {},
		options
	);
        var tm = options.theme ? 'ui' : 'fc'; // for making theme classes


        this.each(function() {


            /* Instance Initialization
            -----------------------------------------------------------------------------*/

            // element
            var _element = this,
			element = $(_element).addClass('fc'),
			elementWidth,
			content = $("<div class='fc-content " + tm + "-widget-content' style='position:relative'/>").prependTo(_element), // relative for ie6
			contentHeight;
            if (options.isRTL) {
                element.addClass('fc-rtl');
            }
            if (options.theme) {
                element.addClass('ui-widget');
            }

            // view managing
            var date = new Date(),
			viewName, view, // the current view
			viewInstances = {};

            if (options.year != undefined && options.year != date.getFullYear()) {
                date.setDate(1);
                date.setMonth(0);
                date.setFullYear(options.year);
            }
            if (options.month != undefined && options.month != date.getMonth()) {
                date.setDate(1);
                date.setMonth(options.month);
            }
            if (options.date != undefined) {
                date.setDate(options.date);
            }



            /* View Rendering
            -----------------------------------------------------------------------------*/

            function changeView(v) {
                if (v != viewName) {
                    fixContentSize();
                    if (view) {
                        if (view.eventsChanged) {
                            eventsDirtyExcept(view);
                            view.eventsChanged = false;
                        }
                        view.element.hide();
                    }
                    if (viewInstances[v]) {
                        (view = viewInstances[v]).element.show();
                        if (view.shown) {
                            view.shown();
                        }
                    } else {
                        view = viewInstances[v] = $.fullCalendar.views[v](
						$("<div class='fc-view fc-view-" + v + "'/>").appendTo(content),
						options);
                    }
                    if (header) {
                        // update 'active' view button
                        header.find('div.fc-button-' + viewName).removeClass(tm + '-state-active');
                        header.find('div.fc-button-' + v).addClass(tm + '-state-active');
                    }
                    view.name = viewName = v;
                    render();
                    unfixContentSize();
                }
            }

            function render(inc, forceUpdateSize) {
                if ((elementWidth = _element.offsetWidth) !== 0) { // visible on the screen
                    if (!contentHeight) {
                        contentHeight = calculateContentHeight();
                    }
                    if (inc || !view.date || +view.date != +date) { // !view.date means it hasn't been rendered yet
                        fixContentSize();
                        view.render(date, inc || 0, contentHeight, function(callback) {
                            // dont refetch if new view contains the same events (or a subset)
                            if (!eventStart || view.visStart < eventStart || view.visEnd > eventEnd) {
                                fetchEvents(callback);
                            } else {
                                callback(events); // no refetching
                            }
                        });
                        unfixContentSize();
                        view.date = cloneDate(date);
                    }
                    else if (view.sizeDirty || forceUpdateSize) {
                        view.updateSize(contentHeight);
                        view.rerenderEvents();
                    }
                    else if (view.eventsDirty) {
                        // ensure events are rerendered if another view messed with them
                        // pass in 'events' b/c event might have been added/removed
                        view.clearEvents();
                        view.renderEvents(events);
                    }
                    if (header) {
                        // update title text
                        header.find('h2.fc-header-title').html(view.title);
                        // enable/disable 'today' button
                        var today = new Date();
                        if (today >= view.start && today < view.end) {
                            header.find('div.fc-button-today').addClass(tm + '-state-disabled');
                        } else {
                            header.find('div.fc-button-today').removeClass(tm + '-state-disabled');
                        }
                    }
                    view.sizeDirty = false;
                    view.eventsDirty = false;
                    view.trigger('viewDisplay', _element);

                    var memid = $("#MemId").val();
                    var newMonth = date.getMonth() + 1;
                    var nowDate = new Date();

                    cal.selectedMonth = newMonth;
                    cal.selectedYear = date.getFullYear();
                    var isStarted = false;
                    $('.ui-state-default').not('th').each(function() {
                        $(this).css('border', '1px solid #C5DBEC');
                    });
                    var newWidth = $('#calendar > div > div > table > thead > tr > th:first').css('width');
                    $('#calendar > div > div > table > thead > tr > th:last').css('width', newWidth);
                    WPS.BLL.WPSWebService.getAvailabilityDuringMonth(memid, newMonth, date.getFullYear(), cal.gettingAvailabilityDuringMonthSuccess);
                }
            }

            // marks other views' events as dirty
            function eventsDirtyExcept(exceptView) {
                $.each(viewInstances, function() {
                    if (this != exceptView) {
                        this.eventsDirty = true;
                    }
                });
            }

            // called when any event objects have been added/removed/changed, rerenders
            function eventsChanged() {
                view.clearEvents();
                view.renderEvents(events);
                eventsDirtyExcept(view);
            }

            // marks other views' sizes as dirty
            function sizesDirtyExcept(exceptView) {
                $.each(viewInstances, function() {
                    if (this != exceptView) {
                        this.sizeDirty = true;
                    }
                });
            }

            // called when we know the element size has changed
            function sizeChanged(fix) {
                contentHeight = calculateContentHeight();
                if (fix) {
                    fixContentSize();
                }
                view.updateSize(contentHeight);
                if (fix) {
                    unfixContentSize();
                }
                sizesDirtyExcept(view);
                view.rerenderEvents(true);
            }

            // calculate what the height of the content should be
            function calculateContentHeight() {
                if (options.contentHeight) {
                    return options.contentHeight;
                }
                else if (options.height) {
                    return options.height - (header ? header.height() : 0) - horizontalSides(content);
                }
                return elementWidth / Math.max(options.aspectRatio, .5);
            }



            /* Event Sources and Fetching
            -----------------------------------------------------------------------------*/

            var events = [],
			eventStart, eventEnd;

            // Fetch from ALL sources. Clear 'events' array and populate
            function fetchEvents(callback) {
                events = [];
                eventStart = cloneDate(view.visStart);
                eventEnd = cloneDate(view.visEnd);
                var queued = eventSources.length,
				sourceDone = function() {
				    if (--queued == 0) {
				        if (callback) {
				            callback(events);
				        }
				    }
				}, i = 0;
                for (; i < eventSources.length; i++) {
                    fetchEventSource(eventSources[i], sourceDone);
                }
            }

            // Fetch from a particular source. Append to the 'events' array
            function fetchEventSource(src, callback) {
                var prevViewName = view.name,
				prevDate = cloneDate(date),
				reportEvents = function(a) {
				    if (prevViewName == view.name && +prevDate == +date && // protects from fast switching
						$.inArray(src, eventSources) != -1) {              // makes sure source hasn't been removed
				        for (var i = 0; i < a.length; i++) {
				            normalizeEvent(a[i], options);
				            a[i].source = src;
				        }
				        events = events.concat(a);
				        if (callback) {
				            callback(a);
				        }
				    }
				},
				reportEventsAndPop = function(a) {
				    reportEvents(a);
				    popLoading();
				};
                if (typeof src == 'string') {
                    var params = {};
                    params[options.startParam] = Math.round(eventStart.getTime() / 1000);
                    params[options.endParam] = Math.round(eventEnd.getTime() / 1000);
                    if (options.cacheParam) {
                        params[options.cacheParam] = (new Date()).getTime(); // TODO: deprecate cacheParam
                    }
                    pushLoading();
                    $.ajax({
                        url: src,
                        dataType: 'json',
                        data: params,
                        cache: options.cacheParam || false, // don't let jquery prevent caching if cacheParam is being used
                        success: reportEventsAndPop
                    });
                }
                else if ($.isFunction(src)) {
                    pushLoading();
                    src(cloneDate(eventStart), cloneDate(eventEnd), reportEventsAndPop);
                }
                else {
                    reportEvents(src); // src is an array
                }
            }



            /* Loading State
            -----------------------------------------------------------------------------*/

            var loadingLevel = 0;

            function pushLoading() {
                if (!loadingLevel++) {
                    view.trigger('loading', _element, true);
                }
            }

            function popLoading() {
                if (! --loadingLevel) {
                    view.trigger('loading', _element, false);
                }
            }



            /* Public Methods
            -----------------------------------------------------------------------------*/

            var publicMethods = {

                render: function() {
                    render(0, true); // true forces size to updated
                },

                changeView: changeView,

                getView: function() {
                    return view;
                },

                getDate: function() {
                    return date;
                },

                option: function(name, value) {
                    if (value == undefined) {
                        return options[name];
                    }
                    if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
                        if (!contentSizeFixed) {
                            options[name] = value;
                            sizeChanged();
                        }
                    }
                },

                destroy: function() {
                    $(window).unbind('resize', windowResize);
                    if (header) {
                        header.remove();
                    }
                    content.remove();
                    $.removeData(_element, 'fullCalendar');
                },

                //
                // Navigation
                //

                prev: function() {
                    render(-1);
                },

                next: function() {
                    render(1);
                },

                prevYear: function() {
                    addYears(date, -1);
                    render();
                },

                nextYear: function() {
                    addYears(date, 1);
                    render();
                },

                today: function() {
                    date = new Date();
                    render();
                },

                gotoDate: function(year, month, dateNum) {
                    if (typeof year == 'object') {
                        date = cloneDate(year); // provided 1 argument, a Date
                    } else {
                        if (year != undefined) {
                            date.setFullYear(year);
                        }
                        if (month != undefined) {
                            date.setMonth(month);
                        }
                        if (dateNum != undefined) {
                            date.setDate(dateNum);
                        }
                    }
                    render();
                },

                incrementDate: function(years, months, days) {
                    if (years != undefined) {
                        addYears(date, years);
                    }
                    if (months != undefined) {
                        addMonths(date, months);
                    }
                    if (days != undefined) {
                        addDays(date, days);
                    }
                    render();
                },

                //
                // Event Manipulation
                //

                updateEvent: function(event) { // update an existing event
                    var i, len = events.length, e,
					startDelta = event.start - event._start,
					endDelta = event.end ?
						(event.end - (event._end || view.defaultEventEnd(event))) // event._end would be null if event.end
						: 0;                                                      // was null and event was just resized
                    for (i = 0; i < len; i++) {
                        e = events[i];
                        if (e._id == event._id && e != event) {
                            e.start = new Date(+e.start + startDelta);
                            if (event.end) {
                                if (e.end) {
                                    e.end = new Date(+e.end + endDelta);
                                } else {
                                    e.end = new Date(+view.defaultEventEnd(e) + endDelta);
                                }
                            } else {
                                e.end = null;
                            }
                            e.title = event.title;
                            e.url = event.url;
                            e.allDay = event.allDay;
                            e.className = event.className;
                            e.editable = event.editable;
                            normalizeEvent(e, options);
                        }
                    }
                    normalizeEvent(event, options);
                    eventsChanged();
                },

                renderEvent: function(event, stick) { // render a new event
                    normalizeEvent(event, options);
                    if (!event.source) {
                        if (stick) {
                            (event.source = eventSources[0]).push(event);
                        }
                        events.push(event);
                    }
                    eventsChanged();
                },

                removeEvents: function(filter) {
                    if (!filter) { // remove all
                        events = [];
                        // clear all array sources
                        for (var i = 0; i < eventSources.length; i++) {
                            if (typeof eventSources[i] == 'object') {
                                eventSources[i] = [];
                            }
                        }
                    } else {
                        if (!$.isFunction(filter)) { // an event ID
                            var id = filter + '';
                            filter = function(e) {
                                return e._id == id;
                            };
                        }
                        events = $.grep(events, filter, true);
                        // remove events from array sources
                        for (var i = 0; i < eventSources.length; i++) {
                            if (typeof eventSources[i] == 'object') {
                                eventSources[i] = $.grep(eventSources[i], filter, true);
                            }
                        }
                    }
                    eventsChanged();
                },

                clientEvents: function(filter) {
                    if ($.isFunction(filter)) {
                        return $.grep(events, filter);
                    }
                    else if (filter) { // an event ID
                        filter += '';
                        return $.grep(events, function(e) {
                            return e._id == filter;
                        });
                    }
                    return events; // else, return all
                },

                rerenderEvents: function() {
                    view.rerenderEvents();
                },

                //
                // Event Source
                //

                addEventSource: function(source) {
                    eventSources.push(source);
                    fetchEventSource(source, function() {
                        eventsChanged();
                    });
                },

                removeEventSource: function(source) {
                    eventSources = $.grep(eventSources, function(src) {
                        return src != source;
                    });
                    // remove all client events from that source
                    events = $.grep(events, function(e) {
                        return e.source != source;
                    });
                    eventsChanged();
                },

                refetchEvents: function() {
                    fetchEvents(eventsChanged);
                }

            };

            $.data(this, 'fullCalendar', publicMethods);



            /* Header
            -----------------------------------------------------------------------------*/

            var header,
			sections = options.header;
            if (sections) {
                header = $("<table class='fc-header'/>")
				.append($("<tr/>")
					.append($("<td class='fc-header-left'/>").append(buildSection(sections.left)))
					.append($("<td class='fc-header-center'/>").append(buildSection(sections.center)))
					.append($("<td class='fc-header-right'/>").append(buildSection(sections.right))))
				.prependTo(element);
            }
            function buildSection(buttonStr) {
                if (buttonStr) {
                    var tr = $("<tr/>");
                    $.each(buttonStr.split(' '), function(i) {
                        if (i > 0) {
                            tr.append("<td><span class='fc-header-space'/></td>");
                        }
                        var prevButton;
                        $.each(this.split(','), function(j, buttonName) {
                            if (buttonName == 'title') {
                                tr.append("<td><h2 class='fc-header-title'>&nbsp;</h2></td>");
                                if (prevButton) {
                                    prevButton.addClass(tm + '-corner-right');
                                }
                                prevButton = null;
                            } else {
                                var buttonClick;
                                if (publicMethods[buttonName]) {
                                    buttonClick = publicMethods[buttonName];
                                }
                                else if (views[buttonName]) {
                                    buttonClick = function() {
                                        button.removeClass(tm + '-state-hover');
                                        changeView(buttonName)
                                    };
                                }
                                if (buttonClick) {
                                    if (prevButton) {
                                        prevButton.addClass(tm + '-no-right');
                                    }
                                    var button,
									icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null,
									text = smartProperty(options.buttonText, buttonName);
                                    if (icon) {
                                        button = $("<div class='fc-button-" + buttonName + " ui-state-default'>" +
										"<a><span class='ui-icon ui-icon-" + icon + "'/></a></div>");
                                    }
                                    else if (text) {
                                        button = $("<div class='fc-button-" + buttonName + " " + tm + "-state-default'>" +
										"<a><span>" + text + "</span></a></div>");
                                    }
                                    if (button) {
                                        button
										.click(function() {
										    if (!button.hasClass(tm + '-state-disabled')) {
										        buttonClick();
										    }
										})
										.mousedown(function() {
										    button
												.not('.' + tm + '-state-active')
												.not('.' + tm + '-state-disabled')
												.addClass(tm + '-state-down');
										})
										.mouseup(function() {
										    button.removeClass(tm + '-state-down');
										})
										.hover(
											function() {
											    button
													.not('.' + tm + '-state-active')
													.not('.' + tm + '-state-disabled')
													.addClass(tm + '-state-hover');
											},
											function() {
											    button
													.removeClass(tm + '-state-hover')
													.removeClass(tm + '-state-down');
											}
										)
										.appendTo($("<td/>").appendTo(tr));
                                        if (prevButton) {
                                            prevButton.addClass(tm + '-no-right');
                                        } else {
                                            button.addClass(tm + '-corner-left');
                                        }
                                        prevButton = button;
                                    }
                                }
                            }
                        });
                        if (prevButton) {
                            prevButton.addClass(tm + '-corner-right');
                        }
                    });
                    return $("<table/>").append(tr);
                }
            }



            /* Resizing
            -----------------------------------------------------------------------------*/

            var contentSizeFixed = false,
			resizeCnt = 0;

            function fixContentSize() {
                if (!contentSizeFixed) {
                    contentSizeFixed = true;
                    content.css({
                        overflow: 'hidden',
                        height: contentHeight
                    });
                    // TODO: previous action might have caused scrollbars
                    // which will make the window width more narrow, possibly changing the aspect ratio
                }
            }

            function unfixContentSize() {
                if (contentSizeFixed) {
                    content.css({
                        overflow: 'visible',
                        height: ''
                    });
                    if ($.browser.msie && ($.browser.version == '6.0' || $.browser.version == '7.0')) {
                        // in IE6/7 the inside of the content div was invisible
                        // bizarre hack to get this work... need both lines
                        content[0].clientHeight;
                        content.hide().show();
                    }
                    contentSizeFixed = false;
                }
            }

            function windowResize() {
                if (!contentSizeFixed) {
                    if (view.date) { // view has already been rendered
                        var rcnt = ++resizeCnt; // add a delay
                        setTimeout(function() {
                            if (rcnt == resizeCnt && !contentSizeFixed) {
                                var newWidth = element.width();
                                if (newWidth != elementWidth) {
                                    elementWidth = newWidth;
                                    sizeChanged(true);
                                    view.trigger('windowResize', _element);
                                }
                            }
                        }, 200);
                    } else {
                        render(); // render for first time
                        // was probably in a 0x0 iframe that has just been resized
                    }
                }
            };
            $(window).resize(windowResize);


            // let's begin...
            changeView(options.defaultView);

            // in IE, when in 0x0 iframe, initial resize never gets called, so do this...
            if ($.browser.msie && !$('body').width()) {
                setTimeout(function() {
                    render();
                    content.hide().show(); // needed for IE 6
                    view.rerenderEvents(); // needed for IE 7
                }, 0);
            }

        });

        return this;

    };



    /* Important Event Utilities
    -----------------------------------------------------------------------------*/

    var fakeID = 0;

    function normalizeEvent(event, options) {
        event._id = event._id || (event.id == undefined ? '_fc' + fakeID++ : event.id + '');
        if (event.date) {
            if (!event.start) {
                event.start = event.date;
            }
            delete event.date;
        }
        event._start = cloneDate(event.start = parseDate(event.start));
        event.end = parseDate(event.end);
        if (event.end && event.end <= event.start) {
            event.end = null;
        }
        event._end = event.end ? cloneDate(event.end) : null;
        if (event.allDay == undefined) {
            event.allDay = options.allDayDefault;
        }
        if (event.className) {
            if (typeof event.className == 'string') {
                event.className = event.className.split(/\s+/);
            }
        } else {
            event.className = [];
        }
    }
    // TODO: if there is no title or start date, return false to indicate an invalid event


    /* Grid-based Views: month, basicWeek, basicDay
    -----------------------------------------------------------------------------*/

    setDefaults({
        weekMode: 'fixed'
    });

    views.month = function(element, options) {
        return new Grid(element, options, {
            render: function(date, delta, height, fetchEvents) {
                if (delta) {
                    addMonths(date, delta);
                    date.setDate(1);
                }
                // start/end
                var start = this.start = cloneDate(date, true);
                start.setDate(1);
                this.end = addMonths(cloneDate(start), 1);
                // visStart/visEnd
                var visStart = this.visStart = cloneDate(start),
				visEnd = this.visEnd = cloneDate(this.end),
				nwe = options.weekends ? 0 : 1;
                if (nwe) {
                    skipWeekend(visStart);
                    skipWeekend(visEnd, -1, true);
                }
                addDays(visStart, -((visStart.getDay() - Math.max(options.firstDay, nwe) + 7) % 7));
                addDays(visEnd, (7 - visEnd.getDay() + Math.max(options.firstDay, nwe)) % 7);
                // row count
                var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
                if (options.weekMode == 'fixed') {
                    addDays(visEnd, (6 - rowCnt) * 7);
                    rowCnt = 6;
                }
                // title
                this.title = formatDate(
				start,
				this.option('titleFormat'),
				options
			);
                // render
                this.renderGrid(
				rowCnt, options.weekends ? 7 : 5,
				this.option('columnFormat'),
				true,
				height,
				fetchEvents
			);
            }
        });
    }

    views.basicWeek = function(element, options) {
        return new Grid(element, options, {
            render: function(date, delta, height, fetchEvents) {
                if (delta) {
                    addDays(date, delta * 7);
                }
                var visStart = this.visStart = cloneDate(
					this.start = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7))
				),
				visEnd = this.visEnd = cloneDate(
					this.end = addDays(cloneDate(visStart), 7)
				);
                if (!options.weekends) {
                    skipWeekend(visStart);
                    skipWeekend(visEnd, -1, true);
                }
                this.title = formatDates(
				visStart,
				addDays(cloneDate(visEnd), -1),
				this.option('titleFormat'),
				options
			);
                this.renderGrid(
				1, options.weekends ? 7 : 5,
				this.option('columnFormat'),
				false,
				height,
				fetchEvents
			);
            }
        });
    };

    views.basicDay = function(element, options) {
        return new Grid(element, options, {
            render: function(date, delta, height, fetchEvents) {
                if (delta) {
                    addDays(date, delta);
                    if (!options.weekends) {
                        skipWeekend(date, delta < 0 ? -1 : 1);
                    }
                }
                this.title = formatDate(date, this.option('titleFormat'), options);
                this.start = this.visStart = cloneDate(date, true);
                this.end = this.visEnd = addDays(cloneDate(this.start), 1);
                this.renderGrid(1, 1, this.option('columnFormat'), false, height, fetchEvents);
            }
        });
    }


    // rendering bugs

    var tdHeightBug, rtlLeftDiff;


    function Grid(element, options, methods) {

        var tm, firstDay,
		nwe,            // no weekends (int)
		rtl, dis, dit,  // day index sign / translate
		rowCnt, colCnt,
		colWidth,
		thead, tbody,
		cachedSegs = [], //...

        // initialize superclass
	view = $.extend(this, viewMethods, methods, {
	    renderGrid: renderGrid,
	    renderEvents: renderEvents,
	    rerenderEvents: rerenderEvents,
	    updateSize: updateSize,
	    defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing
	        return cloneDate(event.start);
	    },
	    visEventEnd: function(event) { // returns exclusive 'visible' end, for rendering
	        if (event.end) {
	            var end = cloneDate(event.end);
	            return (event.allDay || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end;
	        } else {
	            return addDays(cloneDate(event.start), 1);
	        }
	    }
	});
        view.init(element, options);



        /* Grid Rendering
        -----------------------------------------------------------------------------*/


        element.addClass('fc-grid').css('position', 'relative');
        if (element.disableSelection) {
            element.disableSelection();
        }

        function renderGrid(r, c, colFormat, showNumbers, height, fetchEvents) {
            rowCnt = r;
            colCnt = c;

            // update option-derived variables
            tm = options.theme ? 'ui' : 'fc';
            nwe = options.weekends ? 0 : 1;
            firstDay = options.firstDay;
            if (rtl = options.isRTL) {
                dis = -1;
                dit = colCnt - 1;
            } else {
                dis = 1;
                dit = 0;
            }

            var month = view.start.getMonth(),
			today = clearTime(new Date()),
			s, i, j, d = cloneDate(view.visStart);

            if (!tbody) { // first time, build all cells from scratch

                var table = $("<table/>").appendTo(element);

                s = "<thead><tr>";
                for (i = 0; i < colCnt; i++) {
                    s += "<th class='fc-" +
					dayIDs[d.getDay()] + ' ' + // needs to be first
					tm + '-state-default' +
					(i == dit ? ' fc-leftmost' : '') +
					"'>" + formatDate(d, colFormat, options) + "</th>";
                    addDays(d, 1);
                    if (nwe) {
                        skipWeekend(d);
                    }
                }
                thead = $(s + "</tr></thead>").appendTo(table);

                s = "<tbody>";
                d = cloneDate(view.visStart);
                for (i = 0; i < rowCnt; i++) {
                    s += "<tr class='fc-week" + i + "'>";
                    for (j = 0; j < colCnt; j++) {
                        s += "<td class='fc-" +
						dayIDs[d.getDay()] + ' ' + // needs to be first
						tm + '-state-default fc-day' + (i * colCnt + j) +
						(j == dit ? ' fc-leftmost' : '') +
						(rowCnt > 1 && d.getMonth() != month ? ' fc-other-month' : '') +
						(+d == +today ?
						' fc-today ' + tm + '-state-highlight' :
						' fc-not-today') + "'>" +
						(showNumbers ? "<div class='fc-day-number'>" + d.getDate() + "</div>" : '') +
						"<div class='fc-day-content'><div>&nbsp;</div></div></td>";
                        addDays(d, 1);
                        if (nwe) {
                            skipWeekend(d);
                        }
                    }
                    s += "</tr>";
                }
                tbody = $(s + "</tbody>").appendTo(table);
                tbody.find('td').click(dayClick);

            } else { // NOT first time, reuse as many cells as possible

                view.clearEvents();

                var prevRowCnt = tbody.find('tr').length;
                if (rowCnt < prevRowCnt) {
                    tbody.find('tr:gt(' + (rowCnt - 1) + ')').remove(); // remove extra rows
                }
                else if (rowCnt > prevRowCnt) { // needs to create new rows...
                    s = '';
                    for (i = prevRowCnt; i < rowCnt; i++) {
                        s += "<tr class='fc-week" + i + "'>";
                        for (j = 0; j < colCnt; j++) {
                            s += "<td class='fc-" +
							dayIDs[d.getDay()] + ' ' + // needs to be first
							tm + '-state-default fc-new fc-day' + (i * colCnt + j) +
							(j == dit ? ' fc-leftmost' : '') + "'>" +
							(showNumbers ? "<div class='fc-day-number'></div>" : '') +
							"<div class='fc-day-content'><div>&nbsp;</div></div>" +
							"</td>";
                            addDays(d, 1);
                            if (nwe) {
                                skipWeekend(d);
                            }
                        }
                        s += "</tr>";
                    }
                    tbody.append(s);
                }
                tbody.find('td.fc-new').removeClass('fc-new').click(dayClick);

                // re-label and re-class existing cells
                d = cloneDate(view.visStart);
                tbody.find('td').each(function() {
                    var td = $(this);
                    if (rowCnt > 1) {
                        if (d.getMonth() == month) {
                            td.removeClass('fc-other-month');
                        } else {
                            td.addClass('fc-other-month');
                        }
                    }
                    if (+d == +today) {
                        td.removeClass('fc-not-today')
						.addClass('fc-today')
						.addClass(tm + '-state-highlight');
                    } else {
                        td.addClass('fc-not-today')
						.removeClass('fc-today')
						.removeClass(tm + '-state-highlight');
                    }
                    td.find('div.fc-day-number').text(d.getDate());
                    addDays(d, 1);
                    if (nwe) {
                        skipWeekend(d);
                    }
                });

                if (rowCnt == 1) { // more changes likely (week or day view)

                    // redo column header text and class
                    d = cloneDate(view.visStart);
                    thead.find('th').each(function() {
                        $(this).text(formatDate(d, colFormat, options));
                        this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                        addDays(d, 1);
                        if (nwe) {
                            skipWeekend(d);
                        }
                    });

                    // redo cell day-of-weeks
                    d = cloneDate(view.visStart);
                    tbody.find('td').each(function() {
                        this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                        addDays(d, 1);
                        if (nwe) {
                            skipWeekend(d);
                        }
                    });

                }

            }

            updateSize(height / 1.1);
            fetchEvents(renderEvents);

        };


        function dayClick(ev) {
            var n = parseInt(this.className.match(/fc\-day(\d+)/)[1]),
			date = addDays(
				cloneDate(view.visStart),
				Math.floor(n / colCnt) * 7 + n % colCnt
			);
            view.trigger('dayClick', this, date, true, ev);
        }


        function updateSize(height) {

            var leftTDs = tbody.find('tr td:first-child'),
			tbodyHeight = height - thead.height(),
			rowHeight1, rowHeight2;

            if (options.weekMode == 'variable') {
                rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt == 1 ? 2 : 6));
            } else {
                rowHeight1 = Math.floor(tbodyHeight / rowCnt);
                rowHeight2 = tbodyHeight - rowHeight1 * (rowCnt - 1);
            }

            reportTBody(tbody);

            if (tdHeightBug == undefined) {
                // bug in firefox where cell height includes padding
                var tr = tbody.find('tr:first'),
				td = tr.find('td:first');
                td.height(rowHeight1);
                tdHeightBug = rowHeight1 != td.height();
            }

            if (tdHeightBug) {
                leftTDs.slice(0, -1).height(rowHeight1);
                leftTDs.slice(-1).height(rowHeight2);
            } else {
                setOuterHeight(leftTDs.slice(0, -1), rowHeight1);
                setOuterHeight(leftTDs.slice(-1), rowHeight2);
            }

            setOuterWidth(
			thead.find('th').slice(0, -1),
			colWidth = Math.floor(element.width() / colCnt) / 1.1
		);

        }



        /* Event Rendering
        -----------------------------------------------------------------------------*/


        function renderEvents(events) {
            view.reportEvents(events);
            renderSegs(cachedSegs = compileSegs(events));
        }


        function rerenderEvents(skipCompile) {
            view.clearEvents();
            if (skipCompile) {
                renderSegs(cachedSegs);
            } else {
                renderEvents(view.cachedEvents);
            }
        }


        function compileSegs(events) {
            var d1 = cloneDate(view.visStart),
			d2 = addDays(cloneDate(d1), colCnt),
			rows = [],
			i = 0;
            for (; i < rowCnt; i++) {
                rows.push(stackSegs(view.sliceSegs(events, d1, d2)));
                addDays(d1, 7);
                addDays(d2, 7);
            }
            return rows;
        }


        function renderSegs(segRows) {
            var i, len = segRows.length, levels,
			tr, td,
			innerDiv,
			top,
			rowContentHeight,
			j, segs,
			levelHeight,
			k, seg,
			event,
			className,
			startElm, endElm,
			left, right,
			eventElement, eventAnchor,
			triggerRes;
            for (i = 0; i < len; i++) {
                levels = segRows[i];
                tr = tbody.find('tr:eq(' + i + ')');
                td = tr.find('td:first');
                innerDiv = td.find('div.fc-day-content div').css('position', 'relative');
                top = safePosition(innerDiv, td, tr, tbody).top;
                rowContentHeight = 0;
                for (j = 0; j < levels.length; j++) {
                    segs = levels[j];
                    levelHeight = 0;
                    for (k = 0; k < segs.length; k++) {
                        seg = segs[k];
                        event = seg.event;
                        className = 'fc-event fc-event-hori ';
                        startElm = seg.isStart ?
						tr.find('td:eq(' + ((seg.start.getDay() - Math.max(firstDay, nwe) + colCnt) % colCnt) + ') div div') :
						tbody;
                        endElm = seg.isEnd ?
						tr.find('td:eq(' + ((seg.end.getDay() - Math.max(firstDay, nwe) + colCnt - 1) % colCnt) + ') div div') :
						tbody;
                        if (rtl) {
                            left = endElm.position().left;
                            right = startElm.position().left + startElm.width();
                            if (seg.isStart) {
                                className += 'fc-corner-right ';
                            }
                            if (seg.isEnd) {
                                className += 'fc-corner-left ';
                            }
                        } else {
                            left = startElm.position().left;
                            right = endElm.position().left + endElm.width();
                            if (seg.isStart) {
                                className += 'fc-corner-left ';
                            }
                            if (seg.isEnd) {
                                className += 'fc-corner-right ';
                            }
                        }
                        eventElement = $("<div class='" + className + event.className.join(' ') + "'/>")
						.append(eventAnchor = $("<a/>")
							.append(event.allDay || !seg.isStart ? null :
								$("<span class='fc-event-time'/>")
									.html(formatDates(event.start, event.end, view.option('timeFormat'), options)))
							.append($("<span class='fc-event-title'/>")
								.text(event.title)));
                        if (event.url) {
                            eventAnchor.attr('href', event.url);
                        }
                        triggerRes = view.trigger('eventRender', event, event, eventElement);
                        if (triggerRes !== false) {
                            if (triggerRes && typeof triggerRes != 'boolean') {
                                eventElement = $(triggerRes);
                            }
                            eventElement
							.css({
							    position: 'absolute',
							    top: top,
							    left: left + (rtlLeftDiff || 0),
							    zIndex: 8
							})
							.appendTo(element);
                            setOuterWidth(eventElement, right - left, true);
                            if (rtl && rtlLeftDiff == undefined) {
                                // bug in IE6 where offsets are miscalculated with direction:rtl
                                rtlLeftDiff = left - eventElement.position().left;
                                if (rtlLeftDiff) {
                                    eventElement.css('left', left + rtlLeftDiff);
                                }
                            }
                            view.eventElementHandlers(event, eventElement);
                            if (event.editable || event.editable == undefined && options.editable) {
                                draggableEvent(event, eventElement);
                                if (seg.isEnd) {
                                    view.resizableDayEvent(event, eventElement, colWidth);
                                }
                            }
                            view.reportEventElement(event, eventElement);
                            view.trigger('eventAfterRender', event, event, eventElement);
                            levelHeight = Math.max(levelHeight, eventElement.outerHeight(true));
                        }
                    }
                    rowContentHeight += levelHeight;
                    top += levelHeight;
                }
                innerDiv.height(rowContentHeight);
            }
        }



        /* Event Dragging
        -----------------------------------------------------------------------------*/


        function draggableEvent(event, eventElement) {
            if (!options.disableDragging && eventElement.draggable) {
                var matrix;
                eventElement.draggable({
                    zIndex: 9,
                    delay: 50,
                    opacity: view.option('dragOpacity'),
                    revertDuration: options.dragRevertDuration,
                    start: function(ev, ui) {
                        view.hideEvents(event, eventElement);
                        view.trigger('eventDragStart', eventElement, event, ev, ui);
                        matrix = new HoverMatrix(function(cell) {
                            eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
                            if (cell) {
                                view.showOverlay(cell);
                            } else {
                                view.hideOverlay();
                            }
                        });
                        tbody.find('tr').each(function() {
                            matrix.row(this);
                        });
                        var tds = tbody.find('tr:first td');
                        if (rtl) {
                            tds = $(tds.get().reverse());
                        }
                        tds.each(function() {
                            matrix.col(this);
                        });
                        matrix.mouse(ev.pageX, ev.pageY);
                    },
                    drag: function(ev) {
                        matrix.mouse(ev.pageX, ev.pageY);
                    },
                    stop: function(ev, ui) {
                        view.hideOverlay();
                        view.trigger('eventDragStop', eventElement, event, ev, ui);
                        var cell = matrix.cell;
                        if (!cell || !cell.rowDelta && !cell.colDelta) {
                            if ($.browser.msie) {
                                eventElement.css('filter', ''); // clear IE opacity side-effects
                            }
                            view.showEvents(event, eventElement);
                        } else {
                            eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
                            view.eventDrop(this, event, cell.rowDelta * 7 + cell.colDelta * dis, 0, event.allDay, ev, ui);
                        }
                    }
                });
            }
        }


        // event resizing w/ 'view' methods...

    };


    /* Agenda Views: agendaWeek/agendaDay
    -----------------------------------------------------------------------------*/

    setDefaults({
        allDaySlot: true,
        allDayText: 'all-day',
        firstHour: 6,
        slotMinutes: 30,
        defaultEventMinutes: 120,
        axisFormat: 'h(:mm)tt',
        timeFormat: {
            agenda: 'h:mm{ - h:mm}'
        },
        dragOpacity: {
            agenda: .5
        },
        minTime: 0,
        maxTime: 24
    });

    views.agendaWeek = function(element, options) {
        return new Agenda(element, options, {
            render: function(date, delta, height, fetchEvents) {
                if (delta) {
                    addDays(date, delta * 7);
                }
                var visStart = this.visStart = cloneDate(
					this.start = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7))
				),
				visEnd = this.visEnd = cloneDate(
					this.end = addDays(cloneDate(visStart), 7)
				);
                if (!options.weekends) {
                    skipWeekend(visStart);
                    skipWeekend(visEnd, -1, true);
                }
                this.title = formatDates(
				visStart,
				addDays(cloneDate(visEnd), -1),
				this.option('titleFormat'),
				options
			);
                this.renderAgenda(options.weekends ? 7 : 5, this.option('columnFormat'), height, fetchEvents);
            }
        });
    };

    views.agendaDay = function(element, options) {
        return new Agenda(element, options, {
            render: function(date, delta, height, fetchEvents) {
                if (delta) {
                    addDays(date, delta);
                    if (!options.weekends) {
                        skipWeekend(date, delta < 0 ? -1 : 1);
                    }
                }
                this.title = formatDate(date, this.option('titleFormat'), options);
                this.start = this.visStart = cloneDate(date, true);
                this.end = this.visEnd = addDays(cloneDate(this.start), 1);
                this.renderAgenda(1, this.option('columnFormat'), height, fetchEvents);
            }
        });
    };

    function Agenda(element, options, methods) {

        var head, body, bodyContent, bodyTable, bg,
		colCnt,
		axisWidth, colWidth, slotHeight,
		cachedDaySegs = [], cachedSlotSegs = [],
		cachedHeight,
		tm, firstDay,
		nwe,            // no weekends (int)
		rtl, dis, dit,  // day index sign / translate
		minMinute, maxMinute,
        // ...

	view = $.extend(this, viewMethods, methods, {
	    renderAgenda: renderAgenda,
	    renderEvents: renderEvents,
	    rerenderEvents: rerenderEvents,
	    updateSize: updateSize,
	    shown: resetScroll,
	    defaultEventEnd: function(event) {
	        var start = cloneDate(event.start);
	        if (event.allDay) {
	            return start;
	        }
	        return addMinutes(start, options.defaultEventMinutes);
	    },
	    visEventEnd: function(event) {
	        if (event.allDay) {
	            if (event.end) {
	                var end = cloneDate(event.end);
	                return (event.allDay || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end;
	            } else {
	                return addDays(cloneDate(event.start), 1);
	            }
	        }
	        if (event.end) {
	            return cloneDate(event.end);
	        } else {
	            return addMinutes(cloneDate(event.start), options.defaultEventMinutes);
	        }
	    }
	});
        view.init(element, options);



        /* Time-slot rendering
        -----------------------------------------------------------------------------*/


        element.addClass('fc-agenda').css('position', 'relative');
        if (element.disableSelection) {
            element.disableSelection();
        }

        function renderAgenda(c, colFormat, height, fetchEvents) {
            colCnt = c;

            // update option-derived variables
            tm = options.theme ? 'ui' : 'fc';
            nwe = options.weekends ? 0 : 1;
            firstDay = options.firstDay;
            if (rtl = options.isRTL) {
                dis = -1;
                dit = colCnt - 1;
            } else {
                dis = 1;
                dit = 0;
            }
            minMinute = parseTime(options.minTime);
            maxMinute = parseTime(options.maxTime);

            var d0 = rtl ? addDays(cloneDate(view.visEnd), -1) : cloneDate(view.visStart),
			d = cloneDate(d0),
			today = clearTime(new Date());

            if (!head) { // first time rendering, build from scratch

                var i,
				minutes,
				slotNormal = options.slotMinutes % 15 == 0, //...

                // head
			s = "<div class='fc-agenda-head' style='position:relative;z-index:4'>" +
				"<table style='width:100%'>" +
				"<tr class='fc-first" + (options.allDaySlot ? '' : ' fc-last') + "'>" +
				"<th class='fc-leftmost " +
					tm + "-state-default'>&nbsp;</th>";
                for (i = 0; i < colCnt; i++) {
                    s += "<th class='fc-" +
					dayIDs[d.getDay()] + ' ' + // needs to be first
					tm + '-state-default' +
					"'>" + formatDate(d, colFormat, options) + "</th>";
                    addDays(d, dis);
                    if (nwe) {
                        skipWeekend(d, dis);
                    }
                }
                s += "<th class='" + tm + "-state-default'>&nbsp;</th></tr>";
                if (options.allDaySlot) {
                    s += "<tr class='fc-all-day'>" +
						"<th class='fc-axis fc-leftmost " + tm + "-state-default'>" + options.allDayText + "</th>" +
						"<td colspan='" + colCnt + "' class='" + tm + "-state-default'>" +
							"<div class='fc-day-content'><div>&nbsp;</div></div></td>" +
						"<th class='" + tm + "-state-default'>&nbsp;</th>" +
					"</tr><tr class='fc-divider fc-last'><th colspan='" + (colCnt + 2) + "' class='" +
						tm + "-state-default fc-leftmost'><div/></th></tr>";
                }
                s += "</table></div>";
                head = $(s).appendTo(element);
                head.find('td').click(slotClick);

                // body
                d = zeroDate();
                var maxd = addMinutes(cloneDate(d), maxMinute);
                addMinutes(d, minMinute);
                s = "<table>";
                for (i = 0; d < maxd; i++) {
                    minutes = d.getMinutes();
                    s += "<tr class='" +
					(i == 0 ? 'fc-first' : (minutes == 0 ? '' : 'fc-minor')) +
					"'><th class='fc-axis fc-leftmost " + tm + "-state-default'>" +
					((!slotNormal || minutes == 0) ? formatDate(d, options.axisFormat) : '&nbsp;') +
					"</th><td class='fc-slot" + i + ' ' +
						tm + "-state-default'><div>&nbsp;</div></td></tr>";
                    addMinutes(d, options.slotMinutes);
                }
                s += "</table>";
                body = $("<div class='fc-agenda-body' style='position:relative;z-index:2;overflow:auto'/>")
				.append(bodyContent = $("<div style='position:relative;overflow:hidden'>")
					.append(bodyTable = $(s)))
				.appendTo(element);
                body.find('td').click(slotClick);

                // background stripes
                d = cloneDate(d0);
                s = "<div class='fc-agenda-bg' style='position:absolute;z-index:1'>" +
				"<table style='width:100%;height:100%'><tr class='fc-first'>";
                for (i = 0; i < colCnt; i++) {
                    s += "<td class='fc-" +
					dayIDs[d.getDay()] + ' ' + // needs to be first
					tm + '-state-default ' +
					(i == 0 ? 'fc-leftmost ' : '') +
					(+d == +today ? tm + '-state-highlight fc-today' : 'fc-not-today') +
					"'><div class='fc-day-content'><div>&nbsp;</div></div></td>";
                    addDays(d, dis);
                    if (nwe) {
                        skipWeekend(d, dis);
                    }
                }
                s += "</tr></table></div>";
                bg = $(s).appendTo(element);

            } else { // skeleton already built, just modify it

                view.clearEvents();

                // redo column header text and class
                head.find('tr:first th').slice(1, -1).each(function() {
                    $(this).text(formatDate(d, colFormat, options));
                    this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                    addDays(d, dis);
                    if (nwe) {
                        skipWeekend(d, dis);
                    }
                });

                // change classes of background stripes
                d = cloneDate(d0);
                bg.find('td').each(function() {
                    this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                    if (+d == +today) {
                        $(this)
						.removeClass('fc-not-today')
						.addClass('fc-today')
						.addClass(tm + '-state-highlight');
                    } else {
                        $(this)
						.addClass('fc-not-today')
						.removeClass('fc-today')
						.removeClass(tm + '-state-highlight');
                    }
                    addDays(d, dis);
                    if (nwe) {
                        skipWeekend(d, dis);
                    }
                });

            }

            updateSize(height);
            resetScroll();
            fetchEvents(renderEvents);

        };


        function resetScroll() {
            var d0 = zeroDate(),
			scrollDate = cloneDate(d0);
            scrollDate.setHours(options.firstHour);
            var go = function() {
                body.scrollTop(timePosition(d0, scrollDate) + 1); // +1 for the border
                // TODO: +1 doesn't apply when firstHour=0
            }
            if ($.browser.opera) {
                setTimeout(go, 0); // opera 10 (and earlier?) needs this
            } else {
                go();
            }
        }


        function updateSize(height) {
            cachedHeight = height;

            bodyTable.width('');
            body.height(height - head.height());

            // need this for IE6/7. triggers clientWidth to be calculated for 
            // later user in this function. this is ridiculous
            body[0].clientWidth;

            var topTDs = head.find('tr:first th'),
			stripeTDs = bg.find('td'),
			contentWidth = body[0].clientWidth;
            bodyTable.width(contentWidth);

            // time-axis width
            axisWidth = 0;
            setOuterWidth(
			head.find('tr:lt(2) th:first').add(body.find('tr:first th'))
				.width('')
				.each(function() {
				    axisWidth = Math.max(axisWidth, $(this).outerWidth());
				}),
			axisWidth
		);

            // column width
            colWidth = Math.floor((contentWidth - axisWidth) / colCnt);
            setOuterWidth(stripeTDs.slice(0, -1), colWidth);
            setOuterWidth(topTDs.slice(1, -2), colWidth);
            setOuterWidth(topTDs.slice(-2, -1), contentWidth - axisWidth - colWidth * (colCnt - 1));

            bg.css({
                top: head.find('tr').height(),
                left: axisWidth,
                width: contentWidth - axisWidth,
                height: height
            });

            slotHeight = body.find('tr:first div').height() + 1;

            // TODO:
            //reportTBody(bodyTable.find('tbody'));
            // Opera 9.25 doesn't detect the bug when called from agenda
        }

        function slotClick(ev) {
            var col = Math.floor((ev.pageX - bg.offset().left) / colWidth),
			date = addDays(cloneDate(view.visStart), dit + dis * col),
			rowMatch = this.className.match(/fc-slot(\d+)/);
            if (rowMatch) {
                var mins = parseInt(rowMatch[1]) * options.slotMinutes,
				hours = Math.floor(mins / 60);
                date.setHours(hours);
                date.setMinutes(mins % 60 + minMinute);
                view.trigger('dayClick', this, date, false, ev);
            } else {
                view.trigger('dayClick', this, date, true, ev);
            }
        }



        /* Event Rendering
        -----------------------------------------------------------------------------*/


        function renderEvents(events) {
            view.reportEvents(events);

            var i, len = events.length,
			dayEvents = [],
			slotEvents = [];
            for (i = 0; i < len; i++) {
                if (events[i].allDay) {
                    dayEvents.push(events[i]);
                } else {
                    slotEvents.push(events[i]);
                }
            }

            renderDaySegs(cachedDaySegs = stackSegs(view.sliceSegs(dayEvents, view.visStart, view.visEnd)));
            renderSlotSegs(cachedSlotSegs = compileSlotSegs(slotEvents));
        }


        function rerenderEvents(skipCompile) {
            view.clearEvents();
            if (skipCompile) {
                renderDaySegs(cachedDaySegs);
                renderSlotSegs(cachedSlotSegs);
            } else {
                renderEvents(view.cachedEvents);
            }
        }


        function compileSlotSegs(events) {
            var d = addMinutes(cloneDate(view.visStart), minMinute),
			levels,
			segCols = [],
			i = 0;
            for (; i < colCnt; i++) {
                levels = stackSegs(view.sliceSegs(events, d, addMinutes(cloneDate(d), maxMinute - minMinute)));
                countForwardSegs(levels);
                segCols.push(levels);
                addDays(d, 1, true);
            }
            return segCols;
        }



        // renders 'all-day' events at the top

        function renderDaySegs(segRow) {
            if (options.allDaySlot) {
                var td = head.find('td'),
				tdInner = td.find('div div'),
				tr = td.parent(),
				top = safePosition(tdInner, td, tr, tr.parent()).top,
				rowContentHeight = 0,
				i, len = segRow.length, level,
				levelHeight,
				j, seg,
				event,
				className,
				leftDay, leftRounded,
				rightDay, rightRounded,
				left, right,
				eventElement, anchorElement,
				triggerRes;
                for (i = 0; i < len; i++) {
                    level = segRow[i];
                    levelHeight = 0;
                    for (j = 0; j < level.length; j++) {
                        seg = level[j];
                        event = seg.event;
                        className = 'fc-event fc-event-hori ';
                        if (rtl) {
                            leftDay = seg.end.getDay() - 1;
                            leftRounded = seg.isEnd;
                            rightDay = seg.start.getDay();
                            rightRounded = seg.isStart;
                        } else {
                            leftDay = seg.start.getDay();
                            leftRounded = seg.isStart;
                            rightDay = seg.end.getDay() - 1;
                            rightRounded = seg.isEnd;
                        }
                        if (leftRounded) {
                            className += 'fc-corner-left ';
                            left = bg.find('td:eq(' + (((leftDay - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit) + ') div div').position().left + axisWidth;
                        } else {
                            left = axisWidth;
                        }
                        if (rightRounded) {
                            className += 'fc-corner-right ';
                            right = bg.find('td:eq(' + (((rightDay - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit) + ') div div');
                            right = right.position().left + right.width() + axisWidth;
                        } else {
                            right = axisWidth + bg.width();
                        }
                        eventElement = $("<div class='" + className + event.className.join(' ') + "'/>")
						.append(anchorElement = $("<a/>")
							.append($("<span class='fc-event-title' />")
								.text(event.title)));
                        if (event.url) {
                            anchorElement.attr('href', event.url);
                        }
                        triggerRes = view.trigger('eventRender', event, event, eventElement);
                        if (triggerRes !== false) {
                            if (triggerRes && typeof triggerRes != 'boolean') {
                                eventElement = $(triggerRes);
                            }
                            eventElement
							.css({
							    position: 'absolute',
							    top: top,
							    left: left,
							    zIndex: 8
							})
							.appendTo(head);
                            setOuterWidth(eventElement, right - left, true);
                            view.eventElementHandlers(event, eventElement);
                            if (event.editable || event.editable == undefined && options.editable) {
                                draggableDayEvent(event, eventElement, seg.isStart);
                                if (seg.isEnd) {
                                    view.resizableDayEvent(event, eventElement, colWidth);
                                }
                            }
                            view.reportEventElement(event, eventElement);
                            view.trigger('eventAfterRender', event, event, eventElement);
                            levelHeight = Math.max(levelHeight, eventElement.outerHeight(true));
                        }
                    }
                    top += levelHeight;
                    rowContentHeight += levelHeight;
                }
                tdInner.height(rowContentHeight);
                updateSize(cachedHeight); // tdInner might have pushed the body down, so resize
            }
        }



        // renders events in the 'time slots' at the bottom

        function renderSlotSegs(segCols) {
            var colI, colLen = segCols.length, col,
			levelI, level,
			segI, seg,
			forward,
			event,
			top, bottom,
			tdInner,
			width, left,
			className,
			eventElement, anchorElement, timeElement, titleElement,
			triggerRes;
            for (colI = 0; colI < colLen; colI++) {
                col = segCols[colI];
                for (levelI = 0; levelI < col.length; levelI++) {
                    level = col[levelI];
                    for (segI = 0; segI < level.length; segI++) {
                        seg = level[segI];
                        forward = seg.forward || 0;
                        event = seg.event;
                        top = timePosition(seg.start, seg.start);
                        bottom = timePosition(seg.start, seg.end);
                        tdInner = bg.find('td:eq(' + (colI * dis + dit) + ') div div');
                        availWidth = tdInner.width();
                        availWidth = Math.min(availWidth - 6, availWidth * .95); // TODO: move this to CSS
                        if (levelI) {
                            // indented and thin
                            width = availWidth / (levelI + forward + 1);
                        } else {
                            if (forward) {
                                // moderately wide, aligned left still
                                width = ((availWidth / (forward + 1)) - (12 / 2)) * 2; // 12 is the predicted width of resizer =
                            } else {
                                // can be entire width, aligned left
                                width = availWidth;
                            }
                        }
                        left = axisWidth + tdInner.position().left +       // leftmost possible
						(availWidth / (levelI + forward + 1) * levelI) // indentation
						* dis + (rtl ? availWidth - width : 0);        // rtl
                        className = 'fc-event fc-event-vert ';
                        if (seg.isStart) {
                            className += 'fc-corner-top ';
                        }
                        if (seg.isEnd) {
                            className += 'fc-corner-bottom ';
                        }
                        eventElement = $("<div class='" + className + event.className.join(' ') + "' />")
						.append(anchorElement = $("<a><span class='fc-event-bg'/></a>")
							.append(timeElement = $("<span class='fc-event-time'/>")
								.text(formatDates(event.start, event.end, view.option('timeFormat'))))
							.append(titleElement = $("<span class='fc-event-title'/>")
								.text(event.title)))
                        if (event.url) {
                            anchorElement.attr('href', event.url);
                        }
                        triggerRes = view.trigger('eventRender', event, event, eventElement);
                        if (triggerRes !== false) {
                            if (triggerRes && typeof triggerRes != 'boolean') {
                                eventElement = $(triggerRes);
                            }
                            eventElement
							.css({
							    position: 'absolute',
							    zIndex: 8,
							    top: top,
							    left: left
							})
							.appendTo(bodyContent);
                            setOuterWidth(eventElement, width, true);
                            setOuterHeight(eventElement, bottom - top, true);
                            if (eventElement.height() - titleElement.position().top < 10) {
                                // event title doesn't have enough room, put next to the time
                                timeElement.text(formatDate(event.start, view.option('timeFormat')) + ' - ' + event.title);
                                titleElement.remove();
                            }
                            view.eventElementHandlers(event, eventElement);
                            if (event.editable || event.editable == undefined && options.editable) {
                                draggableSlotEvent(event, eventElement, timeElement);
                                if (seg.isEnd) {
                                    resizableSlotEvent(event, eventElement, timeElement);
                                }
                            }
                        }
                        view.reportEventElement(event, eventElement);
                        view.trigger('eventAfterRender', event, event, eventElement);
                    }
                }
            }
        }




        /* Event Dragging
        -----------------------------------------------------------------------------*/



        // when event starts out FULL-DAY

        function draggableDayEvent(event, eventElement, isStart) {
            if (!options.disableDragging && eventElement.draggable) {
                var origPosition, origWidth,
				resetElement,
				allDay = true,
				matrix;
                eventElement.draggable({
                    zIndex: 9,
                    opacity: view.option('dragOpacity', 'month'), // use whatever the month view was using
                    revertDuration: options.dragRevertDuration,
                    start: function(ev, ui) {
                        view.hideEvents(event, eventElement);
                        view.trigger('eventDragStart', eventElement, event, ev, ui);
                        origPosition = eventElement.position();
                        origWidth = eventElement.width();
                        resetElement = function() {
                            if (!allDay) {
                                eventElement
								.width(origWidth)
								.height('')
								.draggable('option', 'grid', null);
                                allDay = true;
                            }
                        };
                        matrix = new HoverMatrix(function(cell) {
                            eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
                            if (cell) {
                                if (!cell.row) { // on full-days
                                    resetElement();
                                    view.showOverlay(cell);
                                } else { // mouse is over bottom slots
                                    if (isStart && allDay) {
                                        // convert event to temporary slot-event
                                        setOuterHeight(
										eventElement.width(colWidth - 10), // don't use entire width
										slotHeight * Math.round(
											(event.end ? ((event.end - event.start) / MINUTE_MS) : options.defaultEventMinutes)
											/ options.slotMinutes)
									);
                                        eventElement.draggable('option', 'grid', [colWidth, 1]);
                                        allDay = false;
                                    }
                                    view.hideOverlay();
                                }
                            } else { // mouse is outside of everything
                                view.hideOverlay();
                            }
                        });
                        matrix.row(head.find('td'));
                        bg.find('td').each(function() {
                            matrix.col(this);
                        });
                        matrix.row(body);
                        matrix.mouse(ev.pageX, ev.pageY);
                    },
                    drag: function(ev, ui) {
                        matrix.mouse(ev.pageX, ev.pageY);
                    },
                    stop: function(ev, ui) {
                        view.hideOverlay();
                        view.trigger('eventDragStop', eventElement, event, ev, ui);
                        var cell = matrix.cell,
						dayDelta = dis * (
							allDay ? // can't trust cell.colDelta when using slot grid
							(cell ? cell.colDelta : 0) :
							Math.floor((ui.position.left - origPosition.left) / colWidth)
						);
                        if (!cell || !dayDelta && !cell.rowDelta) {
                            // over nothing (has reverted)
                            resetElement();
                            if ($.browser.msie) {
                                eventElement.css('filter', ''); // clear IE opacity side-effects
                            }
                            view.showEvents(event, eventElement);
                        } else {
                            eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
                            view.eventDrop(
							this, event, dayDelta,
							allDay ? 0 : // minute delta
								Math.round((eventElement.offset().top - bodyContent.offset().top) / slotHeight)
								* options.slotMinutes
								+ minMinute
								- (event.start.getHours() * 60 + event.start.getMinutes()),
							allDay, ev, ui
						);
                        }
                    }
                });
            }
        }



        // when event starts out IN TIMESLOTS

        function draggableSlotEvent(event, eventElement, timeElement) {
            if (!options.disableDragging && eventElement.draggable) {
                var origPosition,
				resetElement,
				prevSlotDelta, slotDelta,
				allDay = false,
				matrix;
                eventElement.draggable({
                    zIndex: 9,
                    scroll: false,
                    grid: [colWidth, slotHeight],
                    axis: colCnt == 1 ? 'y' : false,
                    opacity: view.option('dragOpacity'),
                    revertDuration: options.dragRevertDuration,
                    start: function(ev, ui) {
                        view.hideEvents(event, eventElement);
                        view.trigger('eventDragStart', eventElement, event, ev, ui);
                        if ($.browser.msie) {
                            eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
                        }
                        origPosition = eventElement.position();
                        resetElement = function() {
                            // convert back to original slot-event
                            if (allDay) {
                                timeElement.css('display', ''); // show() was causing display=inline
                                eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
                                allDay = false;
                            }
                        };
                        prevSlotDelta = 0;
                        matrix = new HoverMatrix(function(cell) {
                            eventElement.draggable('option', 'revert', !cell);
                            if (cell) {
                                if (!cell.row && options.allDaySlot) { // over full days
                                    if (!allDay) {
                                        // convert to temporary all-day event
                                        allDay = true;
                                        timeElement.hide();
                                        eventElement.draggable('option', 'grid', null);
                                    }
                                    view.showOverlay(cell);
                                } else { // on slots
                                    resetElement();
                                    view.hideOverlay();
                                }
                            } else {
                                view.hideOverlay();
                            }
                        });
                        if (options.allDaySlot) {
                            matrix.row(head.find('td'));
                        }
                        bg.find('td').each(function() {
                            matrix.col(this);
                        });
                        matrix.row(body);
                        matrix.mouse(ev.pageX, ev.pageY);
                    },
                    drag: function(ev, ui) {
                        slotDelta = Math.round((ui.position.top - origPosition.top) / slotHeight);
                        if (slotDelta != prevSlotDelta) {
                            if (!allDay) {
                                // update time header
                                var minuteDelta = slotDelta * options.slotMinutes,
								newStart = addMinutes(cloneDate(event.start), minuteDelta),
								newEnd;
                                if (event.end) {
                                    newEnd = addMinutes(cloneDate(event.end), minuteDelta);
                                }
                                timeElement.text(formatDates(newStart, newEnd, view.option('timeFormat')));
                            }
                            prevSlotDelta = slotDelta;
                        }
                        matrix.mouse(ev.pageX, ev.pageY);
                    },
                    stop: function(ev, ui) {
                        view.hideOverlay();
                        view.trigger('eventDragStop', eventElement, event, ev, ui);
                        var cell = matrix.cell,
						dayDelta = dis * (
							allDay ? // can't trust cell.colDelta when using slot grid
							(cell ? cell.colDelta : 0) :
							Math.floor((ui.position.left - origPosition.left) / colWidth)
						);
                        if (!cell || !slotDelta && !dayDelta) {
                            resetElement();
                            if ($.browser.msie) {
                                eventElement
								.css('filter', '') // clear IE opacity side-effects
								.find('span.fc-event-bg').css('display', ''); // .show() made display=inline
                            }
                            eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
                            view.showEvents(event, eventElement);
                        } else {
                            view.eventDrop(
							this, event, dayDelta,
							allDay ? 0 : slotDelta * options.slotMinutes, // minute delta
							allDay, ev, ui
						);
                        }
                    }
                });
            }
        }




        /* Event Resizing
        -----------------------------------------------------------------------------*/

        // for TIMESLOT events

        function resizableSlotEvent(event, eventElement, timeElement) {
            if (!options.disableResizing && eventElement.resizable) {
                var slotDelta, prevSlotDelta;
                eventElement
				.resizable({
				    handles: 's',
				    grid: slotHeight,
				    start: function(ev, ui) {
				        slotDelta = prevSlotDelta = 0;
				        view.hideEvents(event, eventElement);
				        if ($.browser.msie && $.browser.version == '6.0') {
				            eventElement.css('overflow', 'hidden');
				        }
				        eventElement.css('z-index', 9);
				        view.trigger('eventResizeStart', this, event, ev, ui);
				    },
				    resize: function(ev, ui) {
				        // don't rely on ui.size.height, doesn't take grid into account
				        slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
				        if (slotDelta != prevSlotDelta) {
				            timeElement.text(
								formatDates(
									event.start,
									(!slotDelta && !event.end) ? null : // no change, so don't display time range
										addMinutes(view.eventEnd(event), options.slotMinutes * slotDelta),
									view.option('timeFormat')
								)
							);
				            prevSlotDelta = slotDelta;
				        }
				    },
				    stop: function(ev, ui) {
				        view.trigger('eventResizeStop', this, event, ev, ui);
				        if (slotDelta) {
				            view.eventResize(this, event, 0, options.slotMinutes * slotDelta, ev, ui);
				        } else {
				            eventElement.css('z-index', 8);
				            view.showEvents(event, eventElement);
				            // BUG: if event was really short, need to put title back in span
				        }
				    }
				})
				.find('div.ui-resizable-s').text('=');
            }
        }


        // ALL-DAY event resizing w/ 'view' methods...




        /* Misc
        -----------------------------------------------------------------------------*/

        // get the Y coordinate of the given time on the given day (both Date objects)

        function timePosition(day, time) { // both date object. day holds 00:00 of current day
            day = cloneDate(day, true);
            if (time < addMinutes(cloneDate(day), minMinute)) {
                return 0;
            }
            if (time >= addMinutes(cloneDate(day), maxMinute)) {
                return bodyContent.height();
            }
            var slotMinutes = options.slotMinutes,
			minutes = time.getHours() * 60 + time.getMinutes() - minMinute,
			slotI = Math.floor(minutes / slotMinutes),
			tr = body.find('tr:eq(' + slotI + ')'),
			td = tr.find('td'),
			innerDiv = td.find('div');
            return Math.max(0, Math.round(
			safePosition(innerDiv, td, tr, tr.parent()).top - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
		));
        }

    }


    // count the number of colliding, higher-level segments (for event squishing)

    function countForwardSegs(levels) {
        var i, j, k, level, segForward, segBack;
        for (i = levels.length - 1; i > 0; i--) {
            level = levels[i];
            for (j = 0; j < level.length; j++) {
                segForward = level[j];
                for (k = 0; k < levels[i - 1].length; k++) {
                    segBack = levels[i - 1][k];
                    if (segsCollide(segForward, segBack)) {
                        segBack.forward = Math.max(segBack.forward || 0, (segForward.forward || 0) + 1);
                    }
                }
            }
        }
    }


    /* Methods & Utilities for All Views
    -----------------------------------------------------------------------------*/

    var viewMethods = {

        // TODO: maybe change the 'vis' variables to 'excl'

        /*
        * Objects inheriting these methods must implement the following properties/methods:
        * - title
        * - start
        * - end
        * - visStart
        * - visEnd
        * - defaultEventEnd(event)
        * - visEventEnd(event)
        * - render(events)
        * - rerenderEvents()
        *
        *
        * z-index reservations:
        * 3 - day-overlay
        * 8 - events
        * 9 - dragging/resizing events
        *
        */



        init: function(element, options) {
            this.element = element;
            this.options = options;
            this.cachedEvents = [];
            this.eventsByID = {};
            this.eventElements = [];
            this.eventElementsByID = {};
        },



        // triggers an event handler, always append view as last arg

        trigger: function(name, thisObj) {
            if (this.options[name]) {
                return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this]));
            }
        },



        // returns a Date object for an event's end

        eventEnd: function(event) {
            return event.end ? cloneDate(event.end) : this.defaultEventEnd(event); // TODO: make sure always using copies
        },



        // report when view receives new events

        reportEvents: function(events) { // events are already normalized at this point
            var i, len = events.length, event,
			eventsByID = this.eventsByID = {},
			cachedEvents = this.cachedEvents = [];
            for (i = 0; i < len; i++) {
                event = events[i];
                if (eventsByID[event._id]) {
                    eventsByID[event._id].push(event);
                } else {
                    eventsByID[event._id] = [event];
                }
                cachedEvents.push(event);
            }
        },



        // report when view creates an element for an event

        reportEventElement: function(event, element) {
            this.eventElements.push(element);
            var eventElementsByID = this.eventElementsByID;
            if (eventElementsByID[event._id]) {
                eventElementsByID[event._id].push(element);
            } else {
                eventElementsByID[event._id] = [element];
            }
        },



        // event element manipulation

        clearEvents: function() { // only remove ELEMENTS
            $.each(this.eventElements, function() {
                this.remove();
            });
            this.eventElements = [];
            this.eventElementsByID = {};
        },

        showEvents: function(event, exceptElement) {
            this._eee(event, exceptElement, 'show');
        },

        hideEvents: function(event, exceptElement) {
            this._eee(event, exceptElement, 'hide');
        },

        _eee: function(event, exceptElement, funcName) { // event-element-each
            var elements = this.eventElementsByID[event._id],
			i, len = elements.length;
            for (i = 0; i < len; i++) {
                if (elements[i] != exceptElement) {
                    elements[i][funcName]();
                }
            }
        },



        // event modification reporting

        eventDrop: function(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
            var view = this,
			oldAllDay = event.allDay;
            view.moveEvents(view.eventsByID[event._id], dayDelta, minuteDelta, allDay);
            view.trigger('eventDrop', e, event, dayDelta, minuteDelta, allDay, function() { // TODO: change docs
                // TODO: investigate cases where this inverse technique might not work
                view.moveEvents(view.eventsByID[event._id], -dayDelta, -minuteDelta, oldAllDay);
                view.rerenderEvents();
            }, ev, ui);
            view.eventsChanged = true;
            view.rerenderEvents();
        },

        eventResize: function(e, event, dayDelta, minuteDelta, ev, ui) {
            var view = this;
            view.elongateEvents(view.eventsByID[event._id], dayDelta, minuteDelta);
            view.trigger('eventResize', e, event, dayDelta, minuteDelta, function() {
                // TODO: investigate cases where this inverse technique might not work
                view.elongateEvents(view.eventsByID[event._id], -dayDelta, -minuteDelta);
                view.rerenderEvents();
            }, ev, ui);
            view.eventsChanged = true;
            view.rerenderEvents();
        },



        // event modification

        moveEvents: function(events, dayDelta, minuteDelta, allDay) {
            minuteDelta = minuteDelta || 0;
            for (var e, len = events.length, i = 0; i < len; i++) {
                e = events[i];
                if (allDay != undefined) {
                    e.allDay = allDay;
                }
                addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
                if (e.end) {
                    e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
                }
                normalizeEvent(e, this.options);
            }
        },

        elongateEvents: function(events, dayDelta, minuteDelta) {
            minuteDelta = minuteDelta || 0;
            for (var e, len = events.length, i = 0; i < len; i++) {
                e = events[i];
                e.end = addMinutes(addDays(this.eventEnd(e), dayDelta, true), minuteDelta);
                normalizeEvent(e, this.options);
            }
        },



        // semi-transparent overlay (while dragging)

        showOverlay: function(props) {
            if (!this.dayOverlay) {
                this.dayOverlay = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3;display:none'/>")
				.appendTo(this.element);
            }
            var o = this.element.offset();
            this.dayOverlay
			.css({
			    top: props.top - o.top,
			    left: props.left - o.left,
			    width: props.width,
			    height: props.height
			})
			.show();
        },

        hideOverlay: function() {
            if (this.dayOverlay) {
                this.dayOverlay.hide();
            }
        },



        // common horizontal event resizing

        resizableDayEvent: function(event, eventElement, colWidth) {
            var view = this;
            if (!view.options.disableResizing && eventElement.resizable) {
                eventElement.resizable({
                    handles: view.options.isRTL ? 'w' : 'e',
                    grid: colWidth,
                    minWidth: colWidth / 2, // need this or else IE throws errors when too small
                    containment: view.element.parent().parent(), // the main element...
                    // ... a fix. wouldn't allow extending to last column in agenda views (jq ui bug?)
                    start: function(ev, ui) {
                        eventElement.css('z-index', 9);
                        view.hideEvents(event, eventElement);
                        view.trigger('eventResizeStart', this, event, ev, ui);
                    },
                    stop: function(ev, ui) {
                        view.trigger('eventResizeStop', this, event, ev, ui);
                        // ui.size.width wasn't working with grid correctly, use .width()
                        var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth);
                        if (dayDelta) {
                            view.eventResize(this, event, dayDelta, 0, ev, ui);
                        } else {
                            eventElement.css('z-index', 8);
                            view.showEvents(event, eventElement);
                        }
                    }
                });
            }
        },



        // attaches eventClick, eventMouseover, eventMouseout

        eventElementHandlers: function(event, eventElement) {
            var view = this;
            eventElement
			.click(function(ev) {
			    if (!eventElement.hasClass('ui-draggable-dragging') &&
					!eventElement.hasClass('ui-resizable-resizing')) {
			        return view.trigger('eventClick', this, event, ev);
			    }
			})
			.hover(
				function(ev) {
				    view.trigger('eventMouseover', this, event, ev);
				},
				function(ev) {
				    view.trigger('eventMouseout', this, event, ev);
				}
			);
        },



        // get a property from the 'options' object, using smart view naming

        option: function(name, viewName) {
            var v = this.options[name];
            if (typeof v == 'object') {
                return smartProperty(v, viewName || this.name);
            }
            return v;
        },



        // event rendering utilities

        sliceSegs: function(events, start, end) {
            var segs = [],
			i, len = events.length, event,
			eventStart, eventEnd,
			segStart, segEnd,
			isStart, isEnd;
            for (i = 0; i < len; i++) {
                event = events[i];
                eventStart = event.start;
                eventEnd = this.visEventEnd(event);
                if (eventEnd > start && eventStart < end) {
                    if (eventStart < start) {
                        segStart = cloneDate(start);
                        isStart = false;
                    } else {
                        segStart = eventStart;
                        isStart = true;
                    }
                    if (eventEnd > end) {
                        segEnd = cloneDate(end);
                        isEnd = false;
                    } else {
                        segEnd = eventEnd;
                        isEnd = true;
                    }
                    segs.push({
                        event: event,
                        start: segStart,
                        end: segEnd,
                        isStart: isStart,
                        isEnd: isEnd,
                        msLength: segEnd - segStart
                    });
                }
            }
            return segs.sort(segCmp);
        }


    };




    // event rendering calculation utilities

    function stackSegs(segs) {
        var levels = [],
		i, len = segs.length, seg,
		j, collide, k;
        for (i = 0; i < len; i++) {
            seg = segs[i];
            j = 0; // the level index where seg should belong
            while (true) {
                collide = false;
                if (levels[j]) {
                    for (k = 0; k < levels[j].length; k++) {
                        if (segsCollide(levels[j][k], seg)) {
                            collide = true;
                            break;
                        }
                    }
                }
                if (collide) {
                    j++;
                } else {
                    break;
                }
            }
            if (levels[j]) {
                levels[j].push(seg);
            } else {
                levels[j] = [seg];
            }
        }
        return levels;
    }

    function segCmp(a, b) {
        return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
    }

    function segsCollide(seg1, seg2) {
        return seg1.end > seg2.start && seg1.start < seg2.end;
    }


    /* Date Math
    -----------------------------------------------------------------------------*/

    var DAY_MS = 86400000,
	HOUR_MS = 3600000,
	MINUTE_MS = 60000;

    function addYears(d, n, keepTime) {
        d.setFullYear(d.getFullYear() + n);
        if (!keepTime) {
            clearTime(d);
        }
        return d;
    }

    function addMonths(d, n, keepTime) { // prevents day overflow/underflow
        if (+d) { // prevent infinite looping on invalid dates
            var m = d.getMonth() + n,
			check = cloneDate(d);
            check.setDate(1);
            check.setMonth(m);
            d.setMonth(m);
            if (!keepTime) {
                clearTime(d);
            }
            while (d.getMonth() != check.getMonth()) {
                d.setDate(d.getDate() + (d < check ? 1 : -1));
            }
        }
        return d;
    }

    function addDays(d, n, keepTime) { // deals with daylight savings
        if (+d) {
            var dd = d.getDate() + n,
			check = cloneDate(d);
            check.setHours(9); // set to middle of day
            check.setDate(dd);
            d.setDate(dd);
            if (!keepTime) {
                clearTime(d);
            }
            fixDate(d, check);
        }
        return d;
    }
    fc.addDays = addDays;

    function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
        if (+d) { // prevent infinite looping on invalid dates
            while (d.getDate() != check.getDate()) {
                d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
            }
        }
    }

    function addMinutes(d, n) {
        d.setMinutes(d.getMinutes() + n);
        return d;
    }

    function clearTime(d) {
        d.setHours(0);
        d.setMinutes(0);
        d.setSeconds(0);
        d.setMilliseconds(0);
        return d;
    }

    function cloneDate(d, dontKeepTime) {
        if (dontKeepTime) {
            return clearTime(new Date(+d));
        }
        return new Date(+d);
    }

    function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
        var i = 0, d;
        do {
            d = new Date(1970, i++, 1);
        } while (d.getHours() != 0);
        return d;
    }

    function skipWeekend(date, inc, excl) {
        inc = inc || 1;
        while (date.getDay() == 0 || (excl && date.getDay() == 1 || !excl && date.getDay() == 6)) {
            addDays(date, inc);
        }
        return date;
    }



    /* Date Parsing
    -----------------------------------------------------------------------------*/

    var parseDate = fc.parseDate = function(s) {
        if (typeof s == 'object') { // already a Date object
            return s;
        }
        if (typeof s == 'number') { // a UNIX timestamp
            return new Date(s * 1000);
        }
        if (typeof s == 'string') {
            if (s.match(/^\d+$/)) { // a UNIX timestamp
                return new Date(parseInt(s) * 1000);
            }
            return parseISO8601(s, true) || (s ? new Date(s) : null);
        }
        // TODO: never return invalid dates (like from new Date(<string>)), return null instead
        return null;
    }

    var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) {
        // derived from http://delete.me.uk/2005/03/iso8601.html
        // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
        var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/);
        if (!m) {
            return null;
        }
        var date = new Date(m[1], 0, 1),
		check = new Date(m[1], 0, 1, 9, 0),
		offset = 0;
        if (m[3]) {
            date.setMonth(m[3] - 1);
            check.setMonth(m[3] - 1);
        }
        if (m[5]) {
            date.setDate(m[5]);
            check.setDate(m[5]);
        }
        fixDate(date, check);
        if (m[7]) {
            date.setHours(m[7]);
        }
        if (m[8]) {
            date.setMinutes(m[8]);
        }
        if (m[10]) {
            date.setSeconds(m[10]);
        }
        if (m[12]) {
            date.setMilliseconds(Number("0." + m[12]) * 1000);
        }
        fixDate(date, check);
        if (!ignoreTimezone) {
            if (m[14]) {
                offset = Number(m[16]) * 60 + Number(m[17]);
                offset *= m[15] == '-' ? 1 : -1;
            }
            offset -= date.getTimezoneOffset();
        }
        return new Date(+date + (offset * 60 * 1000));
    }

    var parseTime = fc.parseTime = function(s) { // returns minutes since start of day
        if (typeof s == 'number') { // an hour
            return s * 60;
        }
        if (typeof s == 'object') { // a Date object
            return s.getHours() * 60 + s.getMinutes();
        }
        var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
        if (m) {
            var h = parseInt(m[1]);
            if (m[3]) {
                h %= 12;
                if (m[3].toLowerCase().charAt(0) == 'p') {
                    h += 12;
                }
            }
            return h * 60 + (m[2] ? parseInt(m[2]) : 0);
        }
    };



    /* Date Formatting
    -----------------------------------------------------------------------------*/

    var formatDate = fc.formatDate = function(date, format, options) {
        return formatDates(date, null, format, options);
    }

    var formatDates = fc.formatDates = function(date1, date2, format, options) {
        options = options || defaults;
        var date = date1,
		otherDate = date2,
		i, len = format.length, c,
		i2, formatter,
		res = '';
        for (i = 0; i < len; i++) {
            c = format.charAt(i);
            if (c == "'") {
                for (i2 = i + 1; i2 < len; i2++) {
                    if (format.charAt(i2) == "'") {
                        if (date) {
                            if (i2 == i + 1) {
                                res += "'";
                            } else {
                                res += format.substring(i + 1, i2);
                            }
                            i = i2;
                        }
                        break;
                    }
                }
            }
            else if (c == '(') {
                for (i2 = i + 1; i2 < len; i2++) {
                    if (format.charAt(i2) == ')') {
                        var subres = formatDate(date, format.substring(i + 1, i2), options);
                        if (parseInt(subres.replace(/\D/, ''))) {
                            res += subres;
                        }
                        i = i2;
                        break;
                    }
                }
            }
            else if (c == '[') {
                for (i2 = i + 1; i2 < len; i2++) {
                    if (format.charAt(i2) == ']') {
                        var subformat = format.substring(i + 1, i2);
                        var subres = formatDate(date, subformat, options);
                        if (subres != formatDate(otherDate, subformat, options)) {
                            res += subres;
                        }
                        i = i2;
                        break;
                    }
                }
            }
            else if (c == '{') {
                date = date2;
                otherDate = date1;
            }
            else if (c == '}') {
                date = date1;
                otherDate = date2;
            }
            else {
                for (i2 = len; i2 > i; i2--) {
                    if (formatter = dateFormatters[format.substring(i, i2)]) {
                        if (date) {
                            res += formatter(date, options);
                        }
                        i = i2 - 1;
                        break;
                    }
                }
                if (i2 == i) {
                    if (date) {
                        res += c;
                    }
                }
            }
        }
        return res;
    }

    var dateFormatters = {
        s: function(d) { return d.getSeconds() },
        ss: function(d) { return zeroPad(d.getSeconds()) },
        m: function(d) { return d.getMinutes() },
        mm: function(d) { return zeroPad(d.getMinutes()) },
        h: function(d) { return d.getHours() % 12 || 12 },
        hh: function(d) { return zeroPad(d.getHours() % 12 || 12) },
        H: function(d) { return d.getHours() },
        HH: function(d) { return zeroPad(d.getHours()) },
        d: function(d) { return d.getDate() },
        dd: function(d) { return zeroPad(d.getDate()) },
        ddd: function(d, o) { return o.dayNamesShort[d.getDay()] },
        dddd: function(d, o) { return o.dayNames[d.getDay()] },
        M: function(d) { return d.getMonth() + 1 },
        MM: function(d) { return zeroPad(d.getMonth() + 1) },
        MMM: function(d, o) { return o.monthNamesShort[d.getMonth()] },
        MMMM: function(d, o) { return o.monthNames[d.getMonth()] },
        yy: function(d) { return (d.getFullYear() + '').substring(2) },
        yyyy: function(d) { return d.getFullYear() },
        t: function(d) { return d.getHours() < 12 ? 'a' : 'p' },
        tt: function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
        T: function(d) { return d.getHours() < 12 ? 'A' : 'P' },
        TT: function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
        u: function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
        S: function(d) {
            var date = d.getDate();
            if (date > 10 && date < 20) return 'th';
            return ['st', 'nd', 'rd'][date % 10 - 1] || 'th';
        }
    };



    /* Element Dimensions
    -----------------------------------------------------------------------------*/

    function setOuterWidth(element, width, includeMargins) {
        element.each(function() {
            var e = $(this);
            var w = width - horizontalSides(e);
            if (includeMargins) {
                w -= (parseInt(e.css('margin-left')) || 0) +
				(parseInt(e.css('margin-right')) || 0);
            }
            e.width(w);
        });
    }

    function horizontalSides(e) {
        return (parseInt(e.css('border-left-width')) || 0) +
		(parseInt(e.css('padding-left')) || 0) +
		(parseInt(e.css('padding-right')) || 0) +
		(parseInt(e.css('border-right-width')) || 0);
    }

    function setOuterHeight(element, height, includeMargins) {
        element.each(function() {
            var e = $(this);
            var h = height - verticalSides(e);
            if (includeMargins) {
                h -= (parseInt(e.css('margin-top')) || 0) +
				(parseInt(e.css('margin-bottom')) || 0);
            }
            e.height(h);
        });
    }

    function verticalSides(e) {
        return (parseInt(e.css('border-top-width')) || 0) +
		(parseInt(e.css('padding-top')) || 0) +
		(parseInt(e.css('padding-bottom')) || 0) +
		(parseInt(e.css('border-bottom-width')) || 0);
    }



    /* Position Calculation
    -----------------------------------------------------------------------------*/
    // nasty bugs in opera 9.25
    // position() returning relative to direct parent

    var operaPositionBug;

    function reportTBody(tbody) {
        if (operaPositionBug == undefined) {
            operaPositionBug = tbody.position().top != tbody.find('tr').position().top;
        }
    }

    function safePosition(element, td, tr, tbody) {
        var position = element.position();
        if (operaPositionBug) {
            position.top += tbody.position().top + tr.position().top - td.position().top;
        }
        return position;
    }



    /* Hover Matrix
    -----------------------------------------------------------------------------*/

    function HoverMatrix(changeCallback) {

        var tops = [], lefts = [],
		prevRowE, prevColE,
		origRow, origCol,
		currRow, currCol;

        this.row = function(e, topBug) {
            prevRowE = $(e);
            tops.push(prevRowE.offset().top + (
			(operaPositionBug && prevRowE.is('tr')) ? prevRowE.parent().position().top : 0
		));
        };

        this.col = function(e) {
            prevColE = $(e);
            lefts.push(prevColE.offset().left);
        };

        this.mouse = function(x, y) {
            if (origRow == undefined) {
                tops.push(tops[tops.length - 1] + prevRowE.outerHeight());
                lefts.push(lefts[lefts.length - 1] + prevColE.outerWidth());
                currRow = currCol = -1;
            }
            var r, c;
            for (r = 0; r < tops.length && y >= tops[r]; r++);
            for (c = 0; c < lefts.length && x >= lefts[c]; c++);
            r = r >= tops.length ? -1 : r - 1;
            c = c >= lefts.length ? -1 : c - 1;
            if (r != currRow || c != currCol) {
                currRow = r;
                currCol = c;
                if (r == -1 || c == -1) {
                    this.cell = null;
                } else {
                    if (origRow == undefined) {
                        origRow = r;
                        origCol = c;
                    }
                    this.cell = {
                        row: r,
                        col: c,
                        top: tops[r],
                        left: lefts[c],
                        width: lefts[c + 1] - lefts[c],
                        height: tops[r + 1] - tops[r],
                        isOrig: r == origRow && c == origCol,
                        rowDelta: r - origRow,
                        colDelta: c - origCol
                    };
                }
                changeCallback(this.cell);
            }
        };

    }



    /* Misc Utils
    -----------------------------------------------------------------------------*/

    var undefined,
	dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];

    function zeroPad(n) {
        return (n < 10 ? '0' : '') + n;
    }

    function smartProperty(obj, name) { // get a camel-cased/namespaced property
        if (obj[name] != undefined) {
            return obj[name];
        }
        var parts = name.split(/(?=[A-Z])/),
		i = parts.length - 1, res;
        for (; i >= 0; i--) {
            res = obj[parts[i].toLowerCase()];
            if (res != undefined) {
                return res;
            }
        }
        return obj[''];
    }



})(jQuery);
