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]).+)/
  var rnumsplit = new RegExp('^(' + pnum + ')(.*)$', 'i')
  var rrelNum = new RegExp('^([+-])=(' + pnum + ')', 'i')

  var cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' }
  var cssNormalTransform = {
    letterSpacing: '0',
    fontWeight: '400'
  }

  var 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)
    var origName = name
    var 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

    var 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
    var val = name === 'width' ? elem.offsetWidth : elem.offsetHeight
    var styles = getStyles(elem)
    var 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; var elem; var hidden
    var values = []
    var index = 0
    var 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; var type; var hooks
      var origName = jQuery.camelCase(name)
      var 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; var num; var hooks
      var 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
        var expanded = {}

        // Assumes a single number if not a string
        var 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; var len
        var map = {}
        var 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
})
