/* eslint-disable
    camelcase,
    handle-callback-err,
    no-return-assign,
    no-undef,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */

Application.Controllers.controller('EventsController', ['$scope', '$state', 'Event', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'growl',
  function ($scope, $state, Event, categoriesPromise, themesPromise, ageRangesPromise, growl) {
  /* PUBLIC SCOPE */

    /**
    * Callback triggered by react components
    */
    $scope.onError = function (message) {
      growl.error(message);
    };

    // The events displayed on the page
    $scope.events = [];

    // The currently displayed page number
    $scope.page = 1;

    // List of categories for the events
    $scope.categories = categoriesPromise;

    // List of events themes
    $scope.themes = themesPromise;

    // List of age ranges
    $scope.ageRanges = ageRangesPromise;

    // Hide or show the 'load more' button
    $scope.noMoreResults = false;

    // Active filters for the events list
    $scope.filters = {
      category_id: null,
      theme_id: null,
      age_range_id: null
    };

    $scope.monthNames = [<%= I18n.t('date.month_names')[1..-1].map { |m| "\"#{m}\"" }.join(', ') %>];

    /**
     * Adds a resultset of events to the bottom of the page, grouped by month
     */
    $scope.loadMoreEvents = function () {
      $scope.page += 1;
      return Event.query(Object.assign({ page: $scope.page }, $scope.filters), function (data) {
        $scope.events = $scope.events.concat(data);
        groupEvents($scope.events);

        if (!data[0] || (data[0].nb_total_events <= $scope.events.length)) {
          return $scope.noMoreResults = true;
        }
      });
    };

    /**
     * Callback to redirect the user to the specified event page
     * @param event {{id:number}}
     */
    $scope.showEvent = function (event) { $state.go('app.public.events_show', { id: event.id }); };

    /**
     * Callback to refresh the events list according to the filters set
     */
    $scope.filterEvents = function () {
    // reinitialize results datasets
      $scope.page = 1;
      $scope.eventsGroupByMonth = {};
      $scope.featuredEevent = null;
      $scope.events = [];
      $scope.monthOrder = [];
      $scope.noMoreResults = false;

      // run a search query
      return Event.query(Object.assign({ page: $scope.page }, $scope.filters), function (data) {
        $scope.events = data;
        groupEvents(data);

        if (!data[0] || (data[0].nb_total_events <= $scope.events.length)) {
          return $scope.noMoreResults = true;
        }
      });
    };

    /**
     * Test if the provided event occurs on a single day or on many days
     * @param event {{start_date:Date, end_date:Date}} Event object as retreived from the API
     * @return {boolean} false if the event occurs on many days
     */
    $scope.onSingleDay = function (event) { moment(event.start_date).isSame(event.end_date, 'day'); };


    /* PRIVATE SCOPE */

    /**
     * Kind of constructor: these actions will be realized first when the controller is loaded
     */
    const initialize = function () {
      $scope.filterEvents();
    };

    /**
     * Group the provided events by month/year and concat them with existing results
     * Then compute the ordered list of months for the complete resultset.
     * Affect the resulting events groups in $scope.eventsGroupByMonth and the ordered month keys in $scope.monthOrder.
     * @param events {Array} Events retrieved from the API
     */
    const groupEvents = function (events) {
      if (events.length > 0) {
        const eventsGroupedByMonth = _.groupBy(events, function (obj) {
          return _.map(['month_id', 'year'], function (key) {
            return obj[key];
          });
        });
        $scope.eventsGroupByMonth = Object.assign($scope.eventsGroupByMonth, eventsGroupedByMonth);
        $scope.monthOrder = Object.keys($scope.eventsGroupByMonth);
        $scope.featuredEevent = _.minBy(events.filter(e => moment(e.start_date).isAfter()), e => e.start_date);
      }
    };

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

Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'SlotsReservation', 'eventPromise', 'growl', '_t', 'Wallet', 'AuthService', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise', 'LocalPayment', 'Child',
  function ($scope, $state,$rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, SlotsReservation, eventPromise, growl, _t, Wallet, AuthService, helpers, dialogs, priceCategoriesPromise, settingsPromise, LocalPayment, Child) {
    /* PUBLIC SCOPE */

    // reservations for the currently shown event
    $scope.reservations = [];

    // current date & time
    $scope.now = moment();

    // user to deal with
    $scope.ctrl =
      { member: {} };

    // children for the member
    $scope.children = [];

    // parameters for a new reservation
    $scope.reserve = {
      nbPlaces: {
        normal: []
      },
      nbReservePlaces: 0,
      tickets: {},
      toReserve: false,
      amountTotal: 0,
      totalNoCoupon: 0,
      totalSeats: 0,
      bookingUsers: {
        normal: []
      },
    };

    // Discount coupon to apply to the basket, if any
    $scope.coupon =
    { applied: null };

    // Get the details for the current event (event's id is recovered from the current URL)
    $scope.event = eventPromise;
    $scope.eventEndDateTime = moment(`${eventPromise.end_date}T${eventPromise.end_time}:00`);

    // the application global settings
    $scope.settings = settingsPromise;

    // List of price categories for the events
    $scope.priceCategories = priceCategoriesPromise;

    // Global config: is the user authorized to change his bookings slots?
    $scope.enableBookingMove = (settingsPromise.booking_move_enable === 'true');

    // Global config: delay in hours before a booking while changing the booking slot is forbidden
    $scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay);

    // Global config: is the user authorized to cancel his booking slots?
    $scope.enableBookingCancel = settingsPromise.booking_cancel_enable === 'true';

    // Global config: delay in hours from now when restrictions occurs about cancelling reservations
    $scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay);

    // Message displayed to the end user about rules that applies to events reservations
    $scope.eventExplicationsAlert = settingsPromise.event_explications_alert;

    // Global config: is the user validation required ?
    $scope.enableUserValidationRequired = settingsPromise.user_validation_required === 'true';

    // Global config: is the child validation required ?
    $scope.enableChildValidationRequired = settingsPromise.child_validation_required === 'true';

    // online payments (by card)
    $scope.onlinePayment = {
      showModal: false,
      cartItems: undefined
    };

    /**
     * Callback to delete the provided event (admins only)
     */
    $scope.deleteEvent = function () {
      // open a confirmation dialog
      const modalInstance = $uibModal.open({
        animation: true,
        templateUrl: '/events/deleteRecurrent.html',
        size: 'md',
        controller: 'DeleteRecurrentEventController',
        resolve: {
          eventPromise: ['Event', function (Event) { return Event.get({ id: $scope.event.id }).$promise; }]
        }
      });
      // once the dialog was closed, do things depending on the result
      modalInstance.result.then(function (res) {
        if (res.status === 'success') {
          $state.go('app.public.events_list');
        }
      });
    };

    /**
     * Callback to call when the number of tickets to book changes in the current booking
     */
    $scope.changeNbPlaces = function (priceType) {
      let reservedPlaces = 0;
      if ($scope.event.event_type === 'family') {
        const reservations = $scope.reservations.filter((reservation) => {
          return !reservation.slots_reservations_attributes[0].canceled_at;
        });
        reservedPlaces = reservations.reduce((sum, reservation) => {
          return sum + reservation.booking_users_attributes.length;
        }, 0);
      }
      let nb_free_places = $scope.event.nb_free_places;
      if ($scope.event.event_type === 'family') {
        const maxPlaces = $scope.children.length + 1 - reservedPlaces;
        nb_free_places = Math.min(maxPlaces, $scope.event.nb_free_places);
      }
      // compute the total remaining places
      let remain = nb_free_places - $scope.reserve.nbReservePlaces;
      for (let ticket in $scope.reserve.tickets) {
        remain -= $scope.reserve.tickets[ticket];
      }
      // we store the total number of seats booked, this is used to know if the 'pay' button must be shown
      $scope.reserve.totalSeats = $scope.event.nb_free_places - remain;

      // update the available seats for full price tickets
      const fullPriceRemains = $scope.reserve.nbReservePlaces + remain;
      $scope.reserve.nbPlaces.normal = __range__(0, fullPriceRemains, true);

      // update the available seats for other prices tickets
      for (let key in $scope.reserve.nbPlaces) {
        if (key !== 'normal') {
          const priceRemain = $scope.reserve.tickets[key] + remain;
          $scope.reserve.nbPlaces[key] = __range__(0, priceRemain, true);
        }
      }

      if ($scope.event.event_type === 'nominative' || $scope.event.event_type === 'family') {
        const nbBookingUsers = $scope.reserve.bookingUsers[priceType].length;
        const nbReservePlaces = priceType === 'normal' ? $scope.reserve.nbReservePlaces : $scope.reserve.tickets[priceType];
        if (nbReservePlaces > nbBookingUsers) {
          _.times(nbReservePlaces - nbBookingUsers, () => {
            $scope.reserve.bookingUsers[priceType].push({ event_price_category_id: priceType === 'normal' ? null : priceType, bookedUsers: buildBookedUsersOptions() });
          });
        } else {
          _.times(nbBookingUsers - nbReservePlaces, () => {
            $scope.reserve.bookingUsers[priceType].pop();
          });
        }
      }

      // recompute the total price
      return $scope.computeEventAmount();
    };

    $scope.changeBookedUser = function () {
      for (const key of Object.keys($scope.reserve.bookingUsers)) {
        for (const user of $scope.reserve.bookingUsers[key]) {
          user.bookedUsers = buildBookedUsersOptions(user.booked);
        }
      }
    }

    /**
     * Callback to reset the current reservation parameters
     * @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
     */
    $scope.cancelReserve = function (e) {
      e.preventDefault();
      resetEventReserve();
      updateNbReservePlaces();
      return;
    };

    $scope.isUserValidatedByType = () => {
      return helpers.isUserValidatedByType($scope.ctrl.member, $scope.settings, 'event');
    };

    $scope.isShowReserveEventButton = () => {
      const bookable = $scope.event.nb_free_places > 0 &&
        !$scope.reserve.toReserve &&
        $scope.now.isBefore($scope.eventEndDateTime) &&
        helpers.isUserValidatedByType($scope.ctrl.member, $scope.settings, 'event');
      if ($scope.event.pre_registration) {
        const endDate = $scope.event.pre_registration_end_date || $scope.event.end_date
        return bookable && $scope.now.isSameOrBefore(endDate, 'day');
      } else {
        return bookable;
      }
    };

    /**
     * Callback to allow the user to set the details for his reservation
     */
    $scope.reserveEvent = function () {
      if ($scope.event.nb_total_places > 0) {
        $scope.reserveSuccess = false;
        if (!$scope.isAuthenticated()) {
          $scope.login(null, function (user) {
            if (user.role !== 'admin' || user.role !== 'manager') {
              $scope.ctrl.member = user;
            }
            const sameTimeReservations = findReservationsAtSameTime();
            if (sameTimeReservations.length > 0) {
              showReserveSlotSameTimeModal(sameTimeReservations, function(res) {
                $scope.reserve.toReserve = !$scope.reserve.toReserve;
              });
            } else {
              $scope.reserve.toReserve = !$scope.reserve.toReserve;
            }
          });
        } else {
          if (AuthService.isAuthorized(['admin', 'manager'])) {
            $scope.reserve.toReserve = !$scope.reserve.toReserve;
          } else {
            Member.get({ id: $scope.currentUser.id }, function (member) {
              $scope.ctrl.member = member;
              const sameTimeReservations = findReservationsAtSameTime();
              if (sameTimeReservations.length > 0) {
                showReserveSlotSameTimeModal(sameTimeReservations, function(res) {
                  $scope.reserve.toReserve = !$scope.reserve.toReserve;
                });
              } else {
                $scope.reserve.toReserve = !$scope.reserve.toReserve;
              }
            });
          }
        }
      }
    };

    /**
     * Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's
     * reservations. (admins only)
     */
    $scope.updateMember = function () {
      resetEventReserve();
      $scope.reserveSuccess = false;
      if ($scope.ctrl.member) {
        Member.get({ id: $scope.ctrl.member.id }, function (member) {
          $scope.ctrl.member = member;
          getReservations($scope.event.id, 'Event', $scope.ctrl.member.id).then(function () {
            getChildren($scope.ctrl.member.id).then(function (children) {
              updateNbReservePlaces();
            });
          });
        });
      }
    };

    /**
     * Callback to trigger the payment process of the current reservation
     */
    $scope.payEvent = function () {
    // first, we check that a user was selected
      if (Object.keys($scope.ctrl.member).length > 0) {
        const reservation = mkReservation($scope.reserve, $scope.event);

        return Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) {
          const amountToPay = helpers.getAmountToPay($scope.reserve.amountTotal, wallet.amount);
          if ((AuthService.isAuthorized('member') && amountToPay > 0)
            || (AuthService.isAuthorized(['manager', 'admin']) && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) {
            if (settingsPromise.online_payment_module !== 'true') {
              growl.error(_t('app.public.events_show.online_payment_disabled'));
            } else {
              return payOnline(reservation);
            }
          } else {
            if (AuthService.isAuthorized(['manager', 'admin']) && $scope.ctrl.member.id !== $rootScope.currentUser.id
              || amountToPay === 0) {
              return payOnSite(reservation);
            }
          }
        });
      } else {
      // otherwise we alert, this error musn't occur when the current user is not admin
        return growl.error(_t('app.public.events_show.please_select_a_member_first'));
      }
    };

    /**
     * Callback to validate the booking of a free event
     */
    $scope.validReserveEvent = function () {
      const reservation = mkReservation($scope.reserve, $scope.event)
      const cartItems = mkCartItems(reservation, $scope.coupon.applied);
      // set the attempting marker
      $scope.attempting = true;
      // save the reservation to the API
      return LocalPayment.confirm(cartItems, function (reservation) {
      // reservation successful
        afterPayment(reservation);
        return $scope.attempting = false;
      }
      , function (response) {
      // reservation failed
        growl.error(response && response.data && _.keys(response.data)[0] && response.data[_.keys(response.data)[0]][0] || 'server error');
        // unset the attempting marker
        $scope.attempting = false;
      })
    };

    /**
     * Callback to cancel a reservation
     * @param reservation {Reservation}
     */
    $scope.cancelReservation = function(reservation) {
      dialogs.confirm({
        resolve: {
          object: function() {
            return {
              title: _t('app.public.events_show.cancel_the_reservation'),
              msg: _t('app.public.events_show.do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets')
            };
          }
        }
      }, function() { // cancel confirmed
        SlotsReservation.cancel({
          id: reservation.slots_reservations_attributes[0].id
        }, function() { // successfully canceled
          let index;
          growl.success(_t('app.public.events_show.reservation_was_successfully_cancelled'));
          index = $scope.reservations.indexOf(reservation);
          $scope.reservations[index].slots_reservations_attributes[0].canceled_at = new Date();
          Event.get({ id: $scope.event.id }).$promise.then(function (event) {
            $scope.event = event;
            getReservations($scope.event.id, 'Event', $scope.ctrl.member.id).then(function () {
              updateNbReservePlaces();
              $scope.reserveSuccess = true;
              $scope.coupon.applied = null;
            });
            resetEventReserve();
          });
        }, function(error) {
          growl.warning(_t('app.public.events_show.cancellation_failed'));
        });
      });
    };

    /**
     * Test if the provided reservation has been cancelled
     * @param reservation {Reservation}
     * @returns {boolean}
     */
    $scope.isCancelled = function(reservation) {
      return !!(reservation.slots_reservations_attributes[0].canceled_at);
    }

    /**
     * Callback to alter an already booked reservation date. A modal window will be opened to allow the user to choose
     * a new date for his reservation (if any available)
     * @param reservation {Reservation}
     */
    $scope.modifyReservation = function (reservation) {
      const index = $scope.reservations.indexOf(reservation);
      return $uibModal.open({
        templateUrl: '/events/modify_event_reservation_modal.html',
        resolve: {
          event () { return $scope.event; },
          reservation () { return reservation; }
        },
        controller: ['$scope', '$uibModalInstance', 'event', 'reservation', 'Reservation', function ($scope, $uibModalInstance, event, reservation, Reservation) {
        // we copy the controller's resolved parameters into the scope
          $scope.event = event;
          $scope.reservation = angular.copy(reservation);

          // set the reservable_id to the first available event
          for (evt of Array.from(event.recurrence_events)) {
            if (evt.nb_free_places > reservation.total_booked_seats) {
              $scope.reservation.reservable_id = evt.id;
              break;
            }
          }

          // Callback to validate the new reservation's date
          $scope.ok = function () {
            let eventToPlace = null;
            angular.forEach(event.recurrence_events, function (e) {
              if (e.id === parseInt($scope.reservation.reservable_id, 10)) {
                return eventToPlace = e;
              }
            });
            $scope.reservation.slots_reservations_attributes[0].slot_id = eventToPlace.slot_id;
            $scope.attempting = true;
            Reservation.update({ id: reservation.id }, { reservation: $scope.reservation }, function (reservation) {
              $uibModalInstance.close(reservation);
              $scope.attempting = true;
            }
            , function (response) {
              $scope.alerts = [];
              angular.forEach(response, function (v, k) {
                angular.forEach(v, function (err) {
                  $scope.alerts.push({ msg: k + ': ' + err, type: 'danger' });
                });
              });
              $scope.attempting = false;
            });
          };

          // Callback to cancel the modification
          $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
        }
        ] })
        .result['finally'](null).then(function (reservation) {
          // remove the reservation from the user's reservations list for this event (occurrence)
          $scope.reservations.splice(index, 1);
          // add the number of places transfered (to the new date) to the total of free places for this event
          $scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats;
          // remove the number of places transfered from the total of free places of the receiving occurrance
          angular.forEach($scope.event.recurrence_events, function (e) {
            if (e.id === parseInt(reservation.reservable.id, 10)) {
              return e.nb_free_places = e.nb_free_places - reservation.total_booked_seats;
            }
          });
        });
    };

    /**
     * Checks if the provided reservation is able to be moved (date change)
     * @param reservation {Reservation}
     */
    $scope.reservationCanModify = function (reservation) {
      if (AuthService.isAuthorized(['admin', 'manager'])) return true;

      const slotStart = moment(reservation.slots_reservations_attributes[0].slot_attributes.start_at);
      const now = moment();

      let isAble = false;
      angular.forEach($scope.event.recurrence_events, function (e) {
        if (e.nb_free_places >= reservation.total_booked_seats) { return isAble = true; }
      });
      return (isAble && $scope.enableBookingMove && (slotStart.diff(now, 'hours') >= $scope.moveBookingDelay));
    };

    /**
     * Checks if the provided reservation is able to be cancelled
     * @param reservation {Reservation}
     */
    $scope.reservationCanCancel = function(reservation) {
      if (AuthService.isAuthorized(['admin', 'manager'])) return true;

      const slotStart = moment(reservation.slots_reservations_attributes[0].slot_attributes.start_at);
      const now = moment();
      return $scope.enableBookingCancel && slotStart.diff(now, "hours") >= $scope.cancelBookingDelay;
    };

    /**
     * Compute the total amount for the current reservation according to the previously set parameters
     * and assign the result in $scope.reserve.amountTotal
     */
    $scope.computeEventAmount = function () {
    // first we check that a user was selected
      if (Object.keys($scope.ctrl.member).length > 0) {
        const r = mkReservation($scope.reserve, $scope.event);
        return Price.compute(mkCartItems(r, $scope.coupon.applied), function (res) {
          $scope.reserve.amountTotal = res.price;
          return $scope.reserve.totalNoCoupon = res.price_without_coupon;
        });
      } else {
        return $scope.reserve.amountTotal = null;
      }
    };

    /**
     * Return the URL allowing to share the current project on the Facebook social network
     */
    $scope.shareOnFacebook = function () { return `https://www.facebook.com/share.php?u=${$state.href('app.public.events_show', { id: $scope.event.id }, { absolute: true }).replace('#', '%23')}`; };

    /**
     * Return the URL allowing to share the current project on the Twitter social network
     */
    $scope.shareOnTwitter = function () { return `https://twitter.com/intent/tweet?url=${encodeURIComponent($state.href('app.public.events_show', { id: $scope.event.id }, { absolute: true }))}&text=${encodeURIComponent($scope.event.title)}`; };

    /**
     * Return the textual description of the conditions applyable to the given price's category
     * @param category_id {number} ID of the price's category
     */
    $scope.getPriceCategoryConditions = function (category_id) {
      for (let cat of Array.from($scope.priceCategories)) {
        if (cat.id === category_id) {
          return cat.conditions;
        }
      }
    };

    /**
     * This will open/close the online payment modal
     */
    $scope.toggleOnlinePaymentModal = (beforeApply) => {
      setTimeout(() => {
        $scope.onlinePayment.showModal = !$scope.onlinePayment.showModal;
        if (typeof beforeApply === 'function') {
          beforeApply();
        }
        $scope.$apply();
      }, 50);
    };

    /**
     * Invoked atfer a successful card payment
     * @param invoice {*} the invoice
     */
    $scope.afterOnlinePaymentSuccess = (invoice) => {
      $scope.toggleOnlinePaymentModal();
      afterPayment(invoice);
    };

    /**
     * Invoked when something wrong occurred during the payment dialog initialization
     * @param message {string}
     */
    $scope.onOnlinePaymentError = (message) => {
      growl.error(message);
    };

    /**
     * Checks if the reservation of current event is valid
     */
    $scope.reservationIsValid = () => {
      if ($scope.event.event_type === 'nominative') {
        for (const key of Object.keys($scope.reserve.bookingUsers)) {
          for (const user of $scope.reserve.bookingUsers[key]) {
            if (!_.trim(user.name)) {
              return false;
            }
          }
        }
      }
      if ($scope.event.event_type === 'family') {
        for (const key of Object.keys($scope.reserve.bookingUsers)) {
          for (const user of $scope.reserve.bookingUsers[key]) {
            if (!user.booked) {
              return false;
            }
            if ($scope.enableChildValidationRequired && user.booked.type === 'Child' && !user.booked.validated_at) {
              return false;
            }
          }
        }
      }
      return true;
    }

    $scope.isUnder18YearsAgo = (date) => {
      return moment(date).isAfter(moment().subtract(18, 'year'));
    }

    /* PRIVATE SCOPE */

    /**
     * Kind of constructor: these actions will be realized first when the controller is loaded
     */
    const initialize = function () {
    // set the controlled user as the current user if the current user is not an admin
      if ($scope.currentUser) {
        if ($scope.currentUser.role !== 'admin') {
          $scope.ctrl.member = $scope.currentUser;
        }
      }

      // initialize the "reserve" object with the event's data
      resetEventReserve();

      // get the current user's reservations into $scope.reservations
      if ($scope.currentUser) {
        getReservations($scope.event.id, 'Event', $scope.currentUser.id).then(function () {
          getChildren($scope.currentUser.id).then(function (children) {
            updateNbReservePlaces();
          });
        });
      }

      // watch when a coupon is applied to re-compute the total price
      $scope.$watch('coupon.applied', function (newValue, oldValue) {
        if ((newValue !== null) || (oldValue !== null)) {
          return $scope.computeEventAmount();
        }
      });
    };

    /**
     * Retrieve the reservations for the couple event / user
     * @param reservable_id {number} the current event id
     * @param reservable_type {string} 'Event'
     * @param user_id {number} the user's id (current or managed)
     */
    const getReservations = function (reservable_id, reservable_type, user_id) {
      return Reservation.query({
        reservable_id,
        reservable_type,
        user_id
      }).$promise.then(function (reservations) { $scope.reservations = reservations; });
    };

    /**
     * Retrieve the children for the user
     * @param user_id {number} the user's id (current or managed)
     */
    const getChildren = function (user_id) {
      return Child.query({
        user_id
      }).$promise.then(function (children) {
        $scope.children = children;
        return $scope.children;
      });
    };

    /**
     * Update the number of places reserved by the current user
     */
    const hasBookedUser = function (userKey) {
      for (const key of Object.keys($scope.reserve.bookingUsers)) {
        for (const user of $scope.reserve.bookingUsers[key]) {
          if (user.booked && user.booked.key === userKey) {
            return true;
          }
        }
      }
      const reservations = $scope.reservations.filter((reservation) => {
        return !reservation.slots_reservations_attributes[0].canceled_at;
      });
      for (const r of reservations) {
        for (const user of r.booking_users_attributes) {
          const key = user.booked_type === 'User' ? `user_${user.booked_id}` : `child_${user.booked_id}`;
          if (key === userKey) {
            return true;
          }
        }
      }
      return false;
    };

    /**
     * Build the list of options for the select box of the booked users
     * @param booked {object} the booked user
     */
    const buildBookedUsersOptions = function (booked) {
      const options = [];
      const userKey = `user_${$scope.ctrl.member.id}`;
      if ((booked && booked.key === userKey) || !hasBookedUser(userKey)) {
        options.push({ key: userKey, name: $scope.ctrl.member.name, type: 'User', id: $scope.ctrl.member.id });
      }
      for (const child of $scope.children) {
        const key = `child_${child.id}`;
        if ((booked && booked.key === key) || !hasBookedUser(key)) {
          options.push({
            key,
            name: child.first_name + ' ' + child.last_name,
            id: child.id,
            type: 'Child',
            validated_at: child.validated_at,
            birthday: child.birthday
          });
        }
      }
      return options;
    };

    /**
     * update number of places available for each price category for the family event
     */
    const updateNbReservePlaces = function () {
      if ($scope.event.event_type === 'family' && $scope.ctrl.member.id) {
        const reservations = $scope.reservations.filter((reservation) => {
          return !reservation.slots_reservations_attributes[0].canceled_at;
        });
        const reservedPlaces = reservations.reduce((sum, reservation) => {
          return sum + reservation.booking_users_attributes.length;
        }, 0);
        const maxPlaces = $scope.children.length + 1 - reservedPlaces;
        if ($scope.event.nb_free_places > maxPlaces) {
          $scope.reserve.nbPlaces.normal = __range__(0, maxPlaces, true);
          for (let evt_px_cat of Array.from($scope.event.event_price_categories_attributes)) {
            $scope.reserve.nbPlaces[evt_px_cat.id] = __range__(0, maxPlaces, true);
          }
        }
      }
    };

    /**
     * Create a hash map implementing the Reservation specs
     * @param reserve {Object} Reservation parameters (places...)
     * @param event {Object} Current event
     * @return {{reservation: Reservation}}
     */
    const mkReservation = function (reserve, event) {
      const reservation = {
        reservable_id: event.id,
        reservable_type: 'Event',
        slots_reservations_attributes: [],
        nb_reserve_places: reserve.nbReservePlaces,
        tickets_attributes: [],
        booking_users_attributes: []
      };

      reservation.slots_reservations_attributes.push({
        offered: event.offered || false,
        slot_id: event.availability.slot_id
      });

      for (let evt_px_cat of Array.from(event.event_price_categories_attributes)) {
        const booked = reserve.tickets[evt_px_cat.id];
        if (booked > 0) {
          reservation.tickets_attributes.push({
            event_price_category_id: evt_px_cat.id,
            booked
          });
        }
      }

      if (event.event_type === 'nominative' || event.event_type === 'family') {
        for (const key of Object.keys($scope.reserve.bookingUsers)) {
          for (const user of $scope.reserve.bookingUsers[key]) {
            reservation.booking_users_attributes.push({
              event_price_category_id: user.event_price_category_id,
              name: user.booked ? user.booked.name : _.trim(user.name),
              booked_id: user.booked ? user.booked.id : undefined,
              booked_type: user.booked ? user.booked.type : undefined,
            });
          }
        }
      }

      return { reservation };
    };

    /**
     * Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
     * @param reservation {Object} as returned by mkReservation()
     * @param coupon {Object} Coupon as returned from the API
     * @param paymentMethod {string} 'card' | ''
     * @return {ShoppingCart}
     */
    const mkCartItems = function (reservation, coupon, paymentMethod = '') {
      return {
        customer_id: $scope.ctrl.member.id,
        items: [reservation],
        coupon_code: ((coupon ? coupon.code : undefined)),
        payment_method: paymentMethod,
      };
    };

    /**
     * Set the current reservation to the default values. This implies the reservation form to be hidden.
     */
    const resetEventReserve = function () {
      if ($scope.event) {
        $scope.reserve = {
          nbPlaces: {
            normal: __range__(0, $scope.event.nb_free_places, true)
          },
          nbReservePlaces: 0,
          tickets: {},
          toReserve: false,
          amountTotal: 0,
          totalSeats: 0,
          bookingUsers: {
            normal: [],
          },
        };

        for (let evt_px_cat of Array.from($scope.event.event_price_categories_attributes)) {
          $scope.reserve.nbPlaces[evt_px_cat.id] = __range__(0, $scope.event.nb_free_places, true);
          $scope.reserve.bookingUsers[evt_px_cat.id] = [];
          $scope.reserve.tickets[evt_px_cat.id] = 0;
        }

        return $scope.event.offered = false;
      }
    };

    /**
     * Open a modal window which trigger the stripe payment process
     * @param reservation {Object} to book
     */
    const payOnline = function (reservation) {
      // check that the online payment is enabled
      if (settingsPromise.online_payment_module !== 'true') {
        growl.error(_t('app.shared.cart.online_payment_disabled'));
      } else {
        $scope.toggleOnlinePaymentModal(() => {
          $scope.onlinePayment.cartItems = mkCartItems(reservation, $scope.coupon.applied, 'card');
        });
      }
    };

    /**
     * Open a modal window which trigger the local payment process
     * @param reservation {Object} to book
     */
    const payOnSite = function (reservation) {
      $uibModal.open({
        templateUrl: '/shared/valid_reservation_modal.html',
        size: 'sm',
        resolve: {
          reservation () {
            return reservation;
          },
          price () {
            return Price.compute(mkCartItems(reservation, $scope.coupon.applied)).$promise;
          },
          wallet () {
            return Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }).$promise;
          },
          coupon () {
            return $scope.coupon.applied;
          },
          cartItems () {
            return mkCartItems(reservation, $scope.coupon.applied);
          },
          event () {
            return $scope.event;
          }
        },
        controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'LocalPayment', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems', 'event',
          function ($scope, $uibModalInstance, $state, reservation, price, Auth, LocalPayment, wallet, helpers, $filter, coupon, cartItems, event) {
            // User's wallet amount
            $scope.wallet = wallet;

            // Price
            $scope.price = price.price;

            // Cart items
            $scope.cartItems = cartItems;

            // price to pay
            $scope.amount = helpers.getAmountToPay(price.price, wallet.amount);

            // Reservation
            $scope.reservation = reservation;

            // the event
            $scope.bookedEvent = event;

            // Used in wallet info template to interpolate some translations
            $scope.numberFilter = $filter('number');

            // Button label
            if ($scope.amount > 0) {
              $scope.validButtonName = _t('app.public.events_show.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
            } else {
              if ((price.price > 0) && ($scope.walletAmount === 0)) {
                $scope.validButtonName = _t('app.public.events_show.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) });
              } else {
                $scope.validButtonName = _t('app.shared.buttons.confirm');
              }
            }

            // Callback to validate the payment
            $scope.ok = function () {
              $scope.attempting = true;
              return LocalPayment.confirm(cartItems, function (reservation) {
                $uibModalInstance.close(reservation);
                return $scope.attempting = true;
              }
              , function (response) {
                $scope.alerts = [];
                angular.forEach(response, function (v, k) {
                  angular.forEach(v, function (err) {
                    $scope.alerts.push({
                      msg: k + ': ' + err,
                      type: 'danger'
                    });
                  });
                });
                return $scope.attempting = false;
              });
            };

            // Callback to cancel the payment
            return $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
          }
        ] })
        .result['finally'](null).then(function (reservation) { afterPayment(reservation); });
    };

    /**
     * What to do after the payment was successful
     * @param invoice {Object} the invoice for the booked reservation
     */
    const afterPayment = function (invoice) {
      Event.get({ id: $scope.event.id }).$promise.then(function (event) {
        $scope.event = event;
        getReservations($scope.event.id, 'Event', $scope.ctrl.member.id).then(function () {
          updateNbReservePlaces();
          $scope.reserveSuccess = true;
          $scope.coupon.applied = null;
        });
        resetEventReserve();
      });
      if ($scope.currentUser.role === 'admin') {
        return $scope.ctrl.member = null;
      }
    };

    /**
     * Find user's reservations, the same date at the same time, with event
     */
    const findReservationsAtSameTime = function () {
      let sameTimeReservations = [
        'training_reservations',
        'machine_reservations',
        'space_reservations',
        'events_reservations'
      ].map(function(k) {
        return _.filter($scope.ctrl.member[k], function(r) {
          if (r.reservable_type === 'Event' && r.reservable.id === $scope.event.id) {
            return false;
          }
          return moment($scope.event.start_time).isSame(r.start_at) ||
            (moment($scope.event.end_time).isAfter(r.start_at) && moment($scope.event.end_time).isBefore(r.end_at)) ||
            (moment($scope.event.start_time).isAfter(r.start_at) && moment($scope.event.start_time).isBefore(r.end_at)) ||
            (moment($scope.event.start_time).isBefore(r.start_at) && moment($scope.event.end_time).isAfter(r.end_at));
        });
      });
      return _.union.apply(null, sameTimeReservations);
    };

    /**
     * A modal for show reservations the same date at the same time
     *
     * @param sameTimeReservations {Array} reservations the same date at the same time
     * @param callback {function} callback will invoke when user confirm
     */
    const showReserveSlotSameTimeModal = function(sameTimeReservations, callback) {
      const modalInstance = $uibModal.open({
        animation: true,
        templateUrl: '/shared/_reserve_slot_same_time.html',
        size: 'md',
        controller: 'ReserveSlotSameTimeController',
        resolve: {
          sameTimeReservations: function() { return sameTimeReservations; },
          bookOverlappingSlotsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'book_overlapping_slots' }).$promise; }]
        }
      });
      modalInstance.result.then(callback);
    };

    // !!! MUST BE CALLED AT THE END of the controller
    return initialize();
  }
]);

function __range__ (left, right, inclusive) {
  let range = [];
  let ascending = left < right;
  let end = !inclusive ? right : ascending ? right + 1 : right - 1;
  for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
    range.push(i);
  }
  return range;
}


/**
 * Controller used in the event deletion modal window
 */
Application.Controllers.controller('DeleteRecurrentEventController', ['$scope', '$uibModalInstance', 'Event', 'eventPromise', 'growl', '_t',
  function ($scope, $uibModalInstance, Event, eventPromise, growl, _t) {

    // is the current event (to be deleted) recurrent?
    $scope.isRecurrent = eventPromise.recurrence_events.length > 0;

    // with recurrent slots: how many slots should we delete?
    $scope.deleteMode = 'single';

    /**
     * Confirmation callback
     */
    $scope.ok = function () {
      const { id, start_at, end_at } = eventPromise;
      // the admin has confirmed, delete the slot
      Event.delete(
        { id, mode: $scope.deleteMode },
        function (res) {
          // delete success
          if (res.deleted > 1) {
            growl.success(_t(
              'app.public.events_show.events_deleted',
              {COUNT: res.deleted - 1}
            ));
          } else {
            growl.success(_t(
              'app.public.events_show.event_successfully_deleted'
            ));
          }
          $uibModalInstance.close({
            status: 'success',
            events: res.details.map(function (d) { return d.event.id })
          });
        },
        function (res) {
          // not everything was deleted
          const { data } = res;
          if (data.total > 1) {
            growl.warning(_t(
              'app.public.events_show.events_not_deleted',
              {TOTAL: data.total, COUNT: data.total - data.deleted}
            ));
          } else {
            growl.error(_t(
              'app.public.events_show.unable_to_delete_the_event'
            ));
          }
          $uibModalInstance.close({
            status: 'failed',
            availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.event.id })
          });
        });
    }

    /**
     * Cancellation callback
     */
    $scope.cancel = function () {
      $uibModalInstance.dismiss('cancel');
    }
  }
]);
