text: define(['angular', 'services/request-edit'], function(angular, loadRequestEdit) {
  
      'use strict';
  
  
  
  
  
  
  
  
  
  
  
  
      return function(gettextCatalog) {
  
  
          var RequestEdit = loadRequestEdit(gettextCatalog);
  
  
  
          /**
           * store quantity_unit for each loaded right ID
           */
          var quantity_unit = {};
  
  
          /**
           * available quantity per renewal ID
           * @var {object}
           */
          var available = {};
  
  
  
  
  
  
  
          /**
           * Callback used to watch the distribution object in scope
           * @param {object} distribution
           */
          function distributionWatch(distribution, $scope) {
  
  
              /**
               * Browse the rights appliquable for distribution
               * @param {function} action     function to call on each rights
               *
               */
              function browseInputValue(action) {
  
                  for(var renewalId in distribution.renewal) {
                      if (distribution.renewal.hasOwnProperty(renewalId)) {
                          action(renewalId, distribution.renewal[renewalId].right);
                      }
                  }
              }
  
  
              /**
               * Test if distribution is completed
               * @param {Number} days     Sum of days distributed on rights
               * @param {Number} hours    Sum of hours distributed on rights
               *
               * @return {Boolean}
               */
              function isCompleted(days, hours) {
                  var daysCompleted = ($scope.selection.businessDays === days) && !hours;
                  var hoursCompleted = ($scope.selection.duration === (hours*360000)) && !days;
                  return (daysCompleted || hoursCompleted);
              }
  
  
  
              /**
               * Get a classname for the input field
               * @param {Number} value            Consumed quantity
               * @param {Number} availableQte
               * @param {Boolean} completed
               * @return string
               */
              function getValueClass(value, availableQte, completed) {
  
                  if (undefined === value || null === value || null === availableQte) {
                      return '';
                  }
  
                  if (0 === value) {
                      return 'has-warning';
                  }
  
                  if (value < 0) {
                      return 'has-error';
                  }
  
                  if (availableQte < value) {
                      return 'has-error';
                  }
  
                  if (!completed) {
                      return 'has-warning';
                  }
  
                  return 'has-success';
              }
  
  
  
              if (distribution === undefined) {
                  $scope.distribution = {
                      class: {},
                      renewal: {},
                      total: 0,
                      completed: false
                  };
              } else {
  
  
                  var inputQuantity, days = 0, hours = 0, daysConsumed = 0, hoursConsumed = 0;
  
                  // first pass, compute total
                  browseInputValue(function(renewalId, rightId) {
                      var inputValue = distribution.renewal[renewalId].quantity;
                      if (!inputValue) {
                          return;
                      }
  
                      inputQuantity = parseFloat(inputValue);
                      var inputConsumption = distribution.renewal[renewalId].consumedQuantity;
  
                      switch(quantity_unit[rightId]) {
                          case 'D':
                              days  += inputQuantity;
                              daysConsumed += inputConsumption;
                              break;
  
                          case 'H':
                              hours += inputQuantity;
                              hoursConsumed += inputConsumption;
                              break;
                      }
                  });
  
  
                  var completed = isCompleted(days, hours);
                  $scope.distribution.completed = completed;
  
                  // second pass, apply styles on cells
                  browseInputValue(function(renewalId) {
                      var consumedQuantity = distribution.renewal[renewalId].consumedQuantity;
                      var avail = available[renewalId];
                      if (null !== avail && undefined !== $scope.initialQuantity && undefined !== $scope.initialQuantity[renewalId]) {
                          avail = avail + $scope.initialQuantity[renewalId];
                      }
                      $scope.distribution.class[renewalId] = getValueClass(consumedQuantity, avail, completed);
                  });
  
                  // Assigned duration to display to the user
                  distribution.total = RequestEdit.getDuration(days, hours);
                  distribution.totalConsuption = RequestEdit.getDuration(daysConsumed, hoursConsumed);
              }
          }
  
  
  
          /**
           * Create distribution to post for a absence request
           * @param {object} renewals   Rights renenwals distribution with renewal id as property, right id and quantity (the form input) in the value
           * @param {object} periods    The list of selected periods, provided by the period picker widget
           * @param {Boolean} matchQuantity   If true, throw Error if events duration does not match input quantity
           * @return {Array}
           */
          function createDistribution(renewals, periods, accountRights, matchQuantity) {
  
              var totalSeconds = 0;
              var totalDays = 0;
  
              for(var j=0; j<periods.length; j++) {
                  if (undefined === periods[j].businessDays) {
  
                      //throw new Error('Missing businessDays on period from '+periods[j].dtstart+' to '+periods[j].dtend);
                      // This is a partial half day, this is not ideal but here we consider the half day is used
  
                      periods[j].businessDays = 0.5;
                  }
  
                  totalDays += periods[j].businessDays;
                  totalSeconds += (periods[j].dtend.getTime() - periods[j].dtstart.getTime()) / 1000;
              }
  
  
  
              /**
               *
               *
               * @param {object} selectePeriod    selected working period
               * @param {Date} startDate          Start date for the element
               * @param {Number} secQuantity      Duration for the element
               *
               *
               * @return {object}
               */
              function extractEvent(selectedPeriod, startDate, secQuantity)
              {
                  if (selectedPeriod.dtstart > startDate) {
                      // this ignore gaps between selected periods
                      startDate = selectedPeriod.dtstart;
                  }
  
                  var validInterval = true;
  
                  if (selectedPeriod.dtend <= startDate) {
                      validInterval = false;
                  }
  
                  var endDate = new Date(startDate);
                  endDate.setTime(endDate.getTime() + (1000* secQuantity));
  
                  if (selectedPeriod.dtstart >= endDate) {
                      validInterval = false;
                  }
  
                  /*
                  console.log({
                      selectedPeriod: selectedPeriod,
                      startDate: startDate,
                      endDate: endDate,
                      secQuantity: secQuantity
                  });
                  */
  
                  var dtstart = selectedPeriod.dtstart > startDate ? selectedPeriod.dtstart : startDate;
                  var dtend = selectedPeriod.dtend < endDate ? selectedPeriod.dtend : endDate;
  
                  return {
                      validInterval: validInterval,
                      dtstart: dtstart,
                      dtend: dtend
                  };
              }
  
  
              /**
               * create events to embed in distribution
               * One event per working period
               *
               * @param {Number} secQuantity              Duration for the element
               * @param {Date} startDate                  Start date for the element
               * @param {Boolean} testIntervalValidity    Verify duration match periods
               *
               * @return {array}
               */
              function createEvents(secQuantity, startDate, testIntervalValidity)
              {
  
                  var event, events = [], consumed;
  
                  periods.forEach(function(selectedPeriod) {
                      event = extractEvent(selectedPeriod, startDate, secQuantity);
                      if (event.validInterval || !testIntervalValidity) {
                          events.push(event);
                          consumed = Math.round((event.dtend.getTime() - event.dtstart.getTime())/1000);
                          //console.log('consumed='+consumed+' secQuantity='+secQuantity);
                          startDate = event.dtend;
                          secQuantity = secQuantity - consumed;
  
                      }
                  });
  
                  if (0 === events.length) {
                      throw new Error(gettextCatalog.getString('Wrong events count'));
                  }
  
                  if (matchQuantity && (0 !== secQuantity)) {
                      throw new Error(
                          gettextCatalog.getString('Duration mismatch: {duration} hours remain unconsumed after the creation of {nbEvents} events, please check your absence period, it must not overlap another leave')
                          .replace(/\{duration\}/, secQuantity /3600)
                          .replace(/\{nbEvents\}/,events.length)
                      );
                  }
  
                  return events;
              }
  
              /**
               * Get quantity unit from right ID
               * @return {String} D|H
               */
              function getQuantityUnit(rightId)
              {
                  for(var i=0; i<accountRights.length; i++) {
                      if (accountRights[i]._id === rightId) {
                          return accountRights[i].quantity_unit;
                      }
                  }
  
                  if (0 === accountRights.length) {
                      throw new Error('No accountRights');
                  }
  
                  //console.log(accountRights);
                  throw new Error('Right '+rightId+' not found in available accountRights');
              }
  
  
              /**
               * Get quantity in seconds
               * @param {String} rightId
               * @param {Number} inputQuantity
               * @return {Int}
               */
              function getSecQuantity(rightId, inputQuantity)
              {
                  if (null === inputQuantity) {
                      throw new Error(gettextCatalog.getString('Input quantity must be set'));
                  }
  
                  var u = getQuantityUnit(rightId);
  
                  if ('H' === u) {
                      return Math.round(inputQuantity*3600);
                  }
  
                  if ('D' === u) {
  
                      if (0 === totalDays || isNaN(totalDays)) {
                          throw new Error('Conversion not appliquable, totalDays must be greater than 0');
                      }
  
                      return (totalSeconds * inputQuantity / totalDays);
                  }
  
                  throw new Error('Invalid quantity unit');
              }
  
  
              var distribution = [];
              var startDate = periods[0].dtstart;
              var rightId, quantity, elem;
  
  
  
              for(var renewalId in renewals) {
                  if (renewals.hasOwnProperty(renewalId)) {
  
                      quantity = renewals[renewalId].quantity;
  
                      if (undefined === quantity || null === quantity || 0 === quantity) {
                          continue;
                      }
  
                      rightId = renewals[renewalId].right;
                      if (undefined !== rightId._id) {
                          rightId = rightId._id;
                      }
  
                      elem = {
                          right: {
                              id: rightId,
                              renewal:renewalId
                          },
                          quantity: quantity,
                          events: createEvents(getSecQuantity(rightId, quantity), startDate, matchQuantity)
                      };
  
                      distribution.push(elem);
  
                      // set startDate for the next element
                      startDate = elem.events[elem.events.length-1].dtend;
                  }
              }
  
              if (0 === distribution.length) {
                  throw new Error(gettextCatalog.getString('Nothing to save'));
              }
  
              return distribution;
          }
  
          /**
           * Initialize distribution using data from the request
           */
          function setDistributionRequest($scope) {
              var ai, aj;
              var accessiblesRenewals = [];
              for (ai=0; ai<$scope.accountRights.length; ai++) {
                  for (aj=0; aj<$scope.accountRights[ai].renewals.length; aj++) {
                      accessiblesRenewals.push($scope.accountRights[ai].renewals[aj]._id);
                  }
              }
  
              $scope.initialQuantity = {};
  
              $scope.request.absence.distribution.forEach(function(element) {
  
                  // add renewal only if exists in accountRights
  
                  if (-1 !== accessiblesRenewals.indexOf(element.right.renewal.id)) {
  
                      $scope.distribution.renewal[element.right.renewal.id] = {
                          quantity: element.quantity,
                          right: element.right.id
                      };
  
                      $scope.initialQuantity[element.right.renewal.id] = element.quantity;
                  }
              });
          }
  
  
  
          /**
           * Initialize distribution for rights where it has been allowed
           */
          function initializeDistribution($scope) {
  
              //days
              var dispatch = $scope.selection.businessDays;
  
              var ai, aj;
              for (ai=0; ai<$scope.accountRights.length; ai++) {
                  for (aj=0; aj<$scope.accountRights[ai].renewals.length; aj++) {
                      var renewalId = $scope.accountRights[ai].renewals[aj]._id;
                      var right = $scope.accountRights[ai].renewals[aj].right;
  
                      if (!right.autoDistribution) {
                          continue;
                      }
  
                      if ('D' !== $scope.accountRights[ai].quantity_unit) {
                          continue;
                      }
  
                      var quantity = Math.min(dispatch, $scope.accountRights[ai].available_quantity);
  
                      dispatch -= quantity;
  
                      $scope.distribution.renewal[renewalId] = {
                          quantity: quantity,
                          right: right._id
                      };
  
                  }
              }
          }
  
          function cleanDocument(request) {
  
              if (!request) {
                  throw new Error('The request does not exists');
              }
  
              delete request.requestLog;
              delete request.approvalSteps;
  
              if (undefined !== request.time_saving_deposit) {
                  delete request.time_saving_deposit;
              }
  
              if (undefined !== request.workperiod_recover) {
                  delete request.workperiod_recover;
              }
  
          }
  
  
          /**
           * remove consuption on all rows
           */
          function removeAllConsumptions($scope) {
              var row;
  
              for (var id in $scope.distribution.renewal) {
                  if (!$scope.distribution.renewal.hasOwnProperty(id)) {
                      continue;
                  }
  
                  row = $scope.distribution.renewal[id];
  
                  row.consumedQuantity = undefined;
                  //row.isLoading = true;
              }
          }
  
  
          /**
           * @param {Object} $scope
           * @param {Resource} consumption
           * @param {String} user             User ID when admin create/modify absence for another user
           */
          function setConsumedQuantities($scope, consumption, user) {
  
              var renewals = $scope.distribution.renewal;
              var periods = $scope.selection.periods;
              var distribution;
  
              if (0 === periods.length) {
                  throw new Error('No periods in selection');
              }
  
              try {
                  distribution = createDistribution(renewals, periods, $scope.accountRights, false);
              } catch(e) {
                  removeAllConsumptions($scope);
                  return;
              }
  
              var params = {
                  selection: {
                      begin: $scope.selection.begin,
                      end: $scope.selection.end
                  },
                  distribution: distribution,
                  collection: $scope.collection._id
              };
  
  
              if (user) {
                  params.user = user;
              }
  
              var row;
  
              // this is a GET in POST:
              consumption.create(params).$promise
              .then(function(renewalCons) {
                  removeAllConsumptions($scope);
                  for (var id in renewalCons) {
                      if (!renewalCons.hasOwnProperty(id)) {
                          continue;
                      }
  
                      row = $scope.distribution.renewal[id];
                      if (undefined === row) {
                          continue;
                      }
                      row.consumedQuantity = renewalCons[id];
                      //row.isLoading = false;
                  }
  
              });
  
          }
  
  
  
  
          /**
           * The next button, from the period selection to the right distribution interface
           *
           * @param {object} $scope
           * @param {object} user
           * @param {Resource} accountRights
           * @param {Resource} consumption
           */
          function getNextButtonJob($scope, user, accountRights) {
  
              return function() {
  
                  // hide the period selection
                  $scope.periodSelection = false;
  
                  // show the right assignement
                  $scope.assignments = true;
  
                  // init distribution if this is a request modification after accountRights.$promise
                  $scope.distribution = {
                      class: {},
                      renewal: {},
                      total: 0,
                      completed: false
                  };
  
  
  
  
                  /**
                   * Load accountRights
                   * the list of rights accessible on the selected period
                   */
  
                  $scope.accountRights = accountRights.query({
                      user: user._id, // need that if the request is created by the admin, ignored if created by the user
                      dtstart: $scope.selection.begin,
                      dtend: $scope.selection.end
                  });
  
  
  
  
  
  
                  function createAccountRenewal(item, renewalIndex)
                  {
                      var accountRightRenewal = Object.create(item);
                      accountRightRenewal.renewal = item.renewals[renewalIndex];
  
                      $scope.distribution.renewal[accountRightRenewal.renewal._id] = {
                          quantity: null,
                          right: item._id
                      };
  
                      return accountRightRenewal;
                  }
  
  
  
  
                  $scope.getIcon = function(listItem) {
                      if (listItem.fold) {
                          return 'fa-minus-square';
                      } else {
                          return 'fa-plus-square';
                      }
                  };
  
  
  
  
                  $scope.accountRights.$promise.then(function(ar) {
                      // loaded
  
                      var days=0, hours=0;
  
  
                      function updateTotal(right, accountRightRenewal, groupAvailable) {
                          available[accountRightRenewal.renewal._id] = ar[i].available_quantity;
                          quantity_unit[accountRightRenewal._id] = ar[i].quantity_unit;
  
                          switch (accountRightRenewal.quantity_unit) {
                              case 'D':
                                  groupAvailable.days  += accountRightRenewal.renewal.available_quantity;
                                  days                 += accountRightRenewal.renewal.available_quantity;
                                  break;
  
                              case 'H':
                                  groupAvailable.hours += accountRightRenewal.renewal.available_quantity;
                                  hours                += accountRightRenewal.renewal.available_quantity;
                                  break;
                          }
                      }
  
  
                      // Group by types
  
                      var typesIndex = {};
                      $scope.types = [];
                      var accountRightRenewal;
  
                      for (var i=0; i<ar.length; i++) {
  
                          var type = ar[i].type;
  
                          if (ar[i].renewals.length > 0) {
  
                              if (undefined === typesIndex[type._id]) {
                                  $scope.types.push({
                                      fold: !type.group,
                                      type: type,
                                      accountRightsRenewals: [],
                                      available: {
                                          days: 0,
                                          hours: 0
                                      }
                                  });
                                  typesIndex[type._id] = $scope.types.length-1;
                              }
  
                              for(var j=0; j<ar[i].renewals.length; j++) {
                                  accountRightRenewal = createAccountRenewal(ar[i], j);
                                  $scope.types[typesIndex[type._id]].accountRightsRenewals.push(accountRightRenewal);
                                  updateTotal(ar[i], accountRightRenewal, $scope.types[typesIndex[type._id]].available);
                              }
                          }
                      }
  
                      // total on each type group
  
                      $scope.types.forEach(function(item) {
                          item.available.display = RequestEdit.getDuration(item.available.days, item.available.hours);
                      });
  
  
                      // grand total
  
                      $scope.available = {
                          total: RequestEdit.getDuration(days, hours)
                      };
  
  
  
                      // load distribution if this is a request modification
                      if (undefined !== $scope.request.absence && $scope.request.absence.distribution.length > 0) {
  
                          setDistributionRequest($scope);
                          distributionWatch($scope.distribution, $scope);
                      }
                      else {
                          // This is a new request, set input quantity for rights where it is allowed
                          initializeDistribution($scope);
                      }
  
  
                  }, function(err) {
                      err.data.$outcome.alert.forEach(function(e) {
                          $scope.pageAlerts.push({
                              message: e.message,
                              type: 'danger'
                          });
                      });
  
                  });
  
              };
          }
  
  
  
  
  
  
  
          /**
           * Process scope once the user document is available
           * Add the watchs
           *
           * @param   {Object} $scope
           * @param   {Object} user           user document as object
           * @param   {Resource} calendarEvents
           * @param   {Resource} consumption
           */
          function onceUserLoaded($scope, user, calendarEvents, consumption)
          {
              if (!user.roles.account) {
                  throw new Error('the user must have a vacation account');
              }
  
              RequestEdit.onceUserLoaded($scope, user, calendarEvents);
  
              $scope.$watch('distribution', function(distribution) {
                  distributionWatch(distribution, $scope);
              }, true);
  
  
  
              $scope.$watch('distribution.renewal', function(newValue, oldValue) {
  
                  // detect renewal modifications
                  for (var rId in newValue) {
                      if (newValue.hasOwnProperty(rId)) {
                          if (undefined === oldValue[rId] || (newValue[rId].quantity !== oldValue[rId].quantity)) {
                              return setConsumedQuantities($scope, consumption, user._id);
                          }
                      }
                  }
              }, true);
          }
  
  
  
  
  
  
  
          /**
           * Get Period picker callback for working times
           * @param {Resource} calendarEvents
           * @param {Array} personalEventList personal events list, the list of events curently modified
           * @param {Object} user optional parameter used for admin query
           *
           * @return function
           */
          function getLoadWorkingTimes(calendarEvents, personalEventList, user) {
  
  
  
              return function(interval) {
  
  
  
                  var queryParams = {
                      type: 'workschedule',
                      dtstart: interval.from,
                      dtend: interval.to,
                      subtractNonWorkingDays: true,
                      subtractPersonalEvents: true
                  };
  
                  if (user) {
                      queryParams.user = user._id;
                  }
  
                  if (angular.isArray(personalEventList) && personalEventList.length > 0) {
                      queryParams.subtractException = [];
                      personalEventList.forEach(function(personalEvent) {
                          queryParams.subtractException.push(personalEvent._id);
                      });
                  }
  
                  return calendarEvents.query(queryParams).$promise;
              };
          }
  
  
  
  
  
  
          // Service to edit an absence,
          // shared by account/request/absence-edit and admin/request/absence-edit
  
          return {
  
              initScope: RequestEdit.initScope,
  
              setSelectionFromRequest: RequestEdit.setSelectionFromRequest,
  
              getLoadPersonalEvents: RequestEdit.getLoadPersonalEvents,
              getLoadNonWorkingDaysEvents: RequestEdit.getLoadNonWorkingDaysEvents,
              getLoadEvents: RequestEdit.getLoadEvents,
              getLoadScholarHolidays: RequestEdit.getLoadScholarHolidays,
  
              createDistribution: createDistribution,
              cleanDocument: cleanDocument,
              getNextButtonJob: getNextButtonJob,
              onceUserLoaded: onceUserLoaded,
              getLoadWorkingTimes: getLoadWorkingTimes
  
          };
      };
  });
  