'use strict'

##
# Controller used in the calendar management page
##

Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'availabilitiesPromise', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t'
($scope, $state, $uibModal, moment, Availability, Slot, Setting, growl, dialogs, availabilitiesPromise, bookingWindowStart, bookingWindowEnd, machinesPromise, _t) ->



  ### PRIVATE STATIC CONSTANTS ###

  # The calendar is divided in slots of 30 minutes
  BASE_SLOT = '00:30:00'

  # The bookings can be positioned every half hours
  BOOKING_SNAP = '00:30:00'

  # The calendar will be initialized positioned under 9:00 AM
  DEFAULT_CALENDAR_POSITION = '09:00:00'

  # We do not allow the creation of slots that are not a multiple of 60 minutes
  SLOT_MULTIPLE = 60



  ### PUBLIC SCOPE ###

  ## list of the FabLab machines
  $scope.machines = machinesPromise

  ## currently selected availability
  $scope.availability = null

  ## bind the availabilities slots with full-Calendar events
  $scope.eventSources = []
  $scope.eventSources.push
    events: availabilitiesPromise
    textColor: 'black'

  ## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar()
  $scope.calendar = null

  ## fullCalendar (v2) configuration
  $scope.calendarConfig =
    timezone: Fablab.timezone
    lang: Fablab.fullcalendar_locale
    header:
      left: 'month agendaWeek'
      center: 'title'
      right: 'today prev,next'
    firstDay: 1 # Week start on monday (France)
    scrollTime: DEFAULT_CALENDAR_POSITION
    slotDuration: BASE_SLOT
    snapDuration: BOOKING_SNAP
    allDayDefault: false
    minTime: "00:00:00"
    maxTime: "24:00:00"
    height: 'auto'
    buttonIcons:
      prev: 'left-single-arrow'
      next: 'right-single-arrow'
    timeFormat:
      agenda:'H:mm'
      month: 'H(:mm)'
    axisFormat: 'H:mm'

    allDaySlot: false
    defaultView: 'agendaWeek'
    selectable: true
    selecHelper: true
    select: (start, end, jsEvent, view) ->
      calendarSelectCb(start, end, jsEvent, view)
    eventClick: (event, jsEvent, view)->
      calendarEventClickCb(event, jsEvent, view)
    eventRender: (event, element, view) ->
      eventRenderCb(event, element)

  ## fullCalendar time bounds (up & down)
  $scope.calendarConfig.minTime = moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss'))
  $scope.calendarConfig.maxTime = moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss'))



  ##
  # Open a confirmation modal to cancel the booking of a user for the currently selected event.
  # @param slot {Object} reservation slot of a user, inherited from $resource
  ##
  $scope.cancelBooking = (slot) ->
    # open a confirmation dialog
    dialogs.confirm
      resolve:
        object: ->
          title: _t('confirmation_required')
          msg: _t("do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION"
          , { GENDER:getGender($scope.currentUser), USER:slot.user.name, DATE:moment(slot.start_at).format('L'), TIME:moment(slot.start_at).format('LT'), RESERVATION:slot.reservable.name }
          , 'messageformat')
    , ->
      # the admin has confirmed, cancel the subscription
      Slot.cancel {id: slot.slot_id}
      , (data, status) -> # success
        # update the canceled_at attribute
        for resa in $scope.reservations
          if resa.slot_id == data.id
            resa.canceled_at = data.canceled_at
            break
        # notify the admin
        growl.success(_t('reservation_was_successfully_cancelled'))
      , (data, status) -> # failed
        growl.error(_t('reservation_cancellation_failed'))



  ##
  # Open a confirmation modal to remove a machine for the currently selected availability,
  # except if it is the last machine of the reservation.
  # @param machine {Object} must contain the machine ID and name
  ##
  $scope.removeMachine = (machine) ->
    if $scope.availability.machine_ids.length == 1
      growl.error(_t('unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
    else
      # open a confirmation dialog
      dialogs.confirm
        resolve:
          object: ->
            title: _t('confirmation_required')
            msg: _t('do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' +
             _t('this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
             _t('beware_this_cannot_be_reverted')
      , ->
        # the admin has confirmed, remove the machine
        machines = $scope.availability.machine_ids
        for key, m_id in machines
          if m_id == machine.id
            machines.splice(key, 1)

        Availability.update {id: $scope.availability.id}, {availability: {machines_attributes: [{id: machine.id, _destroy: true}]}}
        , (data, status) -> # success
          # update the machine_ids attribute
          $scope.availability.machine_ids = data.machine_ids
          $scope.availability.title = data.title
          $scope.calendar.fullCalendar 'rerenderEvents'
          # notify the admin
          growl.success(_t('the_machine_was_successfully_removed_from_the_slot'))
        , (data, status) -> # failed
          growl.error(_t('deletion_failed'))



  ### PRIVATE SCOPE ###

  ##
  # Return an enumerable meaninful string for the gender of the provider user
  # @param user {Object} Database user record
  # @return {string} 'male' or 'female'
  ##
  getGender = (user) ->
    if user.profile
      if user.profile.gender == "true" then 'male' else 'female'
    else 'other'

  # Triggered when the admin drag on the agenda to create a new reservable slot.
  # @see http://fullcalendar.io/docs/selection/select_callback/
  ##
  calendarSelectCb = (start, end, jsEvent, view) ->
    start = moment.tz(start.toISOString(), Fablab.timezone)
    end = moment.tz(end.toISOString(), Fablab.timezone)
    # first we check that the selected slot is an N-hours multiple (ie. not decimal)
    if Number.isInteger(parseInt((end.valueOf() - start.valueOf()) / (SLOT_MULTIPLE * 1000), 10)/SLOT_MULTIPLE)
      today = new Date()
      if (parseInt((start.valueOf() - today) / (60 * 1000), 10) >= 0)
        # then we open a modal window to let the admin specify the slot type
        modalInstance = $uibModal.open
          templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>'
          controller: 'CreateEventModalController'
          resolve:
            start: -> start
            end: -> end
        # when the modal is closed, we send the slot to the server for saving
        modalInstance.result.then (availability) ->
          $scope.calendar.fullCalendar 'renderEvent',
            id: availability.id
            title: availability.title,
            start: availability.start_at
            end: availability.end_at
            textColor: 'black'
            backgroundColor: availability.backgroundColor
            borderColor: availability.borderColor
            tag_ids: availability.tag_ids
            machine_ids: availability.machine_ids
          , true
        , ->
          $scope.calendar.fullCalendar('unselect')

    $scope.calendar.fullCalendar('unselect')



  ##
  # Triggered when the admin clicks on a availability slot in the agenda.
  # @see http://fullcalendar.io/docs/mouse/eventClick/
  ##
  calendarEventClickCb = (event, jsEvent, view) ->

    $scope.availability = event

    # if the user has clicked on the delete event button, delete the event
    if ($(jsEvent.target).hasClass('remove-event'))
      Availability.delete id: event.id, ->
        $scope.calendar.fullCalendar 'removeEvents', event.id
        for _event, i in $scope.eventSources[0].events
          if _event.id == event.id
            $scope.eventSources[0].events.splice(i,1)

        growl.success(_t('the_slot_START-END_has_been_successfully_deleted', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
      ,->
        growl.error(_t('unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
    # if the user has only clicked on the event, display its reservations
    else
      Availability.reservations {id: event.id}, (reservations) ->
        $scope.reservations = reservations




  ##
  # Triggered when fullCalendar tries to graphicaly render an event block.
  # Append the event tag into the block, just after the event title.
  # @see http://fullcalendar.io/docs/event_rendering/eventRender/
  ##
  eventRenderCb = (event, element) ->
    if event.tag_ids.length > 0
      Availability.get {id: event.id}, (avail) ->
        html = ''
        for tag in avail.tags
          html += "<span class='label label-success text-white'>#{tag.name}</span> "
        element.find('.fc-title').append("<br/>"+html)

]



##
# Controller used in the slot creation modal window
##
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "Machine", "Availability", "Training", 'Tag', 'growl', '_t', ($scope, $uibModalInstance, moment, start, end, Machine, Availability, Training, Tag, growl, _t) ->

  ## $uibModal parameter
  $scope.start = start

  ## $uibModal parameter
  $scope.end = end

  ## machines list
  $scope.machines = []

  ## trainings list
  $scope.trainings = []

  ## machines associated with the created slot
  $scope.selectedMachines = []

  ## the user is not able to edit the ending time of the availability, unless he set the type to 'training'
  $scope.endDateReadOnly = true

  ## timepickers configuration
  $scope.timepickers =
    start:
      hstep: 1
      mstep: 5
    end:
      hstep: 1
      mstep: 5

  ## slot details
  $scope.availability =
    start_at: start
    end_at: end
    available_type: 'machines' # default



  ##
  # Adds or removes the provided machine from the current slot
  # @param machine {Object}
  ##
  $scope.toggleSelection = (machine)->
    index = $scope.selectedMachines.indexOf(machine)
    if index > -1
      $scope.selectedMachines.splice(index, 1)
    else
      $scope.selectedMachines.push(machine)



  ##
  # Callback for the modal window validation: save the slot and closes the modal
  ##
  $scope.ok = ->
    if $scope.availability.available_type == "machines"
      if $scope.selectedMachines.length > 0
        $scope.availability.machine_ids = $scope.selectedMachines.map (m) -> m.id
      else
        growl.error(_t('you_should_link_a_training_or_a_machine_to_this_slot'))
        return
    else
      $scope.availability.training_ids = [$scope.selectedTraining.id]
    Availability.save
      availability: $scope.availability
    , (availability) ->
      $uibModalInstance.close(availability)



  ##
  # Callback to cancel the slot creation
  ##
  $scope.cancel = ->
    $uibModalInstance.dismiss('cancel')



  ##
  # Switches the slot type : machine availability or training availability
  ##
  $scope.changeAvailableType = ->
    if $scope.availability.available_type == "machines"
      $scope.availability.available_type = "training"
    else
      $scope.availability.available_type = "machines"



  ##
  # For training avaiabilities, set the maximum number of people allowed to register on this slot
  ##
  $scope.setNbTotalPlaces = ->
    $scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places


  ### PRIVATE SCOPE ###

  ##
  # Kind of constructor: these actions will be realized first when the controller is loaded
  ##
  initialize = ->
    Machine.query().$promise.then (data)->
      $scope.machines = data.map (d) ->
        id: d.id
        name: d.name
    Training.query().$promise.then (data)->
      $scope.trainings = data.map (d) ->
        id: d.id
        name: d.name
        nb_total_places: d.nb_total_places
      if $scope.trainings.length > 0
        $scope.selectedTraining = $scope.trainings[0]
        $scope.setNbTotalPlaces()
    Tag.query().$promise.then (data) ->
      $scope.tags = data

    ## When we configure a machine availability, do not let the user change the end time, as the total
    ## time must be dividable by 60 minutes (base slot duration). For training availabilities, the user
    ## can configure any duration as it does not matters.
    $scope.$watch 'availability.available_type', (newValue, oldValue, scope) ->
      if newValue == 'machines'
        $scope.endDateReadOnly = true
        diff = moment($scope.end).diff($scope.start, 'hours') # the result is rounded down by moment.js
        $scope.end = moment($scope.start).add(diff, 'hours').toDate()
        $scope.availability.end_at = $scope.end
      else
        $scope.endDateReadOnly = false

    ## When the start date is changed, if we are configuring a machine availability,
    ## maintain the relative length of the slot (ie. change the end time accordingly)
    $scope.$watch 'start', (newValue, oldValue, scope) ->
      # for machine availabilities, adjust the end time
      if $scope.availability.available_type == 'machines'
        end = moment($scope.end)
        end.add(moment(newValue).diff(oldValue), 'milliseconds')
        $scope.end = end.toDate()
      else # for training availabilities
        # prevent the admin from setting the begining after the and
        if moment(newValue).add(1, 'hour').isAfter($scope.end)
          $scope.start = oldValue
      # update availability object
      $scope.availability.start_at = $scope.start

    ## Maintain consistency between the end time and the date object in the availability object
    $scope.$watch 'end', (newValue, oldValue, scope) ->
      ## we prevent the admin from setting the end of the availability before its begining
      if moment($scope.start).add(1, 'hour').isAfter(newValue)
        $scope.end = oldValue
      # update availability object
      $scope.availability.end_at = $scope.end



  ## !!! MUST BE CALLED AT THE END of the controller
  initialize()
]
