'use strict'

###
  @ngdoc factory
  @name ScheduleEvent
  @description An individual schedule event.
###
angular.module('payrollhero.api').factory 'ScheduleEvent', ($q) ->
  addRecurrenceRules = (recurrence, recurrenceInfo) ->
    switch recurrenceInfo.type
      when 'week'
        if recurrenceInfo.count > 1
          throw 'Not currently supported.  Weekly recurrences can only repeat with count=1'
        recurrence.every(weekdaysFromRecurrenceInfo(recurrenceInfo)).daysOfWeek()
      when 'month'
        recurrence.every(recurrenceInfo.count).months()
      else
        throw "#{recurrenceInfo.type} is not a recurring event type!"

  weekdaysFromRecurrenceInfo = (recurrenceInfo) ->
    weekdays = []
    for propName, value of recurrenceInfo
      if value && /on_\w+/.test(propName)
        weekdays.push propName.replace(/^on_/,'')
    weekdays

  exceptionsFromRecurrenceInfo = (recurrenceInfo, tz) ->
    _(recurrenceInfo.exceptions).map( (except) -> moment.tz(except, tz).dateOnly())

  constructMomentRecurrence = (start, end, recurrenceInfo, tz, useExceptions = true) ->
    exceptionInfo = []
    exceptionInfo = exceptionsFromRecurrenceInfo(recurrenceInfo, tz) if useExceptions
    recurrence = moment.recur(
      start: start
      end: end
      exceptions: exceptionInfo
    )
    addRecurrenceRules(recurrence, recurrenceInfo)

  class ScheduleEvent
    ###
      @ngdoc method
      @name ScheduleEvent.addException
      @function

      @description
        Adds an exception on a date to this recurring exception by posting it
        to the server.
      @param {moment} date The date for the exception.
      @returns {Promise} promise that returns this schedule event with the exception
        successfully added.
    ###
    addException: (date) ->
      unless @isRecurringSchedule()
        throw 'You may only add an exception to a recurring schedule!'
      if @isOverriddenOn(date)
        return $q.when(this)
      isoDateString = moment(date).tz(@shift_time_zone).toISODateString()
      @customPOST({date: isoDateString},'exceptions').then (newExceptions) =>
        @recurrence_info.exceptions = newExceptions.exceptions
        @recur = false
        this

    ###
      @ngdoc method
      @name ScheduleEvent.removeExceptionIgnoringNotFound
      @function

      @description
        Remove an exception from this schedule event ignoring missing errors.
      @returns {Promise} A promise on the success of indicates this exception
        has been removed.
    ###
    removeExceptionIgnoringNotFound: (date) ->
      unless @isOverriddenOn(date)
        return $q.when(this)
      @removeException(date).catch (error) ->
        if error.status == 404
          $q.when(this)
        else
          $q.reject(error)

    ###
      @ngdoc method
      @name ScheduleEvent.removeException
      @function

      @description
        Remove an exception from this schedule event.
      @returns {Promise} A promise on the success of indicates this exception
        has been removed or fails if it was not found or could not be removed.
    ###
    removeException: (date) ->
      dateString = moment(date).toISODateString()
      @customDELETE("exceptions/#{dateString}").then (newExceptions) =>
        @recurrence_info.exceptions = newExceptions.exceptions
        @recur = false
        this

    overridesHoliday: ->
      !(@kind in ["recurring_schedule", "note", "event"])

    isRestDay: ->
      @kind in ["recurring_rest_day", "rest_day"]

    ###
      @ngdoc method
      @name ScheduleEvent.removeIgnoringNotFound
      @function

      @description
        Remove this schedule event by deleting it from the server.  Do not throw an error
        if the event no longer exists.
      @returns {Promise} A promise on the success of indicates this schedule event has been
        deleted.
    ###
    removeIgnoringNotFound: ->
      @remove().catch (error) ->
        if error.status == 404
          $q.when(this)
        else
          $q.reject(error)

    ###
      @ngdoc method
      @name ScheduleEvent.occursOn
      @function

      @description
        Answers the question, does this event occur on the requested date?
      @param {moment} date The date to ask about.
      @returns {true|false}
    ###
    occursOn: (date) ->
      if @isRecurringSchedule()
        @recursOn(date)
      else
        @startsOn(date)

    ###
     @ngdoc method
     @name ScheduleEvent.isShift
     @function

     @returns {true|false}
    ###
    isShift: ->
      !@isWholeDay()

    ###
     @ngdoc method
     @name ScheduleEvent.isRecurringSchedule
     @function
     @returns {true|false}
    ###
    isRecurringSchedule: ->
      @kind is 'recurring_schedule'

    ###
     @ngdoc method
     @name ScheduleEvent.rangeOn
     @function
     @params {moment} day
     @returns {moment-range|null} The range on that date, null if it doesn't occur on that date.
    ###
    rangeOn: (day) ->
      return null unless @occursOn(day)
      switch @kind
        when 'recurring_schedule'
          @recurringRangeOn(day)
        else
          moment().range(@_shiftStart(), @_shiftEnd())

    ###
     @ngdoc method
     @name ScheduleEvent.isOverriddenOn
     @function
     @params {moment} day
     @description If this is a recurring event
       tells if it has been overridden on that date.
     @returns {true|false}
    ###
    isOverriddenOn: (day) ->
      return false unless @isRecurringSchedule()
      @recurWithoutExceptions ||= constructMomentRecurrence(@_shiftStart(), @_recurrenceEnd(),
                                                            @recurrence_info, @shift_time_zone, false)
      @recurWithoutExceptions.matches(day.dateOnly()) && _(@recurrence_info.exceptions).contains(day.toISODateString())

    ###
     @ngdoc method
     @name ScheduleEvent.startsOn
     @function
     @params {moment} day
     @returns {true|false}
    ###
    startsOn: (day) ->
      m = @_shiftStart()
      @_shiftStart().isSame(day, 'day')

    ###
     @ngdoc method
     @name ScheduleEvent.isWholeDay
     @function
     @returns {true|false}
    ###
    isWholeDay: ->
      this.day_event_date?

    ###
     @ngdoc method
     @name ScheduleEvent.recursOn
     @function
     @returns {true|false}
    ###
    recursOn: (date) ->
      return false unless @isRecurringSchedule()
      @recur ||= constructMomentRecurrence(@_shiftStart(), @_recurrenceEnd(),
                                        @recurrence_info, @shift_time_zone)
      @recur.matches(date.dateOnly())

    ###
     @ngdoc method
     @name ScheduleEvent.recurringRangeOn
     @function
     @param {moment} date
     @returns moment-range
    ###
    recurringRangeOn: (date) ->
      # Note:  Really important that this uses timezones.  8am to 4pm on Oct 15 repeating should be
      # 8am to 4pm on January 15th
      startDiff = @_shiftStart().diff(moment(@_shiftStart()).startOf('day'))
      shiftLength = @_shiftEnd().diff(@_shiftStart())
      start = moment.tz(date, @shift_time_zone).startOf('day').add(startDiff, 'milliseconds')
      end = moment(start).add(shiftLength, 'milliseconds')
      moment().range(start, end)

    ###
     private methods follow
    ###
    _shiftStart: ->
      if @isWholeDay()
        moment(@day_event_date, 'YYYY-MM-DD')
      else
        moment(@shift_start_time, moment.ISO_8601).tz(@shift_time_zone)

    _shiftEnd: ->
      moment(@shift_end_time, moment.ISO_8601).tz(@shift_time_zone)

    _recurrenceEnd: ->
      if @recurrence_info.end_time
        moment(@recurrence_info.end_time)
      else
        null
