/* 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',
  function ($scope, $state, Event, categoriesPromise, themesPromise, ageRangesPromise) {
  /* PUBLIC SCOPE */

    // 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 = [<%= 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.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);
        return $scope.monthOrder = Object.keys($scope.eventsGroupByMonth);
      }
    };

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

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

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

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

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

    // 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;

    // 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);

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

    /**
     * Callback to delete the provided event (admins only)
     * @param event {$resource} angular's Event $resource
     */
    $scope.deleteEvent = function (event) {
      dialogs.confirm({
        resolve: {
          object () {
            return {
              title: _t('confirmation_required'),
              msg: _t('do_you_really_want_to_delete_this_event')
            };
          }
        }
      }, function () {
        // the admin has confirmed, delete
        event.$delete(function () {
          $state.go('app.public.events_list');
          return growl.info(_t('event_successfully_deleted'));
        }, function (error) {
          console.error(error);
          growl.error(_t('unable_to_delete_the_event_because_some_users_alredy_booked_it'));
        });
      }
      );
    };

    /**
     * Callback to call when the number of tickets to book changes in the current booking
     */
    $scope.changeNbPlaces = function () {
      // compute the total remaning places
      let remain = $scope.event.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);
        }
      }

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

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

    /**
     * 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()) {
          return $scope.login(null, function (user) {
            $scope.reserve.toReserve = !$scope.reserve.toReserve;
            if (user.role !== 'admin') {
              return $scope.ctrl.member = user;
            }
          });
        } else {
          return $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) {
        return Member.get({ id: $scope.ctrl.member.id }, function (member) {
          $scope.ctrl.member = member;
          return getReservations($scope.event.id, 'Event', $scope.ctrl.member.id);
        });
      }
    };

    /**
     * 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.ctrl.member, $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 (($scope.currentUser.role !== 'admin') && (amountToPay > 0)) {
            return payByStripe(reservation);
          } else {
            if (($scope.currentUser.role === 'admin') || (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('please_select_a_member_first'));
      }
    };

    /**
     * Callback to validate the booking of a free event
     */
    $scope.validReserveEvent = function () {
      const reservation = {
        user_id: $scope.ctrl.member.id,
        reservable_id: $scope.event.id,
        reservable_type: 'Event',
        slots_attributes: [],
        nb_reserve_places: $scope.reserve.nbReservePlaces,
        tickets_attributes: []
      };
      // a single slot is used for events
      reservation.slots_attributes.push({
        start_at: $scope.event.start_date,
        end_at: $scope.event.end_date,
        availability_id: $scope.event.availability.id
      });
      // iterate over reservations per prices
      for (let price_id in $scope.reserve.tickets) {
        const seats = $scope.reserve.tickets[price_id];
        reservation.tickets_attributes.push({
          event_price_category_id: price_id,
          booked: seats
        });
      }
      // set the attempting marker
      $scope.attempting = true;
      // save the reservation to the API
      return Reservation.save({ reservation }, function (reservation) {
      // reservation successfull
        afterPayment(reservation);
        return $scope.attempting = false;
      }
      , function (response) {
      // reservation failed
        $scope.alerts = [];
        $scope.alerts.push({
          msg: response.data.card[0],
          type: 'danger'
        });
        // unset the attempting marker
        return $scope.attempting = false;
      });
    };

    /**
     * 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 {{id:number, reservable_id:number, nb_reserve_places:number}}
     * @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
     */
    $scope.modifyReservation = function (reservation, e) {
      e.preventDefault();
      e.stopPropagation();

      const index = $scope.reservations.indexOf(reservation);
      return $uibModal.open({
        templateUrl: '<%= asset_path "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 (e of Array.from(event.recurrence_events)) {
            if (e.nb_free_places > reservation.total_booked_seats) {
              $scope.reservation.reservable_id = e.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[0].start_at = eventToPlace.start_date;
            $scope.reservation.slots[0].end_at = eventToPlace.end_date;
            $scope.reservation.slots[0].availability_id = eventToPlace.availability_id;
            $scope.reservation.slots_attributes = $scope.reservation.slots;
            $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 {{total_booked_seats:number}}
     */
    $scope.reservationCanModify = function (reservation) {
      const slotStart = moment(reservation.slots[0].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));
    };

    /**
     * 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.ctrl.member, $scope.reserve, $scope.event);
        return Price.compute(mkRequestParams(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;
        }
      }
    };

    /* 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();

      // if non-admin, get the current user's reservations into $scope.reservations
      if ($scope.currentUser) {
        getReservations($scope.event.id, 'Event', $scope.currentUser.id);
      }

      // watch when a coupon is applied to re-compute the total price
      return $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)
     */
    var getReservations = function (reservable_id, reservable_type, user_id) {
      Reservation.query({
        reservable_id,
        reservable_type,
        user_id
      }).$promise.then(function (reservations) { $scope.reservations = reservations; });
    };

    /**
     * Create an hash map implementing the Reservation specs
     * @param member {Object} User as retreived from the API: current user / selected user if current is admin
     * @param reserve {Object} Reservation parameters (places...)
     * @param event {Object} Current event
     * @return {{user_id:number, reservable_id:number, reservable_type:string, slots_attributes:Array<Object>, nb_reserve_places:number}}
     */
    var mkReservation = function (member, reserve, event) {
      const reservation = {
        user_id: member.id,
        reservable_id: event.id,
        reservable_type: 'Event',
        slots_attributes: [],
        nb_reserve_places: reserve.nbReservePlaces,
        tickets_attributes: []
      };

      reservation.slots_attributes.push({
        start_at: event.start_date,
        end_at: event.end_date,
        availability_id: event.availability.id,
        offered: event.offered || false
      });

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

      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
     * @return {{reservation:Object, coupon_code:string}}
     */
    var mkRequestParams = function (reservation, coupon) {
      const params = {
        reservation,
        coupon_code: ((coupon ? coupon.code : undefined))
      };

      return params;
    };

    /**
     * Set the current reservation to the default values. This implies to reservation form to be hidden.
     */
    var 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
        };

        for (let evt_px_cat of Array.from($scope.event.prices)) {
          $scope.reserve.nbPlaces[evt_px_cat.id] = __range__(0, $scope.event.nb_free_places, true);
          $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
     */
    var payByStripe = function (reservation) {
      $uibModal.open({
        templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
        size: 'md',
        resolve: {
          reservation () {
            return reservation;
          },
          price () {
            return Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise;
          },
          wallet () {
            return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
          },
          cgv () {
            return CustomAsset.get({ name: 'cgv-file' }).$promise;
          },
          objectToPay () {
            return {
              eventToReserve: $scope.event,
              reserve: $scope.reserve,
              member: $scope.ctrl.member
            };
          },
          coupon () {
            return $scope.coupon.applied;
          }
        },
        controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$filter', 'coupon',
          function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $filter, coupon) {
            // User's wallet amount
            $scope.walletAmount = wallet.amount;

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

            // CGV
            $scope.cgv = cgv.custom_asset;

            // Reservation
            $scope.reservation = reservation;

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

            // Callback for the stripe payment authorization
            return $scope.payment = function (status, response) {
              if (response.error) {
                return growl.error(response.error.message);
              } else {
                $scope.attempting = true;
                $scope.reservation.card_token = response.id;
                Reservation.save(mkRequestParams($scope.reservation, coupon), function (reservation) { $uibModalInstance.close(reservation); }
                  , function (response) {
                    $scope.alerts = [];
                    $scope.alerts.push({
                      msg: response.data.card[0],
                      type: 'danger'
                    });
                    return $scope.attempting = false;
                  });
              }
            };
          }
        ]
      }).result['finally'](null).then(function (reservation) { afterPayment(reservation); });
    };

    /**
     * Open a modal window which trigger the local payment process
     * @param reservation {Object} to book
     */
    var payOnSite = function (reservation) {
      $uibModal.open({
        templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>',
        size: 'sm',
        resolve: {
          reservation () {
            return reservation;
          },
          price () {
            return Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise;
          },
          wallet () {
            return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
          },
          coupon () {
            return $scope.coupon.applied;
          }
        },
        controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
          function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) {
            // User's wallet amount
            $scope.walletAmount = wallet.amount;

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

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

            // Reservation
            $scope.reservation = reservation;

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

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

            // Callback to validate the payment
            $scope.ok = function () {
              $scope.attempting = true;
              return Reservation.save(mkRequestParams($scope.reservation, coupon), 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 resveration {Object} booked reservation
     */
    var afterPayment = function (reservation) {
      $scope.event.nb_free_places = $scope.event.nb_free_places - reservation.total_booked_seats;
      resetEventReserve();
      $scope.reserveSuccess = true;
      $scope.coupon.applied = null;
      $scope.reservations.push(reservation);
      if ($scope.currentUser.role === 'admin') {
        return $scope.ctrl.member = null;
      }
    };

    // !!! 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;
}