/home/ivoiecob/email.hirewise-va.com/modules/CalendarWebclient/js/views/CalendarView.js
'use strict'

const _ = require('underscore'),
  $ = require('jquery'),
  ko = require('knockout'),
  moment = require('moment-timezone')

const DateUtils = require('%PathToCoreWebclientModule%/js/utils/Date.js'),
  TextUtils = require('%PathToCoreWebclientModule%/js/utils/Text.js'),
  Types = require('%PathToCoreWebclientModule%/js/utils/Types.js'),
  Api = require('%PathToCoreWebclientModule%/js/Api.js'),
  App = require('%PathToCoreWebclientModule%/js/App.js'),
  Browser = require('%PathToCoreWebclientModule%/js/Browser.js'),
  CJua = require('%PathToCoreWebclientModule%/js/CJua.js'),
  Screens = require('%PathToCoreWebclientModule%/js/Screens.js'),
  UserSettings = require('%PathToCoreWebclientModule%/js/Settings.js'),
  Pulse = require('%PathToCoreWebclientModule%/js/Pulse.js'),
  CAbstractScreenView = require('%PathToCoreWebclientModule%/js/views/CAbstractScreenView.js'),
  Popups = require('%PathToCoreWebclientModule%/js/Popups.js'),
  ConfirmPopup = require('%PathToCoreWebclientModule%/js/popups/ConfirmPopup.js'),
  Utils = require('%PathToCoreWebclientModule%/js/utils/Common.js')

const EditCalendarPopup = require('modules/%ModuleName%/js/popups/EditCalendarPopup.js'),
  EditEventPopup = require('modules/%ModuleName%/js/popups/EditEventPopup.js'),
  EditEventRecurrencePopup = require('modules/%ModuleName%/js/popups/EditEventRecurrencePopup.js'),
  GetCalendarLinkPopup = require('modules/%ModuleName%/js/popups/GetCalendarLinkPopup.js'),
  ImportCalendarPopup = require('modules/%ModuleName%/js/popups/ImportCalendarPopup.js'),
  SelectCalendarPopup = require('modules/%ModuleName%/js/popups/SelectCalendarPopup.js'),
  CalendarSharePopup = require('modules/%ModuleName%/js/popups/CalendarSharePopup.js'),
  FullCalendarUtils = require('modules/%ModuleName%/js/utils/FullCalendar.js'),
  Ajax = require('modules/%ModuleName%/js/Ajax.js'),
  CalendarCache = require('modules/%ModuleName%/js/Cache.js'),
  Settings = require('modules/%ModuleName%/js/Settings.js'),
  CCalendarListModel = require('modules/%ModuleName%/js/models/CCalendarListModel.js'),
  CCalendarModel = require('modules/%ModuleName%/js/models/CCalendarModel.js')

const bMobileDevice = false

require('jquery-ui/ui/widgets/datepicker')

/**
 * @constructor
 */
function CCalendarView() {
  CAbstractScreenView.call(this, '%ModuleName%')

  this.browserTitle = ko.observable(TextUtils.i18n('%MODULENAME%/HEADING_BROWSER_TAB'))

  var self = this
  this.initialized = ko.observable(false)
  this.isPublic = App.isPublic()

  this.uploaderArea = ko.observable(null)
  this.bDragActive = ko.observable(false)
  this.bDragActiveComp = ko.computed(function () {
    return this.bDragActive()
  }, this)

  this.aDayNames = TextUtils.i18n('COREWEBCLIENT/LIST_DAY_NAMES').split(' ')

  this.popUpStatus = false
  this.linkRow = 0
  this.linkColumn = 0

  this.sTimeFormat = UserSettings.timeFormat() === Enums.TimeFormat.F24 ? 'HH:mm' : 'hh:mm A'
  this.dateFormatForMoment = Utils.getDateFormatForMoment(UserSettings.dateFormat())

  this.topPositionToday = ko.observable('.fc-widget-content.fc-today')
  this.loadOnce = false
  this.scrollModel = ko.observable(null)
  this.scrollHeight = 0

  this.dateTitle = ko.observable('')
  this.aMonthNames = DateUtils.getMonthNamesArray()
  this.selectedView = ko.observable('')
  this.visibleWeekdayHeader = ko.computed(function () {
    return this.selectedView() === 'month'
  }, this)
  this.selectedView.subscribe(function () {
    this.resize()
  }, this)

  this.$calendarGrid = null
  this.calendarGridDom = ko.observable(null)

  this.$datePicker = null
  this.datePickerDom = ko.observable(null)

  this.calendars = new CCalendarListModel({
    onCalendarCollectionChange: function () {
      self.refreshView()
    },
    onCalendarActiveChange: function () {
      self.refreshView()
    },
  })

  this.colors = Settings.CalendarColors

  this.busyDays = ko.observableArray([])

  this.$inlineEditedEvent = null
  this.inlineEditedEventText = null
  this.checkStarted = ko.observable(false)

  this.loaded = false

  this.startDateTime = 0
  this.endDateTime = 0
  this.needsToReload = false
  this.bTimezoneChanged = false

  UserSettings.timezone.subscribe(function () {
    this.startDateTime = 0
    this.endDateTime = 0
    this.needsToReload = true
    this.bTimezoneChanged = true
    this.getCalendars()
  }, this)

  this.calendarListClick = function (oItem) {
    oItem.active(!oItem.active())
  }
  this.currentCalendarDropdown = ko.observable(false)
  this.currentCalendarDropdownOffset = ko.observable(0)
  this.calendarDropdownToggle = function (bValue, oElement) {
    if (oElement && bValue) {
      var position = oElement.position(),
        height = oElement.outerHeight()
      self.currentCalendarDropdownOffset(Types.pInt(position.top) + height)
    }

    self.currentCalendarDropdown(bValue)
  }
  this.calendarDropdownHide = _.throttle(
    _.bind(function () {
      this.calendarDropdownToggle(false)
    }, this),
    500
  )

  this.dayNamesResizeBinding = _.throttle(_.bind(this.resize, this), 50)

  this.customscrollTop = ko.observable(0)
  this.fullcalendarOptions = {
    handleWindowResize: true,
    eventLimit: 10,
    header: false,
    editable: !this.isPublic,
    selectable: !this.isPublic,
    allDayText: TextUtils.i18n('%MODULENAME%/LABEL_ALL_DAY'),
    dayNames: this.aDayNames,
    monthNames: this.aMonthNames,
    isRTL: UserSettings.IsRTL,
    scrollTime: moment.duration(8, 'hours'),
    forceEventDuration: true,
    defaultTimedEventDuration: '00:00:01',
    views: {
      month: {
        columnFormat: 'dddd', // Monday
      },
      week: {
        columnFormat: 'dddd D', // Monday 7
      },
      day: {
        columnFormat: 'dddd D', // Monday 7
      },
      listMonth: {
        listDayFormat: this.dateFormatForMoment
      }
    },
    displayEventEnd: {
      month: true,
      basicWeek: true,
      default: true,
    },
    select: _.bind(this.createEventFromGrid, this),
    eventClick: _.bind(this.eventClickCallback, this),
    eventDragStart: _.bind(this.onEventDragStart, this),
    eventDragStop: _.bind(this.onEventDragStop, this),
    eventResizeStart: _.bind(this.onEventResizeStart, this),
    eventResizeStop: _.bind(this.onEventResizeStop, this),
    eventDrop: _.bind(this.moveEvent, this),
    eventResize: _.bind(this.resizeEvent, this),
    eventRender: function (oEv, oEl) {
      if (Settings.AddDescriptionToTitle) {
        var oTitle = oEl.find('.fc-title')
        oTitle.html(
          '<span class="subject-title">' +
            $.trim(oEv.subject.replace(/[\n\r]/g, ' ')) +
            '</span> ' +
            '<span class="desc-title">' +
            $.trim(oEv.description.replace(/[\n\r]/g, ' ')) +
            '</span> ' +
            '<span class="loc-title">' +
            $.trim(oEv.location.replace(/[\n\r]/g, ' ')) +
            '</span>'
        )
      }

      if (oEv.isCalendarShared && oEv.isPrivate && Settings.AllowPrivateEvents) {
        oEl.css('cursor', 'default')
        // var oTitle = oEl.find('.fc-title');
        // oTitle.html('<span class="subject-title" style="opacity: 0.5">[' + TextUtils.i18n('%MODULENAME%/LABEL_NO_EVENT_INFORMATION') + ']</span> ');
      }

      if (oEv.type === 'VTODO') {
        var content = oEl.find('.fc-content'),
          title = content.find('.fc-title'),
          completed = $(
            '<label class="custom_checkbox round"><span class="icon"></span><input type="checkbox"></label>'
          )
          // fcTime = content.find('.fc-time')
        if (oEv.status) {
          completed.addClass('checked')
          title.css('text-decoration-line', 'line-through')
        } else {
          completed.removeClass('checked')
          title.css('text-decoration-line', 'unset')
        }
        oEl.addClass('fc-custom-task')

        title.prepend(completed)

        if (oEv.isCalendarShared && oEv.isPrivate && Settings.AllowPrivateEvents) {
          completed.attr('readonly', true)
          completed.css('cursor', 'default')
        } else {
          completed.on(function (event) {
            if (oEv.status) {
              oEv.status = false
              completed.removeClass('checked')
              title.css('text-decoration-line', 'unset')
            } else {
              oEv.status = true
              completed.addClass('checked')
              title.css('text-decoration-line', 'line-through')
            }
            oEv.modified = true

            if (oEv.rrule) {
              oEv.allEvents = Enums.CalendarEditRecurrenceEvent.OnlyThisInstance
            }

            self.updateEvent(oEv)

            event.preventDefault()
            event.stopPropagation()
          })
        }
      }
    },
    eventAfterRender: _.bind(function (oEv, oEl) {}, this),
    eventAfterAllRender: _.bind(this.updateAllEvents, this),
    viewRender: _.bind(this.viewRenderCallback, this),
    events: _.bind(this.eventsSource, this),
  }

  this.revertFunction = null

  this.bAllowShare = Settings.AllowShare
  this.bAllowTasks = Settings.AllowTasks

  this.defaultViewName = ko.computed(function () {
    switch (Settings.DefaultTab) {
      case Enums.CalendarDefaultTab.List:
        return 'listWeek'
      case Enums.CalendarDefaultTab.Day:
        return 'agendaDay'
      case Enums.CalendarDefaultTab.Week:
        return 'agendaWeek'
      case Enums.CalendarDefaultTab.Month:
      default:
        return 'month'
    }
  }, this)

  this.iAutoReloadTimer = -1

  this.dragEventTrigger = false
  this.delayOnEventResult = false
  this.delayOnEventResultData = []

  this.refreshView = _.throttle(_.bind(this.refreshViewSingle, this), 100)
  this.uploadCalendarId = ko.observable('')
  this.changeFullCalendarDate = true
  this.domScrollWrapper = null
  this.hotKeysBind()

  this.viewEventRoute = null

  App.broadcastEvent('%ModuleName%::ConstructView::after', { Name: this.ViewConstructorName, View: this })
}

_.extendOwn(CCalendarView.prototype, CAbstractScreenView.prototype)

CCalendarView.prototype.ViewTemplate = '%ModuleName%_CalendarView'
CCalendarView.prototype.ViewConstructorName = 'CCalendarView'

/**
 * Hot keys events
 */
CCalendarView.prototype.hotKeysBind = function () {
  var self = this
  $(document).on('keyup', function (ev) {
    var iKey = ev.keyCode
    /* Close popup "more events" if click Esc button */
    if (self.calendars.getEvents().length > 0 && self.selectedView() === 'month') {
      if (iKey === Enums.Key.Esc && self.popUpStatus) {
        /* two triggers for correct plugin working */
        $('body').trigger('click')
        /* popUpStatus has just been changed */
        if (!self.popUpStatus) {
          $('body').trigger('mousedown')
        }
      }
    }
  })
}

CCalendarView.prototype.getDateFromCurrentView = function (sDateType) {
  var oView = this.$calendarGrid.fullCalendar('getView'),
    oDate = oView && oView[sDateType] ? oView[sDateType] : null
  if (oDate && sDateType === 'end' && oView.name === 'agendaDay') {
    oDate.add(1, 'd')
  }

  return oDate && oDate['unix'] ? oView[sDateType]['unix']() : 0
}

/**
 * @param {Object} oStart
 * @param {Object} oEnd
 * @param {*} mTimezone
 * @param {Function} fCallback
 */
CCalendarView.prototype.eventsSource = function (oStart, oEnd, mTimezone, fCallback) {
  fCallback(this.calendars.getEvents(oStart, oEnd))
}

CCalendarView.prototype.changeView = function (viewName) {
  this.selectedView(viewName)
  if (viewName === 'month') {
    this.loadOnce = false
  }
  this.$calendarGrid.fullCalendar('changeView', viewName)
}

CCalendarView.prototype.recreateFullCalendar = function (viewName) {
  this.$calendarGrid.fullCalendar('destroy')
  this.$calendarGrid.fullCalendar(this.fullcalendarOptions)
  this.changeView(viewName)
}

CCalendarView.prototype.applyCalendarSettings = function () {
  this.sTimeFormat = UserSettings.timeFormat() === Enums.TimeFormat.F24 ? 'HH:mm' : 'hh:mm A'
  this.dateFormatForMoment = Utils.getDateFormatForMoment(UserSettings.dateFormat())
  this.calendarGridDom().removeClass('fc-show-weekends')
  if (Settings.HighlightWorkingDays) {
    this.calendarGridDom().addClass('fc-show-weekends')
  }

  this.fullcalendarOptions.timeFormat = this.sTimeFormat
  this.fullcalendarOptions.views.listMonth.listDayFormat = this.dateFormatForMoment
  this.fullcalendarOptions.slotLabelFormat = this.sTimeFormat
  this.fullcalendarOptions.defaultView = this.defaultViewName()
  this.fullcalendarOptions.lang = moment.locale()

  this.applyFirstDay()

  const showWeekNumbers = Settings.ShowWeekNumbers && Settings.WeekStartsOn == 1;

  this.$datePicker.datepicker('option', 'showWeek', showWeekNumbers);
	this.fullcalendarOptions.weekNumbers = showWeekNumbers;
  this.fullcalendarOptions.weekNumberTitle = TextUtils.i18n('%MODULENAME%/LABEL_WEEK_SHORT') + ' ';
  this.fullcalendarOptions.weekNumberCalculation = 'ISO';

  this.recreateFullCalendar(this.defaultViewName())
}

CCalendarView.prototype.applyFirstDay = function () {
  var aDayNames = [],
    sFirstDay = '',
    sLastDay = ''
  if (App.getUserRole() !== Enums.UserRole.Anonymous) {
    this.fullcalendarOptions.firstDay = Settings.WeekStartsOn
  }

  _.each(this.aDayNames, function (sDayName) {
    aDayNames.push(sDayName)
  })

  switch (Settings.WeekStartsOn) {
    case 1:
      sLastDay = aDayNames.shift()
      aDayNames.push(sLastDay)
      break
    case 6:
      sFirstDay = aDayNames.pop()
      aDayNames.unshift(sFirstDay)
      break
  }

  this.$datePicker.datepicker('option', 'firstDay', Settings.WeekStartsOn)
}

CCalendarView.prototype.initDatePicker = function () {
  this.$datePicker.datepicker({
    showOtherMonths: true,
    selectOtherMonths: true,
    monthNames: this.aMonthNames,
    dayNamesMin: TextUtils.i18n('COREWEBCLIENT/LIST_DAY_NAMES_MIN').split(' '),
    showWeek: false,
		weekHeader: TextUtils.i18n('CALENDARWEBCLIENT/LABEL_WEEK_SHORT'),
    nextText: '',
    prevText: '',
    onChangeMonthYear: _.bind(this.changeMonthYearFromDatePicker, this),
    onSelect: _.bind(this.selectDateFromDatePicker, this),
    beforeShowDay: _.bind(this.getDayDescription, this),
  })
}

CCalendarView.prototype.onBind = function () {
  var self = this
  this.$calendarGrid = $(this.calendarGridDom())

  this.$datePicker = $(this.datePickerDom())
  if (!this.isPublic) {
    this.initUploader()
  }
  /* Click more links */
  $('body').on('click', function (e) {
    if (self.calendars.getEvents().length > 0 && self.selectedView() === 'month') {
      if ($(e.target).hasClass('fc-more')) {
        var $this = $(e.target)
        $('.fc-more-cell.active').removeClass('active')
        $('.fc-row.fc-week.active').removeClass('active')
        $this.closest('.fc-more-cell').addClass('active')
        $this.closest('.fc-row.fc-week').addClass('active')
        var $popup = $('body').find('.fc-popover.fc-more-popover'),
          $parent = $this.closest('tr'),
          $superParent = $this.closest('.fc-day-grid'),
          indexColumn = Types.pInt($parent.find('.fc-more-cell.active').index('.fc-more-cell')),
          indexRow = Types.pInt($superParent.find('.fc-row.fc-week.active').index('.fc-row.fc-week'))
        if ($popup.length > 0) {
          self.linkRow = indexRow
          self.linkColumn = indexColumn
          self.popUpStatus = true
        } else {
          self.popUpStatus = false
          self.linkRow = 0
          self.linkColumn = 0
        }
      } else if ($(e.target).hasClass('checkstate') || $(e.target).parent().hasClass('checkstate')) {
        e.preventDefault()
      } else {
        self.popUpStatus = false
        self.linkRow = 0
        self.linkColumn = 0
      }
    }
  })
}

CCalendarView.prototype.onShow = function () {
  var bInitialized = this.initialized()
  if (!bInitialized) {
    this.initDatePicker()

    var oParent = this.$calendarGrid.parent()
    if (oParent) {
      this.fullcalendarOptions.height = oParent.height()
    }

    this.applyCalendarSettings()
    this.highlightWeekInDayPicker()
    this.initialized(true)
  }

  var sTimeFormat = UserSettings.timeFormat() === Enums.TimeFormat.F24 ? 'HH:mm' : 'hh:mm A'
  const dateFormatForMoment = Utils.getDateFormatForMoment(UserSettings.dateFormat()) 
  if (CalendarCache.calendarSettingsChanged() || this.sTimeFormat !== sTimeFormat || CalendarCache.calendarChanged() || this.dateFormatForMoment !== dateFormatForMoment) {
    if (CalendarCache.calendarSettingsChanged() || this.sTimeFormat !== sTimeFormat || this.dateFormatForMoment !== dateFormatForMoment) {
      this.applyCalendarSettings()
    }
    CalendarCache.calendarSettingsChanged(false)
    CalendarCache.calendarChanged(false)
  } else if (this.isPublic) {
    this.$calendarGrid.fullCalendar('render')
    this.applyDateTime()
  }

  this.$calendarGrid.fullCalendar()
  if (bInitialized) {
    this.getCalendars()
  }
  this.refetchEvents()
}

/**
 * @param {Array} aParams
 */
CCalendarView.prototype.onRoute = function (aParams) {
  var sCalendarId = aParams[0],
    sEventId = aParams[1],
    start = aParams[2],
    oEvent = null
  this.$calendarGrid.fullCalendar('gotoDate', moment(start))
  oEvent = this.getClientEvent(sCalendarId, sEventId)
  if (oEvent !== null) {
    this.eventClickCallback(oEvent)
  } else {
    this.viewEventRoute = {
      CalendarId: sCalendarId,
      EventId: sEventId,
    }
  }
}

CCalendarView.prototype.getClientEvent = function (sCalendarId, sEventId) {
  var oCalendar = null,
    oEvent = null,
    oEventResult = null,
    aEvents = []
  aEvents = this.$calendarGrid.fullCalendar('clientEvents', sEventId)

  if (Array.isArray(aEvents) && aEvents.length > 0) {
    oCalendar = this.calendars.getCalendarById(sCalendarId)
    if (oCalendar !== undefined) {
      oEvent = _.find(
        aEvents,
        function (oEvent) {
          return oEvent.calendarId === sCalendarId
        },
        this
      )
      if (oEvent !== undefined) {
        oEventResult = oEvent
      }
    }
  }

  return oEventResult
}

CCalendarView.prototype.applyDateTime = function () {
  FullCalendarUtils.recreateIfDateChanged(this.$calendarGrid, this.recreateFullCalendar.bind(this))
  FullCalendarUtils.setTimeline()
}

/**
 * When all event's rendered
 */
CCalendarView.prototype.updateAllEvents = function () {
  if (this.calendars.getEvents().length > 0 && this.selectedView() === 'month') {
    if (!this.loadOnce) {
      this.topPositionToday.valueHasMutated()
      this.loadOnce = true
    } else {
      this.scrollModel()['vertical'].set(this.scrollHeight)
    }

    /* open current more link */
    if (this.popUpStatus) {
      $('body')
        .find('.fc-row.fc-week')
        .eq(this.linkRow)
        .find('.fc-more-cell')
        .eq(this.linkColumn)
        .find('a.fc-more')
        .click()
    }
  }
}

/**
 * @param {Object} oView
 * @param {Object} oElement
 */
CCalendarView.prototype.viewRenderCallback = function (oView, oElement) {
  var prevDate = null,
    constDate = '01/01/1971 '
  this.changeDate()

  if (!this.loaded) {
    this.initResizing()
  }

  if (oView.name !== 'month' && Settings.HighlightWorkingHours) {
    $('.fc-slats tr').each(function () {
      $('tr .fc-time span').each(function () {
        var theValue = $(this).eq(0).text(),
          theDate = theValue !== '' ? Date.parse(constDate + theValue) : prevDate,
          rangeTimeFrom = Date.parse(constDate + Settings.WorkdayStarts + ':00'),
          rangeTimeTo = Date.parse(constDate + Settings.WorkdayEnds + ':00')
        prevDate = theDate
        if (theDate < rangeTimeFrom || theDate >= rangeTimeTo) {
          $(this).parent().parent().addClass('fc-non-working-time')
          $(this).parent().parent().next().addClass('fc-non-working-time')
        }
      })
    })
  }

  this.activateCustomScrollInDayAndWeekView()
}

CCalendarView.prototype.collectBusyDays = function () {
  var aBusyDays = [],
    oStart = null,
    oEnd = null,
    iDaysDiff = 0,
    iIndex = 0
  _.each(
    this.calendars.getEvents(),
    function (oEvent) {
      oStart = moment(oEvent.start)
      oEnd = oEvent.end ? moment(oEvent.end) : null
      if (oEvent.allDay && oEnd) {
        oEnd.subtract(1, 'days')
      }

      iDaysDiff = oEnd ? oEnd.diff(oStart, 'days') : 0
      iIndex = 0

      for (; iIndex <= iDaysDiff; iIndex++) {
        aBusyDays.push(oStart.clone().add(iIndex, 'days').toDate())
      }
    },
    this
  )

  this.busyDays(aBusyDays)
}

CCalendarView.prototype.refreshDatePicker = function () {
  var self = this

  _.defer(function () {
    self.collectBusyDays()
    self.$datePicker.datepicker('refresh')
    self.highlightWeekInDayPicker()
  })
}

/**
 * @param {Object} oDate
 */
CCalendarView.prototype.getDayDescription = function (oDate) {
  var bSelectable = true,
    oFoundBusyDay = _.find(
      this.busyDays(),
      function (oBusyDay) {
        return (
          oBusyDay.getDate() === oDate.getDate() &&
          oBusyDay.getMonth() === oDate.getMonth() &&
          oBusyDay.getYear() === oDate.getYear()
        )
      },
      this
    ),
    sDayClass = oFoundBusyDay ? 'day_with_events' : '',
    sDayTitle = ''
  return [bSelectable, sDayClass, sDayTitle]
}

CCalendarView.prototype.initResizing = function () {
  var fResize = _.throttle(_.bind(this.resize, this), 50)

  $(window).bind('resize', function (e) {
    if (e.target !== this && !Browser.ie8AndBelow) {
      return
    }

    fResize()
  })

  fResize()
}

CCalendarView.prototype.resize = function () {
  var oParent = this.$calendarGrid.parent()
  if (oParent) {
    this.$calendarGrid.fullCalendar('option', 'height', oParent.height())
  }
  this.dayNamesResize()
}

CCalendarView.prototype.dayNamesResize = function () {
  if (this.selectedView() === 'month') {
    var oDayNamesHeaderItem = $('div.weekday-header-item'),
      oFirstWeek = $('tr.fc-first td.fc-day'),
      oFirstWeekWidth = $(oFirstWeek[0]).width(),
      iIndex = 0
    if (oDayNamesHeaderItem.length === 7 && oFirstWeek.length === 7 && oFirstWeekWidth !== 0) {
      for (; iIndex < 7; iIndex++) {
        $(oDayNamesHeaderItem[iIndex]).width(oFirstWeekWidth)
      }
    }
  }
}

/**
 * @param {number} iYear
 * @param {number} iMonth
 * @param {Object} oInst
 */
CCalendarView.prototype.changeMonthYearFromDatePicker = function (iYear, iMonth, oInst) {
  if (this.changeFullCalendarDate) {
    var oDate = this.$calendarGrid.fullCalendar('getDate')
    // Date object in javascript and fullcalendar use numbers 0,1,2...11 for monthes
    // datepiker uses numbers 1,2,3...12 for monthes
    oDate.month(iMonth - 1).year(iYear)
    this.$calendarGrid.fullCalendar('gotoDate', oDate)
  }
}

/**
 * @param {string} sDate
 * @param {Object} oInst
 */
CCalendarView.prototype.selectDateFromDatePicker = function (sDate, oInst) {
  var oDate = moment(sDate, 'MM/DD/YYYY')
  this.$calendarGrid.fullCalendar('gotoDate', oDate)

  _.defer(_.bind(this.highlightWeekInDayPicker, this))
}

CCalendarView.prototype.highlightWeekInDayPicker = function () {
  var $currentDay = this.$datePicker.find('td.ui-datepicker-current-day'),
    $currentWeek = $currentDay.parent(),
    $currentMonth = this.$datePicker.find('table.ui-datepicker-calendar'),
    oView = this.$calendarGrid.fullCalendar('getView')
  switch (oView.name) {
    case 'agendaDay':
      $currentMonth.addClass('highlight_day').removeClass('highlight_week')
      break
    case 'agendaWeek':
      $currentMonth.removeClass('highlight_day').addClass('highlight_week')
      break
    default:
      $currentMonth.removeClass('highlight_day').removeClass('highlight_week')
      break
  }

  $currentWeek.addClass('current_week')
}

CCalendarView.prototype.changeDateTitle = function () {
  var oDate = this.$calendarGrid.fullCalendar('getDate').clone().locale(moment.locale()),
    oView = this.$calendarGrid.fullCalendar('getView'),
    sTitle = oDate.format('MMMM YYYY'),
    oStart = oView.intervalStart.clone().locale(moment.locale()),
    oEnd = oView.intervalEnd ? oView.intervalEnd.clone().add(-1, 'days').locale(moment.locale()) : null
  switch (oView.name) {
    case 'agendaDay':
      sTitle = oDate.format('MMMM D, YYYY')
      break
    case 'agendaWeek':
      if (oStart && oEnd) {
        sTitle = oStart.format('MMMM D, YYYY') + ' - ' + oEnd.format('MMMM D, YYYY')
      }
      break
  }

  this.dateTitle(sTitle)
}

CCalendarView.prototype.changeDate = function () {
  this.changeDateInDatePicker()
  this.changeDateTitle()
  this.getTimeLimits()
  this.getCalendars()
}

CCalendarView.prototype.changeDateInDatePicker = function () {
  var oDateMoment = this.$calendarGrid.fullCalendar('getDate')
  this.changeFullCalendarDate = false
  this.$datePicker.datepicker('setDate', oDateMoment.local().toDate())
  this.changeFullCalendarDate = true
  this.highlightWeekInDayPicker()
}

CCalendarView.prototype.activateCustomScrollInDayAndWeekView = function () {
  if (bMobileDevice) {
    return
  }

  var oView = this.$calendarGrid.fullCalendar('getView'),
    sGridType = oView.name === 'month' ? 'day' : 'time',
    oGridContainer = $('.fc-' + sGridType + '-grid-container')
  if (!oGridContainer.hasClass('scroll-inner')) {
    var oScrollWrapper = $('<div></div>')

    oGridContainer.parent().append(oScrollWrapper)
    oGridContainer.appendTo(oScrollWrapper)

    if (!oScrollWrapper.hasClass('scroll-wrap')) {
      oScrollWrapper.attr(
        'data-bind',
        'customScrollbar: {x: false, y: true, top: 0, scrollTo: topPositionToday, oScroll: scrollModel}'
      )
      oGridContainer.css({ overflow: 'hidden' }).addClass('scroll-inner')
      ko.applyBindings(this, oScrollWrapper[0])
    }

    this.domScrollWrapper = oScrollWrapper
  }
}

CCalendarView.prototype.displayToday = function () {
  this.$calendarGrid.fullCalendar('today')
}

CCalendarView.prototype.displayPrev = function () {
  this.$calendarGrid.fullCalendar('prev')
}

CCalendarView.prototype.displayNext = function () {
  this.$calendarGrid.fullCalendar('next')
}

CCalendarView.prototype.setAutoReloadTimer = function () {
  var self = this
  clearTimeout(this.iAutoReloadTimer)

  if (UserSettings.AutoRefreshIntervalMinutes > 0) {
    this.iAutoReloadTimer = setTimeout(function () {
      self.getCalendars()
    }, UserSettings.AutoRefreshIntervalMinutes * 60 * 1000)
  }
}

CCalendarView.prototype.getTimeLimits = function () {
  var iStart = this.getDateFromCurrentView('start'),
    iEnd = this.getDateFromCurrentView('end')
  if (this.startDateTime === 0 && this.endDateTime === 0) {
    this.startDateTime = iStart
    this.endDateTime = iEnd
    this.needsToReload = true
  } else if (iStart < this.startDateTime && iEnd > this.endDateTime) {
    this.startDateTime = iStart
    this.endDateTime = iEnd
    this.needsToReload = true
  } else if (iStart < this.startDateTime) {
    iEnd = this.startDateTime
    this.startDateTime = iStart
    this.needsToReload = true
  } else if (iEnd > this.endDateTime) {
    iStart = this.endDateTime
    this.endDateTime = iEnd
    this.needsToReload = true
  }
}

CCalendarView.prototype.getCalendars = function () {
  this.checkStarted(true)
  this.setCalendarGridVisibility()

  if (this.isPublic) {
    Ajax.send('GetPublicCalendar', { CalendarId: Settings.PublicCalendarId }, this.onGetCalendarsResponse, this)
  } else {
    Ajax.send('GetCalendars', null, this.onGetCalendarsResponse, this)
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oParameters
 */
CCalendarView.prototype.onGetCalendarsResponse = function (oResponse, oParameters) {
  var aCalendarIds = [],
    aNewCalendarIds = [],
    oCalendar = null,
    oClientCalendar = null
  if (this.loadOnce && this.selectedView() === 'month') {
    this.scrollHeight = this.scrollModel()['vertical'].get()
  } else {
    this.scrollHeight = 0
  }

  if (oResponse.Result) {
    this.loaded = true

    _.each(
      oResponse.Result.Calendars,
      function (oCalendarData) {
        if (!_.isEmpty(oCalendarData)) {
          oCalendar = this.calendars.parseCalendar(oCalendarData)
          aCalendarIds.push(oCalendar.id)
          oClientCalendar = this.calendars.getCalendarById(oCalendar.id)
          if (
            this.needsToReload ||
            (oClientCalendar && oClientCalendar.isSharedToAll) ||
            (oClientCalendar && oClientCalendar.sSyncToken) !== (oCalendar && oCalendar.sSyncToken)
          ) {
            oCalendar = this.calendars.parseAndAddCalendar(oCalendarData)
            if (oCalendar) {
              oCalendar.davUrl(Types.pString(oResponse.Result.ServerUrl))
              if (this.isPublic) {
                var oPublicHeaderItem = require('modules/%ModuleName%/js/views/PublicHeaderItem.js')
                oPublicHeaderItem.linkText(oCalendar.name())
                this.browserTitle(oCalendar.name())
              }
              aNewCalendarIds.push(oCalendar.id)
            }
          }
        }
      },
      this
    )

    if (this.calendars.count() === 0 && this.isPublic && this.needsToReload) {
      this.browserTitle(TextUtils.i18n('%MODULENAME%/INFO_NO_CALENDAR_FOUND'))
      Api.showErrorByCode(0, TextUtils.i18n('%MODULENAME%/INFO_NO_CALENDAR_FOUND'), true)
    }

    this.needsToReload = false
    this.calendars.expunge(aCalendarIds)

    _.each(
      aCalendarIds,
      function (sCalendarId) {
        oCalendar = this.calendars.getCalendarById(sCalendarId)
        if (oCalendar && oCalendar.eventsCount() > 0) {
          oCalendar.reloadEvents()
        }
      },
      this
    )

    this.requestEvents(aNewCalendarIds)
  } else {
    this.setCalendarGridVisibility()
    this.checkStarted(false)
  }
}

/**
 * @param {Array} aCalendarIds
 */
CCalendarView.prototype.requestEvents = function (aCalendarIds) {
  var oOptions = {
    CalendarIds: aCalendarIds,
    Start: this.startDateTime,
    End: this.endDateTime,
    IsPublic: this.isPublic,
  }
  if (this.isPublic && !App.getUserId()) {
    oOptions.DefaultTimeZone = moment.tz.guess()
  }

  if (aCalendarIds.length > 0) {
    Ajax.send('GetEvents', oOptions, this.onGetEventsResponse, this)
  } else {
    this.setAutoReloadTimer()
    this.checkStarted(false)
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CCalendarView.prototype.onGetEventsResponse = function (oResponse, oRequest) {
  if (oResponse.Result) {
    var oCalendar = null,
      oParameters = oRequest.Parameters,
      aCalendarIds = _.isArray(oParameters.CalendarIds) ? oParameters.CalendarIds : [],
      aEvents = []
    _.each(
      oResponse.Result,
      function (oEventData) {
        oCalendar = this.calendars.getCalendarById(oEventData.calendarId)
        if (oCalendar) {
          oEventData.isCalendarShared = oCalendar.isShared()
          aEvents.push(oEventData.id)
          var oEvent = oCalendar.getEvent(oEventData.id)
          if (!oEvent) {
            oCalendar.addEvent(oEventData)
          } else if (this.bTimezoneChanged || oEvent.lastModified !== oEventData.lastModified) {
            oCalendar.updateEvent(oEventData)
          }
        }
      },
      this
    )
    this.bTimezoneChanged = false

    _.each(
      aCalendarIds,
      function (sCalendarId) {
        oCalendar = this.calendars.getCalendarById(sCalendarId)
        if (oCalendar && oCalendar.eventsCount() > 0 && oCalendar.active()) {
          oCalendar.expungeEvents(aEvents, this.startDateTime, this.endDateTime, 'VEVENT')
        }
      },
      this
    )

    this.refreshView()
    if (this.viewEventRoute) {
      var oEvent = this.getClientEvent(this.viewEventRoute.CalendarId, this.viewEventRoute.EventId)
      this.viewEventRoute = false
      if (oEvent !== null) {
        this.eventClickCallback(oEvent)
      }
    }
  }

  this.setAutoReloadTimer()
  this.checkStarted(false)
  if (Settings.ShowTasksInCalendars) {
    this.getTasks(aCalendarIds)
  }
}

/**
 * @param {Array} aCalendarIds
 */
CCalendarView.prototype.getTasks = function (aCalendarIds) {
  if (this.bAllowTasks) {
    if (Types.isNonEmptyArray(aCalendarIds)) {
      Ajax.send(
        'GetTasks',
        {
          CalendarIds: aCalendarIds,
          Start: this.startDateTime,
          End: this.endDateTime,
          IsPublic: this.isPublic,
        },
        this.onGetTasksResponse,
        this
      )
    } else {
      this.setAutoReloadTimer()
      this.checkStarted(false)
    }
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CCalendarView.prototype.onGetTasksResponse = function (oResponse, oRequest) {
  if (oResponse.Result) {
    var oCalendar = null,
      oParameters = oRequest.Parameters,
      aCalendarIds = _.isArray(oParameters.CalendarIds) ? oParameters.CalendarIds : [],
      aTasks = []
    _.each(
      oResponse.Result,
      function (oTaskData) {
        oCalendar = this.calendars.getCalendarById(oTaskData.calendarId)
        if (oCalendar) {
          oTaskData.isCalendarShared = oCalendar.isShared()

          aTasks.push(oTaskData.id)
          var oEvent = oCalendar.getEvent(oTaskData.id)
          if (!oEvent) {
            oCalendar.addEvent(oTaskData)
          } else if (oEvent.lastModified !== oTaskData.lastModified) {
            oCalendar.updateEvent(oTaskData)
          }
        }
      },
      this
    )

    _.each(
      aCalendarIds,
      function (sCalendarId) {
        oCalendar = this.calendars.getCalendarById(sCalendarId)
        if (oCalendar && oCalendar.eventsCount() > 0 && oCalendar.active()) {
          oCalendar.expungeEvents(aTasks, this.startDateTime, this.endDateTime, 'VTODO')
        }
      },
      this
    )

    this.refreshView()
  }

  this.setAutoReloadTimer()
  this.checkStarted(false)
}

CCalendarView.prototype.setCalendarGridVisibility = function () {
  this.$calendarGrid.css('visibility', '').find('.fc-view div').first().css('visibility', '')
}

CCalendarView.prototype.getUnusedColor = function () {
  var colors = _.difference(this.colors, this.calendars.getColors())

  return colors.length > 0 ? colors[0] : this.colors[0]
}

CCalendarView.prototype.openCreateCalendarForm = function () {
  if (!this.isPublic) {
    var oCalendar = new CCalendarModel()
    oCalendar.color(this.getUnusedColor())
    Popups.showPopup(EditCalendarPopup, [_.bind(this.createCalendarCallback, this), this.colors, oCalendar])
  }
}

CCalendarView.prototype.createCalendarCallback = function (aCalendar) {
  if (aCalendar) {
    this.calendars.parseAndAddCalendar(aCalendar)
  }
}

/**
 * @param {Object} oCalendar
 */
CCalendarView.prototype.openImportCalendarForm = function (oCalendar) {
  if (!this.isPublic) {
    Popups.showPopup(ImportCalendarPopup, [_.bind(this.getCalendars, this), oCalendar])
  }
}

/**
 * @param {Object} oCalendar
 */
CCalendarView.prototype.openShareCalendarForm = function (oCalendar) {
  Popups.showPopup(CalendarSharePopup, [_.bind(this.shareCalendar, this), oCalendar])
}

/**
 * @param {string} sId
 * @param {boolean} bIsPublic
 * @param {Array} aShares
 * @param {boolean} bShareToAll
 * @param {number} iShareToAllAccess
 */
CCalendarView.prototype.shareCalendar = function (sId, bIsPublic, aShares, bShareToAll, iShareToAllAccess) {
  if (!this.isPublic) {
    Ajax.send(
      'UpdateCalendarShare',
      {
        Id: sId,
        IsPublic: bIsPublic ? 1 : 0,
        Shares: JSON.stringify(aShares),
        ShareToAll: bShareToAll ? 1 : 0,
        ShareToAllAccess: iShareToAllAccess,
      },
      this.onUpdateShareResponse,
      this
    )
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CCalendarView.prototype.onUpdateShareResponse = function (oResponse, oRequest) {
  if (oResponse.Result) {
    this.getCalendars()
    /*		TODO:
		var	oCalendar = this.calendars.getCalendarById(oRequest.Parameters.Id);
		if (oCalendar)
		{
			oCalendar.shares(JSON.parse(oRequest.Parameters.Shares));
			if (oRequest.Parameters.ShareToAll === 1)
			{
				oCalendar.isShared(true);
				oCalendar.isSharedToAll(true);
				oCalendar.sharedToAllAccess = oRequest.Parameters.ShareToAllAccess;
			}
			else
			{
				oCalendar.isSharedToAll(false);
			}
		}
*/
  } else {
    Screens.showError(TextUtils.i18n('%MODULENAME%/ERROR_SHARE_NOT_UPDATED'))
  }
}

/**
 * @param {Object} oCalendar
 */
CCalendarView.prototype.openUpdateCalendarForm = function (oCalendar) {
  if (!this.isPublic) {
    Popups.showPopup(EditCalendarPopup, [_.bind(this.updateCalendarCallback, this), this.colors, oCalendar])
  }
}

CCalendarView.prototype.updateCalendarCallback = function (oParameters) {
  if (oParameters) {
    var oCalendar = this.calendars.getCalendarById(oParameters.Id)

    if (oCalendar) {
      oCalendar.name(oParameters.Name)
      oCalendar.description(oParameters.Description)
      oCalendar.color(oParameters.Color)
      this.refetchEvents()
    }
  }
}

/**
 * @param {string} sColor
 * @param {string} sId
 */
CCalendarView.prototype.updateCalendarColor = function (sColor, sId) {
  if (!this.isPublic) {
    Ajax.send(
      'UpdateCalendarColor',
      {
        Color: sColor,
        Id: sId,
      },
      this.onUpdateCalendarColorResponse,
      this
    )
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CCalendarView.prototype.onUpdateCalendarColorResponse = function (oResponse, oRequest) {
  if (oResponse.Result) {
    var oParameters = oRequest.Parameters,
      oCalendar = this.calendars.getCalendarById(oParameters.Id)
    if (oCalendar) {
      oCalendar.color(oParameters.Color)
      this.refetchEvents()
    }
  }
}

/**
 * @param {Object} oCalendar
 */
CCalendarView.prototype.openGetLinkCalendarForm = function (oCalendar) {
  if (!this.isPublic) {
    Popups.showPopup(GetCalendarLinkPopup, [_.bind(this.publicCalendar, this), oCalendar])
  }
}

/**
 * @param {string} sId
 * @param {boolean} bIsPublic
 */
CCalendarView.prototype.publicCalendar = function (sId, bIsPublic) {
  if (!this.isPublic) {
    Ajax.send(
      'UpdateCalendarPublic',
      {
        Id: sId,
        IsPublic: bIsPublic,
      },
      this.onUpdateCalendarPublicResponse,
      this
    )
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CCalendarView.prototype.onUpdateCalendarPublicResponse = function (oResponse, oRequest) {
  if (oResponse.Result) {
    var oParameters = oRequest.Parameters,
      oCalendar = this.calendars.getCalendarById(oParameters.Id)
    if (oCalendar) {
      oCalendar.isPublic(oParameters.IsPublic)
    }
  }
}

/**
 * @param {string} sId
 * @param {boolean} bIsUnsubscribe
 */
CCalendarView.prototype.deleteCalendar = function (sId, bIsUnsubscribe) {
  var oCalendar = this.calendars.getCalendarById(sId),
    sConfirm = oCalendar
      ? bIsUnsubscribe
        ? TextUtils.i18n('%MODULENAME%/CONFIRM_UNSUBSCRIBE_CALENDAR', {
            CALENDARNAME: TextUtils.encodeHtml(oCalendar.name()),
          })
        : TextUtils.i18n('%MODULENAME%/CONFIRM_REMOVE_CALENDAR', {
            CALENDARNAME: TextUtils.encodeHtml(oCalendar.name()),
          })
      : '',
    fRemove = _.bind(function (bRemove) {
      if (bRemove) {
        Ajax.send('DeleteCalendar', { Id: sId }, this.onDeleteCalendarResponse, this)
      }
    }, this)
  if (!this.isPublic && oCalendar) {
    Popups.showPopup(ConfirmPopup, [sConfirm, fRemove])
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 */
CCalendarView.prototype.onDeleteCalendarResponse = function (oResponse, oRequest) {
  if (oResponse.Result) {
    var oParameters = oRequest.Parameters,
      oCalendar = this.calendars.getCalendarById(oParameters.Id)
    if (oCalendar && !oCalendar.isDefault) {
      if (this.calendars.currentCal().id === oCalendar.id) {
        this.calendars.pickCurrentCalendar()
      }

      this.calendars.removeCalendar(oCalendar.id)
      this.refetchEvents()
    }
  }
}

CCalendarView.prototype.onEventDragStart = function () {
  this.dragEventTrigger = true
  this.refreshDatePicker()
}

CCalendarView.prototype.onEventDragStop = function (oEvent) {
  var self = this
  this.dragEventTrigger = false
  if (this.delayOnEventResult && this.delayOnEventResultData && 0 < this.delayOnEventResultData.length) {
    this.delayOnEventResult = false

    _.each(this.delayOnEventResultData, function (aData) {
      self.onEventActionResponse(aData[0], aData[1], false)
    })

    this.delayOnEventResultData = []
    this.refreshView()
  } else {
    this.refreshDatePicker()
  }
}

CCalendarView.prototype.onEventResizeStart = function () {
  this.dragEventTrigger = true
}

CCalendarView.prototype.onEventResizeStop = function () {
  var self = this
  this.dragEventTrigger = false
  if (this.delayOnEventResult && this.delayOnEventResultData && 0 < this.delayOnEventResultData.length) {
    this.delayOnEventResult = false

    _.each(this.delayOnEventResultData, function (aData) {
      self.onEventActionResponse(aData[0], aData[1], false)
    })

    this.delayOnEventResultData = []
    this.refreshView()
  } else {
    this.refreshDatePicker()
  }
}

CCalendarView.prototype.createEventInCurrentCalendar = function () {
  this.calendars.pickCurrentCalendar()
  this.createEventToday(this.calendars.currentCal())
}

/**
 * @param {Object} oCalendar
 */
CCalendarView.prototype.createEventToday = function (oCalendar) {
  var oToday = moment()

  if (oToday.minutes() > 30) {
    oToday.add(60 - oToday.minutes(), 'minutes')
  } else {
    oToday.minutes(30)
  }
  oToday.seconds(0).milliseconds(0)

  this.openEventPopup(oCalendar, oToday, oToday.clone().add(30, 'minutes'), false)
}

/**
 * @param {Object} oEventData
 */
CCalendarView.prototype.getParamsFromEventData = function (oEventData) {
  var sBrowserTimezone = moment.tz.guess(),
    sServerTimezone = UserSettings.timezone(),
    oStart = moment.tz(oEventData.start.format('YYYY-MM-DD HH:mm:ss'), sServerTimezone || sBrowserTimezone),
    oEnd = moment.tz(oEventData.end.format('YYYY-MM-DD HH:mm:ss'), sServerTimezone || sBrowserTimezone),
    rrule = null

  if (oEventData.rrule) {
    rrule = {
      byDays: oEventData.rrule.byDays,
      count: oEventData.rrule.count,
      end: Types.pInt(oEventData.rrule.end),
      interval: Types.pInt(oEventData.rrule.interval),
      period: Types.pInt(oEventData.rrule.period),
      until: Types.pInt(oEventData.rrule.until),
      weekNum: oEventData.rrule.weekNum,
    }
  }

  return {
    id: oEventData.id,
    uid: oEventData.uid,
    calendarId: oEventData.calendarId,
    newCalendarId: oEventData.newCalendarId || oEventData.calendarId,
    subject: oEventData.subject,
    allDay: oEventData.allDay ? 1 : 0,
    location: oEventData.location,
    description: oEventData.description,
    alarms: oEventData.alarms ? JSON.stringify(oEventData.alarms) : '[]',
    attendees: oEventData.attendees ? JSON.stringify(oEventData.attendees) : '[]',
    owner: oEventData.owner,
    recurrenceId: oEventData.recurrenceId,
    excluded: oEventData.excluded,
    allEvents: oEventData.allEvents,
    modified: oEventData.modified ? 1 : 0,
    start: oStart.format(),
    end: oEnd.format(),
    startTS: oStart.unix(),
    endTS: oEnd.unix(),
    rrule: rrule ? JSON.stringify(rrule) : null,
    type: oEventData.type,
    status: oEventData.status,
    withDate: oEventData.withDate,
    isPrivate: oEventData.isPrivate,
  }
}

/**
 * @param {Array} aParameters
 */
CCalendarView.prototype.getEventDataFromParams = function (aParameters) {
  var oEventData = aParameters
  oEventData.alarms = aParameters.alarms || []
  oEventData.attendees = aParameters.attendees || []

  if (aParameters.rrule) {
    oEventData.rrule = aParameters.rrule
  }

  return oEventData
}

/**
 * @param {Object} oStart
 * @param {Object} oEnd
 */
CCalendarView.prototype.createEventFromGrid = function (oStart, oEnd) {
  var bAllDay = !oStart.hasTime()
  this.calendars.pickCurrentCalendar()
  this.openEventPopup(this.calendars.currentCal(), oStart.local(), oEnd.local(), bAllDay)
}

/**
 * @param {Object} oCalendar
 * @param {Object} oStart
 * @param {Object} oEnd
 * @param {boolean} bAllDay
 */
CCalendarView.prototype.openEventPopup = function (oCalendar, oStart, oEnd, bAllDay) {
  if (!this.isPublic && oCalendar) {
    Popups.showPopup(EditEventPopup, [
      {
        CallbackSave: _.bind(this.createEvent, this),
        CallbackDelete: _.bind(this.deleteEvent, this),
        Calendars: this.calendars,
        SelectedCalendar: oCalendar ? oCalendar.id : 0,
        Start: oStart,
        End: oEnd,
        AllDay: bAllDay,
        TimeFormat: this.sTimeFormat,
        DateFormat: UserSettings.dateFormat(),
        Organizer: App.getUserPublicId(),
        CallbackAttendeeActionDecline: _.bind(this.attendeeActionDecline, this),
      },
    ])
  }
}

/**
 * @param {Object} oEventData
 */
CCalendarView.prototype.createEvent = function (oEventData) {
  var aParameters = this.getParamsFromEventData(oEventData)

  if (!this.isPublic) {
    aParameters.calendarId = oEventData.newCalendarId
    aParameters.selectStart = this.getDateFromCurrentView('start')
    aParameters.selectEnd = this.getDateFromCurrentView('end')
    Ajax.send('CreateEvent', aParameters, this.onEventActionResponseWithSubThrottle, this)
  }
}

/**
 * @param {Object} oEventData
 */
CCalendarView.prototype.eventClickCallback = function (oEventData) {
  if (oEventData.isCalendarShared && oEventData.isPrivate && Settings.AllowPrivateEvents) {
    return // reject editing
  }

  var /**
     * @param {number} iResult
     */
    fCallback = _.bind(function (iResult) {
      var oParams = {
        ID: oEventData.id,
        Uid: oEventData.uid,
        RecurrenceId: oEventData.recurrenceId,
        Calendars: this.calendars,
        SelectedCalendar: oEventData.calendarId,
        AllDay: oEventData.allDay,
        Location: oEventData.location,
        Description: oEventData.description,
        Subject: oEventData.subject,
        Alarms: oEventData.alarms,
        Attendees: oEventData.attendees,
        RRule: oEventData.rrule ? oEventData.rrule : null,
        Excluded: oEventData.excluded ? oEventData.excluded : false,
        Owner: oEventData.owner,
        Organizer: oEventData.organizer,
        Appointment: oEventData.appointment,
        OwnerName: oEventData.ownerName,
        TimeFormat: this.sTimeFormat,
        DateFormat: UserSettings.dateFormat(),
        AllEvents: iResult,
        CallbackSave: _.bind(this.updateEvent, this),
        CallbackDelete: _.bind(this.deleteEvent, this),
        CallbackAttendeeActionDecline: _.bind(this.attendeeActionDecline, this),
        Type: oEventData.type,
        Status: oEventData.status,
        IsPrivate: oEventData.isPrivate,
      }
      if (iResult !== Enums.CalendarEditRecurrenceEvent.None) {
        if (iResult === Enums.CalendarEditRecurrenceEvent.AllEvents && oEventData.rrule) {
          oParams.Start = moment.unix(oEventData.rrule.startBase)
          oParams.End = moment.unix(oEventData.rrule.endBase)
        } else {
          oParams.Start = oEventData.start.clone()
          oParams.Start = oParams.Start.local()

          oParams.End = oEventData.end.clone()
          oParams.End = oParams.End.local()
        }
        Popups.showPopup(EditEventPopup, [oParams])
      }
    }, this),
    oCalendar = this.calendars.getCalendarById(oEventData.calendarId)
  if (oEventData.rrule && !oCalendar.subscribed()) {
    if (oEventData.excluded) {
      fCallback(Enums.CalendarEditRecurrenceEvent.OnlyThisInstance)
    } else {
      Popups.showPopup(EditEventRecurrencePopup, [fCallback, oEventData.type])
    }
  } else {
    fCallback(Enums.CalendarEditRecurrenceEvent.AllEvents)
  }
}

/**
 * @param {string} sMethod
 * @param {Object} oParameters
 * @param {Function=} fRevertFunc = undefined
 */
CCalendarView.prototype.eventAction = function (sMethod, oParameters, fRevertFunc) {
  var oCalendar = this.calendars.getCalendarById(oParameters.calendarId)

  if (!oCalendar.isEditable()) {
    if (fRevertFunc) {
      fRevertFunc()
    }
  } else {
    if (!this.isPublic) {
      if (fRevertFunc) {
        this.revertFunction = fRevertFunc
      }

      Ajax.send(sMethod, oParameters, this.onEventActionResponseWithSubThrottle, this)
    }
  }
}

/**
 * @param {Object} oEventData
 */
CCalendarView.prototype.updateEvent = function (oEventData) {
  var oParameters = this.getParamsFromEventData(oEventData)

  oParameters.selectStart = this.getDateFromCurrentView('start')
  oParameters.selectEnd = this.getDateFromCurrentView('end')

  if (oEventData.modified) {
    this.calendars.setDefault(oEventData.newCalendarId)
    this.eventAction('UpdateEvent', oParameters)
  }
}

/**
 * @param {Object} oEventData
 * @param {number} delta
 * @param {Function} revertFunc
 */
CCalendarView.prototype.moveEvent = function (oEventData, delta, revertFunc) {
  var oParameters = this.getParamsFromEventData(oEventData)

  oParameters.selectStart = this.getDateFromCurrentView('start')
  oParameters.selectEnd = this.getDateFromCurrentView('end')
  if (!this.isPublic) {
    if (oParameters.rrule) {
      revertFunc(false)
    } else {
      oParameters.allEvents = Enums.CalendarEditRecurrenceEvent.AllEvents
      this.eventAction('UpdateEvent', oParameters, revertFunc)
    }
  }
}

/**
 * @param {Object} oEventData
 * @param {number} delta
 * @param {Function} revertFunc
 */
CCalendarView.prototype.resizeEvent = function (oEventData, delta, revertFunc) {
  if (oEventData?.rrule?.until) {
    var localUntill = new Date(oEventData.rrule.until * 1000),
    utcUntil = new Date(
      localUntill.getUTCFullYear(),
      localUntill.getUTCMonth(),
      localUntill.getUTCDate(),
      localUntill.getUTCHours(),
      localUntill.getUTCMinutes(),
      localUntill.getUTCSeconds()
    )
    
    oEventData.rrule.until = moment(utcUntil).unix()
  }

  var oParameters = this.getParamsFromEventData(oEventData),
    /**
     * @param {number} iResult
     */
    fCallback = _.bind(function (iResult) {
      if (iResult !== Enums.CalendarEditRecurrenceEvent.None) {
        oParameters.allEvents = iResult
        this.eventAction('UpdateEvent', oParameters, revertFunc)
      } else {
        revertFunc()
      }
    }, this)
  oParameters.selectStart = this.getDateFromCurrentView('start')
  oParameters.selectEnd = this.getDateFromCurrentView('end')
  if (oEventData.rrule) {
    if (oParameters.excluded) {
      fCallback(Enums.CalendarEditRecurrenceEvent.OnlyThisInstance)
    } else {
      Popups.showPopup(EditEventRecurrencePopup, [fCallback, oEventData.type])
    }
  } else {
    fCallback(Enums.CalendarEditRecurrenceEvent.AllEvents)
  }
}

/**
 * @param {Object} oEventData
 */
CCalendarView.prototype.deleteEvent = function (oEventData) {
  this.eventAction('DeleteEvent', this.getParamsFromEventData(oEventData))
  CalendarCache.markIcalNotSaved(oEventData.uid)
}

/**
 * @param {Object} oData
 * @param {Object} oParameters
 */
CCalendarView.prototype.onEventActionResponseWithSubThrottle = function (oData, oParameters) {
  if (this.dragEventTrigger) {
    this.delayOnEventResult = true
    this.delayOnEventResultData.push([oData, oParameters])
  } else {
    this.onEventActionResponse(oData, oParameters, true)
  }
}

/**
 * @param {Object} oResponse
 * @param {Object} oRequest
 * @param {boolean} bDoRefresh
 */
CCalendarView.prototype.onEventActionResponse = function (oResponse, oRequest, bDoRefresh) {
  var oParameters = oRequest.Parameters,
    oCalendar = this.calendars.getCalendarById(oParameters && oParameters.calendarId),
    oEvent = null,
    iScrollTop = 0
  if (oResponse && oResponse.Result && oCalendar) {
    iScrollTop = $('.calendar .fc-widget-content .scroll-inner').scrollTop()
    if (oRequest.Method === 'CreateEvent' || oRequest.Method === 'UpdateEvent') {
      oEvent = oCalendar.getEvent(oParameters.id)

      if (
        ((oEvent && oEvent.rrule) || oParameters.rrule) &&
        oParameters.allEvents === Enums.CalendarEditRecurrenceEvent.AllEvents
      ) {
        oCalendar.removeEventByUid(oParameters.uid)
      } else {
        oCalendar.removeEvent(oParameters.id)
      }

      if (oParameters.newCalendarId && oParameters.newCalendarId !== oParameters.calendarId) {
        oCalendar = this.calendars.getCalendarById(oParameters.newCalendarId)
      }

      _.each(
        oResponse.Result.Events,
        function (oEventData) {
          oCalendar.addEvent(oEventData)
        },
        this
      )

      oCalendar.sSyncToken = oResponse.Result.SyncToken

      if (!oCalendar.active()) {
        oCalendar.active(true)
      }

      if (bDoRefresh) {
        this.refreshView()
      }

      this.restoreScroll(iScrollTop)
      this.calendars.pickCurrentCalendar()
    } else if (oRequest.Method === 'DeleteEvent') {
      oCalendar.sSyncToken = oResponse.Result
      if (oParameters.allEvents === Enums.CalendarEditRecurrenceEvent.OnlyThisInstance) {
        oCalendar.removeEvent(oParameters.id)
      } else {
        oCalendar.removeEventByUid(oParameters.uid)
      }

      if (bDoRefresh) {
        this.refreshView()
      }

      this.restoreScroll(iScrollTop)
    }
  } else if (
    oRequest.Method === 'UpdateEvent' &&
    !oResponse.Result &&
    Enums.Errors.NotDisplayedError === Types.pInt(oResponse.ErrorCode)
  ) {
    this.revertFunction = null
  } else {
    Api.showErrorByCode(oResponse, TextUtils.i18n('%MODULENAME%/ERROR_EVENT_NOT_UPDATED'))

    if (this.revertFunction) {
      this.revertFunction()
    }
  }

  this.revertFunction = null
}

/**
 * @param {Object} oCalendar
 * @param {string} sId
 */
CCalendarView.prototype.attendeeActionDecline = function (oCalendar, sId) {
  oCalendar.removeEvent(sId)
  this.refreshView()
}

CCalendarView.prototype.refetchEvents = function () {
  this.$calendarGrid.fullCalendar('refetchEvents')
}

CCalendarView.prototype.refreshViewSingle = function () {
  this.refetchEvents()
  this.refreshDatePicker()
}

CCalendarView.prototype.refreshView = function () {}

/**
 * Initializes file uploader.
 */
CCalendarView.prototype.initUploader = function () {
  var self = this
  if (this.uploaderArea()) {
    this.oJua = new CJua({
      action: '?/Api/',
      name: 'jua-uploader',
      queueSize: 2,
      dragAndDropElement: this.uploaderArea(),
      disableAjaxUpload: false,
      disableFolderDragAndDrop: false,
      disableDragAndDrop: false,
      disableAutoUploadOnDrop: true,
      hidden: _.extendOwn(
        {
          Module: Settings.ServerModuleName,
          Method: 'UploadCalendar',
          Parameters: function () {
            return JSON.stringify({
              CalendarID: self.uploadCalendarId(),
            })
          },
        },
        App.getCommonRequestParameters()
      ),
    })

    this.oJua
      .on('onDrop', _.bind(this.onFileDrop, this))
      .on('onComplete', _.bind(this.onFileUploadComplete, this))
      .on('onBodyDragEnter', _.bind(this.bDragActive, this, true))
      .on('onBodyDragLeave', _.bind(this.bDragActive, this, false))
  }
}

CCalendarView.prototype.onFileDrop = function (oFile, oEvent, fProceedUploading) {
  const editableCalendars = this.calendars.collection().filter((calendar) => calendar.isEditable())
  const calendarToSelect = editableCalendars.find((calendar) => calendar.isDefault) || editableCalendars[0]
  if (!calendarToSelect) {
    Screens.showError(TextUtils.i18n('%MODULENAME%/ERROR_NO_EDITABLE_CALENDAR'))
    return
  }

  if (editableCalendars.length > 1) {
    Popups.showPopup(SelectCalendarPopup, [
      {
        CallbackSave: _.bind(this.uploadToSelectedCalendar, this),
        ProceedUploading: fProceedUploading,
        Calendars: this.calendars,
        EditableCalendars: editableCalendars,
        DefaultCalendarId: calendarToSelect.id,
      },
    ])
  } else {
    this.uploadToSelectedCalendar(calendarToSelect.id, fProceedUploading)
  }
}

CCalendarView.prototype.onFileUploadComplete = function (sFileUid, bResponseReceived, oResponse) {
  var bError = !bResponseReceived || !oResponse || !oResponse.Result

  if (!bError) {
    this.getCalendars()
  } else {
    if (oResponse && oResponse.ErrorCode && oResponse.ErrorCode === Enums.Errors.IncorrectFileExtension) {
      Screens.showError(TextUtils.i18n('%MODULENAME%/ERROR_FILE_NOT_ICS'))
    } else {
      Screens.showError(TextUtils.i18n('COREWEBCLIENT/ERROR_UPLOAD_FILE'))
    }
  }
}

CCalendarView.prototype.uploadToSelectedCalendar = function (selectedCalendarId, fProceedUploading) {
  this.uploadCalendarId(selectedCalendarId)
  this.checkStarted(true)
  fProceedUploading()
}

/**
 * @param {number} iScrollTop
 */
CCalendarView.prototype.restoreScroll = function (iScrollTop) {
  if (
    Types.isPositiveNumber(iScrollTop) &&
    this.domScrollWrapper &&
    this.domScrollWrapper.data('customscroll') &&
    this.domScrollWrapper.data('customscroll')['vertical']
  ) {
    this.domScrollWrapper.data('customscroll')['vertical'].set(iScrollTop)
  }
}

const calendarView = new CCalendarView()

Pulse.registerEveryMinuteFunction(() => {
  calendarView.applyDateTime()
})

module.exports = calendarView