text: (function() {
      "use strict";
  
      angular.module('angular-carousel')
  
      .service('DeviceCapabilities', function() {
  
          // TODO: merge in a single function
  
          // detect supported CSS property
          function detectTransformProperty() {
              var transformProperty = 'transform',
                  safariPropertyHack = 'webkitTransform';
              if (typeof document.body.style[transformProperty] !== 'undefined') {
  
                  ['webkit', 'moz', 'o', 'ms'].every(function (prefix) {
                      var e = '-' + prefix + '-transform';
                      if (typeof document.body.style[e] !== 'undefined') {
                          transformProperty = e;
                          return false;
                      }
                      return true;
                  });
              } else if (typeof document.body.style[safariPropertyHack] !== 'undefined') {
                  transformProperty = '-webkit-transform';
              } else {
                  transformProperty = undefined;
              }
              return transformProperty;
          }
  
          //Detect support of translate3d
          function detect3dSupport() {
              var el = document.createElement('p'),
                  has3d,
                  transforms = {
                      'webkitTransform': '-webkit-transform',
                      'msTransform': '-ms-transform',
                      'transform': 'transform'
                  };
              // Add it to the body to get the computed style
              document.body.insertBefore(el, null);
              for (var t in transforms) {
                  if (el.style[t] !== undefined) {
                      el.style[t] = 'translate3d(1px,1px,1px)';
                      has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]);
                  }
              }
              document.body.removeChild(el);
              return (has3d !== undefined && has3d.length > 0 && has3d !== "none");
          }
  
          return {
              has3d: detect3dSupport(),
              transformProperty: detectTransformProperty()
          };
  
      })
  
      .service('computeCarouselSlideStyle', function(DeviceCapabilities) {
          // compute transition transform properties for a given slide and global offset
          return function(slideIndex, offset, transitionType) {
              var style = {
                      display: 'inline-block'
                  },
                  opacity,
                  absoluteLeft = (slideIndex * 100) + offset,
                  slideTransformValue = DeviceCapabilities.has3d ? 'translate3d(' + absoluteLeft + '%, 0, 0)' : 'translate3d(' + absoluteLeft + '%, 0)',
                  distance = ((100 - Math.abs(absoluteLeft)) / 100);
  
              if (!DeviceCapabilities.transformProperty) {
                  // fallback to default slide if transformProperty is not available
                  style['margin-left'] = absoluteLeft + '%';
              } else {
                  if (transitionType == 'fadeAndSlide') {
                      style[DeviceCapabilities.transformProperty] = slideTransformValue;
                      opacity = 0;
                      if (Math.abs(absoluteLeft) < 100) {
                          opacity = 0.3 + distance * 0.7;
                      }
                      style.opacity = opacity;
                  } else if (transitionType == 'hexagon') {
                      var transformFrom = 100,
                          degrees = 0,
                          maxDegrees = 60 * (distance - 1);
  
                      transformFrom = offset < (slideIndex * -100) ? 100 : 0;
                      degrees = offset < (slideIndex * -100) ? maxDegrees : -maxDegrees;
                      style[DeviceCapabilities.transformProperty] = slideTransformValue + ' ' + 'rotateY(' + degrees + 'deg)';
                      style[DeviceCapabilities.transformProperty + '-origin'] = transformFrom + '% 50%';
                  } else if (transitionType == 'zoom') {
                      style[DeviceCapabilities.transformProperty] = slideTransformValue;
                      var scale = 1;
                      if (Math.abs(absoluteLeft) < 100) {
                          scale = 1 + ((1 - distance) * 2);
                      }
                      style[DeviceCapabilities.transformProperty] += ' scale(' + scale + ')';
                      style[DeviceCapabilities.transformProperty + '-origin'] = '50% 50%';
                      opacity = 0;
                      if (Math.abs(absoluteLeft) < 100) {
                          opacity = 0.3 + distance * 0.7;
                      }
                      style.opacity = opacity;
                  } else {
                      style[DeviceCapabilities.transformProperty] = slideTransformValue;
                  }
              }
              return style;
          };
      })
  
      .service('createStyleString', function() {
          return function(object) {
              var styles = [];
              angular.forEach(object, function(value, key) {
                  styles.push(key + ':' + value);
              });
              return styles.join(';');
          };
      })
  
      .directive('rnCarousel', ['$swipe', '$window', '$document', '$parse', '$compile', '$timeout', '$interval', 'computeCarouselSlideStyle', 'createStyleString', 'Tweenable',
          function($swipe, $window, $document, $parse, $compile, $timeout, $interval, computeCarouselSlideStyle, createStyleString, Tweenable) {
              // internal ids to allow multiple instances
              var carouselId = 0,
                  // in absolute pixels, at which distance the slide stick to the edge on release
                  rubberTreshold = 3;
  
              var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame;
  
              function getItemIndex(collection, target, defaultIndex) {
                  var result = defaultIndex;
                  collection.every(function(item, index) {
                      if (angular.equals(item, target)) {
                          result = index;
                          return false;
                      }
                      return true;
                  });
                  return result;
              }
  
              return {
                  restrict: 'A',
                  scope: true,
                  compile: function(tElement, tAttributes) {
                      // use the compile phase to customize the DOM
                      var firstChild = tElement[0].querySelector('li'),
                          firstChildAttributes = (firstChild) ? firstChild.attributes : [],
                          isRepeatBased = false,
                          isBuffered = false,
                          repeatItem,
                          repeatCollection;
  
                      // try to find an ngRepeat expression
                      // at this point, the attributes are not yet normalized so we need to try various syntax
                      ['ng-repeat', 'data-ng-repeat', 'ng:repeat', 'x-ng-repeat'].every(function(attr) {
                          var repeatAttribute = firstChildAttributes[attr];
                          if (angular.isDefined(repeatAttribute)) {
                              // ngRepeat regexp extracted from angular 1.2.7 src
                              var exprMatch = repeatAttribute.value.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),
                                  trackProperty = exprMatch[3];
  
                              repeatItem = exprMatch[1];
                              repeatCollection = exprMatch[2];
  
                              if (repeatItem) {
                                  if (angular.isDefined(tAttributes['rnCarouselBuffered'])) {
                                      // update the current ngRepeat expression and add a slice operator if buffered
                                      isBuffered = true;
                                      repeatAttribute.value = repeatItem + ' in ' + repeatCollection + '|carouselSlice:carouselBufferIndex:carouselBufferSize';
                                      if (trackProperty) {
                                          repeatAttribute.value += ' track by ' + trackProperty;
                                      }
                                  }
                                  isRepeatBased = true;
                                  return false;
                              }
                          }
                          return true;
                      });
  
                      return function(scope, iElement, iAttributes, containerCtrl) {
  
                          carouselId++;
  
                          var defaultOptions = {
                              transitionType: iAttributes.rnCarouselTransition || 'slide',
                              transitionEasing: iAttributes.rnCarouselEasing || 'easeTo',
                              transitionDuration: parseInt(iAttributes.rnCarouselDuration, 10) || 300,
                              isSequential: true,
                              autoSlideDuration: 3,
                              bufferSize: 5,
                              /* in container % how much we need to drag to trigger the slide change */
                              moveTreshold: 0.1,
                              defaultIndex: 0
                          };
  
                          // TODO
                          var options = angular.extend({}, defaultOptions);
  
                          var pressed,
                              startX,
                              isIndexBound = false,
                              offset = 0,
                              destination,
                              swipeMoved = false,
                              //animOnIndexChange = true,
                              currentSlides = [],
                              elWidth = null,
                              elX = null,
                              animateTransitions = true,
                              intialState = true,
                              animating = false,
                              mouseUpBound = false,
                              locked = false;
  
                          //rn-swipe-disabled =true will only disable swipe events
                          if(iAttributes.rnSwipeDisabled !== "true") {
                              $swipe.bind(iElement, {
                                  start: swipeStart,
                                  move: swipeMove,
                                  end: swipeEnd,
                                  cancel: function(event) {
                                      swipeEnd({}, event);
                                  }
                              });
                          }
  
                          function getSlidesDOM() {
                              return iElement[0].querySelectorAll('ul[rn-carousel] > li');
                          }
  
                          function documentMouseUpEvent(event) {
                              // in case we click outside the carousel, trigger a fake swipeEnd
                              swipeMoved = true;
                              swipeEnd({
                                  x: event.clientX,
                                  y: event.clientY
                              }, event);
                          }
  
                          function updateSlidesPosition(offset) {
                              // manually apply transformation to carousel childrens
                              // todo : optim : apply only to visible items
                              var x = scope.carouselBufferIndex * 100 + offset;
                              angular.forEach(getSlidesDOM(), function(child, index) {
                                  child.style.cssText = createStyleString(computeCarouselSlideStyle(index, x, options.transitionType));
                              });
                          }
  
                          scope.nextSlide = function(slideOptions) {
                              var index = scope.carouselIndex + 1;
                              if (index > currentSlides.length - 1) {
                                  index = 0;
                              }
                              if (!locked) {
                                  goToSlide(index, slideOptions);
                              }
                          };
  
                          scope.prevSlide = function(slideOptions) {
                              var index = scope.carouselIndex - 1;
                              if (index < 0) {
                                  index = currentSlides.length - 1;
                              }
                              goToSlide(index, slideOptions);
                          };
  
                          function goToSlide(index, slideOptions) {
                              //console.log('goToSlide', arguments);
                              // move a to the given slide index
                              if (index === undefined) {
                                  index = scope.carouselIndex;
                              }
  
                              slideOptions = slideOptions || {};
                              if (slideOptions.animate === false || options.transitionType === 'none') {
                                  locked = false;
                                  offset = index * -100;
                                  scope.carouselIndex = index;
                                  updateBufferIndex();
                                  return;
                              }
  
                              locked = true;
                              var tweenable = new Tweenable();
                              tweenable.tween({
                                  from: {
                                      'x': offset
                                  },
                                  to: {
                                      'x': index * -100
                                  },
                                  duration: options.transitionDuration,
                                  easing: options.transitionEasing,
                                  step: function(state) {
                                      updateSlidesPosition(state.x);
                                  },
                                  finish: function() {
                                      scope.$apply(function() {
                                          scope.carouselIndex = index;
                                          offset = index * -100;
                                          updateBufferIndex();
                                          $timeout(function () {
                                            locked = false;
                                          }, 0, false);
                                      });
                                  }
                              });
                          }
  
                          function getContainerWidth() {
                              var rect = iElement[0].getBoundingClientRect();
                              return rect.width ? rect.width : rect.right - rect.left;
                          }
  
                          function updateContainerWidth() {
                              elWidth = getContainerWidth();
                          }
  
                          function bindMouseUpEvent() {
                              if (!mouseUpBound) {
                                mouseUpBound = true;
                                $document.bind('mouseup', documentMouseUpEvent);
                              }
                          }
  
                          function unbindMouseUpEvent() {
                              if (mouseUpBound) {
                                mouseUpBound = false;
                                $document.unbind('mouseup', documentMouseUpEvent);
                              }
                          }
  
                          function swipeStart(coords, event) {
                              // console.log('swipeStart', coords, event);
                              if (locked || currentSlides.length <= 1) {
                                  return;
                              }
                              updateContainerWidth();
                              elX = iElement[0].querySelector('li').getBoundingClientRect().left;
                              pressed = true;
                              startX = coords.x;
                              return false;
                          }
  
                          function swipeMove(coords, event) {
                              //console.log('swipeMove', coords, event);
                              var x, delta;
                              bindMouseUpEvent();
                              if (pressed) {
                                  x = coords.x;
                                  delta = startX - x;
                                  if (delta > 2 || delta < -2) {
                                      swipeMoved = true;
                                      var moveOffset = offset + (-delta * 100 / elWidth);
                                      updateSlidesPosition(moveOffset);
                                  }
                              }
                              return false;
                          }
  
                          var init = true;
                          scope.carouselIndex = 0;
  
                          if (!isRepeatBased) {
                              // fake array when no ng-repeat
                              currentSlides = [];
                              angular.forEach(getSlidesDOM(), function(node, index) {
                                  currentSlides.push({id: index});
                              });
                          }
  
                          if (iAttributes.rnCarouselControls!==undefined) {
                              // dont use a directive for this
                              var nextSlideIndexCompareValue = isRepeatBased ? repeatCollection.replace('::', '') + '.length - 1' : currentSlides.length - 1;
                              var tpl = '<div class="rn-carousel-controls">\n' +
                                  '  <span class="rn-carousel-control rn-carousel-control-prev" ng-click="prevSlide()" ng-if="carouselIndex > 0"></span>\n' +
                                  '  <span class="rn-carousel-control rn-carousel-control-next" ng-click="nextSlide()" ng-if="carouselIndex < ' + nextSlideIndexCompareValue + '"></span>\n' +
                                  '</div>';
                              iElement.parent().append($compile(angular.element(tpl))(scope));
                          }
  
                          if (iAttributes.rnCarouselAutoSlide!==undefined) {
                              var duration = parseInt(iAttributes.rnCarouselAutoSlide, 10) || options.autoSlideDuration;
                              scope.autoSlide = function() {
                                  if (scope.autoSlider) {
                                      $interval.cancel(scope.autoSlider);
                                      scope.autoSlider = null;
                                  }
                                  scope.autoSlider = $interval(function() {
                                      if (!locked && !pressed) {
                                          scope.nextSlide();
                                      }
                                  }, duration * 1000);
                              };
                          }
  
                          if (iAttributes.rnCarouselDefaultIndex) {
                              var defaultIndexModel = $parse(iAttributes.rnCarouselDefaultIndex);
                              options.defaultIndex = defaultIndexModel(scope.$parent) || 0;
                          }
  
                          if (iAttributes.rnCarouselIndex) {
                              var updateParentIndex = function(value) {
                                  indexModel.assign(scope.$parent, value);
                              };
                              var indexModel = $parse(iAttributes.rnCarouselIndex);
                              if (angular.isFunction(indexModel.assign)) {
                                  /* check if this property is assignable then watch it */
                                  scope.$watch('carouselIndex', function(newValue) {
                                      updateParentIndex(newValue);
                                  });
                                  scope.$parent.$watch(indexModel, function(newValue, oldValue) {
  
                                      if (newValue !== undefined && newValue !== null) {
                                          if (currentSlides && currentSlides.length > 0 && newValue >= currentSlides.length) {
                                              newValue = currentSlides.length - 1;
                                              updateParentIndex(newValue);
                                          } else if (currentSlides && newValue < 0) {
                                              newValue = 0;
                                              updateParentIndex(newValue);
                                          }
                                          if (!locked) {
                                              goToSlide(newValue, {
                                                  animate: !init
                                              });
                                          }
                                          init = false;
                                      }
                                  });
                                  isIndexBound = true;
  
                                  if (options.defaultIndex) {
                                      goToSlide(options.defaultIndex, {
                                          animate: !init
                                      });
                                  }
                              } else if (!isNaN(iAttributes.rnCarouselIndex)) {
                                  /* if user just set an initial number, set it */
                                  goToSlide(parseInt(iAttributes.rnCarouselIndex, 10), {
                                      animate: false
                                  });
                              }
                          } else {
                              goToSlide(options.defaultIndex, {
                                  animate: !init
                              });
                              init = false;
                          }
  
                          if (iAttributes.rnCarouselLocked) {
                              scope.$watch(iAttributes.rnCarouselLocked, function(newValue, oldValue) {
                                  // only bind swipe when it's not switched off
                                  if(newValue === true) {
                                      locked = true;
                                  } else {
                                      locked = false;
                                  }
                              });
                          }
  
                          if (isRepeatBased) {
                              // use rn-carousel-deep-watch to fight the Angular $watchCollection weakness : https://github.com/angular/angular.js/issues/2621
                              // optional because it have some performance impacts (deep watch)
                              var deepWatch = (iAttributes.rnCarouselDeepWatch!==undefined);
  
                              scope[deepWatch?'$watch':'$watchCollection'](repeatCollection, function(newValue, oldValue) {
                                  //console.log('repeatCollection', currentSlides);
                                  currentSlides = newValue;
                                  // if deepWatch ON ,manually compare objects to guess the new position
                                  if (deepWatch && angular.isArray(newValue)) {
                                      var activeElement = oldValue[scope.carouselIndex];
                                      var newIndex = getItemIndex(newValue, activeElement, scope.carouselIndex);
                                      goToSlide(newIndex, {animate: false});
                                  } else {
                                      goToSlide(scope.carouselIndex, {animate: false});
                                  }
                              }, true);
                          }
  
                          function swipeEnd(coords, event, forceAnimation) {
                              //  console.log('swipeEnd', 'scope.carouselIndex', scope.carouselIndex);
                              // Prevent clicks on buttons inside slider to trigger "swipeEnd" event on touchend/mouseup
                              // console.log(iAttributes.rnCarouselOnInfiniteScroll);
                              if (event && !swipeMoved) {
                                  return;
                              }
                              unbindMouseUpEvent();
                              pressed = false;
                              swipeMoved = false;
                              destination = startX - coords.x;
                              if (destination===0) {
                                  return;
                              }
                              if (locked) {
                                  return;
                              }
                              offset += (-destination * 100 / elWidth);
                              if (options.isSequential) {
                                  var minMove = options.moveTreshold * elWidth,
                                      absMove = -destination,
                                      slidesMove = -Math[absMove >= 0 ? 'ceil' : 'floor'](absMove / elWidth),
                                      shouldMove = Math.abs(absMove) > minMove;
  
                                  if (currentSlides && (slidesMove + scope.carouselIndex) >= currentSlides.length) {
                                      slidesMove = currentSlides.length - 1 - scope.carouselIndex;
                                  }
                                  if ((slidesMove + scope.carouselIndex) < 0) {
                                      slidesMove = -scope.carouselIndex;
                                  }
                                  var moveOffset = shouldMove ? slidesMove : 0;
  
                                  destination = (scope.carouselIndex + moveOffset);
  
                                  goToSlide(destination);
                                  if(iAttributes.rnCarouselOnInfiniteScrollRight!==undefined && slidesMove === 0 && scope.carouselIndex !== 0) {
                                      $parse(iAttributes.rnCarouselOnInfiniteScrollRight)(scope)
                                      goToSlide(0);
                                  }
                                  if(iAttributes.rnCarouselOnInfiniteScrollLeft!==undefined && slidesMove === 0 && scope.carouselIndex === 0 && moveOffset === 0) {
                                      $parse(iAttributes.rnCarouselOnInfiniteScrollLeft)(scope)
                                      goToSlide(currentSlides.length);
                                  }
  
                              } else {
                                  scope.$apply(function() {
                                      scope.carouselIndex = parseInt(-offset / 100, 10);
                                      updateBufferIndex();
                                  });
  
                              }
  
                          }
  
                          scope.$on('$destroy', function() {
                              unbindMouseUpEvent();
                          });
  
                          scope.carouselBufferIndex = 0;
                          scope.carouselBufferSize = options.bufferSize;
  
                          function updateBufferIndex() {
                              // update and cap te buffer index
                              var bufferIndex = 0;
                              var bufferEdgeSize = (scope.carouselBufferSize - 1) / 2;
                              if (isBuffered) {
                                  if (scope.carouselIndex <= bufferEdgeSize) {
                                      // first buffer part
                                      bufferIndex = 0;
                                  } else if (currentSlides && currentSlides.length < scope.carouselBufferSize) {
                                      // smaller than buffer
                                      bufferIndex = 0;
                                  } else if (currentSlides && scope.carouselIndex > currentSlides.length - scope.carouselBufferSize) {
                                      // last buffer part
                                      bufferIndex = currentSlides.length - scope.carouselBufferSize;
                                  } else {
                                      // compute buffer start
                                      bufferIndex = scope.carouselIndex - bufferEdgeSize;
                                  }
  
                                  scope.carouselBufferIndex = bufferIndex;
                                  $timeout(function() {
                                      updateSlidesPosition(offset);
                                  }, 0, false);
                              } else {
                                  $timeout(function() {
                                      updateSlidesPosition(offset);
                                  }, 0, false);
                              }
                          }
  
                          function onOrientationChange() {
                              updateContainerWidth();
                              goToSlide();
                          }
  
                          // handle orientation change
                          var winEl = angular.element($window);
                          winEl.bind('orientationchange', onOrientationChange);
                          winEl.bind('resize', onOrientationChange);
  
                          scope.$on('$destroy', function() {
                              unbindMouseUpEvent();
                              winEl.unbind('orientationchange', onOrientationChange);
                              winEl.unbind('resize', onOrientationChange);
                          });
                      };
                  }
              };
          }
      ]);
  })();
  