define([
  './core',
  './var/pnum',
  './core/access',
  './css/var/rmargin',
  './css/var/rnumnonpx',
  './css/var/cssExpand',
  './css/var/isHidden',
  './css/var/getStyles',
  './css/curCSS',
  './css/defaultDisplay',
  './css/addGetHookIf',
  './css/support',
  './data/var/data_priv',

  './core/init',
  './css/swap',
  './core/ready',
  './selector', // contains
], function(
  jQuery,
  pnum,
  access,
  rmargin,
  rnumnonpx,
  cssExpand,
  isHidden,
  getStyles,
  curCSS,
  defaultDisplay,
  addGetHookIf,
  support,
  data_priv
) {
  var // Swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
    // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
    rdisplayswap = /^(none|table(?!-c[ea]).+)/,
    rnumsplit = new RegExp('^(' + pnum + ')(.*)$', 'i'),
    rrelNum = new RegExp('^([+-])=(' + pnum + ')', 'i'),
    cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' },
    cssNormalTransform = {
      letterSpacing: '0',
      fontWeight: '400',
    },
    cssPrefixes = ['Webkit', 'O', 'Moz', 'ms'];

  // Return a css property mapped to a potentially vendor prefixed property
  function vendorPropName(style, name) {
    // Shortcut for names that are not vendor prefixed
    if (name in style) {
      return name;
    }

    // Check for vendor prefixed names
    var capName = name[0].toUpperCase() + name.slice(1),
      origName = name,
      i = cssPrefixes.length;

    while (i--) {
      name = cssPrefixes[i] + capName;
      if (name in style) {
        return name;
      }
    }

    return origName;
  }

  function setPositiveNumber(elem, value, subtract) {
    var matches = rnumsplit.exec(value);
    return matches
      ? // Guard against undefined "subtract", e.g., when used as in cssHooks
        Math.max(0, matches[1] - (subtract || 0)) + (matches[2] || 'px')
      : value;
  }

  function augmentWidthOrHeight(elem, name, extra, isBorderBox, styles) {
    var i =
        extra === (isBorderBox ? 'border' : 'content')
          ? // If we already have the right measurement, avoid augmentation
            4
          : // Otherwise initialize for horizontal or vertical properties
            name === 'width' ? 1 : 0,
      val = 0;

    for (; i < 4; i += 2) {
      // Both box models exclude margin, so add it if we want it
      if (extra === 'margin') {
        val += jQuery.css(elem, extra + cssExpand[i], true, styles);
      }

      if (isBorderBox) {
        // border-box includes padding, so remove it if we want content
        if (extra === 'content') {
          val -= jQuery.css(elem, 'padding' + cssExpand[i], true, styles);
        }

        // At this point, extra isn't border nor margin, so remove border
        if (extra !== 'margin') {
          val -= jQuery.css(
            elem,
            'border' + cssExpand[i] + 'Width',
            true,
            styles
          );
        }
      } else {
        // At this point, extra isn't content, so add padding
        val += jQuery.css(elem, 'padding' + cssExpand[i], true, styles);

        // At this point, extra isn't content nor padding, so add border
        if (extra !== 'padding') {
          val += jQuery.css(
            elem,
            'border' + cssExpand[i] + 'Width',
            true,
            styles
          );
        }
      }
    }

    return val;
  }

  function getWidthOrHeight(elem, name, extra) {
    // Start with offset property, which is equivalent to the border-box value
    var valueIsBorderBox = true,
      val = name === 'width' ? elem.offsetWidth : elem.offsetHeight,
      styles = getStyles(elem),
      isBorderBox =
        jQuery.css(elem, 'boxSizing', false, styles) === 'border-box';

    // Some non-html elements return undefined for offsetWidth, so check for null/undefined
    // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
    // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
    if (val <= 0 || val == null) {
      // Fall back to computed then uncomputed css if necessary
      val = curCSS(elem, name, styles);
      if (val < 0 || val == null) {
        val = elem.style[name];
      }

      // Computed unit is not pixels. Stop here and return.
      if (rnumnonpx.test(val)) {
        return val;
      }

      // Check for style in case a browser which returns unreliable values
      // for getComputedStyle silently falls back to the reliable elem.style
      valueIsBorderBox =
        isBorderBox &&
        (support.boxSizingReliable() || val === elem.style[name]);

      // Normalize "", auto, and prepare for extra
      val = parseFloat(val) || 0;
    }

    // Use the active box-sizing model to add/subtract irrelevant styles
    return (
      val +
      augmentWidthOrHeight(
        elem,
        name,
        extra || (isBorderBox ? 'border' : 'content'),
        valueIsBorderBox,
        styles
      ) +
      'px'
    );
  }

  function showHide(elements, show) {
    var display,
      elem,
      hidden,
      values = [],
      index = 0,
      length = elements.length;

    for (; index < length; index++) {
      elem = elements[index];
      if (!elem.style) {
        continue;
      }

      values[index] = data_priv.get(elem, 'olddisplay');
      display = elem.style.display;
      if (show) {
        // Reset the inline display of this element to learn if it is
        // being hidden by cascaded rules or not
        if (!values[index] && display === 'none') {
          elem.style.display = '';
        }

        // Set elements which have been overridden with display: none
        // in a stylesheet to whatever the default browser style is
        // for such an element
        if (elem.style.display === '' && isHidden(elem)) {
          values[index] = data_priv.access(
            elem,
            'olddisplay',
            defaultDisplay(elem.nodeName)
          );
        }
      } else {
        hidden = isHidden(elem);

        if (display !== 'none' || !hidden) {
          data_priv.set(
            elem,
            'olddisplay',
            hidden ? display : jQuery.css(elem, 'display')
          );
        }
      }
    }

    // Set the display of most of the elements in a second loop
    // to avoid the constant reflow
    for (index = 0; index < length; index++) {
      elem = elements[index];
      if (!elem.style) {
        continue;
      }
      if (!show || elem.style.display === 'none' || elem.style.display === '') {
        elem.style.display = show ? values[index] || '' : 'none';
      }
    }

    return elements;
  }

  jQuery.extend({
    // Add in style property hooks for overriding the default
    // behavior of getting and setting a style property
    cssHooks: {
      opacity: {
        get: function(elem, computed) {
          if (computed) {
            // We should always get a number back from opacity
            var ret = curCSS(elem, 'opacity');
            return ret === '' ? '1' : ret;
          }
        },
      },
    },

    // Don't automatically add "px" to these possibly-unitless properties
    cssNumber: {
      columnCount: true,
      fillOpacity: true,
      flexGrow: true,
      flexShrink: true,
      fontWeight: true,
      lineHeight: true,
      opacity: true,
      order: true,
      orphans: true,
      widows: true,
      zIndex: true,
      zoom: true,
    },

    // Add in properties whose names you wish to fix before
    // setting or getting the value
    cssProps: {
      float: 'cssFloat',
    },

    // Get and set the style property on a DOM Node
    style: function(elem, name, value, extra) {
      // Don't set styles on text and comment nodes
      if (!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style) {
        return;
      }

      // Make sure that we're working with the right name
      var ret,
        type,
        hooks,
        origName = jQuery.camelCase(name),
        style = elem.style;

      name =
        jQuery.cssProps[origName] ||
        (jQuery.cssProps[origName] = vendorPropName(style, origName));

      // Gets hook for the prefixed version, then unprefixed version
      hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName];

      // Check if we're setting a value
      if (value !== undefined) {
        type = typeof value;

        // Convert "+=" or "-=" to relative numbers (#7345)
        if (type === 'string' && (ret = rrelNum.exec(value))) {
          value = (ret[1] + 1) * ret[2] + parseFloat(jQuery.css(elem, name));
          // Fixes bug #9237
          type = 'number';
        }

        // Make sure that null and NaN values aren't set (#7116)
        if (value == null || value !== value) {
          return;
        }

        // If a number, add 'px' to the (except for certain CSS properties)
        if (type === 'number' && !jQuery.cssNumber[origName]) {
          value += 'px';
        }

        // Support: IE9-11+
        // background-* props affect original clone's values
        if (
          !support.clearCloneStyle &&
          value === '' &&
          name.indexOf('background') === 0
        ) {
          style[name] = 'inherit';
        }

        // If a hook was provided, use that value, otherwise just set the specified value
        if (
          !hooks ||
          !('set' in hooks) ||
          (value = hooks.set(elem, value, extra)) !== undefined
        ) {
          style[name] = value;
        }
      } else {
        // If a hook was provided get the non-computed value from there
        if (
          hooks &&
          'get' in hooks &&
          (ret = hooks.get(elem, false, extra)) !== undefined
        ) {
          return ret;
        }

        // Otherwise just get the value from the style object
        return style[name];
      }
    },

    css: function(elem, name, extra, styles) {
      var val,
        num,
        hooks,
        origName = jQuery.camelCase(name);

      // Make sure that we're working with the right name
      name =
        jQuery.cssProps[origName] ||
        (jQuery.cssProps[origName] = vendorPropName(elem.style, origName));

      // Try prefixed name followed by the unprefixed name
      hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName];

      // If a hook was provided get the computed value from there
      if (hooks && 'get' in hooks) {
        val = hooks.get(elem, true, extra);
      }

      // Otherwise, if a way to get the computed value exists, use that
      if (val === undefined) {
        val = curCSS(elem, name, styles);
      }

      // Convert "normal" to computed value
      if (val === 'normal' && name in cssNormalTransform) {
        val = cssNormalTransform[name];
      }

      // Make numeric if forced or a qualifier was provided and val looks numeric
      if (extra === '' || extra) {
        num = parseFloat(val);
        return extra === true || jQuery.isNumeric(num) ? num || 0 : val;
      }
      return val;
    },
  });

  jQuery.each(['height', 'width'], function(i, name) {
    jQuery.cssHooks[name] = {
      get: function(elem, computed, extra) {
        if (computed) {
          // Certain elements can have dimension info if we invisibly show them
          // but it must have a current display style that would benefit
          return rdisplayswap.test(jQuery.css(elem, 'display')) &&
            elem.offsetWidth === 0
            ? jQuery.swap(elem, cssShow, function() {
                return getWidthOrHeight(elem, name, extra);
              })
            : getWidthOrHeight(elem, name, extra);
        }
      },

      set: function(elem, value, extra) {
        var styles = extra && getStyles(elem);
        return setPositiveNumber(
          elem,
          value,
          extra
            ? augmentWidthOrHeight(
                elem,
                name,
                extra,
                jQuery.css(elem, 'boxSizing', false, styles) === 'border-box',
                styles
              )
            : 0
        );
      },
    };
  });

  // Support: Android 2.3
  jQuery.cssHooks.marginRight = addGetHookIf(
    support.reliableMarginRight,
    function(elem, computed) {
      if (computed) {
        return jQuery.swap(elem, { display: 'inline-block' }, curCSS, [
          elem,
          'marginRight',
        ]);
      }
    }
  );

  // These hooks are used by animate to expand properties
  jQuery.each(
    {
      margin: '',
      padding: '',
      border: 'Width',
    },
    function(prefix, suffix) {
      jQuery.cssHooks[prefix + suffix] = {
        expand: function(value) {
          var i = 0,
            expanded = {},
            // Assumes a single number if not a string
            parts = typeof value === 'string' ? value.split(' ') : [value];

          for (; i < 4; i++) {
            expanded[prefix + cssExpand[i] + suffix] =
              parts[i] || parts[i - 2] || parts[0];
          }

          return expanded;
        },
      };

      if (!rmargin.test(prefix)) {
        jQuery.cssHooks[prefix + suffix].set = setPositiveNumber;
      }
    }
  );

  jQuery.fn.extend({
    css: function(name, value) {
      return access(
        this,
        function(elem, name, value) {
          var styles,
            len,
            map = {},
            i = 0;

          if (jQuery.isArray(name)) {
            styles = getStyles(elem);
            len = name.length;

            for (; i < len; i++) {
              map[name[i]] = jQuery.css(elem, name[i], false, styles);
            }

            return map;
          }

          return value !== undefined
            ? jQuery.style(elem, name, value)
            : jQuery.css(elem, name);
        },
        name,
        value,
        arguments.length > 1
      );
    },
    show: function() {
      return showHide(this, true);
    },
    hide: function() {
      return showHide(this);
    },
    toggle: function(state) {
      if (typeof state === 'boolean') {
        return state ? this.show() : this.hide();
      }

      return this.each(function() {
        if (isHidden(this)) {
          jQuery(this).show();
        } else {
          jQuery(this).hide();
        }
      });
    },
  });

  return jQuery;
});
