‘use strict’;

angular.module(‘mgcrea.ngStrap.helpers.dimensions’, [])

.factory('dimensions', function($document, $window) {

  var jqLite = angular.element;
  var fn = {};

  /**
   * Test the element nodeName
   * @param element
   * @param name
   */
  var nodeName = fn.nodeName = function(element, name) {
    return element.nodeName && element.nodeName.toLowerCase() === name.toLowerCase();
  };

  /**
   * Returns the element computed style
   * @param element
   * @param prop
   * @param extra
   */
  fn.css = function(element, prop, extra) {
    var value;
    if (element.currentStyle) { //IE
      value = element.currentStyle[prop];
    } else if (window.getComputedStyle) {
      value = window.getComputedStyle(element)[prop];
    } else {
      value = element.style[prop];
    }
    return extra === true ? parseFloat(value) || 0 : value;
  };

  /**
   * Provides read-only equivalent of jQuery's offset function:
   * @required-by bootstrap-tooltip, bootstrap-affix
   * @url http://api.jquery.com/offset/
   * @param element
   */
  fn.offset = function(element) {
    var boxRect = element.getBoundingClientRect();
    var docElement = element.ownerDocument;
    return {
      width: boxRect.width || element.offsetWidth,
      height: boxRect.height || element.offsetHeight,
      top: boxRect.top + (window.pageYOffset || docElement.documentElement.scrollTop) - (docElement.documentElement.clientTop || 0),
      left: boxRect.left + (window.pageXOffset || docElement.documentElement.scrollLeft) - (docElement.documentElement.clientLeft || 0)
    };
  };

  /**
   * Provides set equivalent of jQuery's offset function:
   * @required-by bootstrap-tooltip
   * @url http://api.jquery.com/offset/
   * @param element
   * @param options
   * @param i
   */
  fn.setOffset = function (element, options, i) {
    var curPosition,
        curLeft,
        curCSSTop,
        curTop,
        curOffset,
        curCSSLeft,
        calculatePosition,
        position = fn.css(element, 'position'),
        curElem = angular.element(element),
        props = {};

    // Set position first, in-case top/left are set even on static elem
    if (position === 'static') {
      element.style.position = 'relative';
    }

    curOffset = fn.offset(element);
    curCSSTop = fn.css(element, 'top');
    curCSSLeft = fn.css(element, 'left');
    calculatePosition = (position === 'absolute' || position === 'fixed') && 
                        (curCSSTop + curCSSLeft).indexOf('auto') > -1;

    // Need to be able to calculate position if either
    // top or left is auto and position is either absolute or fixed
    if (calculatePosition) {
      curPosition = fn.position(element);
      curTop = curPosition.top;
      curLeft = curPosition.left;
    } else {
      curTop = parseFloat(curCSSTop) || 0;
      curLeft = parseFloat(curCSSLeft) || 0;
    }

    if (angular.isFunction(options)) {
      options = options.call(element, i, curOffset);
    }

    if (options.top !== null ) {
      props.top = (options.top - curOffset.top) + curTop;
    }
    if ( options.left !== null ) {
      props.left = (options.left - curOffset.left) + curLeft;
    }

    if ('using' in options) {
      options.using.call(curElem, props);
    } else {
      curElem.css({
        top: props.top + 'px',
        left: props.left + 'px'
      });
    }
  };

  /**
   * Provides read-only equivalent of jQuery's position function
   * @required-by bootstrap-tooltip, bootstrap-affix
   * @url http://api.jquery.com/offset/
   * @param element
   */
  fn.position = function(element) {

    var offsetParentRect = {top: 0, left: 0},
        offsetParentElement,
        offset;

    // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
    if (fn.css(element, 'position') === 'fixed') {

      // We assume that getBoundingClientRect is available when computed position is fixed
      offset = element.getBoundingClientRect();

    } else {

      // Get *real* offsetParentElement
      offsetParentElement = offsetParent(element);

      // Get correct offsets
      offset = fn.offset(element);
      if (!nodeName(offsetParentElement, 'html')) {
        offsetParentRect = fn.offset(offsetParentElement);
      }

      // Add offsetParent borders
      offsetParentRect.top += fn.css(offsetParentElement, 'borderTopWidth', true);
      offsetParentRect.left += fn.css(offsetParentElement, 'borderLeftWidth', true);
    }

    // Subtract parent offsets and element margins
    return {
      width: element.offsetWidth,
      height: element.offsetHeight,
      top: offset.top - offsetParentRect.top - fn.css(element, 'marginTop', true),
      left: offset.left - offsetParentRect.left - fn.css(element, 'marginLeft', true)
    };

  };

  /**
   * Returns the closest, non-statically positioned offsetParent of a given element
   * @required-by fn.position
   * @param element
   */
  var offsetParent = function offsetParentElement(element) {
    var docElement = element.ownerDocument;
    var offsetParent = element.offsetParent || docElement;
    if(nodeName(offsetParent, '#document')) return docElement.documentElement;
    while(offsetParent && !nodeName(offsetParent, 'html') && fn.css(offsetParent, 'position') === 'static') {
      offsetParent = offsetParent.offsetParent;
    }
    return offsetParent || docElement.documentElement;
  };

  /**
   * Provides equivalent of jQuery's height function
   * @required-by bootstrap-affix
   * @url http://api.jquery.com/height/
   * @param element
   * @param outer
   */
  fn.height = function(element, outer) {
    var value = element.offsetHeight;
    if(outer) {
      value += fn.css(element, 'marginTop', true) + fn.css(element, 'marginBottom', true);
    } else {
      value -= fn.css(element, 'paddingTop', true) + fn.css(element, 'paddingBottom', true) + fn.css(element, 'borderTopWidth', true) + fn.css(element, 'borderBottomWidth', true);
    }
    return value;
  };

  /**
   * Provides equivalent of jQuery's width function
   * @required-by bootstrap-affix
   * @url http://api.jquery.com/width/
   * @param element
   * @param outer
   */
  fn.width = function(element, outer) {
    var value = element.offsetWidth;
    if(outer) {
      value += fn.css(element, 'marginLeft', true) + fn.css(element, 'marginRight', true);
    } else {
      value -= fn.css(element, 'paddingLeft', true) + fn.css(element, 'paddingRight', true) + fn.css(element, 'borderLeftWidth', true) + fn.css(element, 'borderRightWidth', true);
    }
    return value;
  };

  return fn;

});