/*
 * jQuery.weekCalendar v2.0-dev
 *
 * for support join us at the google group:
 *    - http://groups.google.com/group/jquery-week-calendar
 * have a look to the wiki for documentation:
 *    - http://wiki.github.com/themouette/jquery-week-calendar/
 * something went bad ? report an issue:
 *    - http://github.com/themouette/jquery-week-calendar/issues
 * get the last version on github:
 *    - http://github.com/themouette/jquery-week-calendar
 *
 * Copyright (c) 2009 Rob Monie
 * Copyright (c) 2010 Julien MUETTON
 * Dual licensed under the MIT and GPL licenses:
 *    http://www.opensource.org/licenses/mit-license.php
 *    http://www.gnu.org/licenses/gpl.html
 *
 * If you're after a monthly calendar plugin, check out this one :
 * http://arshaw.com/fullcalendar/
 */

(function($) {
  // check the jquery version
  var _v = $.fn.jquery.split('.'),
      _jQuery14OrLower = (10 * _v[0] + _v[1]) < 15;

  $.widget('ui.weekCalendar', (function() {
    var _currentAjaxCall, _hourLineTimeout;

    return {
      options: {
        date: new Date(),
        timeFormat: null,
        dateFormat: 'M d, Y',
        alwaysDisplayTimeMinutes: true,
        use24Hour: false,
        daysToShow: 7,
        minBodyHeight: 100,
        firstDayOfWeek: function(calendar) {
                  if ($(calendar).weekCalendar('option', 'daysToShow') != 5) {
                    return 0;
                  } else {
                    //workweek
                    return 1;
                  }
              }, // 0 = Sunday, 1 = Monday, 2 = Tuesday, ... , 6 = Saturday
        useShortDayNames: false,
        timeSeparator: ' to ',
        startParam: 'start',
        endParam: 'end',
        businessHours: {start: 8, end: 18, limitDisplay: false},
        newEventText: 'New Event',
        timeslotHeight: 20,
        defaultEventLength: 2,
        timeslotsPerHour: 4,
        minDate: null,
        maxDate: null,
        showHeader: true,
        buttons: true,
        buttonText: {
          today: 'today',
          lastWeek: 'previous',
          nextWeek: 'next'
        },
        switchDisplay: {},
        scrollToHourMillis: 500,
        allowEventDelete: false,
        allowCalEventOverlap: false,
        overlapEventsSeparate: false,
        totalEventsWidthPercentInOneColumn: 100,
        readonly: false,
        allowEventCreation: true,
        hourLine: false,
        deletable: function(calEvent, element) {
          return true;
        },
        draggable: function(calEvent, element) {
          return true;
        },
        resizable: function(calEvent, element) {
          return true;
        },
        eventClick: function(calEvent, element, dayFreeBusyManager, 
                                                      calendar, clickEvent) {
        },
        eventRender: function(calEvent, element) {
          return element;
        },
        eventAfterRender: function(calEvent, element) {
          return element;
        },
        eventRefresh: function(calEvent, element) {
          return element;
        },
        eventDrag: function(calEvent, element) {
        },
        eventDrop: function(calEvent, element) {
        },
        eventResize: function(calEvent, element) {
        },
        eventNew: function(calEvent, element, dayFreeBusyManager, 
                                                    calendar, mouseupEvent) {
        },
        eventMouseover: function(calEvent, $event) {
        },
        eventMouseout: function(calEvent, $event) {
        },
        eventDelete: function(calEvent, element, dayFreeBusyManager, 
                                                      calendar, clickEvent) {
            calendar.weekCalendar('removeEvent',calEvent.id);
	},
        calendarBeforeLoad: function(calendar) {
        },
        calendarAfterLoad: function(calendar) {
        },
        noEvents: function() {
        },
        eventHeader: function(calEvent, calendar) {
          var options = calendar.weekCalendar('option');
          var one_hour = 3600000;
          var displayTitleWithTime = calEvent.end.getTime() - calEvent.start.getTime() <= (one_hour / options.timeslotsPerHour);
          if (displayTitleWithTime) {
            return calendar.weekCalendar(
                        'formatTime', calEvent.start) +
                        ': ' + calEvent.title;
          } else {
            return calendar.weekCalendar(
                        'formatTime', calEvent.start) +
                    options.timeSeparator +
                    calendar.weekCalendar(
                        'formatTime', calEvent.end);
          }
        },
        eventBody: function(calEvent, calendar) {
          return calEvent.title;
        },
        shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        /* multi-users options */
        /**
         * the available users for calendar.
         * if you want to display users separately, enable the
         * showAsSeparateUsers option.
         * if you provide a list of user and do not enable showAsSeparateUsers
         * option, then only the events that belongs to one or several of
         * given users will be displayed
         * @type {array}
         */
        users: [],
        /**
         * should the calendar be displayed with separate column for each
         * users.
         * note that this option does nothing if you do not provide at least
         * one user.
         * @type {boolean}
         */
        showAsSeparateUsers: true,
        /**
         * callback used to read user id from a user object.
         * @param {Object} user the user to retrieve the id from.
         * @param {number} index the user index from user list.
         * @param {jQuery} calendar the calendar object.
         * @return {int|String} the user id.
         */
        getUserId: function(user, index, calendar) {
          return index;
        },
        /**
         * callback used to read user name from a user object.
         * @param {Object} user the user to retrieve the name from.
         * @param {number} index the user index from user list.
         * @param {jQuery} calendar the calendar object.
         * @return {String} the user name.
         */
        getUserName: function(user, index, calendar) {
          return user;
        },
        /**
         * reads the id(s) of user(s) for who the event should be displayed.
         * @param {Object} calEvent the calEvent to read informations from.
         * @param {jQuery} calendar the calendar object.
         * @return {number|String|Array} the user id(s) to appened events for.
         */
        getEventUserId: function(calEvent, calendar) {
          return calEvent.userId;
        },
        /**
         * sets user id(s) to the calEvent
         * @param {Object} calEvent the calEvent to set informations to.
         * @param {jQuery} calendar the calendar object.
         * @return {Object} the calEvent with modified user id.
         */
        setEventUserId: function(userId, calEvent, calendar) {
          calEvent.userId = userId;
          return calEvent;
        },
        /* freeBusy options */
        /**
         * should the calendar display freebusys ?
         * @type {boolean}
         */
        displayFreeBusys: false,
        /**
         * read the id(s) for who the freebusy is available
         * @param {Object} calEvent the calEvent to read informations from.
         * @param {jQuery} calendar the calendar object.
         * @return {number|String|Array} the user id(s) to appened events for.
         */
        getFreeBusyUserId: function(calFreeBusy, calendar) {
          return calFreeBusy.userId;
        },
        /**
         * the default freeBusy object, used to manage default state
         * @type {Object}
         */
        defaultFreeBusy: {free: false},
        /**
         * function used to display the freeBusy element
         * @type {Function}
         * @param {Object} freeBusy the freeBusy timeslot to render.
         * @param {jQuery} $freeBusy the freeBusy HTML element.
         * @param {jQuery} calendar the calendar element.
         */
        freeBusyRender: function(freeBusy, $freeBusy, calendar) {
          if (!freeBusy.free) {
            $freeBusy.addClass('free-busy-busy');
          }
          else {
            $freeBusy.addClass('free-busy-free');
          }
          return $freeBusy;
        },
        /* other options */
        /**
         * true means start on first day of week, false means starts on
         * startDate.
         * @param {jQuery} calendar the calendar object.
         * @type {Function|bool}
         */
        startOnFirstDayOfWeek: function(calendar) {
          return $(calendar).weekCalendar('option', 'daysToShow') >= 5;
        },
        /**
         * should the columns be rendered alternatively using odd/even
         * class
         * @type {boolean}
         */
        displayOddEven: false,
        textSize: 13,
        /**
         * the title attribute for the calendar. possible placeholders are:
         * <ul>
         *  <li>%start%</li>
         *  <li>%end%</li>
         *  <li>%date%</li>
         * </ul>
         * @type {Function|string}
         * @param {number} option daysToShow.
         * @return {String} the title attribute for the calendar.
         */
        title: '%start% - %end%',
        /**
         * default options to pass to callback
         * you can pass a function returning an object or a litteral object
         * @type {object|function(#calendar)}
         */
        jsonOptions: {},
        headerSeparator: '<br />',
        /**
          * returns formatted header for day display
          * @type {function(date,calendar)}
          */
        getHeaderDate: null,
        preventDragOnEventCreation: false,
        /**
          * the event on which to bind calendar resize
          * @type {string}
          */
        resizeEvent: 'resize.weekcalendar'
      },

      /***********************
       * Initialise calendar *
       ***********************/
      _create: function() {
        var self = this;
        self._computeOptions();
        self._setupEventDelegation();
        self._renderCalendar();
        self._loadCalEvents();
        self._resizeCalendar();
        self._scrollToHour(self.options.date.getHours(), true);

        if (this.options.resizeEvent) {
          $(window).unbind(this.options.resizeEvent);
          $(window).bind(this.options.resizeEvent, function() {
            self._resizeCalendar();
          });
        }

      },

      /********************
       * public functions *
       ********************/
      /*
       * Refresh the events for the currently displayed week.
       */
      refresh: function() {
        //reload with existing week
        this._loadCalEvents(this.element.data('startDate'));
      },

      /*
       * Clear all events currently loaded into the calendar
       */
      clear: function() {
        this._clearCalendar();
      },

      /*
       * Go to this week
       */
      today: function() {
        this._clearCalendar();
        this._loadCalEvents(new Date());
      },

      /*
       * Go to the previous week relative to the currently displayed week
       */
      prevWeek: function() {
        //minus more than 1 day to be sure we're in previous week - account for daylight savings or other anomolies
        var newDate = new Date(this.element.data('startDate').getTime() - (MILLIS_IN_WEEK / 6));
        this._clearCalendar();
        this._loadCalEvents(newDate);
      },

      /*
        * Go to the next week relative to the currently displayed week
        */
      nextWeek: function() {
          //add 8 days to be sure of being in prev week - allows for daylight savings or other anomolies
          var newDate = new Date(this.element.data('startDate').getTime() + MILLIS_IN_WEEK + MILLIS_IN_DAY);
          this._clearCalendar();
          this._loadCalEvents(newDate);
      },

      /*
        * Reload the calendar to whatever week the date passed in falls on.
        */
      gotoWeek: function(date) {
          this._clearCalendar();
          this._loadCalEvents(date);
      },

      /*
        * Reload the calendar to whatever week the date passed in falls on.
        */
      gotoDate: function(date) {
          this._clearCalendar();
          this._loadCalEvents(date);
      },

      /**
        * change the number of days to show
        */
      setDaysToShow: function(daysToShow) {
          var self = this;
          var hour = self._getCurrentScrollHour();
          self.options.daysToShow = daysToShow;
          $(self.element).html('');
          self._renderCalendar();
          self._loadCalEvents();
          self._resizeCalendar();
          self._scrollToHour(hour, false);

          if (this.options.resizeEvent) {
            $(window).unbind(this.options.resizeEvent);
            $(window).bind(this.options.resizeEvent, function() {
              self._resizeCalendar();
            });
        }
      },

      /*
        * Remove an event based on it's id
        */
      removeEvent: function(eventId) {

          var self = this;

          self.element.find('.wc-cal-event').each(function() {
            if ($(this).data('calEvent').id === eventId) {
                $(this).remove();
                return false;
            }
          });

          //this could be more efficient rather than running on all days regardless...
          self.element.find('.wc-day-column-inner').each(function() {
            self._adjustOverlappingEvents($(this));
          });
      },

      /*
        * Removes any events that have been added but not yet saved (have no id).
        * This is useful to call after adding a freshly saved new event.
        */
      removeUnsavedEvents: function() {

          var self = this;

          self.element.find('.wc-new-cal-event').each(function() {
            $(this).remove();
          });

          //this could be more efficient rather than running on all days regardless...
          self.element.find('.wc-day-column-inner').each(function() {
            self._adjustOverlappingEvents($(this));
          });
      },

      /*
        * update an event in the calendar. If the event exists it refreshes
        * it's rendering. If it's a new event that does not exist in the calendar
        * it will be added.
        */
      updateEvent: function(calEvent) {
          this._updateEventInCalendar(calEvent);
      },

      /*
        * Returns an array of timeslot start and end times based on
        * the configured grid of the calendar. Returns in both date and
        * formatted time based on the 'timeFormat' config option.
        */
      getTimeslotTimes: function(date) {
          var options = this.options;
          var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
          var startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), firstHourDisplayed);

          var times = [],
              startMillis = startDate.getTime();
          for (var i = 0; i < options.timeslotsPerDay; i++) {
            var endMillis = startMillis + options.millisPerTimeslot;
            times[i] = {
                start: new Date(startMillis),
                startFormatted: this.formatTime(new Date(startMillis), options.timeFormat),
                end: new Date(endMillis),
                endFormatted: this.formatTime(new Date(endMillis), options.timeFormat)
            };
            startMillis = endMillis;
          }
          return times;
      },

      formatDate: function(date, format) {
          if (format) {
            return this._formatDate(date, format);
          } else {
            return this._formatDate(date, this.options.dateFormat);
          }
      },

      formatTime: function(date, format) {
          if (format) {
            return this._formatDate(date, format);
          } else if (this.options.timeFormat) {
            return this._formatDate(date, this.options.timeFormat);
          } else if (this.options.use24Hour) {
            return this._formatDate(date, 'H:i');
          } else {
            return this._formatDate(date, 'h:i a');
          }
      },

      serializeEvents: function() {
        var self = this;
        var calEvents = [];

        self.element.find('.wc-cal-event').each(function() {
          calEvents.push($(this).data('calEvent'));
        });
        return calEvents;
      },

      next: function() {
        if (this._startOnFirstDayOfWeek()) {
          return this.nextWeek();
        }
        var newDate = new Date(this.element.data('startDate').getTime());
        newDate.setDate(newDate.getDate() + this.options.daysToShow);

        this._clearCalendar();
        this._loadCalEvents(newDate);
      },

      prev: function() {
        if (this._startOnFirstDayOfWeek()) {
          return this.prevWeek();
        }
        var newDate = new Date(this.element.data('startDate').getTime());
        newDate.setDate(newDate.getDate() - this.options.daysToShow);

        this._clearCalendar();
        this._loadCalEvents(newDate);
      },
      getCurrentFirstDay: function() {
        return this._dateFirstDayOfWeek(this.options.date || new Date());
      },
      getCurrentLastDay: function() {
        return this._addDays(this.getCurrentFirstDay(), this.options.daysToShow - 1);
      },

      /*********************
        * private functions *
        *********************/
      _setOption: function(key, value) {
        var self = this;
        if (self.options[key] != value) {
          // event callback change, no need to re-render the events
          if (key == 'beforeEventNew') {
            self.options[key] = value;
            return;
          }

          // this could be made more efficient at some stage by caching the
          // events array locally in a store but this should be done in conjunction
          // with a proper binding model.

          var currentEvents = self.element.find('.wc-cal-event').map(function() {
            return $(this).data('calEvent');
          });

          var newOptions = {};
          newOptions[key] = value;
          self._renderEvents({events: currentEvents, options: newOptions}, self.element.find('.wc-day-column-inner'));
        }
      },

      // compute dynamic options based on other config values
      _computeOptions: function() {
        var options = this.options;
        if (options.businessHours.limitDisplay) {
          options.timeslotsPerDay = options.timeslotsPerHour * (options.businessHours.end - options.businessHours.start);
          options.millisToDisplay = (options.businessHours.end - options.businessHours.start) * 3600000; // 60 * 60 * 1000
          options.millisPerTimeslot = options.millisToDisplay / options.timeslotsPerDay;
        } else {
          options.timeslotsPerDay = options.timeslotsPerHour * 24;
          options.millisToDisplay = MILLIS_IN_DAY;
          options.millisPerTimeslot = MILLIS_IN_DAY / options.timeslotsPerDay;
        }
      },

      /*
        * Resize the calendar scrollable height based on the provided function in options.
        */
      _resizeCalendar: function() {
        var options = this.options;
        if (options && $.isFunction(options.height)) {
          var calendarHeight = options.height(this.element);
          var headerHeight = this.element.find('.wc-header').outerHeight();
          var navHeight = this.element.find('.wc-toolbar').outerHeight();
          var scrollContainerHeight = Math.max(calendarHeight - navHeight - headerHeight, options.minBodyHeight);
          var timeslotHeight = this.element.find('.wc-time-slots').outerHeight();
          this.element.find('.wc-scrollable-grid').height(scrollContainerHeight);
          if (timeslotHeight <= scrollContainerHeight) {
            this.element.find('.wc-scrollbar-shim').width(0);
          }
          else {
            this.element.find('.wc-scrollbar-shim').width(this._findScrollBarWidth());
          }
          this._trigger('resize', this.element);
        }
      },

      _findScrollBarWidth: function() {
        var parent = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo('body');
        var child = parent.children();
        var width = child.innerWidth() - child.height(99).innerWidth();
        parent.remove();
        return width || /* default to 16 that is the average */ 16;
      },

      /*
        * configure calendar interaction events that are able to use event
        * delegation for greater efficiency
        */
      _setupEventDelegation: function() {
        var self = this;
        var options = this.options;

        this.element.click(function(event) {
          var $target = $(event.target),
              freeBusyManager;

          // click is disabled
          if ($target.data('preventClick')) {
            return;
          }

          var $calEvent = $target.hasClass('wc-cal-event') ?
            $target :
            $target.parents('.wc-cal-event');
          if (!$calEvent.length || !$calEvent.data('calEvent')) {
            return;
          }

          freeBusyManager = self.getFreeBusyManagerForEvent($calEvent.data('calEvent'));

          if (options.allowEventDelete && $target.hasClass('wc-cal-event-delete')) {
            options.eventDelete($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
          } else {
            options.eventClick($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
          }
        }).mouseover(function(event) {
          var $target = $(event.target);
          var $calEvent = $target.hasClass('wc-cal-event') ?
            $target :
            $target.parents('.wc-cal-event');

          if (!$calEvent.length || !$calEvent.data('calEvent')) {
            return;
          }

          if (self._isDraggingOrResizing($calEvent)) {
            return;
          }

          options.eventMouseover($calEvent.data('calEvent'), $calEvent, event);
        }).mouseout(function(event) {
          var $target = $(event.target);
          var $calEvent = $target.hasClass('wc-cal-event') ?
            $target :
            $target.parents('.wc-cal-event');

          if (!$calEvent.length || !$calEvent.data('calEvent')) {
            return;
          }

          if (self._isDraggingOrResizing($calEvent)) {
            return;
          }

          options.eventMouseout($calEvent.data('calEvent'), $calEvent, event);
        });
      },

      /**
       * check if a ui draggable or resizable is currently being dragged or
       * resized.
       */
      _isDraggingOrResizing: function($target) {
        return $target.hasClass('ui-draggable-dragging') ||
               $target.hasClass('ui-resizable-resizing');
      },

      /*
        * Render the main calendar layout
        */
      _renderCalendar: function() {
        var $calendarContainer, $weekDayColumns;
        var self = this;
        var options = this.options;

        $calendarContainer = $('<div class=\"ui-widget wc-container\">').appendTo(self.element);

        //render the different parts
          // nav links
        self._renderCalendarButtons($calendarContainer);
          // header
        self._renderCalendarHeader($calendarContainer);
          // body
        self._renderCalendarBody($calendarContainer);

        $weekDayColumns = $calendarContainer.find('.wc-day-column-inner');
        $weekDayColumns.each(function(i, val) {
            if (!options.readonly) {
              self._addDroppableToWeekDay($(this));
              if (options.allowEventCreation) {
                self._setupEventCreationForWeekDay($(this));
              }
            }
        });
      },

      /**
        * render the nav buttons on top of the calendar
        */
      _renderCalendarButtons: function($calendarContainer) {
        var self = this, options = this.options;
        if ( !options.showHeader ) return;
        if (options.buttons) {
            var calendarNavHtml = '';

            calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
              calendarNavHtml += '<div class=\"wc-display\"></div>';
              calendarNavHtml += '<div class=\"wc-nav\">';
                calendarNavHtml += '<button class=\"wc-prev\">' + options.buttonText.lastWeek + '</button>';
                calendarNavHtml += '<button class=\"wc-today\">' + options.buttonText.today + '</button>';
                calendarNavHtml += '<button class=\"wc-next\">' + options.buttonText.nextWeek + '</button>';
              calendarNavHtml += '</div>';
              calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
            calendarNavHtml += '</div>';

            $(calendarNavHtml).appendTo($calendarContainer);

            $calendarContainer.find('.wc-nav .wc-today')
              .button({
                icons: {primary: 'ui-icon-home'}})
              .click(function() {
                    self.today();
                    return false;
                  });

            $calendarContainer.find('.wc-nav .wc-prev')
              .button({
                text: false,
                icons: {primary: 'ui-icon-seek-prev'}})
              .click(function() {
                  self.element.weekCalendar('prev');
                  return false;
                });

            $calendarContainer.find('.wc-nav .wc-next')
              .button({
                text: false,
                icons: {primary: 'ui-icon-seek-next'}})
              .click(function() {
                  self.element.weekCalendar('next');
                  return false;
                });

            // now add buttons to switch display
            if (this.options.switchDisplay && $.isPlainObject(this.options.switchDisplay)) {
              var $container = $calendarContainer.find('.wc-display');
              $.each(this.options.switchDisplay, function(label, option) {
                      var _id = 'wc-switch-display-' + option;
                      var _input = $('<input type="radio" id="' + _id + '" name="wc-switch-display" class="wc-switch-display"/>');
                      var _label = $('<label for="' + _id + '"></label>');
                      _label.html(label);
                      _input.val(option);
                      if (parseInt(self.options.daysToShow, 10) === parseInt(option, 10)) {
                        _input.prop('checked', true); 
                      }
                      $container
                        .append(_input)
                        .append(_label);
                    });
              $container.find('input').change(function() {
                  self.setDaysToShow(parseInt($(this).val(), 10));
                });
            }
            $calendarContainer.find('.wc-nav, .wc-display').buttonset();
            var _height = $calendarContainer.find('.wc-nav').outerHeight();
            $calendarContainer.find('.wc-title')
              .height(_height)
              .css('line-height', _height + 'px');
        }else{
            var calendarNavHtml = '';
            calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
              calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
            calendarNavHtml += '</div>';
            $(calendarNavHtml).appendTo($calendarContainer);

        }
      },

      /**
        * render the calendar header, including date and user header
        */
      _renderCalendarHeader: function($calendarContainer) {
        var self = this, options = this.options,
            showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
            rowspan = '', colspan = '', calendarHeaderHtml;

        if (showAsSeparatedUser) {
          rowspan = ' rowspan=\"2\"';
          colspan = ' colspan=\"' + options.users.length + '\" ';
        }

        //first row
        calendarHeaderHtml = '<div class=\"ui-widget-content wc-header\">';
        calendarHeaderHtml += '<table><tbody><tr><td class=\"wc-time-column-header\"></td>';
        for (var i = 1; i <= options.daysToShow; i++) {
          calendarHeaderHtml += '<td class=\"wc-day-column-header wc-day-' + i + '\"' + colspan + '></td>';
        }
        calendarHeaderHtml += '<td class=\"wc-scrollbar-shim\"' + rowspan + '></td></tr>';

        //users row
        if (showAsSeparatedUser) {
          calendarHeaderHtml += '<tr><td class=\"wc-time-column-header\"></td>';
          var uLength = options.users.length,
              _headerClass = '';

          for (var i = 1; i <= options.daysToShow; i++) {
            for (var j = 0; j < uLength; j++) {
              _headerClass = [];
              if (j == 0) {
                _headerClass.push('wc-day-column-first');
              }
              if (j == uLength - 1) {
                _headerClass.push('wc-day-column-last');
              }
              if (!_headerClass.length) {
                _headerClass = 'wc-day-column-middle';
              }
              else {
                _headerClass = _headerClass.join(' ');
              }
              calendarHeaderHtml += '<td class=\"' + _headerClass + ' wc-user-header wc-day-' + i + ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
//              calendarHeaderHtml+=    "<div class=\"wc-user-header wc-day-" + i + " wc-user-" + self._getUserIdFromIndex(j) +"\" >";
              calendarHeaderHtml += self._getUserName(j);
//              calendarHeaderHtml+=    "</div>";
              calendarHeaderHtml += '</td>';
            }
          }
          calendarHeaderHtml += '</tr>';
        }
        //close the header
        calendarHeaderHtml += '</tbody></table></div>';

        $(calendarHeaderHtml).appendTo($calendarContainer);
      },

      /**
        * render the calendar body.
        * Calendar body is composed of several distinct parts.
        * Each part is displayed in a separated row to ease rendering.
        * for further explanations, see each part rendering function.
        */
      _renderCalendarBody: function($calendarContainer) {
        var self = this, options = this.options,
            showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
            $calendarBody, $calendarTableTbody;
        // create the structure
        $calendarBody = '<div class=\"wc-scrollable-grid\">';
        $calendarBody += '<table class=\"wc-time-slots\">';
        $calendarBody += '<tbody>';
        $calendarBody += '</tbody>';
        $calendarBody += '</table>';
        $calendarBody += '</div>';
        $calendarBody = $($calendarBody);
        $calendarTableTbody = $calendarBody.find('tbody');

        self._renderCalendarBodyTimeSlots($calendarTableTbody);
        self._renderCalendarBodyOddEven($calendarTableTbody);
        self._renderCalendarBodyFreeBusy($calendarTableTbody);
        self._renderCalendarBodyEvents($calendarTableTbody);

        $calendarBody.appendTo($calendarContainer);

        //set the column height
        $calendarContainer.find('.wc-full-height-column').height(options.timeslotHeight * options.timeslotsPerDay);
        //set the timeslot height
        $calendarContainer.find('.wc-time-slot').height(options.timeslotHeight - 1); //account for border
        //init the time row header height
        /**
  TODO    if total height for an hour is less than 11px, there is a display problem.
          Find a way to handle it
        */
        $calendarContainer.find('.wc-time-header-cell').css({
          height: (options.timeslotHeight * options.timeslotsPerHour) - 11,
          padding: 5
        });
        //add the user data to every impacted column
        if (showAsSeparatedUser) {
          for (var i = 0, uLength = options.users.length; i < uLength; i++) {
            $calendarContainer.find('.wc-user-' + self._getUserIdFromIndex(i))
                  .data('wcUser', options.users[i])
                  .data('wcUserIndex', i)
                  .data('wcUserId', self._getUserIdFromIndex(i));
          }
        }
      },

      /**
        * render the timeslots separation
        */
      _renderCalendarBodyTimeSlots: function($calendarTableTbody) {
        var options = this.options,
            renderRow, i, j,
            showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
            start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
            end = (options.businessHours.limitDisplay ? options.businessHours.end : 24),
            rowspan = 1;

        //calculate the rowspan
        if (options.displayOddEven) { rowspan += 1; }
        if (options.displayFreeBusys) { rowspan += 1; }
        if (rowspan > 1) {
          rowspan = ' rowspan=\"' + rowspan + '\"';
        }
        else {
          rowspan = '';
        }

        renderRow = '<tr class=\"wc-grid-row-timeslot\">';
        renderRow += '<td class=\"wc-grid-timeslot-header\"' + rowspan + '></td>';
        renderRow += '<td colspan=\"' + options.daysToShow * (showAsSeparatedUser ? options.users.length : 1) + '\">';
        renderRow += '<div class=\"wc-no-height-wrapper wc-time-slot-wrapper\">';
        renderRow += '<div class=\"wc-time-slots\">';

        for (i = start; i < end; i++) {
          for (j = 0; j < options.timeslotsPerHour - 1; j++) {
            renderRow += '<div class=\"wc-time-slot\"></div>';
          }
          renderRow += '<div class=\"wc-time-slot wc-hour-end\"></div>';
        }

        renderRow += '</div>';
        renderRow += '</div>';
        renderRow += '</td>';
        renderRow += '</tr>';

        $(renderRow).appendTo($calendarTableTbody);
      },

      /**
        * render the odd even columns
        */
      _renderCalendarBodyOddEven: function($calendarTableTbody) {
        if (this.options.displayOddEven) {
          var options = this.options,
              renderRow = '<tr class=\"wc-grid-row-oddeven\">',
              showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
              oddEven,
              // let's take advantage of the jquery ui framework
              oddEvenClasses = {'odd': 'wc-column-odd', 'even': 'ui-state-hover wc-column-even'};

          //now let's display oddEven placeholders
          for (var i = 1; i <= options.daysToShow; i++) {
              if (!showAsSeparatedUser) {
                oddEven = (oddEven == 'odd' ? 'even' : 'odd');
                renderRow += '<td class=\"wc-day-column day-' + i + '\">';
                renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
                renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\"></div>';
                renderRow += '</div>';
                renderRow += '</td>';
              }
              else {
                var uLength = options.users.length;
                for (var j = 0; j < uLength; j++) {
                    oddEven = (oddEven == 'odd' ? 'even' : 'odd');
                    renderRow += '<td class=\"wc-day-column day-' + i + '\">';
                    renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
                    renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\" ></div>';
                    renderRow += '</div>';
                    renderRow += '</td>';
                }
            }
          }
          renderRow += '</tr>';

          $(renderRow).appendTo($calendarTableTbody);
        }
      },

      /**
        * render the freebusy placeholders
        */
      _renderCalendarBodyFreeBusy: function($calendarTableTbody) {
        if (this.options.displayFreeBusys) {
          var self = this, options = this.options,
              renderRow = '<tr class=\"wc-grid-row-freebusy\">',
              showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
          renderRow += '</td>';

          //now let's display freebusy placeholders
          for (var i = 1; i <= options.daysToShow; i++) {
            if (options.displayFreeBusys) {
              if (!showAsSeparatedUser) {
                renderRow += '<td class=\"wc-day-column day-' + i + '\">';
                renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
                renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i + '\"></div>';
                renderRow += '</div>';
                renderRow += '</td>';
              }
              else {
                var uLength = options.users.length;
                for (var j = 0; j < uLength; j++) {
                    renderRow += '<td class=\"wc-day-column day-' + i + '\">';
                    renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
                    renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i;
                    renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
                    renderRow += '</div>';
                    renderRow += '</div>';
                    renderRow += '</td>';
                }
              }
            }
          }

          renderRow += '</tr>';

          $(renderRow).appendTo($calendarTableTbody);
        }
      },

      /**
        * render the calendar body for event placeholders
        */
      _renderCalendarBodyEvents: function($calendarTableTbody) {
        var self = this, options = this.options,
            renderRow,
            showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
            start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
            end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
        renderRow = '<tr class=\"wc-grid-row-events\">';
        renderRow += '<td class=\"wc-grid-timeslot-header\">';
        for (var i = start; i < end; i++) {
          var bhClass = (options.businessHours.start <= i && options.businessHours.end > i) ? 'ui-state-active wc-business-hours' : 'ui-state-default';
          renderRow += '<div class=\"wc-hour-header ' + bhClass + '\">';
          if (options.use24Hour) {
            renderRow += '<div class=\"wc-time-header-cell\">' + self._24HourForIndex(i) + '</div>';
          }
          else {
            renderRow += '<div class=\"wc-time-header-cell\">' + self._hourForIndex(i) + '<span class=\"wc-am-pm\">' + self._amOrPm(i) + '</span></div>';
          }
          renderRow += '</div>';
        }
        renderRow += '</td>';

        //now let's display events placeholders
        var _columnBaseClass = 'ui-state-default wc-day-column';
        for (var i = 1; i <= options.daysToShow; i++) {
          if (!showAsSeparatedUser) {
            renderRow += '<td class=\"' + _columnBaseClass + ' wc-day-column-first wc-day-column-last day-' + i + '\">';
            renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i + '\"></div>';
            renderRow += '</td>';
          }
          else {
            var uLength = options.users.length;
            var columnclass;
            for (var j = 0; j < uLength; j++) {
              columnclass = [];
              if (j == 0) {
                columnclass.push('wc-day-column-first');
              }
              if (j == uLength - 1) {
                columnclass.push('wc-day-column-last');
              }
              if (!columnclass.length) {
                columnclass = 'wc-day-column-middle';
              }
              else {
                columnclass = columnclass.join(' ');
              }
              renderRow += '<td class=\"' + _columnBaseClass + ' ' + columnclass + ' day-' + i + '\">';
              renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i;
              renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
              renderRow += '</div>';
              renderRow += '</td>';
            }
          }
        }

        renderRow += '</tr>';

        $(renderRow).appendTo($calendarTableTbody);
      },

      /*
        * setup mouse events for capturing new events
        */
      _setupEventCreationForWeekDay: function($weekDay) {
          var self = this;
          var options = this.options;
          $weekDay.mousedown(function(event) {
            var $target = $(event.target);
            if ($target.hasClass('wc-day-column-inner')) {

                var $newEvent = $('<div class=\"wc-cal-event wc-new-cal-event wc-new-cal-event-creating\"></div>');

                $newEvent.css({lineHeight: (options.timeslotHeight - 2) + 'px', fontSize: (options.timeslotHeight / 2) + 'px'});
                $target.append($newEvent);

                var columnOffset = $target.offset().top;
                var clickY = event.pageY - columnOffset;
                var clickYRounded = (clickY - (clickY % options.timeslotHeight)) / options.timeslotHeight;
                var topPosition = clickYRounded * options.timeslotHeight;
                $newEvent.css({top: topPosition});

                if (!options.preventDragOnEventCreation) {
                  $target.bind('mousemove.newevent', function(event) {
                    $newEvent.show();
                    $newEvent.addClass('ui-resizable-resizing');
                    var height = Math.round(event.pageY - columnOffset - topPosition);
                    var remainder = height % options.timeslotHeight;
                    //snap to closest timeslot
                    if (remainder < 0) {
                        var useHeight = height - remainder;
                        $newEvent.css('height', useHeight < options.timeslotHeight ? options.timeslotHeight : useHeight);
                    } else {
                        $newEvent.css('height', height + (options.timeslotHeight - remainder));
                    }
                  }).mouseup(function() {
                    $target.unbind('mousemove.newevent');
                    $newEvent.addClass('ui-corner-all');
                  });
                }
            }

          }).mouseup(function(event) {
            var $target = $(event.target);

            var $weekDay = $target.closest('.wc-day-column-inner');
            var $newEvent = $weekDay.find('.wc-new-cal-event-creating');

            if ($newEvent.length) {
                var createdFromSingleClick = !$newEvent.hasClass('ui-resizable-resizing');

                //if even created from a single click only, default height
                if (createdFromSingleClick) {
                  $newEvent.css({height: options.timeslotHeight * options.defaultEventLength}).show();
                }
                var top = parseInt($newEvent.css('top'));
                var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $newEvent, top);

                $newEvent.remove();
                var newCalEvent = {start: eventDuration.start, end: eventDuration.end, title: options.newEventText};
                var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;

                if (showAsSeparatedUser) {
                  newCalEvent = self._setEventUserId(newCalEvent, $weekDay.data('wcUserId'));
                }
                else if (!options.showAsSeparateUsers && options.users && options.users.length == 1) {
                  newCalEvent = self._setEventUserId(newCalEvent, self._getUserIdFromIndex(0));
                }

                var freeBusyManager = self.getFreeBusyManagerForEvent(newCalEvent);

                var $renderedCalEvent = self._renderEvent(newCalEvent, $weekDay);

                if (!options.allowCalEventOverlap) {
                  self._adjustForEventCollisions($weekDay, $renderedCalEvent, newCalEvent, newCalEvent);
                  self._positionEvent($weekDay, $renderedCalEvent);
                } else {
                  self._adjustOverlappingEvents($weekDay);
                }

                var proceed = self._trigger('beforeEventNew', event, {
                  'calEvent': newCalEvent,
                  'createdFromSingleClick': createdFromSingleClick,
                  'calendar': self.element
                });
  							if (proceed) {
									options.eventNew(newCalEvent, $renderedCalEvent, freeBusyManager, self.element, event);
								}
								else {
									$($renderedCalEvent).remove();
								}
            }
          });
      },

      /*
        * load calendar events for the week based on the date provided
        */
      _loadCalEvents: function(dateWithinWeek) {

          var date, weekStartDate, weekEndDate, $weekDayColumns;
          var self = this;
          var options = this.options;
          date = this._fixMinMaxDate(dateWithinWeek || options.date);
          // if date is not provided
          // or was not set
          // or is different than old one
          if ((!date || !date.getTime) ||
              (!options.date || !options.date.getTime) ||
              date.getTime() != options.date.getTime()
          ) {
            // trigger the changedate event
            this._trigger('changedate', this.element, date);
          }
          this.options.date = date;
          weekStartDate = self._dateFirstDayOfWeek(date);
          weekEndDate = self._dateLastMilliOfWeek(date);

          options.calendarBeforeLoad(self.element);

          self.element.data('startDate', weekStartDate);
          self.element.data('endDate', weekEndDate);

          $weekDayColumns = self.element.find('.wc-day-column-inner');

          self._updateDayColumnHeader($weekDayColumns);

          //load events by chosen means
          if (typeof options.data == 'string') {
            if (options.loading) {
              options.loading(true);
            }
            if (_currentAjaxCall) {
              // first abort current request.
              if (!_jQuery14OrLower) {
                _currentAjaxCall.abort();
              } else {
                // due to the fact that jquery 1.4 does not detect a request was
                // aborted, we need to replace the onreadystatechange and
                // execute the "complete" callback.
                _currentAjaxCall.onreadystatechange = null;
                _currentAjaxCall.abort();
                _currentAjaxCall = null;
                if (options.loading) {
                  options.loading(false);
                }
              }
            }
            var jsonOptions = self._getJsonOptions();
            jsonOptions[options.startParam || 'start'] = Math.round(weekStartDate.getTime() / 1000);
            jsonOptions[options.endParam || 'end'] = Math.round(weekEndDate.getTime() / 1000);
            _currentAjaxCall = $.ajax({
              url: options.data,
              data: jsonOptions,
              dataType: 'json',
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                // only prevent error with jQuery 1.5
                // see issue #34. thanks to dapplebeforedawn
                // (https://github.com/themouette/jquery-week-calendar/issues#issue/34)
                // for 1.5+, aborted request mean errorThrown == 'abort'
                // for prior version it means !errorThrown && !XMLHttpRequest.status
                // fixes #55
                if (errorThrown != 'abort' && XMLHttpRequest.status != 0) {
                  alert('unable to get data, error:' + textStatus);
                }
              },
              success: function(data) {
                self._renderEvents(data, $weekDayColumns);
              },
              complete: function() {
                _currentAjaxCall = null;
                if (options.loading) {
                  options.loading(false);
                }
              }
            });
          }
          else if ($.isFunction(options.data)) {
            options.data(weekStartDate, weekEndDate,
                  function(data) {
                      self._renderEvents(data, $weekDayColumns);
                  });
          }
          else if (options.data) {
                self._renderEvents(options.data, $weekDayColumns);
            }

          self._disableTextSelect($weekDayColumns);
      },

      /**
       * Draws a thin line which indicates the current time.
       */
      _drawCurrentHourLine: function() {
        var d = new Date(),
            options = this.options,
            businessHours = options.businessHours;

        // first, we remove the old hourline if it exists
        $('.wc-hourline', this.element).remove();

        // the line does not need to be displayed
        if (businessHours.limitDisplay && d.getHours() > businessHours.end) {
          return;
        }

        // then we recreate it
        var paddingStart = businessHours.limitDisplay ? businessHours.start : 0;
        var nbHours = d.getHours() - paddingStart + d.getMinutes() / 60;
        var positionTop = nbHours * options.timeslotHeight * options.timeslotsPerHour;
        var lineWidth = $('.wc-scrollable-grid .wc-today', this.element).width() + 3;

        $('.wc-scrollable-grid .wc-today', this.element).append(
          $('<div>', {
            'class': 'wc-hourline',
            style: 'top: ' + positionTop + 'px; width: ' + lineWidth + 'px'
          })
        );
      },

      /*
        * update the display of each day column header based on the calendar week
        */
      _updateDayColumnHeader: function($weekDayColumns) {
          var self = this;
          var options = this.options;
          var currentDay = self._cloneDate(self.element.data('startDate'));
          var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
          var todayClass = 'ui-state-active wc-today';

          self.element.find('.wc-header td.wc-day-column-header').each(function(i, val) {
            $(this).html(self._getHeaderDate(currentDay));
            if (self._isToday(currentDay)) {
                $(this).addClass(todayClass);
            } else {
                $(this).removeClass(todayClass);
            }
            currentDay = self._addDays(currentDay, 1);

          });

          currentDay = self._cloneDate(self.element.data('startDate'));
          if (showAsSeparatedUser)
          {
            self.element.find('.wc-header td.wc-user-header').each(function(i, val) {
              if (self._isToday(currentDay)) {
                  $(this).addClass(todayClass);
              } else {
                  $(this).removeClass(todayClass);
              }
              currentDay = ((i + 1) % options.users.length) ? currentDay : self._addDays(currentDay, 1);
            });
          }

          currentDay = self._cloneDate(self.element.data('startDate'));

          $weekDayColumns.each(function(i, val) {

            $(this).data('startDate', self._cloneDate(currentDay));
            $(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
            if (self._isToday(currentDay)) {
                $(this).parent()
                    .addClass(todayClass)
                    .removeClass('ui-state-default');
            } else {
                $(this).parent()
                    .removeClass(todayClass)
                    .addClass('ui-state-default');
            }

            if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
              currentDay = self._addDays(currentDay, 1);
            }
          });

          //now update the freeBusy placeholders
          if (options.displayFreeBusys) {
            currentDay = self._cloneDate(self.element.data('startDate'));
            self.element.find('.wc-grid-row-freebusy .wc-column-freebusy').each(function(i, val) {
              $(this).data('startDate', self._cloneDate(currentDay));
              $(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
              if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
                currentDay = self._addDays(currentDay, 1);
              }
            });
          }

          // now update the calendar title
          if (this.options.title) {
            var date = this.options.date,
                start = self._cloneDate(self.element.data('startDate')),
                end = self._dateLastDayOfWeek(new Date(this._cloneDate(self.element.data('endDate')).getTime() - (MILLIS_IN_DAY))),
                title = this._getCalendarTitle(),
                date_format = options.dateFormat;

            // replace the placeholders contained in the title
            title = title.replace('%start%', self._formatDate(start, date_format));
            title = title.replace('%end%', self._formatDate(end, date_format));
            title = title.replace('%date%', self._formatDate(date, date_format));

            $('.wc-toolbar .wc-title', self.element).html(title);
          }
          //self._clearFreeBusys();
      },

      /**
       * Gets the calendar raw title.
       */
      _getCalendarTitle: function() {
        if ($.isFunction(this.options.title)) {
          return this.options.title(this.options.daysToShow);
        }

        return this.options.title || '';
      },

      /**
       * Render the events into the calendar
       */
      _renderEvents: function(data, $weekDayColumns) {
        var self = this;
        var options = this.options;
        var eventsToRender, nbRenderedEvents = 0;

        if (data.options) {
          var updateLayout = false;
          // update options
          $.each(data.options, function(key, value) {
            if (value !== options[key]) {
              options[key] = value;
              updateLayout = updateLayout || $.ui.weekCalendar.updateLayoutOptions[key];
            }
          });

          self._computeOptions();

          if (updateLayout) {
            var hour = self._getCurrentScrollHour();
            self.element.empty();
            self._renderCalendar();
            $weekDayColumns = self.element.find('.wc-time-slots .wc-day-column-inner');
            self._updateDayColumnHeader($weekDayColumns);
            self._resizeCalendar();
            self._scrollToHour(hour, false);
          }
        }
        this._clearCalendar();

        if ($.isArray(data)) {
          eventsToRender = self._cleanEvents(data);
        } else if (data.events) {
          eventsToRender = self._cleanEvents(data.events);
          self._renderFreeBusys(data);
        }

        $.each(eventsToRender, function(i, calEvent) {
          // render a multi day event as various event :
          // thanks to http://github.com/fbeauchamp/jquery-week-calendar
          var initialStart = new Date(calEvent.start);
          var initialEnd = new Date(calEvent.end);
          var maxHour = self.options.businessHours.limitDisplay ? self.options.businessHours.end : 24;
          var minHour = self.options.businessHours.limitDisplay ? self.options.businessHours.start : 0;
          var start = new Date(initialStart);
          var startDate = self._formatDate(start, 'Ymd');
          var endDate = self._formatDate(initialEnd, 'Ymd');
          var $weekDay;
          var isMultiday = false;

          while (startDate < endDate) {
            calEvent.start = start;

            // end of this virual calEvent is set to the end of the day
            calEvent.end.setFullYear(start.getFullYear());
            calEvent.end.setDate(start.getDate());
            calEvent.end.setMonth(start.getMonth());
            calEvent.end.setHours(maxHour, 0, 0);

            if (($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
              self._renderEvent(calEvent, $weekDay);
              nbRenderedEvents += 1;
            }

            // start is set to the begin of the new day
            start.setDate(start.getDate() + 1);
            start.setHours(minHour, 0, 0);

            startDate = self._formatDate(start, 'Ymd');
            isMultiday = true;
          }

          if (start <= initialEnd) {
            calEvent.start = start;
            calEvent.end = initialEnd;

            if (((isMultiday && calEvent.start.getTime() != calEvent.end.getTime()) || !isMultiday) && ($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
              self._renderEvent(calEvent, $weekDay);
              nbRenderedEvents += 1;
            }
          }

          // put back the initial start date
          calEvent.start = initialStart;
        });

        $weekDayColumns.each(function() {
          self._adjustOverlappingEvents($(this));
        });

        options.calendarAfterLoad(self.element);

        _hourLineTimeout && clearInterval(_hourLineTimeout);

        if (options.hourLine) {
          self._drawCurrentHourLine();

          _hourLineTimeout = setInterval(function() {
            self._drawCurrentHourLine();
          }, 60 * 1000); // redraw the line each minute
        }

        !nbRenderedEvents && options.noEvents();
      },

      /*
        * Render a specific event into the day provided. Assumes correct
        * day for calEvent date
        */
      _renderEvent: function(calEvent, $weekDay) {
          var self = this;
          var options = this.options;
          if (calEvent.start.getTime() > calEvent.end.getTime()) {
            return; // can't render a negative height
          }

          var eventClass, eventHtml, $calEventList, $modifiedEvent;

          eventClass = calEvent.id ? 'wc-cal-event' : 'wc-cal-event wc-new-cal-event';
          eventHtml = '<div class=\"' + eventClass + ' ui-corner-all\">';
          eventHtml += '<div class=\"wc-time ui-corner-top\"></div>';
          eventHtml += '<div class=\"wc-title\"></div></div>';

          $weekDay.each(function() {
            var $calEvent = $(eventHtml);
            $modifiedEvent = options.eventRender(calEvent, $calEvent);
            $calEvent = $modifiedEvent ? $modifiedEvent.appendTo($(this)) : $calEvent.appendTo($(this));
            $calEvent.css({lineHeight: (options.textSize + 2) + 'px', fontSize: options.textSize + 'px'});

            self._refreshEventDetails(calEvent, $calEvent);
            self._positionEvent($(this), $calEvent);

            //add to event list
            if ($calEventList) {
              $calEventList = $calEventList.add($calEvent);
            }
            else {
              $calEventList = $calEvent;
            }
          });
          $calEventList.show();

          if (!options.readonly && options.resizable(calEvent, $calEventList)) {
            self._addResizableToCalEvent(calEvent, $calEventList, $weekDay);
          }
          if (!options.readonly && options.draggable(calEvent, $calEventList)) {
            self._addDraggableToCalEvent(calEvent, $calEventList);
          }
          options.eventAfterRender(calEvent, $calEventList);

          return $calEventList;

      },
      addEvent: function() {
        return this._renderEvent.apply(this, arguments);
      },

      _adjustOverlappingEvents: function($weekDay) {
          var self = this;
          if (self.options.allowCalEventOverlap) {
            var groupsList = self._groupOverlappingEventElements($weekDay);
            $.each(groupsList, function() {
                var curGroups = this;
                $.each(curGroups, function(groupIndex) {
                  var curGroup = this;

                  // do we want events to be displayed as overlapping
                  if (self.options.overlapEventsSeparate) {
                      var newWidth = self.options.totalEventsWidthPercentInOneColumn / curGroups.length;
                      var newLeft = groupIndex * newWidth;
                  } else {
                      // TODO what happens when the group has more than 10 elements
                      var newWidth = self.options.totalEventsWidthPercentInOneColumn - ((curGroups.length - 1) * 10);
                      var newLeft = groupIndex * 10;
                  }
                  $.each(curGroup, function() {
                      // bring mouseovered event to the front
                      if (!self.options.overlapEventsSeparate) {
                        $(this).bind('mouseover.z-index', function() {
                            var $elem = $(this);
                            $.each(curGroup, function() {
                              $(this).css({'z-index': '1'});
                            });
                            $elem.css({'z-index': '3'});
                        });
                      }
                      $(this).css({width: newWidth + '%', left: newLeft + '%', right: 0});
                  });
                });
            });
          }
      },


      /*
        * Find groups of overlapping events
        */
      _groupOverlappingEventElements: function($weekDay) {
          var $events = $weekDay.find('.wc-cal-event:visible');
          var sortedEvents = $events.sort(function(a, b) {
            return $(a).data('calEvent').start.getTime() - $(b).data('calEvent').start.getTime();
          });

          var lastEndTime = new Date(0, 0, 0);
          var groups = [];
          var curGroups = [];
          var $curEvent;
          $.each(sortedEvents, function() {
            $curEvent = $(this);
            //checks, if the current group list is not empty, if the overlapping is finished
            if (curGroups.length > 0) {
                if (lastEndTime.getTime() <= $curEvent.data('calEvent').start.getTime()) {
                  //finishes the current group list by adding it to the resulting list of groups and cleans it

                  groups.push(curGroups);
                  curGroups = [];
                }
            }

            //finds the first group to fill with the event
            for (var groupIndex = 0; groupIndex < curGroups.length; groupIndex++) {
                if (curGroups[groupIndex].length > 0) {
                  //checks if the event starts after the end of the last event of the group
                  if (curGroups[groupIndex][curGroups[groupIndex].length - 1].data('calEvent').end.getTime() <= $curEvent.data('calEvent').start.getTime()) {
                      curGroups[groupIndex].push($curEvent);
                      if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
                        lastEndTime = $curEvent.data('calEvent').end;
                      }
                      return;
                  }
                }
            }
            //if not found, creates a new group
            curGroups.push([$curEvent]);
            if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
                lastEndTime = $curEvent.data('calEvent').end;
            }
          });
          //adds the last groups in result
          if (curGroups.length > 0) {
            groups.push(curGroups);
          }
          return groups;
      },


      /*
        * find the weekday in the current calendar that the calEvent falls within
        */
      _findWeekDayForEvent: function(calEvent, $weekDayColumns) {

          var $weekDay,
              options = this.options,
              showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
              user_ids = this._getEventUserId(calEvent);

          if (!$.isArray(user_ids)) {
            user_ids = [user_ids];
          }

          $weekDayColumns.each(function(index, curDay) {
            if ($(this).data('startDate').getTime() <= calEvent.start.getTime() &&
                  $(this).data('endDate').getTime() >= calEvent.end.getTime() &&
                  (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), user_ids) !== -1)
            ) {
                if ($weekDay) {
                $weekDay = $weekDay.add($(curDay));
                }
                else {
                $weekDay = $(curDay);
                }
            }
          });

          return $weekDay;
      },

      /*
        * update the events rendering in the calendar. Add if does not yet exist.
        */
      _updateEventInCalendar: function(calEvent) {
          var self = this;
          self._cleanEvent(calEvent);

          if (calEvent.id) {
            self.element.find('.wc-cal-event').each(function() {
                if ($(this).data('calEvent').id === calEvent.id || $(this).hasClass('wc-new-cal-event')) {
                  $(this).remove();
              //     return false;
                }
            });
          }

          var $weekDays = self._findWeekDayForEvent(calEvent, self.element.find('.wc-grid-row-events .wc-day-column-inner'));
          if ($weekDays) {
            $weekDays.each(function(index, weekDay) {
              var $weekDay = $(weekDay);
              var $calEvent = self._renderEvent(calEvent, $weekDay);
              self._adjustForEventCollisions($weekDay, $calEvent, calEvent, calEvent);
              self._refreshEventDetails(calEvent, $calEvent);
              self._positionEvent($weekDay, $calEvent);
              self._adjustOverlappingEvents($weekDay);
            });
          }
      },

      /*
        * Position the event element within the weekday based on it's start / end dates.
        */
      _positionEvent: function($weekDay, $calEvent) {
          var options = this.options;
          var calEvent = $calEvent.data('calEvent');
          var pxPerMillis = $weekDay.height() / options.millisToDisplay;
          var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
          var startMillis = this._getDSTdayShift(calEvent.start).getTime() - this._getDSTdayShift(new Date(calEvent.start.getFullYear(), calEvent.start.getMonth(), calEvent.start.getDate(), firstHourDisplayed)).getTime();
          var eventMillis = this._getDSTdayShift(calEvent.end).getTime() - this._getDSTdayShift(calEvent.start).getTime();
          var pxTop = pxPerMillis * startMillis;
          var pxHeight = pxPerMillis * eventMillis;
          //var pxHeightFallback = pxPerMillis * (60 / options.timeslotsPerHour) * 60 * 1000;
      $calEvent.css({top: pxTop, height: pxHeight || (pxPerMillis * 3600000 / options.timeslotsPerHour)});
      },

      /*
        * Determine the actual start and end times of a calevent based on it's
        * relative position within the weekday column and the starting hour of the
        * displayed calendar.
        */
      _getEventDurationFromPositionedEventElement: function($weekDay, $calEvent, top) {
          var options = this.options;
          var startOffsetMillis = options.businessHours.limitDisplay ? options.businessHours.start * 3600000 : 0;
          var start = new Date($weekDay.data('startDate').getTime() + startOffsetMillis + Math.round(top / options.timeslotHeight) * options.millisPerTimeslot);
          var end = new Date(start.getTime() + ($calEvent.height() / options.timeslotHeight) * options.millisPerTimeslot);
          return {start: this._getDSTdayShift(start, -1), end: this._getDSTdayShift(end, -1)};
      },

      /*
        * If the calendar does not allow event overlap, adjust the start or end date if necessary to
        * avoid overlapping of events. Typically, shortens the resized / dropped event to it's max possible
        * duration  based on the overlap. If no satisfactory adjustment can be made, the event is reverted to
        * it's original location.
        */
      _adjustForEventCollisions: function($weekDay, $calEvent, newCalEvent, oldCalEvent, maintainEventDuration) {
          var options = this.options;

          if (options.allowCalEventOverlap) {
            return;
          }
          var adjustedStart, adjustedEnd;
          var self = this;

          $weekDay.find('.wc-cal-event').not($calEvent).each(function() {
            var currentCalEvent = $(this).data('calEvent');

            //has been dropped onto existing event overlapping the end time
            if (newCalEvent.start.getTime() < currentCalEvent.end.getTime() &&
                  newCalEvent.end.getTime() >= currentCalEvent.end.getTime()) {

                adjustedStart = currentCalEvent.end;
            }


            //has been dropped onto existing event overlapping the start time
            if (newCalEvent.end.getTime() > currentCalEvent.start.getTime() &&
                  newCalEvent.start.getTime() <= currentCalEvent.start.getTime()) {

                adjustedEnd = currentCalEvent.start;
            }
            //has been dropped inside existing event with same or larger duration
            if (oldCalEvent.resizable == false ||
                  (newCalEvent.end.getTime() <= currentCalEvent.end.getTime() &&
                    newCalEvent.start.getTime() >= currentCalEvent.start.getTime())) {

                adjustedStart = oldCalEvent.start;
                adjustedEnd = oldCalEvent.end;
                return false;
            }

          });


          newCalEvent.start = adjustedStart || newCalEvent.start;

          if (adjustedStart && maintainEventDuration) {
            newCalEvent.end = new Date(adjustedStart.getTime() + (oldCalEvent.end.getTime() - oldCalEvent.start.getTime()));
            self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, oldCalEvent);
          } else {
            newCalEvent.end = adjustedEnd || newCalEvent.end;
          }


          //reset if new cal event has been forced to zero size
          if (newCalEvent.start.getTime() >= newCalEvent.end.getTime()) {
            newCalEvent.start = oldCalEvent.start;
            newCalEvent.end = oldCalEvent.end;
          }

          $calEvent.data('calEvent', newCalEvent);
      },

      /**
       * Add draggable capabilities to an event
       */
      _addDraggableToCalEvent: function(calEvent, $calEvent) {
        var options = this.options;

        $calEvent.draggable({
          handle: '.wc-time',
          containment: 'div.wc-time-slots',
          snap: '.wc-day-column-inner',
          snapMode: 'inner',
          snapTolerance: options.timeslotHeight - 1,
          revert: 'invalid',
          opacity: 0.5,
          grid: [$calEvent.outerWidth() + 1, options.timeslotHeight],
          start: function(event, ui) {
            var $calEvent = ui.draggable || ui.helper;
            options.eventDrag(calEvent, $calEvent);
          }
        });
      },

      /*
        * Add droppable capabilites to weekdays to allow dropping of calEvents only
        */
      _addDroppableToWeekDay: function($weekDay) {
          var self = this;
          var options = this.options;
          $weekDay.droppable({
            accept: '.wc-cal-event',
            drop: function(event, ui) {
                var $calEvent = ui.draggable;
                var top = Math.round(parseInt(ui.position.top));
                var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $calEvent, top);
                var calEvent = $calEvent.data('calEvent');
                var newCalEvent = $.extend(true, {}, calEvent, {start: eventDuration.start, end: eventDuration.end});
                var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
                if (showAsSeparatedUser) {
                  // we may have dragged the event on column with a new user.
                  // nice way to handle that is:
                  //  - get the newly dragged on user
                  //  - check if user is part of the event
                  //  - if yes, nothing changes, if not, find the old owner to remove it and add new one
                  var newUserId = $weekDay.data('wcUserId');
                  var userIdList = self._getEventUserId(calEvent);
                  var oldUserId = $(ui.draggable.parents('.wc-day-column-inner').get(0)).data('wcUserId');
                  if (!$.isArray(userIdList)) {
                    userIdList = [userIdList];
                  }
                  if ($.inArray(newUserId, userIdList) == -1) {
                    // remove old user
                    var _index = $.inArray(oldUserId, userIdList);
                    userIdList.splice(_index, 1);
                    // add new user ?
                    if ($.inArray(newUserId, userIdList) == -1) {
                      userIdList.push(newUserId);
                    }
                  }
                  newCalEvent = self._setEventUserId(newCalEvent, ((userIdList.length == 1) ? userIdList[0] : userIdList));
                }
                self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent, true);
                var $weekDayColumns = self.element.find('.wc-day-column-inner');

                //trigger drop callback
                options.eventDrop(newCalEvent, calEvent, $calEvent);

                var $newEvent = self._renderEvent(newCalEvent, self._findWeekDayForEvent(newCalEvent, $weekDayColumns));
                $calEvent.hide();

                $calEvent.data('preventClick', true);

                var $weekDayOld = self._findWeekDayForEvent($calEvent.data('calEvent'), self.element.find('.wc-time-slots .wc-day-column-inner'));

                if ($weekDayOld.data('startDate') != $weekDay.data('startDate')) {
                  self._adjustOverlappingEvents($weekDayOld);
                }
                self._adjustOverlappingEvents($weekDay);

                setTimeout(function() {
                  $calEvent.remove();
                }, 1000);

            }
          });
      },

      /*
        * Add resizable capabilities to a calEvent
        */
      _addResizableToCalEvent: function(calEvent, $calEvent, $weekDay) {
          var self = this;
          var options = this.options;
          $calEvent.resizable({
            grid: options.timeslotHeight,
            containment: $weekDay,
            handles: 's',
            minHeight: options.timeslotHeight,
            stop: function(event, ui) {
                var $calEvent = ui.element;
                var newEnd = new Date($calEvent.data('calEvent').start.getTime() + Math.max(1, Math.round(ui.size.height / options.timeslotHeight)) * options.millisPerTimeslot);
                if (self._needDSTdayShift($calEvent.data('calEvent').start, newEnd))
            newEnd = self._getDSTdayShift(newEnd, -1);
                var newCalEvent = $.extend(true, {}, calEvent, {start: calEvent.start, end: newEnd});
                self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent);

                //trigger resize callback
                options.eventResize(newCalEvent, calEvent, $calEvent);
                self._refreshEventDetails(newCalEvent, $calEvent);
                self._positionEvent($weekDay, $calEvent);
                self._adjustOverlappingEvents($weekDay);
                $calEvent.data('preventClick', true);
                setTimeout(function() {
                  $calEvent.removeData('preventClick');
                }, 500);
            }
          });
          $('.ui-resizable-handle', $calEvent).text('=');
      },

      /*
        * Refresh the displayed details of a calEvent in the calendar
        */
      _refreshEventDetails: function(calEvent, $calEvent) {
	  var suffix = '';
	  if (!this.options.readonly &&
		 this.options.allowEventDelete &&
		 this.options.deletable(calEvent,$calEvent)) {
	      suffix = '<div class="wc-cal-event-delete ui-icon ui-icon-close"></div>';
	  }
          $calEvent.find('.wc-time').html(this.options.eventHeader(calEvent, this.element) + suffix);
          $calEvent.find('.wc-title').html(this.options.eventBody(calEvent, this.element));
          $calEvent.data('calEvent', calEvent);
          this.options.eventRefresh(calEvent, $calEvent);
      },

      /*
        * Clear all cal events from the calendar
        */
      _clearCalendar: function() {
          this.element.find('.wc-day-column-inner div').remove();
          this._clearFreeBusys();
      },

      /*
        * Scroll the calendar to a specific hour
        */
      _scrollToHour: function(hour, animate) {
          var self = this;
          var options = this.options;
          var $scrollable = this.element.find('.wc-scrollable-grid');
          var slot = hour;
          if (self.options.businessHours.limitDisplay) {
            if (hour <= self.options.businessHours.start) {
                slot = 0;
            } else if (hour >= self.options.businessHours.end) {
                slot = self.options.businessHours.end - self.options.businessHours.start - 1;
            } else {
                slot = hour - self.options.businessHours.start;
            }
          }

          var $target = this.element.find('.wc-grid-timeslot-header .wc-hour-header:eq(' + slot + ')');

          $scrollable.animate({scrollTop: 0}, 0, function() {
            var targetOffset = $target.offset().top;
            var scroll = targetOffset - $scrollable.offset().top - $target.outerHeight();
            if (animate) {
              $scrollable.animate({scrollTop: scroll}, options.scrollToHourMillis);
            }
            else {
              $scrollable.animate({scrollTop: scroll}, 0);
            }
          });
      },

      /*
        * find the hour (12 hour day) for a given hour index
        */
      _hourForIndex: function(index) {
          if (index === 0) { //midnight
            return 12;
          } else if (index < 13) { //am
            return index;
          } else { //pm
            return index - 12;
          }
      },

      _24HourForIndex: function(index) {
          if (index === 0) { //midnight
            return '00:00';
          } else if (index < 10) {
            return '0' + index + ':00';
          } else {
            return index + ':00';
          }
      },

      _amOrPm: function(hourOfDay) {
          return hourOfDay < 12 ? 'AM' : 'PM';
      },

      _isToday: function(date) {
          var clonedDate = this._cloneDate(date);
          this._clearTime(clonedDate);
          var today = new Date();
          this._clearTime(today);
          return today.getTime() === clonedDate.getTime();
      },

      /*
       * Clean events to ensure correct format
       */
      _cleanEvents: function(events) {
          var self = this;
          $.each(events, function(i, event) {
            self._cleanEvent(event);
          });
          return events;
      },

      /*
       * Clean specific event
       */
      _cleanEvent: function(event) {
          if (event.date) {
            event.start = event.date;
          }
          event.start = this._cleanDate(event.start);
          event.end = this._cleanDate(event.end);
          if (!event.end) {
            event.end = this._addDays(this._cloneDate(event.start), 1);
          }
      },

      /*
       * Disable text selection of the elements in different browsers
       */
      _disableTextSelect: function($elements) {
          $elements.each(function() {
            if ($.browser.mozilla) {//Firefox
                $(this).css('MozUserSelect', 'none');
            } else if ($.browser.msie) {//IE
                $(this).bind('selectstart', function() {
                  return false;
                });
            } else {//Opera, etc.
                $(this).mousedown(function() {
                  return false;
                });
            }
          });
      },

      /*
       * returns the date on the first millisecond of the week
       */
      _dateFirstDayOfWeek: function(date) {
        var self = this;
        var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        var adjustedDate = new Date(midnightCurrentDate);
        adjustedDate.setDate(adjustedDate.getDate() - self._getAdjustedDayIndex(midnightCurrentDate));

        return adjustedDate;
      },

      /*
       * returns the date on the first millisecond of the last day of the week
       */
      _dateLastDayOfWeek: function(date) {
        var self = this;
        var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        var adjustedDate = new Date(midnightCurrentDate);
        var daysToAdd = (self.options.daysToShow - 1 - self._getAdjustedDayIndex(midnightCurrentDate));
        adjustedDate.setDate(adjustedDate.getDate() + daysToAdd);

        return adjustedDate;
      },

      /**
       * fix the date if it is not within given options
       * minDate and maxDate
       */
      _fixMinMaxDate: function(date) {
        var minDate, maxDate;
        date = this._cleanDate(date);

        // not less than minDate
        if (this.options.minDate) {
          minDate = this._cleanDate(this.options.minDate);
          // midnight on minDate
          minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
          if (date.getTime() < minDate.getTime()) {
            this._trigger('reachedmindate', this.element, date);
          }
          date = this._cleanDate(Math.max(date.getTime(), minDate.getTime()));
        }

        // not more than maxDate
        if (this.options.maxDate) {
          maxDate = this._cleanDate(this.options.maxDate);
          // apply correction for max date if not startOnFirstDayOfWeek
          // to make sure no further date is displayed.
          // otherwise, the complement will still be shown
          if (!this._startOnFirstDayOfWeek()) {
            var day = maxDate.getDate() - this.options.daysToShow + 1;
            maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), day);
          }
          // microsecond before midnight on maxDate
          maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 23, 59, 59, 999);
          if (date.getTime() > maxDate.getTime()) {
            this._trigger('reachedmaxdate', this.element, date);
          }
          date = this._cleanDate(Math.min(date.getTime(), maxDate.getTime()));
        }

        return date;
      },

      /*
        * gets the index of the current day adjusted based on options
        */
      _getAdjustedDayIndex: function(date) {
          if (!this._startOnFirstDayOfWeek()) {
            return 0;
          }

          var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
          var currentDayOfStandardWeek = midnightCurrentDate.getDay();
          var days = [0, 1, 2, 3, 4, 5, 6];
          this._rotate(days, this._firstDayOfWeek());
          return days[currentDayOfStandardWeek];
      },

      _firstDayOfWeek: function() {
        if ($.isFunction(this.options.firstDayOfWeek)) {
          return this.options.firstDayOfWeek(this.element);
        }
        return this.options.firstDayOfWeek;
      },

      /*
        * returns the date on the last millisecond of the week
        */
      _dateLastMilliOfWeek: function(date) {
          var lastDayOfWeek = this._dateLastDayOfWeek(date);
          lastDayOfWeek = this._cloneDate(lastDayOfWeek);
          lastDayOfWeek.setDate(lastDayOfWeek.getDate() + 1);
          return lastDayOfWeek;

      },

      /*
        * Clear the time components of a date leaving the date
        * of the first milli of day
        */
      _clearTime: function(d) {
          d.setHours(0);
          d.setMinutes(0);
          d.setSeconds(0);
          d.setMilliseconds(0);
          return d;
      },

      /*
        * add specific number of days to date
        */
      _addDays: function(d, n, keepTime) {
          d.setDate(d.getDate() + n);
          if (keepTime) {
            return d;
          }
          return this._clearTime(d);
      },

      /*
        * Rotate an array by specified number of places.
        */
      _rotate: function(a /*array*/, p /* integer, positive integer rotate to the right, negative to the left... */) {
          for (var l = a.length, p = (Math.abs(p) >= l && (p %= l), p < 0 && (p += l), p), i, x; p; p = (Math.ceil(l / p) - 1) * p - l + (l = p)) {
            for (i = l; i > p; x = a[--i], a[i] = a[i - p], a[i - p] = x) {}
          }
          return a;
      },

      _cloneDate: function(d) {
          return new Date(d.getTime());
      },

      /**
       * Return a Date instance for different representations.
       * Valid representations are:
       *  * timestamps
       *  * Date objects
       *  * textual representations (only these accepted by the Date
       *    constructor)
       *
       *  @return {Date} The clean date object.
       */
      _cleanDate: function(d) {
          if (typeof d === 'string') {
            // if is numeric
            if (!isNaN(Number(d))) {
              return this._cleanDate(parseInt(d, 10));
            }

            // this is a human readable date
            return Date.parse(d) || new Date(d);
          }

          if (typeof d == 'number') {
            return new Date(d);
          }

          return d;
      },

      /*
        * date formatting is adapted from
        * http://jacwright.com/projects/javascript/date_format
        */
      _formatDate: function(date, format) {
        var returnStr = '';
        for (var i = 0; i < format.length; i++) {
          var curChar = format.charAt(i);
          if (i != 0 && format.charAt(i - 1) == '\\') {
            returnStr += curChar;
          }
          else if (this._replaceChars[curChar]) {
            returnStr += this._replaceChars[curChar](date, this);
          } else if (curChar != '\\') {
            returnStr += curChar;
          }
        }
        return returnStr;
      },

      _replaceChars: {
      // Day
      d: function(date) { return (date.getDate() < 10 ? '0' : '') + date.getDate(); },
      D: function(date, calendar) { return calendar.options.shortDays[date.getDay()]; },
      j: function(date) { return date.getDate(); },
      l: function(date, calendar) { return calendar.options.longDays[date.getDay()]; },
      N: function(date) { var _d = date.getDay(); return _d ? _d : 7; },
      S: function(date) { return (date.getDate() % 10 == 1 && date.getDate() != 11 ? 'st' : (date.getDate() % 10 == 2 && date.getDate() != 12 ? 'nd' : (date.getDate() % 10 == 3 && date.getDate() != 13 ? 'rd' : 'th'))); },
      w: function(date) { return date.getDay(); },
      z: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((date - d) / 86400000); }, // Fixed now
      // Week
      W: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((((date - d) / 86400000) + d.getDay() + 1) / 7); }, // Fixed now
      // Month
      F: function(date, calendar) { return calendar.options.longMonths[date.getMonth()]; },
      m: function(date) { return (date.getMonth() < 9 ? '0' : '') + (date.getMonth() + 1); },
      M: function(date, calendar) { return calendar.options.shortMonths[date.getMonth()]; },
      n: function(date) { return date.getMonth() + 1; },
      t: function(date) { var d = date; return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate() }, // Fixed now, gets #days of date
      // Year
      L: function(date) { var year = date.getFullYear(); return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)); },  // Fixed now
      o: function(date) { var d = new Date(date.valueOf()); d.setDate(d.getDate() - ((date.getDay() + 6) % 7) + 3); return d.getFullYear();}, //Fixed now
      Y: function(date) { return date.getFullYear(); },
      y: function(date) { return ('' + date.getFullYear()).substr(2); },
      // Time
      a: function(date) { return date.getHours() < 12 ? 'am' : 'pm'; },
      A: function(date) { return date.getHours() < 12 ? 'AM' : 'PM'; },
      B: function(date) { return Math.floor((((date.getUTCHours() + 1) % 24) + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600) * 1000 / 24); }, // Fixed now
      g: function(date) { return date.getHours() % 12 || 12; },
      G: function(date) { return date.getHours(); },
      h: function(date) { return ((date.getHours() % 12 || 12) < 10 ? '0' : '') + (date.getHours() % 12 || 12); },
      H: function(date) { return (date.getHours() < 10 ? '0' : '') + date.getHours(); },
      i: function(date) { return (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); },
      s: function(date) { return (date.getSeconds() < 10 ? '0' : '') + date.getSeconds(); },
      u: function(date) { var m = date.getMilliseconds(); return (m < 10 ? '00' : (m < 100 ? '0' : '')) + m; },
      // Timezone
      e: function(date) { return 'Not Yet Supported'; },
      I: function(date) { return 'Not Yet Supported'; },
      O: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + '00'; },
      P: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + ':00'; }, // Fixed now
      T: function(date) { var m = date.getMonth(); date.setMonth(0); var result = date.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); date.setMonth(m); return result;},
      Z: function(date) { return -date.getTimezoneOffset() * 60; },
      // Full Date/Time
      c: function(date, calendar) { return calendar._formatDate(date, 'Y-m-d\\TH:i:sP'); }, // Fixed now
      r: function(date, calendar) { return calendar._formatDate(date, 'D, d M Y H:i:s O'); },
      U: function(date) { return date.getTime() / 1000; }
      },

      /* USER MANAGEMENT FUNCTIONS */

      getUserForId: function(id) {
        return $.extend({}, this.options.users[this._getUserIndexFromId(id)]);
      },

      /**
        * return the user name for header
        */
      _getUserName: function(index) {
          var self = this;
          var options = this.options;
          var user = options.users[index];
          if ($.isFunction(options.getUserName)) {
          return options.getUserName(user, index, self.element);
          }
          else {
          return user;
          }
      },
      /**
        * return the user id for given index
        */
      _getUserIdFromIndex: function(index) {
          var self = this;
          var options = this.options;
          if ($.isFunction(options.getUserId)) {
          return options.getUserId(options.users[index], index, self.element);
          }
          return index;
      },
      /**
        * returns the associated user index for given ID
        */
      _getUserIndexFromId: function(id) {
          var self = this;
          var options = this.options;
          for (var i = 0; i < options.users.length; i++) {
            if (self._getUserIdFromIndex(i) == id) {
              return i;
            }
          }
          return 0;
      },
      /**
        * return the user ids for given calEvent.
        * default is calEvent.userId field.
        */
      _getEventUserId: function(calEvent) {
          var self = this;
          var options = this.options;
          if (options.showAsSeparateUsers && options.users && options.users.length) {
            if ($.isFunction(options.getEventUserId)) {
            return options.getEventUserId(calEvent, self.element);
            }
            return calEvent.userId;
          }
          return [];
      },
      /**
        * sets the event user id on given calEvent
        * default is calEvent.userId field.
        */
      _setEventUserId: function(calEvent, userId) {
          var self = this;
          var options = this.options;
          if ($.isFunction(options.setEventUserId)) {
            return options.setEventUserId(userId, calEvent, self.element);
          }
          calEvent.userId = userId;
          return calEvent;
      },
      /**
        * return the user ids for given freeBusy.
        * default is freeBusy.userId field.
        */
      _getFreeBusyUserId: function(freeBusy) {
        var self = this;
        var options = this.options;
        if ($.isFunction(options.getFreeBusyUserId)) {
          return options.getFreeBusyUserId(freeBusy.getOption(), self.element);
        }
        return freeBusy.getOption('userId');
      },

      /* FREEBUSY MANAGEMENT */

      /**
        * ckean the free busy managers and remove all the freeBusy
        */
      _clearFreeBusys: function() {
        if (this.options.displayFreeBusys) {
          var self = this,
              options = this.options,
              $freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
          $freeBusyPlaceholders.each(function() {
            $(this).data('wcFreeBusyManager', new FreeBusyManager({
                start: self._cloneDate($(this).data('startDate')),
                end: self._cloneDate($(this).data('endDate')),
                defaultFreeBusy: options.defaultFreeBusy || {}
            }));
          });
          self.element.find('.wc-grid-row-freebusy .wc-freebusy').remove();
        }
      },
      /**
        * retrieve placeholders for given freebusy
        */
      _findWeekDaysForFreeBusy: function(freeBusy, $weekDays) {
        var $returnWeekDays,
            options = this.options,
            showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
            self = this,
            userList = self._getFreeBusyUserId(freeBusy);
        if (!$.isArray(userList)) {
          userList = userList != 'undefined' ? [userList] : [];
        }
        if (!$weekDays) {
          $weekDays = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
        }
        $weekDays.each(function() {
          var manager = $(this).data('wcFreeBusyManager'),
              has_overlap = manager.isWithin(freeBusy.getStart()) ||
                              manager.isWithin(freeBusy.getEnd()) ||
                              freeBusy.isWithin(manager.getStart()) ||
                              freeBusy.isWithin(manager.getEnd()),
              userId = $(this).data('wcUserId');
          if (has_overlap && (!showAsSeparatedUser || ($.inArray(userId, userList) != -1))) {
            $returnWeekDays = $returnWeekDays ? $returnWeekDays.add($(this)) : $(this);
          }
        });
        return $returnWeekDays;
      },

      /**
        * used to render all freeBusys
        */
      _renderFreeBusys: function(freeBusys) {
        if (this.options.displayFreeBusys) {
          var self = this,
              $freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
              freebusysToRender;
          //insert freebusys to dedicated placeholders freebusy managers
          if ($.isArray(freeBusys)) {
            freebusysToRender = self._cleanFreeBusys(freeBusys);
          } else if (freeBusys.freebusys) {
            freebusysToRender = self._cleanFreeBusys(freeBusys.freebusys);
          }
          else {
            freebusysToRender = [];
          }

          $.each(freebusysToRender, function(index, freebusy) {
              var $placeholders = self._findWeekDaysForFreeBusy(freebusy, $freeBusyPlaceholders);
              if ($placeholders) {
                $placeholders.each(function() {
                  var manager = $(this).data('wcFreeBusyManager');
                  manager.insertFreeBusy(new FreeBusy(freebusy.getOption()));
                  $(this).data('wcFreeBusyManager', manager);
                });
              }
            });

          //now display freebusys on  place holders
          self._refreshFreeBusys($freeBusyPlaceholders);
        }
      },
      /**
        * refresh freebusys for given placeholders
        */
      _refreshFreeBusys: function($freeBusyPlaceholders) {
        if (this.options.displayFreeBusys && $freeBusyPlaceholders) {
          var self = this,
              options = this.options,
              start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
              end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);

          $freeBusyPlaceholders.each(function() {
              var $placehoder = $(this);
              var s = self._cloneDate($placehoder.data('startDate')),
                  e = self._cloneDate(s);
              s.setHours(start);
              e.setHours(end);
              $placehoder.find('.wc-freebusy').remove();
              $.each($placehoder.data('wcFreeBusyManager').getFreeBusys(s, e), function() {
                  self._renderFreeBusy(this, $placehoder);
                });
            });
        }
      },
      /**
        * render a freebusy item on dedicated placeholders
        */
      _renderFreeBusy: function(freeBusy, $freeBusyPlaceholder) {
        if (this.options.displayFreeBusys) {
          var self = this,
              options = this.options,
              freeBusyHtml = '<div class="wc-freebusy"></div>';

          var $fb = $(freeBusyHtml);
          $fb.data('wcFreeBusy', new FreeBusy(freeBusy.getOption()));
          this._positionFreeBusy($freeBusyPlaceholder, $fb);
          $fb = options.freeBusyRender(freeBusy.getOption(), $fb, self.element);
          if ($fb) {
            $fb.appendTo($freeBusyPlaceholder);
          }
        }
      },
      /*
        * Position the freebusy element within the weekday based on it's start / end dates.
        */
      _positionFreeBusy: function($placeholder, $freeBusy) {
          var options = this.options;
          var freeBusy = $freeBusy.data('wcFreeBusy');
          var pxPerMillis = $placeholder.height() / options.millisToDisplay;
          var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
          var startMillis = freeBusy.getStart().getTime() - new Date(freeBusy.getStart().getFullYear(), freeBusy.getStart().getMonth(), freeBusy.getStart().getDate(), firstHourDisplayed).getTime();
          var eventMillis = freeBusy.getEnd().getTime() - freeBusy.getStart().getTime();
          var pxTop = pxPerMillis * startMillis;
          var pxHeight = pxPerMillis * eventMillis;
          $freeBusy.css({top: pxTop, height: pxHeight});
      },
      /*
        * Clean freebusys to ensure correct format
        */
      _cleanFreeBusys: function(freebusys) {
          var self = this,
              freeBusyToReturn = [];
          if (!$.isArray(freebusys)) {
            var freebusys = [freebusys];
          }
          $.each(freebusys, function(i, freebusy) {
            freeBusyToReturn.push(new FreeBusy(self._cleanFreeBusy(freebusy)));
          });
          return freeBusyToReturn;
      },

      /*
        * Clean specific freebusy
        */
      _cleanFreeBusy: function(freebusy) {
          if (freebusy.date) {
            freebusy.start = freebusy.date;
          }
          freebusy.start = this._cleanDate(freebusy.start);
          freebusy.end = this._cleanDate(freebusy.end);
          return freebusy;
      },

      /**
        * retrives the first freebusy manager matching demand.
        */
      getFreeBusyManagersFor: function(date, users) {
        var calEvent = {
          start: date,
          end: date
        };
        this._setEventUserId(calEvent, users);
        return this.getFreeBusyManagerForEvent(calEvent);
      },
      /**
        * retrives the first freebusy manager for given event.
        */
      getFreeBusyManagerForEvent: function(newCalEvent) {
        var self = this,
            options = this.options,
            freeBusyManager;
        if (options.displayFreeBusys) {
          var $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
              freeBusy = new FreeBusy({start: newCalEvent.start, end: newCalEvent.end}),
              showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
              userId = showAsSeparatedUser ? self._getEventUserId(newCalEvent) : null;
          if (!$.isArray(userId)) {
            userId = [userId];
          }
          $freeBusyPlaceHoders.each(function() {
            var manager = $(this).data('wcFreeBusyManager'),
                has_overlap = manager.isWithin(freeBusy.getEnd()) ||
                                manager.isWithin(freeBusy.getEnd()) ||
                                freeBusy.isWithin(manager.getStart()) ||
                                freeBusy.isWithin(manager.getEnd());
            if (has_overlap && (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), userId) != -1)) {
              freeBusyManager = $(this).data('wcFreeBusyManager');
              return false;
            }
          });
        }
        return freeBusyManager;
      },
      /**
        * appends the freebusys to replace the old ones.
        * @param {array|object} freeBusys freebusy(s) to apply.
        */
      updateFreeBusy: function(freeBusys) {
        var self = this,
            options = this.options;
        if (options.displayFreeBusys) {
          var $toRender,
              $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
              _freeBusys = self._cleanFreeBusys(freeBusys);

          $.each(_freeBusys, function(index, _freeBusy) {

            var $weekdays = self._findWeekDaysForFreeBusy(_freeBusy, $freeBusyPlaceHoders);
            //if freebusy has a placeholder
            if ($weekdays && $weekdays.length) {
              $weekdays.each(function(index, day) {
                var manager = $(day).data('wcFreeBusyManager');
                manager.insertFreeBusy(_freeBusy);
                $(day).data('wcFreeBusyManager', manager);
              });
              $toRender = $toRender ? $toRender.add($weekdays) : $weekdays;
            }
          });
          self._refreshFreeBusys($toRender);
        }
      },

      /* NEW OPTIONS MANAGEMENT */

      /**
        * checks wether or not the calendar should be displayed starting on first day of week
        */
      _startOnFirstDayOfWeek: function() {
        return jQuery.isFunction(this.options.startOnFirstDayOfWeek) ? this.options.startOnFirstDayOfWeek(this.element) : this.options.startOnFirstDayOfWeek;
      },

      /**
        * finds out the current scroll to apply it when changing the view
        */
      _getCurrentScrollHour: function() {
          var self = this;
          var options = this.options;
          var $scrollable = this.element.find('.wc-scrollable-grid');
          var scroll = $scrollable.scrollTop();
          if (self.options.businessHours.limitDisplay) {
            scroll = scroll + options.businessHours.start * options.timeslotHeight * options.timeslotsPerHour;
          }
          return Math.round(scroll / (options.timeslotHeight * options.timeslotsPerHour)) + 1;
      },
      _getJsonOptions: function() {
        if ($.isFunction(this.options.jsonOptions)) {
            return $.extend({}, this.options.jsonOptions(this.element));
        }
        if ($.isPlainObject(this.options.jsonOptions)) {
          return $.extend({}, this.options.jsonOptions);
        }
        return {};
      },
      _getHeaderDate: function(date) {
        var options = this.options;
        if (options.getHeaderDate && $.isFunction(options.getHeaderDate))
        {
          return options.getHeaderDate(date, this.element);
        }
        var dayName = options.useShortDayNames ? options.shortDays[date.getDay()] : options.longDays[date.getDay()];
        return dayName + (options.headerSeparator) + this._formatDate(date, options.dateFormat);
      },



      /**
        * returns corrected date related to DST problem
        */
      _getDSTdayShift: function(date, shift) {
      var start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0);
      var offset1 = start.getTimezoneOffset();
      var offset2 = date.getTimezoneOffset();
          if (offset1 == offset2)
        return date;
      shift = shift ? shift : 1;
      return new Date(date.getTime() - shift * (offset1 > offset2 ? -1 : 1) * (Math.max(offset1, offset2) - Math.min(offset1, offset2)) * 60000);
      },
      _needDSTdayShift: function(date1, date2) {
      return date1.getTimezoneOffset() != date2.getTimezoneOffset();
      }



    }; // end of widget function return
    })() //end of widget function closure execution
  ); // end of $.widget("ui.weekCalendar"...

    $.extend($.ui.weekCalendar, {
      version: '2.0-dev',
      updateLayoutOptions: {
        startOnFirstDayOfWeek: true,
        firstDayOfWeek: true,
        daysToShow: true,
        displayOddEven: true,
        timeFormat: true,
        dateFormat: true,
        use24Hour: true,
        useShortDayNames: true,
        businessHours: true,
        timeslotHeight: true,
        timeslotsPerHour: true,
        buttonText: true,
        height: true,
        shortMonths: true,
        longMonths: true,
        shortDays: true,
        longDays: true,
        textSize: true,
        users: true,
        showAsSeparateUsers: true,
        displayFreeBusys: true
      }
    });

    var MILLIS_IN_DAY = 86400000;
    var MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;

    /* FREE BUSY MANAGERS */
    var FreeBusyProto = {
      getStart: function() {return this.getOption('start')},
      getEnd: function() {return this.getOption('end')},
      getOption: function() {
          if (!arguments.length) { return this.options }
          if (typeof(this.options[arguments[0]]) !== 'undefined') {
            return this.options[arguments[0]];
          }
          else if (typeof(arguments[1]) !== 'undefined') {
            return arguments[1];
          }
          return null;
        },
      setOption: function(key, value) {
          if (arguments.length == 1) {
            $.extend(this.options, arguments[0]);
            return this;
          }
          this.options[key] = value;
          return this;
        },
      isWithin: function(dateTime) {return Math.floor(dateTime.getTime() / 1000) >= Math.floor(this.getStart().getTime() / 1000) && Math.floor(dateTime.getTime() / 1000) <= Math.floor(this.getEnd().getTime() / 1000)},
      isValid: function() {return this.getStart().getTime() < this.getEnd().getTime()}
    };

    /**
      * @constructor
      * single user freebusy manager.
      */
    var FreeBusy = function(options) {
      this.options = $.extend({}, options || {});
    };
    $.extend(FreeBusy.prototype, FreeBusyProto);

    var FreeBusyManager = function(options) {
      this.options = $.extend({
          defaultFreeBusy: {}
        }, options || {});
      this.freeBusys = [];
      this.freeBusys.push(new FreeBusy($.extend({
            start: this.getStart(),
            end: this.getEnd()
          }, this.options.defaultFreeBusy)));
    };
    $.extend(FreeBusyManager.prototype, FreeBusyProto, {
      /**
        * return matching freeBusys.
        * if you do not pass any argument, returns all freebusys.
        * if you only pass a start date, only matchinf freebusy will be returned.
        * if you pass 2 arguments, then all freebusys available within the time period will be returned
        * @param {Date} start [optionnal] if you do not pass end date, will return the freeBusy within which this date falls.
        * @param {Date} end [optionnal] the date where to stop the search.
        * @return {Array} an array of FreeBusy matching arguments.
        */
      getFreeBusys: function() {
          switch (arguments.length) {
            case 0:
              return this.freeBusys;
            case 1:
              var freeBusy = [];
              var start = arguments[0];
              if (!this.isWithin(start)) {
                return freeBusy;
              }
              $.each(this.freeBusys, function() {
                if (this.isWithin(start)) {
                  freeBusy.push(this);
                }
                if (Math.floor(this.getEnd().getTime() / 1000) > Math.floor(start.getTime() / 1000)) {
                  return false;
                }
              });
              return freeBusy;
            default:
              //we assume only 2 first args are revealants
              var freeBusy = [];
              var start = arguments[0], end = arguments[1];
              var tmpFreeBusy = new FreeBusy({start: start, end: end});
              if (end.getTime() < start.getTime() || this.getStart().getTime() > end.getTime() || this.getEnd().getTime() < start.getTime()) {
                return freeBusy;
              }
              $.each(this.freeBusys, function() {
                if (this.getStart().getTime() >= end.getTime()) {
                  return false;
                }
                if (tmpFreeBusy.isWithin(this.getStart()) && tmpFreeBusy.isWithin(this.getEnd())) {
                  freeBusy.push(this);
                }
                else if (this.isWithin(tmpFreeBusy.getStart()) && this.isWithin(tmpFreeBusy.getEnd())) {
                  var _f = new FreeBusy(this.getOption());
                  _f.setOption('end', tmpFreeBusy.getEnd());
                  _f.setOption('start', tmpFreeBusy.getStart());
                  freeBusy.push(_f);
                }
                else if (this.isWithin(tmpFreeBusy.getStart()) && this.getStart().getTime() < start.getTime()) {
                  var _f = new FreeBusy(this.getOption());
                  _f.setOption('start', tmpFreeBusy.getStart());
                  freeBusy.push(_f);
                }
                else if (this.isWithin(tmpFreeBusy.getEnd()) && this.getEnd().getTime() > end.getTime()) {
                  var _f = new FreeBusy(this.getOption());
                  _f.setOption('end', tmpFreeBusy.getEnd());
                  freeBusy.push(_f);
                }
              });
              return freeBusy;
          }
        },
      insertFreeBusy: function(freeBusy) {
          var freeBusy = new FreeBusy(freeBusy.getOption());
          //first, if inserted freebusy is bigger than manager
          if (freeBusy.getStart().getTime() < this.getStart().getTime()) {
            freeBusy.setOption('start', this.getStart());
          }
          if (freeBusy.getEnd().getTime() > this.getEnd().getTime()) {
            freeBusy.setOption('end', this.getEnd());
          }
          var start = freeBusy.getStart(), end = freeBusy.getEnd(),
              startIndex = 0, endIndex = this.freeBusys.length - 1,
              newFreeBusys = [];
          var pushNewFreeBusy = function(_f) {if (_f.isValid()) newFreeBusys.push(_f);};

          $.each(this.freeBusys, function(index) {
            //within the loop, we have following vars:
            // curFreeBusyItem: the current iteration freeBusy, part of manager freeBusys list
            // start: the insterted freeBusy start
            // end: the inserted freebusy end
            var curFreeBusyItem = this;
            if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.isWithin(end)) {
              /*
                we are in case where inserted freebusy fits in curFreeBusyItem:
                curFreeBusyItem:    *-----------------------------*
                freeBusy:                *-------------*
                obviously, start and end indexes are this item.
              */
              startIndex = index;
              endIndex = index;
              if (start.getTime() == curFreeBusyItem.getStart().getTime() && end.getTime() == curFreeBusyItem.getEnd().getTime()) {
                /*
                  in this case, inserted freebusy is exactly curFreeBusyItem:
                  curFreeBusyItem:    *-----------------------------*
                  freeBusy:            *-----------------------------*

                  just replace curFreeBusyItem with freeBusy.
                */
                var _f1 = new FreeBusy(freeBusy.getOption());
                pushNewFreeBusy(_f1);
              }
              else if (start.getTime() == curFreeBusyItem.getStart().getTime()) {
                /*
                  in this case inserted freebusy starts with curFreeBusyItem:
                  curFreeBusyItem:    *-----------------------------*
                  freeBusy:            *--------------*

                  just replace curFreeBusyItem with freeBusy AND the rest.
                */
                var _f1 = new FreeBusy(freeBusy.getOption());
                var _f2 = new FreeBusy(curFreeBusyItem.getOption());
                _f2.setOption('start', end);
                pushNewFreeBusy(_f1);
                pushNewFreeBusy(_f2);
              }
              else if (end.getTime() == curFreeBusyItem.getEnd().getTime()) {
                /*
                  in this case inserted freebusy ends with curFreeBusyItem:
                  curFreeBusyItem:    *-----------------------------*
                  freeBusy:                          *--------------*

                  just replace curFreeBusyItem with before part AND freeBusy.
                */
                var _f1 = new FreeBusy(curFreeBusyItem.getOption());
                _f1.setOption('end', start);
                var _f2 = new FreeBusy(freeBusy.getOption());
                pushNewFreeBusy(_f1);
                pushNewFreeBusy(_f2);
              }
              else {
                /*
                  in this case inserted freebusy is within curFreeBusyItem:
                  curFreeBusyItem:    *-----------------------------*
                  freeBusy:                    *--------------*

                  just replace curFreeBusyItem with before part AND freeBusy AND the rest.
                */
                var _f1 = new FreeBusy(curFreeBusyItem.getOption());
                var _f2 = new FreeBusy(freeBusy.getOption());
                var _f3 = new FreeBusy(curFreeBusyItem.getOption());
                _f1.setOption('end', start);
                _f3.setOption('start', end);
                pushNewFreeBusy(_f1);
                pushNewFreeBusy(_f2);
                pushNewFreeBusy(_f3);
              }
              /*
                as work is done, no need to go further.
                return false
              */
              return false;
            }
            else if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.getEnd().getTime() != start.getTime()) {
              /*
                in this case, inserted freebusy starts within curFreeBusyItem:
                curFreeBusyItem:    *----------*
                freeBusy:                *-------------------*

                set start index AND insert before part, we'll insert freebusy later
              */
              if (curFreeBusyItem.getStart().getTime() != start.getTime()) {
                var _f1 = new FreeBusy(curFreeBusyItem.getOption());
                _f1.setOption('end', start);
                pushNewFreeBusy(_f1);
              }
              startIndex = index;
            }
            else if (curFreeBusyItem.isWithin(end) && curFreeBusyItem.getStart().getTime() != end.getTime()) {
              /*
                in this case, inserted freebusy starts within curFreeBusyItem:
                curFreeBusyItem:                  *----------*
                freeBusy:            *-------------------*

                set end index AND insert freebusy AND insert after part if needed
              */
              pushNewFreeBusy(new FreeBusy(freeBusy.getOption()));
              if (end.getTime() < curFreeBusyItem.getEnd().getTime()) {
                var _f1 = new FreeBusy(curFreeBusyItem.getOption());
                _f1.setOption('start', end);
                pushNewFreeBusy(_f1);
              }
              endIndex = index;
              return false;
            }
          });
          //now compute arguments
          var tmpFB = this.freeBusys;
          this.freeBusys = [];

          if (startIndex) {
            this.freeBusys = this.freeBusys.concat(tmpFB.slice(0, startIndex));
          }
          this.freeBusys = this.freeBusys.concat(newFreeBusys);
          if (endIndex < tmpFB.length) {
            this.freeBusys = this.freeBusys.concat(tmpFB.slice(endIndex + 1));
          }
/*          if(start.getDate() == 1){
          console.info('insert from '+freeBusy.getStart() +' to '+freeBusy.getEnd());
            console.log('index from '+ startIndex + ' to ' + endIndex);
            var str = [];
            $.each(tmpFB, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' : 'busy'))});
            console.log(str.join('\n'));

            console.log('insert');
            var str = [];
            $.each(newFreeBusys, function(i){str.push(this.getStart().getHours() + ' > ' + this.getEnd().getHours())});
            console.log(str.join(', '));

            console.log('results');
            var str = [];
            $.each(this.freeBusys, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours()  + ' ' + (this.getOption('free') ? 'free' :'busy'))});
            console.log(str.join('\n'));
          }*/
          return this;
        }
    });

})(jQuery);
