text: IonicModule
  
  .controller('$ionicHeaderBar', [
    '$scope',
    '$element',
    '$attrs',
    '$q',
    '$ionicConfig',
    '$ionicHistory',
  function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
    var TITLE = 'title';
    var BACK_TEXT = 'back-text';
    var BACK_BUTTON = 'back-button';
    var DEFAULT_TITLE = 'default-title';
    var PREVIOUS_TITLE = 'previous-title';
    var HIDE = 'hide';
  
    var self = this;
    var titleText = '';
    var previousTitleText = '';
    var titleLeft = 0;
    var titleRight = 0;
    var titleCss = '';
    var isBackEnabled = false;
    var isBackShown = true;
    var isNavBackShown = true;
    var isBackElementShown = false;
    var titleTextWidth = 0;
  
  
    self.beforeEnter = function(viewData) {
      $scope.$broadcast('$ionicView.beforeEnter', viewData);
    };
  
  
    self.title = function(newTitleText) {
      if (arguments.length && newTitleText !== titleText) {
        getEle(TITLE).innerHTML = newTitleText;
        titleText = newTitleText;
        titleTextWidth = 0;
      }
      return titleText;
    };
  
  
    self.enableBack = function(shouldEnable, disableReset) {
      // whether or not the back button show be visible, according
      // to the navigation and history
      if (arguments.length) {
        isBackEnabled = shouldEnable;
        if (!disableReset) self.updateBackButton();
      }
      return isBackEnabled;
    };
  
  
    self.showBack = function(shouldShow, disableReset) {
      // different from enableBack() because this will always have the back
      // visually hidden if false, even if the history says it should show
      if (arguments.length) {
        isBackShown = shouldShow;
        if (!disableReset) self.updateBackButton();
      }
      return isBackShown;
    };
  
  
    self.showNavBack = function(shouldShow) {
      // different from showBack() because this is for the entire nav bar's
      // setting for all of it's child headers. For internal use.
      isNavBackShown = shouldShow;
      self.updateBackButton();
    };
  
  
    self.updateBackButton = function() {
      var ele;
      if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
        isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
        ele = getEle(BACK_BUTTON);
        ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
      }
  
      if (isBackEnabled) {
        ele = ele || getEle(BACK_BUTTON);
        if (ele) {
          if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
            ele = getEle(BACK_BUTTON + ' .icon');
            if (ele) {
              self.backButtonIcon = $ionicConfig.backButton.icon();
              ele.className = 'icon ' + self.backButtonIcon;
            }
          }
  
          if (self.backButtonText !== $ionicConfig.backButton.text()) {
            ele = getEle(BACK_BUTTON + ' .back-text');
            if (ele) {
              ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
            }
          }
        }
      }
    };
  
  
    self.titleTextWidth = function() {
      var element = getEle(TITLE);
      if ( element ) {
        // If the element has a nav-bar-title, use that instead
        // to calculate the width of the title
        var children = angular.element(element).children();
        for ( var i = 0; i < children.length; i++ ) {
          if ( angular.element(children[i]).hasClass('nav-bar-title') ) {
            element = children[i];
            break;
          }
        }
      }
      var bounds = ionic.DomUtil.getTextBounds(element);
      titleTextWidth = Math.min(bounds && bounds.width || 30);
      return titleTextWidth;
    };
  
  
    self.titleWidth = function() {
      var titleWidth = self.titleTextWidth();
      var offsetWidth = getEle(TITLE).offsetWidth;
      if (offsetWidth < titleWidth) {
        titleWidth = offsetWidth + (titleLeft - titleRight - 5);
      }
      return titleWidth;
    };
  
  
    self.titleTextX = function() {
      return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
    };
  
  
    self.titleLeftRight = function() {
      return titleLeft - titleRight;
    };
  
  
    self.backButtonTextLeft = function() {
      var offsetLeft = 0;
      var ele = getEle(BACK_TEXT);
      while (ele) {
        offsetLeft += ele.offsetLeft;
        ele = ele.parentElement;
      }
      return offsetLeft;
    };
  
  
    self.resetBackButton = function(viewData) {
      if ($ionicConfig.backButton.previousTitleText()) {
        var previousTitleEle = getEle(PREVIOUS_TITLE);
        if (previousTitleEle) {
          previousTitleEle.classList.remove(HIDE);
  
          var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
          var newPreviousTitleText = $ionicHistory.backTitle(view);
  
          if (newPreviousTitleText !== previousTitleText) {
            previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
          }
        }
        var defaultTitleEle = getEle(DEFAULT_TITLE);
        if (defaultTitleEle) {
          defaultTitleEle.classList.remove(HIDE);
        }
      }
    };
  
  
    self.align = function(textAlign) {
      var titleEle = getEle(TITLE);
  
      textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
  
      var widths = self.calcWidths(textAlign, false);
  
      if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
        var previousTitleWidths = self.calcWidths(textAlign, true);
  
        var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
  
        if (self.titleTextWidth() <= availableTitleWidth) {
          widths = previousTitleWidths;
        }
      }
  
      return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
    };
  
  
    self.calcWidths = function(textAlign, isPreviousTitle) {
      var titleEle = getEle(TITLE);
      var backBtnEle = getEle(BACK_BUTTON);
      var x, y, z, b, c, d, childSize, bounds;
      var childNodes = $element[0].childNodes;
      var buttonsLeft = 0;
      var buttonsRight = 0;
      var isCountRightOfTitle;
      var updateTitleLeft = 0;
      var updateTitleRight = 0;
      var updateCss = '';
      var backButtonWidth = 0;
  
      // Compute how wide the left children are
      // Skip all titles (there may still be two titles, one leaving the dom)
      // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
      for (x = 0; x < childNodes.length; x++) {
        c = childNodes[x];
  
        childSize = 0;
        if (c.nodeType == 1) {
          // element node
          if (c === titleEle) {
            isCountRightOfTitle = true;
            continue;
          }
  
          if (c.classList.contains(HIDE)) {
            continue;
          }
  
          if (isBackShown && c === backBtnEle) {
  
            for (y = 0; y < c.childNodes.length; y++) {
              b = c.childNodes[y];
  
              if (b.nodeType == 1) {
  
                if (b.classList.contains(BACK_TEXT)) {
                  for (z = 0; z < b.children.length; z++) {
                    d = b.children[z];
  
                    if (isPreviousTitle) {
                      if (d.classList.contains(DEFAULT_TITLE)) continue;
                      backButtonWidth += d.offsetWidth;
                    } else {
                      if (d.classList.contains(PREVIOUS_TITLE)) continue;
                      backButtonWidth += d.offsetWidth;
                    }
                  }
  
                } else {
                  backButtonWidth += b.offsetWidth;
                }
  
              } else if (b.nodeType == 3 && b.nodeValue.trim()) {
                bounds = ionic.DomUtil.getTextBounds(b);
                backButtonWidth += bounds && bounds.width || 0;
              }
  
            }
            childSize = backButtonWidth || c.offsetWidth;
  
          } else {
            // not the title, not the back button, not a hidden element
            childSize = c.offsetWidth;
          }
  
        } else if (c.nodeType == 3 && c.nodeValue.trim()) {
          // text node
          bounds = ionic.DomUtil.getTextBounds(c);
          childSize = bounds && bounds.width || 0;
        }
  
        if (isCountRightOfTitle) {
          buttonsRight += childSize;
        } else {
          buttonsLeft += childSize;
        }
      }
  
      // Size and align the header titleEle based on the sizes of the left and
      // right children, and the desired alignment mode
      if (textAlign == 'left') {
        updateCss = 'title-left';
        if (buttonsLeft) {
          updateTitleLeft = buttonsLeft + 15;
        }
        if (buttonsRight) {
          updateTitleRight = buttonsRight + 15;
        }
  
      } else if (textAlign == 'right') {
        updateCss = 'title-right';
        if (buttonsLeft) {
          updateTitleLeft = buttonsLeft + 15;
        }
        if (buttonsRight) {
          updateTitleRight = buttonsRight + 15;
        }
  
      } else {
        // center the default
        var margin = Math.max(buttonsLeft, buttonsRight) + 10;
        if (margin > 10) {
          updateTitleLeft = updateTitleRight = margin;
        }
      }
  
      return {
        backButtonWidth: backButtonWidth,
        buttonsLeft: buttonsLeft,
        buttonsRight: buttonsRight,
        titleLeft: updateTitleLeft,
        titleRight: updateTitleRight,
        showPrevTitle: isPreviousTitle,
        css: updateCss
      };
    };
  
  
    self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
      var deferred = $q.defer();
  
      // only make DOM updates when there are actual changes
      if (titleEle) {
        if (updateTitleLeft !== titleLeft) {
          titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
          titleLeft = updateTitleLeft;
        }
        if (updateTitleRight !== titleRight) {
          titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
          titleRight = updateTitleRight;
        }
  
        if (updateCss !== titleCss) {
          updateCss && titleEle.classList.add(updateCss);
          titleCss && titleEle.classList.remove(titleCss);
          titleCss = updateCss;
        }
      }
  
      if ($ionicConfig.backButton.previousTitleText()) {
        var prevTitle = getEle(PREVIOUS_TITLE);
        var defaultTitle = getEle(DEFAULT_TITLE);
  
        prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
        defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
      }
  
      ionic.requestAnimationFrame(function() {
        if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
          var minRight = buttonsRight + 5;
          var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
          updateTitleRight = testRight < minRight ? minRight : testRight;
          if (updateTitleRight !== titleRight) {
            titleEle.style.right = updateTitleRight + 'px';
            titleRight = updateTitleRight;
          }
        }
        deferred.resolve();
      });
  
      return deferred.promise;
    };
  
  
    self.setCss = function(elementClassname, css) {
      ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
    };
  
  
    var eleCache = {};
    function getEle(className) {
      if (!eleCache[className]) {
        eleCache[className] = $element[0].querySelector('.' + className);
      }
      return eleCache[className];
    }
  
  
    $scope.$on('$destroy', function() {
      for (var n in eleCache) eleCache[n] = null;
    });
  
  }]);
  