/**
* @preserve
* FullCalendar v1.4.8
* http://arshaw.com/fullcalendar/
*
* Use fullcalendar.css for basic styling.
* For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*
* Copyright (c) 2010 Adam Shaw
* Dual licensed under the MIT and GPL licenses, located in
* MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
* Date: Sat Oct 16 17:10:03 2010 -0700
*
*/

(function($, undefined) {


    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,
        ignoreTimezone: true,

        // event ajax
        lazyFetching: true,
        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)TT' // default - kishore
        },

        // 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'
        }, //- kishore

        // jquery-ui theming
        theme: false,
        buttonIcons: {
            prev: 'circle-triangle-w',
            next: 'circle-triangle-e'
        },

        //selectable: false,
        unselectAuto: true,

        dropAccept: '*'

    };

    // 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'
        }
    };



    var fc = $.fullCalendar = { version: "1.4.8" };
    var fcViews = fc.views = {};


    $.fn.fullCalendar = function(options) {


        // method calling
        if (typeof options == 'string') {
            var args = Array.prototype.slice.call(arguments, 1);
            var res;
            this.each(function() {
                var calendar = $.data(this, 'fullCalendar');
                if (calendar && $.isFunction(calendar[options])) {
                    var r = calendar[options].apply(calendar, args);
                    if (res === undefined) {
                        res = r;
                    }
                    if (options == 'destroy') {
                        $.removeData(this, 'fullCalendar');
                    }
                }
            });
            if (res !== undefined) {
                return res;
            }
            return this;
        }


        // would like to have this logic in EventManager, but needs to happen before options are extended
        var eventSources = options.eventSources || [];
        delete options.eventSources;
        if (options.events) {
            eventSources.push(options.events);
            delete options.events;
        }


        options = $.extend(true, {},
		defaults,
		(options.isRTL || options.isRTL === undefined && defaults.isRTL) ? rtlDefaults : {},
		options
	);


        this.each(function(i, _element) {
            var element = $(_element);
            var calendar = new Calendar(element, options, eventSources);
            element.data('fullCalendar', calendar); // TODO: look into memory leak implications
            calendar.render();
        });


        return this;

    };


    // function for adding/overriding defaults
    function setDefaults(d) {
        $.extend(true, defaults, d);
    }




    function Calendar(element, options, eventSources) {
        var t = this;


        // exports
        t.options = options;
        t.render = render;
        t.destroy = destroy;
        t.changeView = changeView;
        t.select = select;
        t.unselect = unselect;
        t.rerenderEvents = rerenderEvents; // todo: seems liks an EventManager thing
        t.prev = prev;
        t.next = next;
        t.prevYear = prevYear;
        t.nextYear = nextYear;
        t.today = today;
        t.gotoDate = gotoDate;
        t.incrementDate = incrementDate;
        t.formatDate = function(format, date) { return formatDate(format, date, options) };
        t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
        t.getDate = getDate;
        t.getView = getView;
        t.option = option;
        t.trigger = trigger;


        // imports
        EventManager.call(t, options, eventSources);
        var fetchEvents = t.fetchEvents;
        var isFetchNeeded = t.isFetchNeeded;
        var clientEvents = t.clientEvents;


        // locals
        var _element = element[0];
        var header;
        var headerElement;
        var content;
        var tm; // for making theme classes
        var currentView;
        var viewInstances = {};
        var elementOuterWidth;
        var suggestedViewHeight;
        var absoluteViewElement;
        var resizeUID = 0;
        var ignoreWindowResize = 0;
        var date = new Date();



        /* Main Rendering
        -----------------------------------------------------------------------------*/


        setYMD(date, options.year, options.month, options.date);


        function render(inc) {
            if (!content) {
                initialRender();
            } else {
                calcSize();
                sizesDirty();
                eventsDirty();
                renderView(inc);
            }
        }


        function initialRender() {
            tm = options.theme ? 'ui' : 'fc';
            element.addClass('fc');
            if (options.isRTL) {
                element.addClass('fc-rtl');
            }
            if (options.theme) {
                element.addClass('ui-widget');
            }
            content = $("<div class='fc-content " + tm + "-widget-content' style='position:relative'/>")
			.prependTo(element);
            header = new Header(t, options);
            headerElement = header.render();
            if (headerElement) {
                element.prepend(headerElement);
            }
            changeView(options.defaultView);
            $(window).resize(windowResize);
            // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
            if (!bodyVisible()) {
                lateRender();
            }
        }


        // called when we know the calendar couldn't be rendered when it was initialized,
        // but we think it's ready now
        function lateRender() {
            setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
                if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
                    renderView();
                }
            }, 0);
        }


        function destroy() {
            $(window).unbind('resize', windowResize);
            header.destroy();
            content.remove();
            element.removeClass('fc fc-rtl fc-ui-widget');
        }



        function elementVisible() {
            return _element.offsetWidth !== 0;
        }


        function bodyVisible() {
            return $('body')[0].offsetWidth !== 0;
        }



        /* View Rendering
        -----------------------------------------------------------------------------*/


        function changeView(newViewName) {
            //hide the event popups on view change - kishore
            if ($('#viewCalEvent').length) {
                $('#viewCalEvent').hide();
            }

            if ($('#addCalEvent').length) {
                $('#addCalEvent').hide();
            }
            if (!currentView || newViewName != currentView.name) {
                ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached

                unselect();

                var oldView = currentView;
                var newViewElement;

                if (oldView) {
                    if (oldView.eventsChanged) {
                        eventsDirty();
                        oldView.eventDirty = oldView.eventsChanged = false;
                    }
                    (oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
                    setMinHeight(content, content.height());
                    oldView.element.hide();
                } else {
                    setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
                }
                content.css('overflow', 'hidden');

                currentView = viewInstances[newViewName];
                if (currentView) {
                    currentView.element.show();
                } else {
                    currentView = viewInstances[newViewName] = new fcViews[newViewName](
					newViewElement = absoluteViewElement =
						$("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
							.appendTo(content),
					t // the calendar object
				);
                }

                if (oldView) {
                    header.deactivateButton(oldView.name);
                }
                header.activateButton(newViewName);

                renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null

                content.css('overflow', '');
                if (oldView) {
                    setMinHeight(content, 1);
                }

                if (!newViewElement) {
                    (currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
                }

                ignoreWindowResize--;
            }
        }



        function renderView(inc) {
            if (elementVisible()) {
                ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached

                unselect();

                if (suggestedViewHeight === undefined) {
                    calcSize();
                }

                if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
                    currentView.render(date, inc || 0); // responsible for clearing events
                    setSize(true);
                    if (!options.lazyFetching || isFetchNeeded()) {
                        fetchAndRenderEvents();
                    } else {
                        currentView.renderEvents(clientEvents()); // don't refetch
                    }
                }
                else if (currentView.sizeDirty || currentView.eventsDirty || !options.lazyFetching) {
                    currentView.clearEvents();
                    if (currentView.sizeDirty) {
                        setSize();
                    }
                    if (!options.lazyFetching || isFetchNeeded()) {
                        fetchAndRenderEvents();
                    } else {
                        currentView.renderEvents(clientEvents()); // don't refetch
                    }
                }
                elementOuterWidth = element.outerWidth();
                currentView.sizeDirty = false;
                currentView.eventsDirty = false;

                header.updateTitle(currentView.title);
                var today = new Date();
                if (today >= currentView.start && today < currentView.end) {
                    header.disableButton('today');
                } else {
                    header.enableButton('today');
                }

                ignoreWindowResize--;
                currentView.trigger('viewDisplay', _element);
            }
        }



        /* Resizing
        -----------------------------------------------------------------------------*/


        function calcSize() {
            if (options.contentHeight) {
                suggestedViewHeight = options.contentHeight;
            }
            else if (options.height) {
                suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content[0]);
            }
            else {
                suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
            }
        }


        function setSize(dateChanged) { // todo: dateChanged?
            ignoreWindowResize++;
            currentView.setHeight(suggestedViewHeight, dateChanged);
            if (absoluteViewElement) {
                absoluteViewElement.css('position', 'relative');
                absoluteViewElement = null;
            }
            currentView.setWidth(content.width(), dateChanged);
            ignoreWindowResize--;
        }


        function windowResize() {
            if (!ignoreWindowResize) {
                if (currentView.start) { // view has already been rendered
                    var uid = ++resizeUID;
                    setTimeout(function() { // add a delay
                        if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
                            if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
                                ignoreWindowResize++; // in case the windowResize callback changes the height
                                sizeChanged();
                                currentView.trigger('windowResize', _element);
                                ignoreWindowResize--;
                            }
                        }
                    }, 200);
                } else {
                    // calendar must have been initialized in a 0x0 iframe that has just been resized
                    lateRender();
                }
            }
        }


        // called when we know the element size has changed
        function sizeChanged() {
            sizesDirty();
            if (elementVisible()) {
                calcSize();
                setSize();
                unselect();
                currentView.rerenderEvents();
                currentView.sizeDirty = false;
            }
        }


        // marks other views' sizes as dirty
        function sizesDirty() {
            $.each(viewInstances, function(i, inst) {
                inst.sizeDirty = true;
            });
        }



        /* Event Rendering
        -----------------------------------------------------------------------------*/


        // called when any event objects have been added/removed/changed, rerenders
        function rerenderEvents() {
            eventsDirty();
            if (elementVisible()) {
                currentView.clearEvents();
                currentView.renderEvents(clientEvents());
                currentView.eventsDirty = false;
            }
        }


        // marks every views' events as dirty
        function eventsDirty() {
            $.each(viewInstances, function(i, inst) {
                inst.eventsDirty = true;
            });
        }


        // for convenience
        function fetchAndRenderEvents() {
            fetchEvents(function(events) {
                currentView.renderEvents(events); // maintain `this` in view
            });
        }



        /* Selection
        -----------------------------------------------------------------------------*/


        function select(start, end, allDay) {
            currentView.select(start, end, allDay === undefined ? true : allDay);
        }


        function unselect() { // safe to be called before renderView
            if (currentView) {
                currentView.unselect();
            }
        }



        /* Date
        -----------------------------------------------------------------------------*/


        function prev() {
            renderView(-1);
        }


        function next() {
            renderView(1);
        }


        function prevYear() {
            addYears(date, -1);
            renderView();
        }


        function nextYear() {
            addYears(date, 1);
            renderView();
        }


        function today() {
            date = new Date();
            renderView();
        }


        function gotoDate(year, month, dateOfMonth) {
            if (year instanceof Date) {
                date = cloneDate(year); // provided 1 argument, a Date
            } else {
                setYMD(date, year, month, dateOfMonth);
            }
            renderView();
        }


        function incrementDate(years, months, days) {
            if (years !== undefined) {
                addYears(date, years);
            }
            if (months !== undefined) {
                addMonths(date, months);
            }
            if (days !== undefined) {
                addDays(date, days);
            }
            renderView();
        }


        function getDate() {
            return cloneDate(date);
        }



        /* Misc
        -----------------------------------------------------------------------------*/


        function getView() {
            return currentView;
        }


        function option(name, value) {
            if (value === undefined) {
                return options[name];
            }
            if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
                options[name] = value;
                sizeChanged();
            }
        }


        function trigger(name, thisObj) {
            if (options[name]) {
                return options[name].apply(
				thisObj || _element,
				Array.prototype.slice.call(arguments, 2)
			);
            }
        }



        /* External Dragging
        ------------------------------------------------------------------------*/

        var _dragElement;

        if (options.droppable) {
            $(document)
			.bind('dragstart', function(ev, ui) {
			    var _e = ev.target;
			    var e = $(_e);
			    if (!e.parents('.fc').length) { // not already inside a calendar
			        var accept = options.dropAccept;
			        if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
			            _dragElement = _e;
			            currentView.dragStart(_dragElement, ev, ui);
			        }
			    }
			})
			.bind('dragstop', function(ev, ui) {
			    if (_dragElement) {
			        currentView.dragStop(_dragElement, ev, ui);
			        _dragElement = null;
			    }
			});
        }


    }

    function Header(calendar, options) {
        var t = this;


        // exports
        t.render = render;
        t.destroy = destroy;
        t.updateTitle = updateTitle;
        t.activateButton = activateButton;
        t.deactivateButton = deactivateButton;
        t.disableButton = disableButton;
        t.enableButton = enableButton;


        // locals
        var element = $([]);
        var tm;



        function render() {
            tm = options.theme ? 'ui' : 'fc';
            var sections = options.header;
            if (sections) {
                element = $("<table class='fc-header'/>")
				.append($("<tr/>")
					.append($("<td class='fc-header-left'/>")
						.append(renderSection(sections.left)))
					.append($("<td class='fc-header-center'/>")
						.append(renderSection(sections.center)))
					.append($("<td class='fc-header-right'/>")
						.append(renderSection(sections.right))));
                return element;
            }
        }


        function destroy() {
            element.remove();
        }


        function renderSection(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 (calendar[buttonName]) {
                                buttonClick = calendar[buttonName]; // calendar method
                            }
                            else if (fcViews[buttonName]) {
                                buttonClick = function() {
                                    button.removeClass(tm + '-state-hover'); // forget why
                                    calendar.changeView(buttonName);
                                };
                            }
                            if (buttonClick) {
                                if (prevButton) {
                                    prevButton.addClass(tm + '-no-right');
                                }
                                var button;
                                var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null;
                                var 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);
            }
        }


        function updateTitle(html) {
            element.find('h2.fc-header-title')
			.html(html);
        }


        function activateButton(buttonName) {
            element.find('div.fc-button-' + buttonName)
			.addClass(tm + '-state-active');
        }


        function deactivateButton(buttonName) {
            element.find('div.fc-button-' + buttonName)
			.removeClass(tm + '-state-active');
        }


        function disableButton(buttonName) {
            element.find('div.fc-button-' + buttonName)
			.addClass(tm + '-state-disabled');
        }


        function enableButton(buttonName) {
            element.find('div.fc-button-' + buttonName)
			.removeClass(tm + '-state-disabled');
        }


    }

    var eventGUID = 1;

    function EventManager(options, eventSources) {
        var t = this;


        // exports
        t.fetchEvents = fetchEvents;
        t.refetchEvents = refetchEvents;
        t.isFetchNeeded = isFetchNeeded;
        t.addEventSource = addEventSource;
        t.removeEventSource = removeEventSource;
        t.updateEvent = updateEvent;
        t.renderEvent = renderEvent;
        t.removeEvents = removeEvents;
        t.clientEvents = clientEvents;
        t.normalizeEvent = normalizeEvent;


        // imports
        var getDate = t.getDate;
        var getView = t.getView;
        var trigger = t.trigger;
        var rerenderEvents = t.rerenderEvents;


        // locals
        var fetchID = 0;
        var eventStart, eventEnd;
        var events = [];
        var loadingLevel = 0;



        /* Sources
        -----------------------------------------------------------------------------*/


        eventSources.unshift([]); // first event source reserved for 'sticky' events


        function addEventSource(source) {
            eventSources.push(source);
            fetchEventSource(source, rerenderEvents);
        }


        function removeEventSource(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;
            });
            rerenderEvents();
        }



        /* Fetching
        -----------------------------------------------------------------------------*/


        // Fetch from ALL sources. Clear 'events' array and populate
        function fetchEvents(callback) {
            events = [];
            fetchEventSources(eventSources, callback);
        }


        // appends to the events array
        function fetchEventSources(sources, callback) {
            var savedID = ++fetchID;
            var queued = sources.length;
            var origView = getView();
            eventStart = cloneDate(origView.visStart); // we don't need to make local copies b/c
            eventEnd = cloneDate(origView.visEnd);     //   eventStart/eventEnd is only assigned/manipulated here
            function sourceDone(source, sourceEvents) {
                var currentView = getView();
                if (origView != currentView) {
                    origView.eventsDirty = true; // sort of a hack
                }
                if (savedID == fetchID && eventStart <= currentView.visStart && eventEnd >= currentView.visEnd) {
                    // same fetchEventSources call, and still in correct date range
                    if ($.inArray(source, eventSources) != -1) { // source hasn't been removed since we started
                        for (var i = 0; i < sourceEvents.length; i++) {
                            normalizeEvent(sourceEvents[i]);
                            sourceEvents[i].source = source;
                        }
                        events = events.concat(sourceEvents);
                    }
                    if (! --queued) {
                        if (callback) {
                            callback(events);
                        }
                    }
                }
            }
            for (var i = 0; i < sources.length; i++) {
                _fetchEventSource(sources[i], sourceDone);
            }
        }


        function _fetchEventSource(src, callback) {
            function reportEvents(a) {
                callback(src, a);
            }
            function reportEventsAndPop(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();
                // TODO: respect cache param in ajaxSetup
                $.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
            }
        }


        function fetchEventSource(src, callback) {
            fetchEventSources([src], callback);
        }


        function refetchEvents() {
            fetchEvents(rerenderEvents);
        }


        function isFetchNeeded() {
            var view = getView();
            return !eventStart || view.visStart < eventStart || view.visEnd > eventEnd;
        }



        /* Manipulation
        -----------------------------------------------------------------------------*/


        function updateEvent(event) { // update an existing event
            var i, len = events.length, e,
			defaultEventEnd = getView().defaultEventEnd,
			startDelta = event.start - event._start,
			endDelta = event.end ?
				(event.end - (event._end || 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(+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);
                }
            }
            normalizeEvent(event);
            rerenderEvents();
        }


        function renderEvent(event, stick) { // render a new event
            normalizeEvent(event);
            if (!event.source) {
                if (stick) {
                    (event.source = eventSources[0]).push(event);
                }
                events.push(event);
            }
            rerenderEvents();
        }


        function removeEvents(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);
                    }
                }
            }
            rerenderEvents();
        }


        function clientEvents(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
        }



        /* Loading State
        -----------------------------------------------------------------------------*/


        function pushLoading() {
            if (!loadingLevel++) {
                trigger('loading', null, true);
            }
        }


        function popLoading() {
            if (! --loadingLevel) {
                trigger('loading', null, false);
            }
        }



        /* Event Normalization
        -----------------------------------------------------------------------------*/


        function normalizeEvent(event) {
            event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
            if (event.date) {
                if (!event.start) {
                    event.start = event.date;
                }
                delete event.date;
            }
            event._start = cloneDate(event.start = parseDate(event.start, options.ignoreTimezone));
            event.end = parseDate(event.end, options.ignoreTimezone);
            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 start date, return false to indicate an invalid event
        }


    }

    fcViews.month = MonthView;

    function MonthView(element, calendar) {
        var t = this;


        // exports
        t.render = render;


        // imports
        BasicView.call(t, element, calendar, 'month');
        var opt = t.opt;
        var renderBasic = t.renderBasic;
        var formatDate = calendar.formatDate;



        function render(date, delta) {
            if (delta) {
                addMonths(date, delta);
                date.setDate(1);
            }
            var start = cloneDate(date, true);
            start.setDate(1);
            var end = addMonths(cloneDate(start), 1);
            var visStart = cloneDate(start);
            var visEnd = cloneDate(end);
            var firstDay = opt('firstDay');
            var nwe = opt('weekends') ? 0 : 1;
            if (nwe) {
                skipWeekend(visStart);
                skipWeekend(visEnd, -1, true);
            }
            addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
            addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
            var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
            if (opt('weekMode') == 'fixed') {
                addDays(visEnd, (6 - rowCnt) * 7);
                rowCnt = 6;
            }
            t.title = formatDate(start, opt('titleFormat'));
            t.start = start;
            t.end = end;
            t.visStart = visStart;
            t.visEnd = visEnd;
            renderBasic(rowCnt, nwe ? 5 : 7, true);
        }


    }

    fcViews.basicWeek = BasicWeekView;

    function BasicWeekView(element, calendar) {
        var t = this;


        // exports
        t.render = render;


        // imports
        BasicView.call(t, element, calendar, 'basicWeek');
        var opt = t.opt;
        var renderBasic = t.renderBasic;
        var formatDates = calendar.formatDates;



        function render(date, delta) {
            if (delta) {
                addDays(date, delta * 7);
            }
            var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
            var end = addDays(cloneDate(start), 7);
            var visStart = cloneDate(start);
            var visEnd = cloneDate(end);
            var weekends = opt('weekends');
            if (!weekends) {
                skipWeekend(visStart);
                skipWeekend(visEnd, -1, true);
            }
            t.title = formatDates(
			visStart,
			addDays(cloneDate(visEnd), -1),
			opt('titleFormat')
		);
            t.start = start;
            t.end = end;
            t.visStart = visStart;
            t.visEnd = visEnd;
            renderBasic(1, weekends ? 7 : 5, false);
        }


    }

    fcViews.basicDay = BasicDayView;

    //TODO: when calendar's date starts out on a weekend, shouldn't happen


    function BasicDayView(element, calendar) {
        var t = this;


        // exports
        t.render = render;


        // imports
        BasicView.call(t, element, calendar, 'basicDay');
        var opt = t.opt;
        var renderBasic = t.renderBasic;
        var formatDate = calendar.formatDate;



        function render(date, delta) {
            if (delta) {
                addDays(date, delta);
                if (!opt('weekends')) {
                    skipWeekend(date, delta < 0 ? -1 : 1);
                }
            }
            t.title = formatDate(date, opt('titleFormat'));
            t.start = t.visStart = cloneDate(date, true);
            t.end = t.visEnd = addDays(cloneDate(t.start), 1);
            renderBasic(1, 1, false);
        }


    }

    var tdHeightBug;

    setDefaults({
        weekMode: 'fixed'
    });


    function BasicView(element, calendar, viewName) {
        var t = this;


        // exports
        t.renderBasic = renderBasic;
        t.setHeight = setHeight;
        t.setWidth = setWidth;
        t.renderDayOverlay = renderDayOverlay;
        t.defaultSelectionEnd = defaultSelectionEnd;
        t.renderSelection = renderSelection;
        t.clearSelection = clearSelection;
        t.dragStart = dragStart;
        t.dragStop = dragStop;
        t.defaultEventEnd = defaultEventEnd;
        t.getHoverListener = function() { return hoverListener };
        t.colContentLeft = colContentLeft;
        t.colContentRight = colContentRight;
        t.dayOfWeekCol = dayOfWeekCol;
        t.cellDate = cellDate;
        t.cellIsAllDay = function() { return true };
        t.allDayTR = allDayTR;
        t.allDayBounds = allDayBounds;
        t.getRowCnt = function() { return rowCnt };
        t.getColCnt = function() { return colCnt };
        t.getColWidth = function() { return colWidth };
        t.getDaySegmentContainer = function() { return daySegmentContainer };


        // imports
        View.call(t, element, calendar, viewName);
        OverlayManager.call(t);
        SelectionManager.call(t);
        BasicEventRenderer.call(t);
        var opt = t.opt;
        var trigger = t.trigger;
        var clearEvents = t.clearEvents;
        var renderOverlay = t.renderOverlay;
        var clearOverlays = t.clearOverlays;
        var daySelectionMousedown = t.daySelectionMousedown;
        var formatDate = calendar.formatDate;


        // locals
        var rtl, dis, dit;
        var firstDay;
        var nwe;
        var rowCnt, colCnt;
        var colWidth;
        var viewWidth, viewHeight;
        var thead, tbody;
        var daySegmentContainer;
        var coordinateGrid;
        var hoverListener;
        var colContentPositions;



        /* Rendering
        ------------------------------------------------------------*/


        disableTextSelection(element.addClass('fc-grid'));


        function renderBasic(r, c, showNumbers) {

            rowCnt = r;
            colCnt = c;
            rtl = opt('isRTL');
            if (rtl) {
                dis = -1;
                dit = colCnt - 1;
            } else {
                dis = 1;
                dit = 0;
            }
            firstDay = opt('firstDay');
            nwe = opt('weekends') ? 0 : 1;

            var tm = opt('theme') ? 'ui' : 'fc';
            var colFormat = opt('columnFormat');
            var month = t.start.getMonth();
            var today = clearTime(new Date());
            var s, i, j, d = cloneDate(t.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) + "</th>";
                    addDays(d, 1);
                    if (nwe) {
                        skipWeekend(d);
                    }
                }
                thead = $(s + "</tr></thead>").appendTo(table);

                s = "<tbody>";
                d = cloneDate(t.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 style='position:relative'>&nbsp;</div></div></td>";
                        addDays(d, 1);
                        if (nwe) {
                            skipWeekend(d);
                        }
                    }
                    s += "</tr>";
                }
                tbody = $(s + "</tbody>").appendTo(table);
                dayBind(tbody.find('td'));

                daySegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(element);

            } else { // NOT first time, reuse as many cells as possible

                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 style='position:relative'>&nbsp;</div></div>" +
							"</td>";
                            addDays(d, 1);
                            if (nwe) {
                                skipWeekend(d);
                            }
                        }
                        s += "</tr>";
                    }
                    tbody.append(s);
                }
                dayBind(tbody.find('td.fc-new').removeClass('fc-new'));

                // re-label and re-class existing cells
                d = cloneDate(t.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(t.visStart);
                    thead.find('th').each(function(i, th) {
                        $(th).text(formatDate(d, colFormat));
                        th.className = th.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                        addDays(d, 1);
                        if (nwe) {
                            skipWeekend(d);
                        }
                    });

                    // redo cell day-of-weeks
                    d = cloneDate(t.visStart);
                    tbody.find('td').each(function(i, td) {
                        td.className = td.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                        addDays(d, 1);
                        if (nwe) {
                            skipWeekend(d);
                        }
                    });

                }

            }

        }


        function setHeight(height) {
            viewHeight = height;
            var leftTDs = tbody.find('tr td:first-child'),
			tbodyHeight = viewHeight - thead.height(),
			rowHeight1, rowHeight2;
            if (opt('weekMode') == 'variable') {
                rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt == 1 ? 2 : 6));
            } else {
                rowHeight1 = Math.floor(tbodyHeight / rowCnt);
                rowHeight2 = tbodyHeight - rowHeight1 * (rowCnt - 1);
            }
            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);
            }
        }


        function setWidth(width) {
            viewWidth = width;
            colContentPositions.clear();
            colWidth = Math.floor(viewWidth / colCnt);
            setOuterWidth(thead.find('th').slice(0, -1), colWidth);
        }



        /* Day clicking and binding
        -----------------------------------------------------------*/


        function dayBind(days) {
            days.click(dayClick)
			.mousedown(daySelectionMousedown);
        }


        function dayClick(ev) {
            if (!opt('selectable')) { // SelectionManager will worry about dayClick
                var n = parseInt(this.className.match(/fc\-day(\d+)/)[1]),
				date = addDays(
					cloneDate(t.visStart),
					Math.floor(n / colCnt) * 7 + n % colCnt
				);
                // TODO: what about weekends in middle of week?
                trigger('dayClick', this, date, true, ev);
            }
        }



        /* Semi-transparent Overlay Helpers
        ------------------------------------------------------*/


        function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
            if (refreshCoordinateGrid) {
                coordinateGrid.build();
            }
            var rowStart = cloneDate(t.visStart);
            var rowEnd = addDays(cloneDate(rowStart), colCnt);
            for (var i = 0; i < rowCnt; i++) {
                var stretchStart = new Date(Math.max(rowStart, overlayStart));
                var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
                if (stretchStart < stretchEnd) {
                    var colStart, colEnd;
                    if (rtl) {
                        colStart = dayDiff(stretchEnd, rowStart) * dis + dit + 1;
                        colEnd = dayDiff(stretchStart, rowStart) * dis + dit + 1;
                    } else {
                        colStart = dayDiff(stretchStart, rowStart);
                        colEnd = dayDiff(stretchEnd, rowStart);
                    }
                    dayBind(
					renderCellOverlay(i, colStart, i, colEnd - 1)
				);
                }
                addDays(rowStart, 7);
                addDays(rowEnd, 7);
            }
        }


        function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
            var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
            return renderOverlay(rect, element);
        }



        /* Selection
        -----------------------------------------------------------------------*/


        function defaultSelectionEnd(startDate, allDay) {
            return cloneDate(startDate);
        }


        function renderSelection(startDate, endDate, allDay) {
            renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
        }


        function clearSelection() {
            clearOverlays();
        }



        /* External Dragging
        -----------------------------------------------------------------------*/


        function dragStart(_dragElement, ev, ui) {
            hoverListener.start(function(cell) {
                clearOverlays();
                if (cell) {
                    renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
                }
            }, ev);
        }


        function dragStop(_dragElement, ev, ui) {
            var cell = hoverListener.stop();
            clearOverlays();
            if (cell) {
                var d = cellDate(cell);
                trigger('drop', _dragElement, d, true, ev, ui);
            }
        }



        /* Utilities
        --------------------------------------------------------*/


        function defaultEventEnd(event) {
            return cloneDate(event.start);
        }


        coordinateGrid = new CoordinateGrid(function(rows, cols) {
            var e, n, p;
            var tds = tbody.find('tr:first td');
            if (rtl) {
                tds = $(tds.get().reverse());
            }
            tds.each(function(i, _e) {
                e = $(_e);
                n = e.offset().left;
                if (i) {
                    p[1] = n;
                }
                p = [n];
                cols[i] = p;
            });
            p[1] = n + e.outerWidth();
            tbody.find('tr').each(function(i, _e) {
                e = $(_e);
                n = e.offset().top;
                if (i) {
                    p[1] = n;
                }
                p = [n];
                rows[i] = p;
            });
            p[1] = n + e.outerHeight();
        });


        hoverListener = new HoverListener(coordinateGrid);


        colContentPositions = new HorizontalPositionCache(function(col) {
            return tbody.find('td:eq(' + col + ') div div');
        });


        function colContentLeft(col) {
            return colContentPositions.left(col);
        }


        function colContentRight(col) {
            return colContentPositions.right(col);
        }


        function dayOfWeekCol(dayOfWeek) {
            return (dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt;
        }


        function cellDate(cell) {
            return addDays(cloneDate(t.visStart), cell.row * 7 + cell.col * dis + dit);
            // TODO: what about weekends in middle of week?
        }


        function allDayTR(i) {
            return tbody.find('tr:eq(' + i + ')');
        }


        function allDayBounds(i) {
            return {
                left: 0,
                right: viewWidth
            };
        }


    }

    function BasicEventRenderer() {
        var t = this;


        // exports
        t.renderEvents = renderEvents;
        t.rerenderEvents = rerenderEvents;
        t.clearEvents = clearEvents;
        t.bindDaySeg = bindDaySeg;


        // imports
        DayEventRenderer.call(t);
        var opt = t.opt;
        var trigger = t.trigger;
        var reportEvents = t.reportEvents;
        var clearEventData = t.clearEventData;
        var eventElementHandlers = t.eventElementHandlers;
        var showEvents = t.showEvents;
        var hideEvents = t.hideEvents;
        var eventDrop = t.eventDrop;
        var getDaySegmentContainer = t.getDaySegmentContainer;
        var getHoverListener = t.getHoverListener;
        var renderDayOverlay = t.renderDayOverlay;
        var clearOverlays = t.clearOverlays;
        var getRowCnt = t.getRowCnt;
        var getColCnt = t.getColCnt;
        var renderDaySegs = t.renderDaySegs;
        var resizableDayEvent = t.resizableDayEvent;


        // locals
        var cachedEvents = [];



        /* Rendering
        --------------------------------------------------------------------*/


        function renderEvents(events) {
            reportEvents(cachedEvents = events);
            renderDaySegs(compileSegs(events));
        }


        function rerenderEvents(modifiedEventId) {
            clearEvents();
            renderDaySegs(compileSegs(cachedEvents), modifiedEventId);
        }


        function clearEvents() {
            clearEventData();
            getDaySegmentContainer().empty();
        }


        function compileSegs(events) {
            var rowCnt = getRowCnt(),
			colCnt = getColCnt(),
			d1 = cloneDate(t.visStart),
			d2 = addDays(cloneDate(d1), colCnt),
			visEventsEnds = $.map(events, exclEndDay),
			i, row,
			j, level,
			k, seg,
			segs = [];
            for (i = 0; i < rowCnt; i++) {
                row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
                for (j = 0; j < row.length; j++) {
                    level = row[j];
                    for (k = 0; k < level.length; k++) {
                        seg = level[k];
                        seg.row = i;
                        seg.level = j;
                        segs.push(seg);
                    }
                }
                addDays(d1, 7);
                addDays(d2, 7);
            }
            return segs;
        }


        function bindDaySeg(event, eventElement, seg) {
            eventElementHandlers(event, eventElement);
            if (event.editable || event.editable === undefined && opt('editable')) {
                draggableDayEvent(event, eventElement);
                if (seg.isEnd) {
                    resizableDayEvent(event, eventElement);
                }
            }
        }



        /* Dragging
        ----------------------------------------------------------------------------*/


        function draggableDayEvent(event, eventElement) {
            if (!opt('disableDragging') && eventElement.draggable) {
                var hoverListener = getHoverListener();
                var dayDelta;
                eventElement.draggable({
                    zIndex: 9,
                    delay: 50,
                    opacity: opt('dragOpacity'),
                    revertDuration: opt('dragRevertDuration'),
                    start: function(ev, ui) {
                        trigger('eventDragStart', eventElement, event, ev, ui);
                        hideEvents(event, eventElement);
                        hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
                            eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
                            clearOverlays();
                            if (cell) {
                                dayDelta = rowDelta * 7 + colDelta * (opt('isRTL') ? -1 : 1);
                                renderDayOverlay(
								addDays(cloneDate(event.start), dayDelta),
								addDays(exclEndDay(event), dayDelta)
							);
                            } else {
                                dayDelta = 0;
                            }
                        }, ev, 'drag');
                    },
                    stop: function(ev, ui) {
                        hoverListener.stop();
                        clearOverlays();
                        trigger('eventDragStop', eventElement, event, ev, ui);
                        if (dayDelta) {
                            eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
                            eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
                        } else {
                            if ($.browser.msie) {
                                eventElement.css('filter', ''); // clear IE opacity side-effects
                            }
                            showEvents(event, eventElement);
                        }
                    }
                });
            }
        }


    }

    fcViews.agendaWeek = AgendaWeekView;

    function AgendaWeekView(element, calendar) {
        var t = this;


        // exports
        t.render = render;


        // imports
        AgendaView.call(t, element, calendar, 'agendaWeek');
        var opt = t.opt;
        var renderAgenda = t.renderAgenda;
        var formatDates = calendar.formatDates;



        function render(date, delta) {
            if (delta) {
                addDays(date, delta * 7);
            }
            var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
            var end = addDays(cloneDate(start), 7);
            var visStart = cloneDate(start);
            var visEnd = cloneDate(end);
            var weekends = opt('weekends');
            if (!weekends) {
                skipWeekend(visStart);
                skipWeekend(visEnd, -1, true);
            }
            t.title = formatDates(
			visStart,
			addDays(cloneDate(visEnd), -1),
			opt('titleFormat')
		);
            t.start = start;
            t.end = end;
            t.visStart = visStart;
            t.visEnd = visEnd;
            renderAgenda(weekends ? 7 : 5);
        }


    }

    fcViews.agendaDay = AgendaDayView;

    function AgendaDayView(element, calendar) {
        var t = this;


        // exports
        t.render = render;


        // imports
        AgendaView.call(t, element, calendar, 'agendaDay');
        var opt = t.opt;
        var renderAgenda = t.renderAgenda;
        var formatDate = calendar.formatDate;



        function render(date, delta) {
            if (delta) {
                addDays(date, delta);
                if (!opt('weekends')) {
                    skipWeekend(date, delta < 0 ? -1 : 1);
                }
            }
            var start = cloneDate(date, true);
            var end = addDays(cloneDate(start), 1);
            t.title = formatDate(date, opt('titleFormat'));
            t.start = t.visStart = start;
            t.end = t.visEnd = end;
            renderAgenda(1);
        }


    }

    setDefaults({
        allDaySlot: true,
        allDayText: 'all-day',
        firstHour: 6,
        slotMinutes: 30,
        defaultEventMinutes: 120,
        //change timeformat - Kishore
        axisFormat: 'h(:mm)TT',
        timeFormat: {
            //change timeformat - Kishore
            agenda: 'h(:mm)TT{ - h(:mm)TT}'
        },
        dragOpacity: {
            agenda: .5
        },
        minTime: 0,
        maxTime: 24
    });


    function AgendaView(element, calendar, viewName) {
        var t = this;


        // exports
        t.renderAgenda = renderAgenda;
        t.setWidth = setWidth;
        t.setHeight = setHeight;
        t.beforeHide = beforeHide;
        t.afterShow = afterShow;
        t.defaultEventEnd = defaultEventEnd;
        t.timePosition = timePosition;
        t.dayOfWeekCol = dayOfWeekCol;
        t.cellDate = cellDate;
        t.cellIsAllDay = cellIsAllDay;
        t.allDayTR = allDayTR;
        t.allDayBounds = allDayBounds;
        t.getHoverListener = function() { return hoverListener };
        t.colContentLeft = colContentLeft;
        t.colContentRight = colContentRight;
        t.getDaySegmentContainer = function() { return daySegmentContainer };
        t.getSlotSegmentContainer = function() { return slotSegmentContainer };
        t.getMinMinute = function() { return minMinute };
        t.getMaxMinute = function() { return maxMinute };
        t.getBodyContent = function() { return bodyContent };
        t.getRowCnt = function() { return 1 };
        t.getColCnt = function() { return colCnt };
        t.getColWidth = function() { return colWidth };
        t.getSlotHeight = function() { return slotHeight };
        t.defaultSelectionEnd = defaultSelectionEnd;
        t.renderDayOverlay = renderDayOverlay;
        t.renderSelection = renderSelection;
        t.clearSelection = clearSelection;
        t.dragStart = dragStart;
        t.dragStop = dragStop;


        // imports
        View.call(t, element, calendar, viewName);
        OverlayManager.call(t);
        SelectionManager.call(t);
        AgendaEventRenderer.call(t);
        var opt = t.opt;
        var trigger = t.trigger;
        var clearEvents = t.clearEvents;
        var renderOverlay = t.renderOverlay;
        var clearOverlays = t.clearOverlays;
        var reportSelection = t.reportSelection;
        var unselect = t.unselect;
        var daySelectionMousedown = t.daySelectionMousedown;
        var slotSegHtml = t.slotSegHtml;
        var formatDate = calendar.formatDate;


        // locals
        var head, body, bodyContent, bodyTable, bg;
        var colCnt;
        var slotCnt = 0; // spanning all the way across
        var axisWidth, colWidth, slotHeight;
        var viewWidth, viewHeight;
        var savedScrollTop;
        var tm, firstDay;
        var nwe;            // no weekends (int)
        var rtl, dis, dit;  // day index sign / translate
        var minMinute, maxMinute;
        var coordinateGrid;
        var hoverListener;
        var colContentPositions;
        var slotTopCache = {};
        var selectionHelper;
        var daySegmentContainer;
        var slotSegmentContainer;



        /* Rendering
        -----------------------------------------------------------------------------*/


        disableTextSelection(element.addClass('fc-agenda'));


        function renderAgenda(c) {

            colCnt = c;

            // update option-derived variables
            tm = opt('theme') ? 'ui' : 'fc';
            nwe = opt('weekends') ? 0 : 1;
            firstDay = opt('firstDay');
            if (rtl = opt('isRTL')) {
                dis = -1;
                dit = colCnt - 1;
            } else {
                dis = 1;
                dit = 0;
            }
            minMinute = parseTime(opt('minTime'));
            maxMinute = parseTime(opt('maxTime'));

            var d0 = rtl ? addDays(cloneDate(t.visEnd), -1) : cloneDate(t.visStart),
			d = cloneDate(d0),
			today = clearTime(new Date()),
			colFormat = opt('columnFormat');

            if (!head) { // first time rendering, build from scratch

                var i,
				minutes,
				slotNormal = opt('slotMinutes') % 15 == 0, //...

                // head
			s = "<div class='fc-agenda-head' style='position:relative;z-index:4'>" +
				"<table style='width:100%'>" +
				"<tr class='fc-first" + (opt('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) + "</th>";
                    addDays(d, dis);
                    if (nwe) {
                        skipWeekend(d, dis);
                    }
                }
                s += "<th class='" + tm + "-state-default'>&nbsp;</th></tr>";
                if (opt('allDaySlot')) {
                    s += "<tr class='fc-all-day'>" +
						"<th class='fc-axis fc-leftmost " + tm + "-state-default'>" + opt('allDayText') + "</th>" +
						"<td colspan='" + colCnt  + "' class='" + tm + "-state-default'>" + 
							"<div class='fc-day-content'><div style='position:relative'>&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);
                dayBind(head.find('td'));

                // all-day event container
                daySegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(head);

                // 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 ? 'fc-first' : (!minutes ? '' : 'fc-minor')) +
					"'><th class='fc-axis fc-leftmost " + tm + "-state-default'>" +
					((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
					"</th><td class='fc-slot" + i + ' ' +
						tm + "-state-default'><div style='position:relative'>&nbsp;</div></td></tr>";
                    addMinutes(d, opt('slotMinutes'));
                    slotCnt++;
                }
                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);
                slotBind(body.find('td'));

                // slot event container
                slotSegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(bodyContent);

                // 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 ? '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

                clearEvents();

                // redo column header text and class
                head.find('tr:first th').slice(1, -1).each(function(i, th) {
                    $(th).text(formatDate(d, colFormat));
                    th.className = th.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(i, td) {
                    td.className = td.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
                    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');
                    }
                    addDays(d, dis);
                    if (nwe) {
                        skipWeekend(d, dis);
                    }
                });

            }

        }



        function setHeight(height, dateChanged) {

            if (height === undefined) {
                height = viewHeight;
            }

            viewHeight = height;
            slotTopCache = {};

            var bodyHeight = height - head.height();
            bodyHeight = Math.min(bodyHeight, bodyTable.height()); // shrink to fit table
            body.height(bodyHeight);

            slotHeight = body.find('tr:first div').height() + 1;

            if (dateChanged) {
                resetScroll();
            }
        }



        function setWidth(width) {
            viewWidth = width;
            colContentPositions.clear();

            body.width(width).css('overflow', 'auto');
            bodyTable.width('');

            var topTDs = head.find('tr:first th'),
			allDayLastTH = head.find('tr.fc-all-day th:last'),
			stripeTDs = bg.find('td'),
			clientWidth = body[0].clientWidth;

            bodyTable.width(clientWidth);
            clientWidth = body[0].clientWidth; // in ie6, sometimes previous clientWidth was wrongly reported
            bodyTable.width(clientWidth);

            // time-axis width
            axisWidth = 0;
            setOuterWidth(
			head.find('tr:lt(2) th:first').add(body.find('tr:first th'))
				.width(1)
				.each(function() {
				    axisWidth = Math.max(axisWidth, $(this).outerWidth());
				}),
			axisWidth
		);

            // column width, except for last column
            colWidth = Math.floor((clientWidth - axisWidth) / colCnt);
            setOuterWidth(stripeTDs.slice(0, -1), colWidth);
            setOuterWidth(topTDs.slice(1, -2), colWidth);

            // column width for last column
            if (width != clientWidth) { // has scrollbar
                setOuterWidth(topTDs.slice(-2, -1), clientWidth - axisWidth - colWidth * (colCnt - 1));
                topTDs.slice(-1).show();
                allDayLastTH.show();
            } else {
                body.css('overflow', 'hidden');
                topTDs.slice(-2, -1).width('');
                topTDs.slice(-1).hide();
                allDayLastTH.hide();
            }

            bg.css({
                top: head.find('tr').height(),
                left: axisWidth,
                width: clientWidth - axisWidth,
                height: viewHeight
            });
        }


        function resetScroll() {
            var d0 = zeroDate(),
			scrollDate = cloneDate(d0);
            scrollDate.setHours(opt('firstHour'));
            var top = timePosition(d0, scrollDate) + 1, // +1 for the border
			scroll = function() {
			    body.scrollTop(top);
			};
            scroll();
            setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
        }


        function beforeHide() {
            savedScrollTop = body.scrollTop();
        }


        function afterShow() {
            body.scrollTop(savedScrollTop);
        }



        /* Slot/Day clicking and binding
        -----------------------------------------------------------------------*/


        function dayBind(tds) {
            tds.click(slotClick)
			.mousedown(daySelectionMousedown);
        }


        function slotBind(tds) {
            tds.click(slotClick)
			.mousedown(slotSelectionMousedown);
        }


        function slotClick(ev) {
            if (!opt('selectable')) { // SelectionManager will worry about dayClick
                var col = Math.min(colCnt - 1, Math.floor((ev.pageX - bg.offset().left) / colWidth)),
				date = addDays(cloneDate(t.visStart), col * dis + dit),
				rowMatch = this.className.match(/fc-slot(\d+)/);
                if (rowMatch) {
                    var mins = parseInt(rowMatch[1]) * opt('slotMinutes'),
					hours = Math.floor(mins / 60);
                    date.setHours(hours);
                    date.setMinutes(mins % 60 + minMinute);
                    trigger('dayClick', this, date, false, ev);
                } else {
                    trigger('dayClick', this, date, true, ev);
                }
            }
        }



        /* Semi-transparent Overlay Helpers
        -----------------------------------------------------*/


        function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
            if (refreshCoordinateGrid) {
                coordinateGrid.build();
            }
            var visStart = cloneDate(t.visStart);
            var startCol, endCol;
            if (rtl) {
                startCol = dayDiff(endDate, visStart) * dis + dit + 1;
                endCol = dayDiff(startDate, visStart) * dis + dit + 1;
            } else {
                startCol = dayDiff(startDate, visStart);
                endCol = dayDiff(endDate, visStart);
            }
            startCol = Math.max(0, startCol);
            endCol = Math.min(colCnt, endCol);
            if (startCol < endCol) {
                dayBind(
				renderCellOverlay(0, startCol, 0, endCol - 1)
			);
            }
        }


        function renderCellOverlay(col0, row0, col1, row1) {
            var rect = coordinateGrid.rect(col0, row0, col1, row1, head);
            return renderOverlay(rect, head);
        }


        function renderSlotOverlay(overlayStart, overlayEnd) {
            var dayStart = cloneDate(t.visStart);
            var dayEnd = addDays(cloneDate(dayStart), 1);
            for (var i = 0; i < colCnt; i++) {
                var stretchStart = new Date(Math.max(dayStart, overlayStart));
                var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
                if (stretchStart < stretchEnd) {
                    var col = i * dis + dit;
                    var rect = coordinateGrid.rect(0, col, 0, col, bodyContent); // only use it for horizontal coords
                    var top = timePosition(dayStart, stretchStart);
                    var bottom = timePosition(dayStart, stretchEnd);
                    rect.top = top;
                    rect.height = bottom - top;
                    slotBind(
					renderOverlay(rect, bodyContent)
				);
                }
                addDays(dayStart, 1);
                addDays(dayEnd, 1);
            }
        }



        /* Coordinate Utilities
        -----------------------------------------------------------------------------*/


        coordinateGrid = new CoordinateGrid(function(rows, cols) {
            var e, n, p;
            bg.find('td').each(function(i, _e) {
                e = $(_e);
                n = e.offset().left;
                if (i) {
                    p[1] = n;
                }
                p = [n];
                cols[i] = p;
            });
            p[1] = n + e.outerWidth();
            if (opt('allDaySlot')) {
                e = head.find('td');
                n = e.offset().top;
                rows[0] = [n, n + e.outerHeight()];
            }
            var bodyContentTop = bodyContent.offset().top;
            var bodyTop = body.offset().top;
            var bodyBottom = bodyTop + body.outerHeight();
            function constrain(n) {
                return Math.max(bodyTop, Math.min(bodyBottom, n));
            }
            for (var i = 0; i < slotCnt; i++) {
                rows.push([
				constrain(bodyContentTop + slotHeight * i),
				constrain(bodyContentTop + slotHeight * (i + 1))
			]);
            }
        });


        hoverListener = new HoverListener(coordinateGrid);


        colContentPositions = new HorizontalPositionCache(function(col) {
            return bg.find('td:eq(' + col + ') div div');
        });


        function colContentLeft(col) {
            return axisWidth + colContentPositions.left(col);
        }


        function colContentRight(col) {
            return axisWidth + colContentPositions.right(col);
        }


        function dayOfWeekCol(dayOfWeek) {
            return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
        }


        // get the Y coordinate of the given time on the given day (both Date objects)
        function timePosition(day, time) { // both date objects. 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 = opt('slotMinutes'),
			minutes = time.getHours() * 60 + time.getMinutes() - minMinute,
			slotI = Math.floor(minutes / slotMinutes),
			slotTop = slotTopCache[slotI];
            if (slotTop === undefined) {
                slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI + ') td div')[0].offsetTop;
            }
            return Math.max(0, Math.round(
			slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
		));
        }


        function cellDate(cell) {
            var d = addDays(cloneDate(t.visStart), cell.col * dis + dit);
            var slotIndex = cell.row;
            if (opt('allDaySlot')) {
                slotIndex--;
            }
            if (slotIndex >= 0) {
                addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
            }
            return d;
        }


        function cellIsAllDay(cell) {
            return opt('allDaySlot') && !cell.row;
        }


        function allDayBounds() {
            return {
                left: axisWidth,
                right: viewWidth
            }
        }


        function allDayTR(index) {
            return head.find('tr.fc-all-day');
        }


        function defaultEventEnd(event) {
            var start = cloneDate(event.start);
            if (event.allDay) {
                return start;
            }
            return addMinutes(start, opt('defaultEventMinutes'));
        }



        /* Selection
        ---------------------------------------------------------------------------------*/


        function defaultSelectionEnd(startDate, allDay) {
            if (allDay) {
                return cloneDate(startDate);
            }
            return addMinutes(cloneDate(startDate), opt('slotMinutes'));
        }


        function renderSelection(startDate, endDate, allDay) {
            if (allDay) {
                if (opt('allDaySlot')) {
                    renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
                }
            } else {
                renderSlotSelection(startDate, endDate);
            }
        }


        function renderSlotSelection(startDate, endDate) {
            var helperOption = opt('selectHelper');
            coordinateGrid.build();
            if (helperOption) {
                var col = dayDiff(startDate, t.visStart) * dis + dit;
                if (col >= 0 && col < colCnt) { // only works when times are on same day
                    var rect = coordinateGrid.rect(0, col, 0, col, bodyContent); // only for horizontal coords
                    var top = timePosition(startDate, startDate);
                    var bottom = timePosition(startDate, endDate);
                    if (bottom > top) { // protect against selections that are entirely before or after visible range
                        rect.top = top;
                        rect.height = bottom - top;
                        rect.left += 2;
                        rect.width -= 5;
                        if ($.isFunction(helperOption)) {
                            var helperRes = helperOption(startDate, endDate);
                            if (helperRes) {
                                rect.position = 'absolute';
                                rect.zIndex = 8;
                                selectionHelper = $(helperRes)
								.css(rect)
								.appendTo(bodyContent);
                            }
                        } else {
                            selectionHelper = $(slotSegHtml(
							{
							    title: '',
							    start: startDate,
							    end: endDate,
							    className: [],
							    editable: false
							},
							rect,
							'fc-event fc-event-vert fc-corner-top fc-corner-bottom '
						));
                            if ($.browser.msie) {
                                selectionHelper.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
                            }
                            selectionHelper.css('opacity', opt('dragOpacity'));
                        }
                        if (selectionHelper) {
                            slotBind(selectionHelper);
                            bodyContent.append(selectionHelper);
                            setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
                            setOuterHeight(selectionHelper, rect.height, true);
                        }
                    }
                }
            } else {
                renderSlotOverlay(startDate, endDate);
            }
        }


        function clearSelection() {
            clearOverlays();
            if (selectionHelper) {
                selectionHelper.remove();
                selectionHelper = null;
            }
        }


        function slotSelectionMousedown(ev) {
            if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
                unselect(ev);
                var _mousedownElement = this;
                var dates;
                hoverListener.start(function(cell, origCell) {
                    clearSelection();
                    if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
                        var d1 = cellDate(origCell);
                        var d2 = cellDate(cell);
                        dates = [
						d1,
						addMinutes(cloneDate(d1), opt('slotMinutes')),
						d2,
						addMinutes(cloneDate(d2), opt('slotMinutes'))
					].sort(cmp);
                        renderSlotSelection(dates[0], dates[3]);
                    } else {
                        dates = null;
                    }
                }, ev);
                $(document).one('mouseup', function(ev) {
                    hoverListener.stop();
                    if (dates) {
                        if (+dates[0] == +dates[1]) {
                            trigger('dayClick', _mousedownElement, dates[0], false, ev);
                            // BUG: _mousedownElement will sometimes be the overlay
                        }
                        reportSelection(dates[0], dates[3], false, ev);
                    }
                });
            }
        }



        /* External Dragging
        --------------------------------------------------------------------------------*/


        function dragStart(_dragElement, ev, ui) {
            hoverListener.start(function(cell) {
                clearOverlays();
                if (cell) {
                    if (cellIsAllDay(cell)) {
                        renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
                    } else {
                        var d1 = cellDate(cell);
                        var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
                        renderSlotOverlay(d1, d2);
                    }
                }
            }, ev);
        }


        function dragStop(_dragElement, ev, ui) {
            var cell = hoverListener.stop();
            clearOverlays();
            if (cell) {
                trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
            }
        }


    }

    function AgendaEventRenderer() {
        var t = this;


        // exports
        t.renderEvents = renderEvents;
        t.rerenderEvents = rerenderEvents;
        t.clearEvents = clearEvents;
        t.slotSegHtml = slotSegHtml;
        t.bindDaySeg = bindDaySeg;


        // imports
        DayEventRenderer.call(t);
        var opt = t.opt;
        var trigger = t.trigger;
        var eventEnd = t.eventEnd;
        var reportEvents = t.reportEvents;
        var clearEventData = t.clearEventData;
        var eventElementHandlers = t.eventElementHandlers;
        var setHeight = t.setHeight;
        var getDaySegmentContainer = t.getDaySegmentContainer;
        var getSlotSegmentContainer = t.getSlotSegmentContainer;
        var getHoverListener = t.getHoverListener;
        var getMaxMinute = t.getMaxMinute;
        var getMinMinute = t.getMinMinute;
        var timePosition = t.timePosition;
        var colContentLeft = t.colContentLeft;
        var colContentRight = t.colContentRight;
        var renderDaySegs = t.renderDaySegs;
        var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
        var getColCnt = t.getColCnt;
        var getColWidth = t.getColWidth;
        var getSlotHeight = t.getSlotHeight;
        var getBodyContent = t.getBodyContent;
        var reportEventElement = t.reportEventElement;
        var showEvents = t.showEvents;
        var hideEvents = t.hideEvents;
        var eventDrop = t.eventDrop;
        var eventResize = t.eventResize;
        var renderDayOverlay = t.renderDayOverlay;
        var clearOverlays = t.clearOverlays;
        var calendar = t.calendar;
        var formatDate = calendar.formatDate;
        var formatDates = calendar.formatDates;



        /* Rendering
        ----------------------------------------------------------------------------*/


        var cachedEvents = [];


        function renderEvents(events, modifiedEventId) {
            reportEvents(cachedEvents = 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]);
                }
            }
            if (opt('allDaySlot')) {
                renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
                setHeight(); // no params means set to viewHeight
            }
            renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
        }


        function rerenderEvents(modifiedEventId) {
            clearEvents();
            renderEvents(cachedEvents, modifiedEventId);
        }


        function clearEvents() {
            clearEventData();
            getDaySegmentContainer().empty();
            getSlotSegmentContainer().empty();
        }


        function compileDaySegs(events) {
            var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
			i, levelCnt = levels.length, level,
			j, seg,
			segs = [];
            for (i = 0; i < levelCnt; i++) {
                level = levels[i];
                for (j = 0; j < level.length; j++) {
                    seg = level[j];
                    seg.row = 0;
                    seg.level = i;
                    segs.push(seg);
                }
            }
            return segs;
        }


        function compileSlotSegs(events) {
            var colCnt = getColCnt(),
			minMinute = getMinMinute(),
			maxMinute = getMaxMinute(),
			d = addMinutes(cloneDate(t.visStart), minMinute),
			visEventEnds = $.map(events, slotEventEnd),
			i, col,
			j, level,
			k, seg,
			segs = [];
            for (i = 0; i < colCnt; i++) {
                col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute - minMinute)));
                countForwardSegs(col);
                for (j = 0; j < col.length; j++) {
                    level = col[j];
                    for (k = 0; k < level.length; k++) {
                        seg = level[k];
                        seg.col = i;
                        seg.level = j;
                        segs.push(seg);
                    }
                }
                addDays(d, 1, true);
            }
            return segs;
        }


        function slotEventEnd(event) {
            if (event.end) {
                return cloneDate(event.end);
            } else {
                return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
            }
        }


        // renders events in the 'time slots' at the bottom

        function renderSlotSegs(segs, modifiedEventId) {

            var i, segCnt = segs.length, seg,
			event,
			className,
			top, bottom,
			colI, levelI, forward,
			leftmost,
			availWidth,
			outerWidth,
			left,
			html = '',
			eventElements,
			eventElement,
			triggerRes,
			vsideCache = {},
			hsideCache = {},
			key, val,
			titleSpan,
			height,
			slotSegmentContainer = getSlotSegmentContainer(),
			rtl, dis, dit,
			colCnt = getColCnt();

            if (rtl = opt('isRTL')) {
                dis = -1;
                dit = colCnt - 1;
            } else {
                dis = 1;
                dit = 0;
            }

            // calculate position/dimensions, create html
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                event = seg.event;
                className = 'fc-event fc-event-vert ';
                if (seg.isStart) {
                    className += 'fc-corner-top ';
                }
                if (seg.isEnd) {
                    className += 'fc-corner-bottom ';
                }
                top = timePosition(seg.start, seg.start);
                bottom = timePosition(seg.start, seg.end);
                colI = seg.col;
                levelI = seg.level;
                forward = seg.forward || 0;
                leftmost = colContentLeft(colI * dis + dit);
                availWidth = colContentRight(colI * dis + dit) - leftmost;
                availWidth = Math.min(availWidth - 6, availWidth * .95); // TODO: move this to CSS
                if (levelI) {
                    // indented and thin
                    outerWidth = availWidth / (levelI + forward + 1);
                } else {
                    if (forward) {
                        // moderately wide, aligned left still
                        outerWidth = ((availWidth / (forward + 1)) - (12 / 2)) * 2; // 12 is the predicted width of resizer =
                    } else {
                        // can be entire width, aligned left
                        outerWidth = availWidth;
                    }
                }
                left = leftmost +                                  // leftmost possible
				(availWidth / (levelI + forward + 1) * levelI) // indentation
				* dis + (rtl ? availWidth - outerWidth : 0);   // rtl
                seg.top = top;
                seg.left = left;
                seg.outerWidth = outerWidth;
                seg.outerHeight = bottom - top;
                html += slotSegHtml(event, seg, className);
            }
            slotSegmentContainer[0].innerHTML = html; // faster than html()
            eventElements = slotSegmentContainer.children();

            // retrieve elements, run through eventRender callback, bind event handlers
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                event = seg.event;
                eventElement = $(eventElements[i]); // faster than eq()
                triggerRes = trigger('eventRender', event, event, eventElement);
                if (triggerRes === false) {
                    eventElement.remove();
                } else {
                    if (triggerRes && triggerRes !== true) {
                        eventElement.remove();
                        eventElement = $(triggerRes)
						.css({
						    position: 'absolute',
						    top: seg.top,
						    left: seg.left
						})
						.appendTo(slotSegmentContainer);
                    }
                    seg.element = eventElement;
                    if (event._id === modifiedEventId) {
                        bindSlotSeg(event, eventElement, seg);
                    } else {
                        eventElement[0]._fci = i; // for lazySegBind
                    }
                    reportEventElement(event, eventElement);
                }
            }

            lazySegBind(slotSegmentContainer, segs, bindSlotSeg);

            // record event sides and title positions
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                if (eventElement = seg.element) {
                    val = vsideCache[key = seg.key = cssKey(eventElement[0])];
                    seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement[0], true)) : val;
                    val = hsideCache[key];
                    seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement[0], true)) : val;
                    titleSpan = eventElement.find('span.fc-event-title');
                    if (titleSpan.length) {
                        seg.titleTop = titleSpan[0].offsetTop;
                    }
                }
            }

            // set all positions/dimensions at once
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                if (eventElement = seg.element) {
                    eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
                    height = Math.max(0, seg.outerHeight - seg.vsides);
                    eventElement[0].style.height = height + 'px';
                    event = seg.event;
                    if (seg.titleTop !== undefined && height - seg.titleTop < 10) {
                        // not enough room for title, put it in the time header
                        eventElement.find('span.fc-event-time')
						.text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
                        eventElement.find('span.fc-event-title')
						.remove();
                    }
                    trigger('eventAfterRender', event, event, eventElement);
                }
            }

        }


        function slotSegHtml(event, seg, className) {
            //Add Activity Image to the Title - Kishore
            var eventTitle = '';
            if (event.TicklerType == '1') {
                eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_appt.gif") + '" title="Appointment"></img> ';
            }
            else if (event.TicklerType == '2') {
                eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_call.gif") + '" title="Call"></img> ';
            }
            else if (event.TicklerType == '3') {
                eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_party.gif") + '" title="Party"></img> ';
            }
            else if (event.TicklerType == '4') {
                eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_task.gif") + '" title="Task"></img> ';
            }
            else {
                eventTitle = '';
            }

            eventTitle += htmlEscape(formatDates(event.start, event.end, opt('timeFormat')));
            return "<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px'>" +
			"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + " style='background-color:" + event.ColorCode + "'>" +
						"<span class='fc-event-bg' style='background-color:" + event.ColorCode + "'></span>" +
						"<span class='fc-event-time' style='background-color:" + event.ColorCode + "'>" + eventTitle + "</span>" +
						"<span class='fc-event-title' style='background-color:" + event.ColorCode + "'>" + htmlEscape(event.title) + "</span>" +
					"</a>" +
			((event.editable || event.editable === undefined && opt('editable')) && !opt('disableResizing') && $.fn.resizable ?
				"<div class='ui-resizable-handle ui-resizable-s'>=</div>"
				: '') +
		"</div>";
        }


        function bindDaySeg(event, eventElement, seg) {
            eventElementHandlers(event, eventElement);
            if (event.editable || event.editable === undefined && opt('editable')) {
                draggableDayEvent(event, eventElement, seg.isStart);
                if (seg.isEnd) {
                    resizableDayEvent(event, eventElement, getColWidth());
                }
            }
        }


        function bindSlotSeg(event, eventElement, seg) {
            eventElementHandlers(event, eventElement);
            if (event.editable || event.editable === undefined && opt('editable')) {
                var timeElement = eventElement.find('span.fc-event-time');
                draggableSlotEvent(event, eventElement, timeElement);
                if (seg.isEnd) {
                    resizableSlotEvent(event, eventElement, timeElement);
                }
            }
        }



        /* Dragging
        -----------------------------------------------------------------------------------*/


        // when event starts out FULL-DAY

        function draggableDayEvent(event, eventElement, isStart) {
            if (!opt('disableDragging') && eventElement.draggable) {
                var origWidth;
                var allDay = true;
                var dayDelta;
                var dis = opt('isRTL') ? -1 : 1;
                var hoverListener = getHoverListener();
                var colWidth = getColWidth();
                var slotHeight = getSlotHeight();
                var minMinute = getMinMinute();
                eventElement.draggable({
                    zIndex: 9,
                    opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
                    revertDuration: opt('dragRevertDuration'),
                    start: function(ev, ui) {
                        trigger('eventDragStart', eventElement, event, ev, ui);
                        hideEvents(event, eventElement);
                        origWidth = eventElement.width();
                        hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
                            eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
                            clearOverlays();
                            if (cell) {
                                dayDelta = colDelta * dis;
                                if (!cell.row) {
                                    // on full-days
                                    renderDayOverlay(
									addDays(cloneDate(event.start), dayDelta),
									addDays(exclEndDay(event), dayDelta)
								);
                                    resetElement();
                                } 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) : opt('defaultEventMinutes'))
											/ opt('slotMinutes')
										)
									);
                                        eventElement.draggable('option', 'grid', [colWidth, 1]);
                                        allDay = false;
                                    }
                                }
                            }
                        }, ev, 'drag');
                    },
                    stop: function(ev, ui) {
                        var cell = hoverListener.stop();
                        clearOverlays();
                        trigger('eventDragStop', eventElement, event, ev, ui);
                        if (cell && (!allDay || dayDelta)) {
                            // changed!
                            eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
                            var minuteDelta = 0;
                            if (!allDay) {
                                minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
								* opt('slotMinutes')
								+ minMinute
								- (event.start.getHours() * 60 + event.start.getMinutes());
                            }
                            eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
                        } else {
                            // hasn't moved or is out of bounds (draggable has already reverted)
                            resetElement();
                            if ($.browser.msie) {
                                eventElement.css('filter', ''); // clear IE opacity side-effects
                            }
                            showEvents(event, eventElement);
                        }
                    }
                });
                function resetElement() {
                    if (!allDay) {
                        eventElement
						.width(origWidth)
						.height('')
						.draggable('option', 'grid', null);
                        allDay = true;
                    }
                }
            }
        }


        // when event starts out IN TIMESLOTS

        function draggableSlotEvent(event, eventElement, timeElement) {
            if (!opt('disableDragging') && eventElement.draggable) {
                var origPosition;
                var allDay = false;
                var dayDelta;
                var minuteDelta;
                var prevMinuteDelta;
                var dis = opt('isRTL') ? -1 : 1;
                var hoverListener = getHoverListener();
                var colCnt = getColCnt();
                var colWidth = getColWidth();
                var slotHeight = getSlotHeight();
                eventElement.draggable({
                    zIndex: 9,
                    scroll: false,
                    grid: [colWidth, slotHeight],
                    axis: colCnt == 1 ? 'y' : false,
                    opacity: opt('dragOpacity'),
                    revertDuration: opt('dragRevertDuration'),
                    start: function(ev, ui) {
                        trigger('eventDragStart', eventElement, event, ev, ui);
                        hideEvents(event, eventElement);
                        if ($.browser.msie) {
                            eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
                        }
                        origPosition = eventElement.position();
                        minuteDelta = prevMinuteDelta = 0;
                        hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
                            eventElement.draggable('option', 'revert', !cell);
                            clearOverlays();
                            if (cell) {
                                dayDelta = colDelta * dis;
                                if (opt('allDaySlot') && !cell.row) {
                                    // over full days
                                    if (!allDay) {
                                        // convert to temporary all-day event
                                        allDay = true;
                                        timeElement.hide();
                                        eventElement.draggable('option', 'grid', null);
                                    }
                                    renderDayOverlay(
									addDays(cloneDate(event.start), dayDelta),
									addDays(exclEndDay(event), dayDelta)
								);
                                } else {
                                    // on slots
                                    resetElement();
                                }
                            }
                        }, ev, 'drag');
                    },
                    drag: function(ev, ui) {
                        minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
                        if (minuteDelta != prevMinuteDelta) {
                            if (!allDay) {
                                updateTimeText(minuteDelta);
                            }
                            prevMinuteDelta = minuteDelta;
                        }
                    },
                    stop: function(ev, ui) {
                        var cell = hoverListener.stop();
                        clearOverlays();
                        trigger('eventDragStop', eventElement, event, ev, ui);
                        if (cell && (dayDelta || minuteDelta || allDay)) {
                            // changed!
                            eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
                        } else {
                            // either no change or out-of-bounds (draggable has already reverted)
                            resetElement();
                            eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
                            updateTimeText(0);
                            if ($.browser.msie) {
                                eventElement
								.css('filter', '') // clear IE opacity side-effects
								.find('span.fc-event-bg')
									.css('display', ''); // .show() made display=inline
                            }
                            showEvents(event, eventElement);
                        }
                    }
                });
                function updateTimeText(minuteDelta) {
                    var newStart = addMinutes(cloneDate(event.start), minuteDelta);
                    var newEnd;
                    if (event.end) {
                        newEnd = addMinutes(cloneDate(event.end), minuteDelta);
                    }
                    timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
                }
                function resetElement() {
                    // convert back to original slot-event
                    if (allDay) {
                        timeElement.css('display', ''); // show() was causing display=inline
                        eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
                        allDay = false;
                    }
                }
            }
        }



        /* Resizing
        --------------------------------------------------------------------------------------*/


        function resizableSlotEvent(event, eventElement, timeElement) {
            if (!opt('disableResizing') && eventElement.resizable) {
                var slotDelta, prevSlotDelta;
                var slotHeight = getSlotHeight();
                eventElement.resizable({
                    handles: {
                        s: 'div.ui-resizable-s'
                    },
                    grid: slotHeight,
                    start: function(ev, ui) {
                        slotDelta = prevSlotDelta = 0;
                        hideEvents(event, eventElement);
                        if ($.browser.msie && $.browser.version == '6.0') {
                            eventElement.css('overflow', 'hidden');
                        }
                        eventElement.css('z-index', 9);
                        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(eventEnd(event), opt('slotMinutes') * slotDelta),
								opt('timeFormat')
							)
						);
                            prevSlotDelta = slotDelta;
                        }
                    },
                    stop: function(ev, ui) {
                        trigger('eventResizeStop', this, event, ev, ui);
                        if (slotDelta) {
                            eventResize(this, event, 0, opt('slotMinutes') * slotDelta, ev, ui);
                        } else {
                            eventElement.css('z-index', 8);
                            showEvents(event, eventElement);
                            // BUG: if event was really short, need to put title back in span
                        }
                    }
                });
            }
        }


    }


    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);
                    }
                }
            }
        }
    }




    function View(element, calendar, viewName) {
        var t = this;


        // exports
        t.element = element;
        t.calendar = calendar;
        t.name = viewName;
        t.opt = opt;
        t.trigger = trigger;
        t.reportEvents = reportEvents;
        t.clearEventData = clearEventData;
        t.eventEnd = eventEnd;
        t.reportEventElement = reportEventElement;
        t.eventElementHandlers = eventElementHandlers;
        t.showEvents = showEvents;
        t.hideEvents = hideEvents;
        t.eventDrop = eventDrop;
        t.eventResize = eventResize;
        // t.title
        // t.start, t.end
        // t.visStart, t.visEnd
        // t.eventsChanged // todo: maybe report to calendar instead


        // imports
        var defaultEventEnd = t.defaultEventEnd;
        var normalizeEvent = calendar.normalizeEvent; // in EventManager
        var rerenderEvents = calendar.rerenderEvents;


        // locals
        var eventsByID = {};
        var eventElements = [];
        var eventElementsByID = {};
        var options = calendar.options;



        function opt(name, viewNameOverride) {
            var v = options[name];
            if (typeof v == 'object') {
                return smartProperty(v, viewNameOverride || viewName);
            }
            return v;
        }


        function trigger(name, thisObj) {
            return calendar.trigger.apply(
			calendar,
			[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
		);
        }



        /* Event Data
        ------------------------------------------------------------------------------*/


        // report when view receives new events
        function reportEvents(events) { // events are already normalized at this point
            eventsByID = {};
            var i, len = events.length, event;
            for (i = 0; i < len; i++) {
                event = events[i];
                if (eventsByID[event._id]) {
                    eventsByID[event._id].push(event);
                } else {
                    eventsByID[event._id] = [event];
                }
            }
        }


        function clearEventData() { // todo: rename to clearReportedEvents or something
            eventElements = [];
            eventElementsByID = {};
        }


        // returns a Date object for an event's end
        function eventEnd(event) {
            return event.end ? cloneDate(event.end) : defaultEventEnd(event);
        }



        /* Event Elements
        ------------------------------------------------------------------------------*/


        // report when view creates an element for an event
        function reportEventElement(event, element) {
            eventElements.push(element);
            if (eventElementsByID[event._id]) {
                eventElementsByID[event._id].push(element);
            } else {
                eventElementsByID[event._id] = [element];
            }
        }


        // attaches eventClick, eventMouseover, eventMouseout
        function eventElementHandlers(event, eventElement) {
            eventElement
			.click(function(ev) {
			    if (!eventElement.hasClass('ui-draggable-dragging') &&
					!eventElement.hasClass('ui-resizable-resizing')) {
			        return trigger('eventClick', this, event, ev);
			    }
			})
			.hover(
				function(ev) {
				    trigger('eventMouseover', this, event, ev);
				},
				function(ev) {
				    trigger('eventMouseout', this, event, ev);
				}
			);
            // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
            // TODO: same for resizing
        }


        function showEvents(event, exceptElement) {
            eachEventElement(event, exceptElement, 'show');
        }


        function hideEvents(event, exceptElement) {
            eachEventElement(event, exceptElement, 'hide');
        }


        function eachEventElement(event, exceptElement, funcName) {
            var elements = eventElementsByID[event._id],
			i, len = elements.length;
            for (i = 0; i < len; i++) {
                if (elements[i][0] != exceptElement[0]) {
                    elements[i][funcName]();
                }
            }
        }



        /* Event Modification Reporting
        ---------------------------------------------------------------------------------*/


        function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
            var oldAllDay = event.allDay;
            var eventId = event._id;
            moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
            trigger('eventDrop', e, event, dayDelta, minuteDelta, allDay, function() {
                // TODO: investigate cases where this inverse technique might not work
                moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
                rerenderEvents();
            }, ev, ui);
            t.eventsChanged = true;
            rerenderEvents(eventId);
        }


        function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
            var eventId = event._id;
            elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
            trigger('eventResize', e, event, dayDelta, minuteDelta, function() {
                // TODO: investigate cases where this inverse technique might not work
                elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
                rerenderEvents();
            }, ev, ui);
            t.eventsChanged = true;
            rerenderEvents(eventId);
        }



        /* Event Modification Math
        ---------------------------------------------------------------------------------*/


        function moveEvents(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, options);
            }
        }


        function elongateEvents(events, dayDelta, minuteDelta) {
            minuteDelta = minuteDelta || 0;
            for (var e, len = events.length, i = 0; i < len; i++) {
                e = events[i];
                e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
                normalizeEvent(e, options);
            }
        }


    }

    function DayEventRenderer() {
        var t = this;


        // exports
        t.renderDaySegs = renderDaySegs;
        t.resizableDayEvent = resizableDayEvent;


        // imports
        var opt = t.opt;
        var trigger = t.trigger;
        var reportEventElement = t.reportEventElement;
        var showEvents = t.showEvents;
        var hideEvents = t.hideEvents;
        var eventResize = t.eventResize;
        var getRowCnt = t.getRowCnt;
        var getColCnt = t.getColCnt;
        var getColWidth = t.getColWidth;
        var allDayTR = t.allDayTR;
        var allDayBounds = t.allDayBounds;
        var colContentLeft = t.colContentLeft;
        var colContentRight = t.colContentRight;
        var dayOfWeekCol = t.dayOfWeekCol;
        var getDaySegmentContainer = t.getDaySegmentContainer;
        var bindDaySeg = t.bindDaySeg; //TODO: streamline this
        var formatDates = t.calendar.formatDates;



        /* Rendering
        -----------------------------------------------------------------------------*/


        function renderDaySegs(segs, modifiedEventId) {

            var rtl = opt('isRTL'),
			i, segCnt = segs.length, seg,
			event,
			className,
			left, right,
			html = '',
			eventElements,
			eventElement,
			triggerRes,
			hsideCache = {},
			vmarginCache = {},
			key, val,
			rowI, top, levelI, levelHeight,
			rowDivs = [],
			rowDivTops = [],
			bounds = allDayBounds(),
			minLeft = bounds.left,
			maxLeft = bounds.right,
			rowCnt = getRowCnt(),
			colCnt = getColCnt(),
			segmentContainer = getDaySegmentContainer();

            // calculate desired position/dimensions, create html
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                event = seg.event;
                className = 'fc-event fc-event-hori ';
                if (rtl) {
                    if (seg.isStart) {
                        className += 'fc-corner-right ';
                    }
                    if (seg.isEnd) {
                        className += 'fc-corner-left ';
                    }
                    left = seg.isEnd ? colContentLeft(dayOfWeekCol(seg.end.getDay() - 1)) : minLeft;
                    right = seg.isStart ? colContentRight(dayOfWeekCol(seg.start.getDay())) : maxLeft;
                } else {
                    if (seg.isStart) {
                        className += 'fc-corner-left ';
                    }
                    if (seg.isEnd) {
                        className += 'fc-corner-right ';
                    }
                    left = seg.isStart ? colContentLeft(dayOfWeekCol(seg.start.getDay())) : minLeft;
                    right = seg.isEnd ? colContentRight(dayOfWeekCol(seg.end.getDay() - 1)) : maxLeft;
                }
                //Add Activity Image to the Title - Kishore
                var eventTitle = '';
                if (event.TicklerType == '1') {
                    eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_appt.gif") + '" title="Appointment"></img> ';
                }
                else if (event.TicklerType == '2') {
                    eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_call.gif") + '" title="Call"></img> ';
                }
                else if (event.TicklerType == '3') {
                    eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_party.gif") + '" title="Party"></img> ';
                }
                else if (event.TicklerType == '4') {
                    eventTitle = '<img src="' + ResolveUrl("~/Content/Images/activity_task.gif") + '" title="Task"></img> ';
                }
                else {
                    eventTitle = '';
                }
                html +=
				"<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;left:" + left + "px'>" +
					"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + " style='background-color:" + event.ColorCode + "'>" +
						(!event.allDay && seg.isStart ?
							"<span class='fc-event-time' style='background-color:" + event.ColorCode + "'>" + eventTitle + htmlEscape(formatDates(event.start, event.end, opt('timeFormat')) + ((event.end == null) ? '' : '-' + formatDates(event.end, event.start, opt('timeFormat')))) + "</span>"
					: '') +
					"<span class='fc-event-title' style='background-color:" + event.ColorCode + "'>" + (event.allDay ? eventTitle + htmlEscape(event.title) : htmlEscape(event.title)) + "</span>" +
				"</a>" +
					((event.editable || event.editable === undefined && opt('editable')) && !opt('disableResizing') && $.fn.resizable ?
						"<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'></div>"
						: '') +
				"</div>";
                seg.left = left;
                seg.outerWidth = right - left;
            }
            segmentContainer[0].innerHTML = html; // faster than html()
            eventElements = segmentContainer.children();

            // retrieve elements, run through eventRender callback, bind handlers
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                eventElement = $(eventElements[i]); // faster than eq()
                event = seg.event;
                triggerRes = trigger('eventRender', event, event, eventElement);
                if (triggerRes === false) {
                    eventElement.remove();
                } else {
                    if (triggerRes && triggerRes !== true) {
                        eventElement.remove();
                        eventElement = $(triggerRes)
						.css({
						    position: 'absolute',
						    left: seg.left
						})
						.appendTo(segmentContainer);
                    }
                    seg.element = eventElement;
                    if (event._id === modifiedEventId) {
                        bindDaySeg(event, eventElement, seg);
                    } else {
                        eventElement[0]._fci = i; // for lazySegBind
                    }
                    reportEventElement(event, eventElement);
                }
            }

            lazySegBind(segmentContainer, segs, bindDaySeg);

            // record event horizontal sides
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                if (eventElement = seg.element) {
                    val = hsideCache[key = seg.key = cssKey(eventElement[0])];
                    seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement[0], true)) : val;
                }
            }

            // set event widths
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                if (eventElement = seg.element) {
                    eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
                }
            }

            // record event heights
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                if (eventElement = seg.element) {
                    val = vmarginCache[key = seg.key];
                    seg.outerHeight = eventElement[0].offsetHeight + (
					val === undefined ? (vmarginCache[key] = vmargins(eventElement[0])) : val
				);
                }
            }

            // set row heights, calculate event tops (in relation to row top)
            for (i = 0, rowI = 0; rowI < rowCnt; rowI++) {
                top = levelI = levelHeight = 0;
                while (i < segCnt && (seg = segs[i]).row == rowI) {
                    if (seg.level != levelI) {
                        top += levelHeight;
                        levelHeight = 0;
                        levelI++;
                    }
                    levelHeight = Math.max(levelHeight, seg.outerHeight || 0);
                    seg.top = top;
                    i++;
                }
                rowDivs[rowI] = allDayTR(rowI).find('td:first div.fc-day-content > div') // optimal selector?
				.height(top + levelHeight);
            }

            // calculate row tops
            for (rowI = 0; rowI < rowCnt; rowI++) {
                rowDivTops[rowI] = rowDivs[rowI][0].offsetTop;
            }

            // set event tops
            for (i = 0; i < segCnt; i++) {
                seg = segs[i];
                if (eventElement = seg.element) {
                    eventElement[0].style.top = rowDivTops[seg.row] + seg.top + 'px';
                    event = seg.event;
                    trigger('eventAfterRender', event, event, eventElement);
                }
            }

        }



        /* Resizing
        -----------------------------------------------------------------------------------*/


        function resizableDayEvent(event, eventElement) {
            if (!opt('disableResizing') && eventElement.resizable) {
                var colWidth = getColWidth();
                eventElement.resizable({
                    handles: opt('isRTL') ? { w: 'div.ui-resizable-w'} : { e: 'div.ui-resizable-e' },
                    grid: colWidth,
                    minWidth: colWidth / 2, // need this or else IE throws errors when too small
                    containment: t.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);
                        hideEvents(event, eventElement);
                        trigger('eventResizeStart', this, event, ev, ui);
                    },
                    stop: function(ev, ui) {
                        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) {
                            eventResize(this, event, dayDelta, 0, ev, ui);
                        } else {
                            eventElement.css('z-index', 8);
                            showEvents(event, eventElement);
                        }
                    }
                });
            }
        }


    }

    //BUG: unselect needs to be triggered when events are dragged+dropped

    function SelectionManager() {
        var t = this;


        // exports
        t.select = select;
        t.unselect = unselect;
        t.reportSelection = reportSelection;
        t.daySelectionMousedown = daySelectionMousedown;


        // imports
        var opt = t.opt;
        var trigger = t.trigger;
        var defaultSelectionEnd = t.defaultSelectionEnd;
        var renderSelection = t.renderSelection;
        var clearSelection = t.clearSelection;


        // locals
        var selected = false;



        // unselectAuto
        if (opt('selectable') && opt('unselectAuto')) {
            $(document).mousedown(function(ev) {
                var ignore = opt('unselectCancel');
                if (ignore) {
                    if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
                        return;
                    }
                }
                unselect(ev);
            });
        }


        function select(startDate, endDate, allDay) {
            unselect();
            if (!endDate) {
                endDate = defaultSelectionEnd(startDate, allDay);
            }
            renderSelection(startDate, endDate, allDay);
            reportSelection(startDate, endDate, allDay);
        }


        function unselect(ev) {
            if (selected) {
                selected = false;
                clearSelection();
                trigger('unselect', null, ev);
            }
        }


        function reportSelection(startDate, endDate, allDay, ev) {
            selected = true;
            trigger('select', null, startDate, endDate, allDay, ev);
        }


        function daySelectionMousedown(ev) { // not really a generic manager method, oh well
            var cellDate = t.cellDate;
            var cellIsAllDay = t.cellIsAllDay;
            var hoverListener = t.getHoverListener();
            if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
                unselect(ev);
                var _mousedownElement = this;
                var dates;
                hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
                    clearSelection();
                    if (cell && cellIsAllDay(cell)) {
                        dates = [cellDate(origCell), cellDate(cell)].sort(cmp);
                        renderSelection(dates[0], dates[1], true);
                    } else {
                        dates = null;
                    }
                }, ev);
                $(document).one('mouseup', function(ev) {
                    hoverListener.stop();
                    if (dates) {
                        if (+dates[0] == +dates[1]) {
                            trigger('dayClick', _mousedownElement, dates[0], true, ev);
                            // BUG: _mousedownElement will sometimes be the overlay
                        }
                        reportSelection(dates[0], dates[1], true, ev);
                    }
                });
            }
        }


    }

    function OverlayManager() {
        var t = this;


        // exports
        t.renderOverlay = renderOverlay;
        t.clearOverlays = clearOverlays;


        // locals
        var usedOverlays = [];
        var unusedOverlays = [];


        function renderOverlay(rect, parent) {
            var e = unusedOverlays.shift();
            if (!e) {
                e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
            }
            if (e[0].parentNode != parent[0]) {
                e.appendTo(parent);
            }
            usedOverlays.push(e.css(rect).show());
            return e;
        }


        function clearOverlays() {
            var e;
            while (e = usedOverlays.shift()) {
                unusedOverlays.push(e.hide().unbind());
            }
        }


    }

    function CoordinateGrid(buildFunc) {

        var t = this;
        var rows;
        var cols;

        t.build = function() {
            rows = [];
            cols = [];
            buildFunc(rows, cols);
        };

        t.cell = function(x, y) {
            var rowCnt = rows.length;
            var colCnt = cols.length;
            var i, r = -1, c = -1;
            for (i = 0; i < rowCnt; i++) {
                if (y >= rows[i][0] && y < rows[i][1]) {
                    r = i;
                    break;
                }
            }
            for (i = 0; i < colCnt; i++) {
                if (x >= cols[i][0] && x < cols[i][1]) {
                    c = i;
                    break;
                }
            }
            return (r >= 0 && c >= 0) ? { row: r, col: c} : null;
        };

        t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
            var origin = originElement.offset();
            return {
                top: rows[row0][0] - origin.top,
                left: cols[col0][0] - origin.left,
                width: cols[col1][1] - cols[col0][0],
                height: rows[row1][1] - rows[row0][0]
            };
        };

    }

    function HoverListener(coordinateGrid) {


        var t = this;
        var bindType;
        var change;
        var firstCell;
        var cell;


        t.start = function(_change, ev, _bindType) {
            change = _change;
            firstCell = cell = null;
            coordinateGrid.build();
            mouse(ev);
            bindType = _bindType || 'mousemove';
            $(document).bind(bindType, mouse);
        };


        function mouse(ev) {
            var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
            if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
                if (newCell) {
                    if (!firstCell) {
                        firstCell = newCell;
                    }
                    change(newCell, firstCell, newCell.row - firstCell.row, newCell.col - firstCell.col);
                } else {
                    change(newCell, firstCell);
                }
                cell = newCell;
            }
        }


        t.stop = function() {
            $(document).unbind(bindType, mouse);
            return cell;
        };


    }

    function HorizontalPositionCache(getElement) {

        var t = this,
		elements = {},
		lefts = {},
		rights = {};

        function e(i) {
            return elements[i] = elements[i] || getElement(i);
        }

        t.left = function(i) {
            return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
        };

        t.right = function(i) {
            return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
        };

        t.clear = function() {
            elements = {};
            lefts = {};
            rights = {};
        };

    }


    fc.addDays = addDays;
    fc.cloneDate = cloneDate;
    fc.parseDate = parseDate;
    fc.parseISO8601 = parseISO8601;
    fc.parseTime = parseTime;
    fc.formatDate = formatDate;
    fc.formatDates = formatDates;



    /* Date Math
    -----------------------------------------------------------------------------*/

    var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
	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;
    }


    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() || (excl && date.getDay() == 1 || !excl && date.getDay() == 6)) {
            addDays(date, inc);
        }
        return date;
    }


    function dayDiff(d1, d2) { // d1 - d2
        return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
    }


    function setYMD(date, y, m, d) {
        if (y !== undefined && y != date.getFullYear()) {
            date.setDate(1);
            date.setMonth(0);
            date.setFullYear(y);
        }
        if (m !== undefined && m != date.getMonth()) {
            date.setDate(1);
            date.setMonth(m);
        }
        if (d !== undefined) {
            date.setDate(d);
        }
    }



    /* Date Parsing
    -----------------------------------------------------------------------------*/


    function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
        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);
            }
            if (ignoreTimezone === undefined) {
                ignoreTimezone = true;
            }
            return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
        }
        // TODO: never return invalid dates (like from new Date(<string>)), return null instead
        return null;
    }


    function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
        // 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);
        if (ignoreTimezone || !m[14]) {
            var check = new Date(m[1], 0, 1, 9, 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);
        } else {
            date.setUTCFullYear(
			m[1],
			m[3] ? m[3] - 1 : 0,
			m[5] || 1
		);
            date.setUTCHours(
			m[7] || 0,
			m[8] || 0,
			m[10] || 0,
			m[12] ? Number("0." + m[12]) * 1000 : 0
		);
            var offset = Number(m[16]) * 60 + Number(m[17]);
            offset *= m[15] == '-' ? 1 : -1;
            date = new Date(+date + (offset * 60 * 1000));
        }
        return date;
    }


    function parseTime(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
    -----------------------------------------------------------------------------*/
    // TODO: use same function formatDate(date, [date2], format, [options])


    function formatDate(date, format, options) {
        return formatDates(date, null, format, options);
    }


    function formatDates(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';
        }
    };




    /* Event Date Math
    -----------------------------------------------------------------------------*/


    function exclEndDay(event) {
        if (event.end) {
            return _exclEndDay(event.end, event.allDay);
        } else {
            return addDays(cloneDate(event.start), 1);
        }
    }


    function _exclEndDay(end, allDay) {
        end = cloneDate(end);
        return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
    }


    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;
    }



    /* Event Sorting
    -----------------------------------------------------------------------------*/


    // event rendering utilities
    function sliceSegs(events, visEventEnds, 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 = visEventEnds[i];
            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;
    }



    /* Event Element Binding
    -----------------------------------------------------------------------------*/


    function lazySegBind(container, segs, bindHandlers) {
        container.unbind('mouseover').mouseover(function(ev) {
            var parent = ev.target, e,
			i, seg;
            while (parent != this) {
                e = parent;
                parent = parent.parentNode;
            }
            if ((i = e._fci) !== undefined) {
                e._fci = undefined;
                seg = segs[i];
                bindHandlers(seg.event, seg.element, seg);
                $(ev.target).trigger(ev);
            }
            ev.stopPropagation();
        });
    }



    /* Element Dimensions
    -----------------------------------------------------------------------------*/


    function setOuterWidth(element, width, includeMargins) {
        element.each(function(i, _element) {
            _element.style.width = Math.max(0, width - hsides(_element, includeMargins)) + 'px';
        });
    }


    function setOuterHeight(element, height, includeMargins) {
        element.each(function(i, _element) {
            _element.style.height = Math.max(0, height - vsides(_element, includeMargins)) + 'px';
        });
    }


    // TODO: curCSS has been deprecated


    function hsides(_element, includeMargins) {
        return (parseFloat($.curCSS(_element, 'paddingLeft', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'paddingRight', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'borderLeftWidth', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'borderRightWidth', true)) || 0) +
	       (includeMargins ? hmargins(_element) : 0);
    }


    function hmargins(_element) {
        return (parseFloat($.curCSS(_element, 'marginLeft', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'marginRight', true)) || 0);
    }


    function vsides(_element, includeMargins) {
        return (parseFloat($.curCSS(_element, 'paddingTop', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'paddingBottom', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'borderTopWidth', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'borderBottomWidth', true)) || 0) +
	       (includeMargins ? vmargins(_element) : 0);
    }


    function vmargins(_element) {
        return (parseFloat($.curCSS(_element, 'marginTop', true)) || 0) +
	       (parseFloat($.curCSS(_element, 'marginBottom', true)) || 0);
    }


    function setMinHeight(element, h) {
        h = typeof h == 'number' ? h + 'px' : h;
        element[0].style.cssText += ';min-height:' + h + ';_height:' + h;
    }



    /* Position Calculation
    -----------------------------------------------------------------------------*/
    // nasty bugs in opera 9.25
    // position()'s top returning incorrectly with TR/TD or elements within TD

    var topBug;

    function topCorrect(tr) { // tr/th/td or anything else
        if (topBug !== false) {
            var cell;
            if (tr.is('th,td')) {
                tr = (cell = tr).parent();
            }
            if (topBug === undefined && tr.is('tr')) {
                topBug = tr.position().top != tr.children().position().top;
            }
            if (topBug) {
                return tr.parent().position().top + (cell ? tr.position().top - cell.position().top : 0);
            }
        }
        return 0;
    }



    /* Misc Utils
    -----------------------------------------------------------------------------*/


    //TODO: arraySlice
    //TODO: isFunction, grep ?


    function noop() { }


    function cmp(a, b) {
        return a - b;
    }


    function zeroPad(n) {
        return (n < 10 ? '0' : '') + n;
    }


    function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
        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[''];
    }


    function htmlEscape(s) {
        return s.replace(/&/g, '&amp;')
		.replace(/</g, '&lt;')
		.replace(/>/g, '&gt;')
		.replace(/'/g, '&#039;')
		.replace(/"/g, '&quot;')
		.replace(/\n/g, '<br />');
    }

    function cssKey(_element) {
        return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
    }

    function disableTextSelection(element) {
        element
		.attr('unselectable', 'on')
		.css('MozUserSelect', 'none')
		.bind('selectstart.ui', function() { return false; });
    }

    /*
    function enableTextSelection(element) {
    element
    .attr('unselectable', 'off')
    .css('MozUserSelect', '')
    .unbind('selectstart.ui');
    }
    */
})(jQuery);
