!function (name, context, definition) {

if (typeof module != 'undefined' && module.exports) module.exports = definition()
else if (typeof define == 'function' && define.amd) define(definition)
else context[name] = definition()

}('reqwest', this, function () {

var win = window
  , doc = document
  , twoHundo = /^20\d$/
  , byTag = 'getElementsByTagName'
  , readyState = 'readyState'
  , contentType = 'Content-Type'
  , requestedWith = 'X-Requested-With'
  , head = doc[byTag]('head')[0]
  , uniqid = 0
  , callbackPrefix = 'reqwest_' + (+new Date())
  , lastValue // data stored by the most recent JSONP callback
  , xmlHttpRequest = 'XMLHttpRequest'
  , noop = function () {}

  , isArray = typeof Array.isArray == 'function'
      ? Array.isArray
      : function (a) {
          return a instanceof Array
        }

  , defaultHeaders = {
        contentType: 'application/x-www-form-urlencoded'
      , requestedWith: xmlHttpRequest
      , accept: {
            '*':  'text/javascript, text/html, application/xml, text/xml, */*'
          , xml:  'application/xml, text/xml'
          , html: 'text/html'
          , text: 'text/plain'
          , json: 'application/json, text/javascript'
          , js:   'application/javascript, text/javascript'
        }
    }

  , xhr = win[xmlHttpRequest]
      ? function () {
          return new XMLHttpRequest()
        }
      : function () {
          return new ActiveXObject('Microsoft.XMLHTTP')
        }
  , globalSetupOptions = {
      dataFilter: function (data) {
        return data
      }
    }

function handleReadyState(r, success, error) {
  return function () {
    // use _aborted to mitigate against IE err c00c023f
    // (can't read props on aborted request objects)
    if (r._aborted) return error(r.request)
    if (r.request && r.request[readyState] == 4) {
      r.request.onreadystatechange = noop
      if (twoHundo.test(r.request.status))
        success(r.request)
      else
        error(r.request)
    }
  }
}

function setHeaders(http, o) {
  var headers = o.headers || {}
    , h

  headers.Accept = headers.Accept
    || defaultHeaders.accept[o.type]
    || defaultHeaders.accept['*']

  // breaks cross-origin requests with legacy browsers
  if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith
  if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType
  for (h in headers)
    headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h])
}

function setCredentials(http, o) {
  if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') {
    http.withCredentials = !!o.withCredentials
  }
}

function generalCallback(data) {
  lastValue = data
}

function urlappend (url, s) {
  return url + (/\?/.test(url) ? '&' : '?') + s
}

function handleJsonp(o, fn, err, url) {
  var reqId = uniqid++
    , cbkey = o.jsonpCallback || 'callback' // the 'callback' key
    , cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId)
    // , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value
    , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
    , match = url.match(cbreg)
    , script = doc.createElement('script')
    , loaded = 0
    , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1

  if (match) {
    if (match[3] === '?') {
      url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
    } else {
      cbval = match[3] // provided callback func name
    }
  } else {
    url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
  }

  win[cbval] = generalCallback

  script.type = 'text/javascript'
  script.src = url
  script.async = true
  if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
    // need this for IE due to out-of-order onreadystatechange(), binding script
    // execution to an event listener gives us control over when the script
    // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
    //
    // if this hack is used in IE10 jsonp callback are never called
    script.event = 'onclick'
    script.htmlFor = script.id = '_reqwest_' + reqId
  }

  script.onload = script.onreadystatechange = function () {
    if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
      return false
    }
    script.onload = script.onreadystatechange = null
    script.onclick && script.onclick()
    // Call the user callback with the last value stored and clean up values and scripts.
    fn(lastValue)
    lastValue = undefined
    head.removeChild(script)
    loaded = 1
  }

  // Add the script to the DOM head
  head.appendChild(script)

  // Enable JSONP timeout
  return {
    abort: function () {
      script.onload = script.onreadystatechange = null
      err({}, 'Request is aborted: timeout', {})
      lastValue = undefined
      head.removeChild(script)
      loaded = 1
    }
  }
}

function getRequest(fn, err) {
  var o = this.o
    , method = (o.method || 'GET').toUpperCase()
    , url = typeof o === 'string' ? o : o.url
    // convert non-string objects to query-string form unless o.processData is false
    , data = (o.processData !== false && o.data && typeof o.data !== 'string')
      ? reqwest.toQueryString(o.data)
      : (o.data || null)
    , http

  // if we're working on a GET request and we have data then we should append
  // query string to end of URL and not post data
  if ((o.type == 'jsonp' || method == 'GET') && data) {
    url = urlappend(url, data)
    data = null
  }

  if (o.type == 'jsonp') return handleJsonp(o, fn, err, url)

  http = xhr()
  http.open(method, url, o.async === false ? false : true)
  setHeaders(http, o)
  setCredentials(http, o)
  http.onreadystatechange = handleReadyState(this, fn, err)
  o.before && o.before(http)
  http.send(data)
  return http
}

function Reqwest(o, fn) {
  this.o = o
  this.fn = fn

  init.apply(this, arguments)
}

function setType(url) {
  var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/)
  return m ? m[1] : 'js'
}

function init(o, fn) {

  this.url = typeof o == 'string' ? o : o.url
  this.timeout = null

  // whether request has been fulfilled for purpose
  // of tracking the Promises
  this._fulfilled = false
  // success handlers
  this._fulfillmentHandlers = []
  // error handlers
  this._errorHandlers = []
  // complete (both success and fail) handlers
  this._completeHandlers = []
  this._erred = false
  this._responseArgs = {}

  var self = this
    , type = o.type || setType(this.url)

  fn = fn || function () {}

  if (o.timeout) {
    this.timeout = setTimeout(function () {
      self.abort()
    }, o.timeout)
  }

  if (o.success) {
    this._fulfillmentHandlers.push(function () {
      o.success.apply(o, arguments)
    })
  }

  if (o.error) {
    this._errorHandlers.push(function () {
      o.error.apply(o, arguments)
    })
  }

  if (o.complete) {
    this._completeHandlers.push(function () {
      o.complete.apply(o, arguments)
    })
  }

  function complete (resp) {
    o.timeout && clearTimeout(self.timeout)
    self.timeout = null
    while (self._completeHandlers.length > 0) {
      self._completeHandlers.shift()(resp)
    }
  }

  function success (resp) {
    // use global data filter on response text
    var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
      , r = filteredResponse
    try {
      resp.responseText = r
    } catch (e) {
      // can't assign this in IE<=8, just ignore
    }
    if (r) {
      switch (type) {
      case 'json':
        try {
          resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
        } catch (err) {
          return error(resp, 'Could not parse JSON in response', err)
        }
        break
      case 'js':
        resp = eval(r)
        break
      case 'html':
        resp = r
        break
      case 'xml':
        resp = resp.responseXML
            && resp.responseXML.parseError // IE trololo
            && resp.responseXML.parseError.errorCode
            && resp.responseXML.parseError.reason
          ? null
          : resp.responseXML
        break
      }
    }

    self._responseArgs.resp = resp
    self._fulfilled = true
    fn(resp)
    while (self._fulfillmentHandlers.length > 0) {
      self._fulfillmentHandlers.shift()(resp)
    }

    complete(resp)
  }

  function error(resp, msg, t) {
    self._responseArgs.resp = resp
    self._responseArgs.msg = msg
    self._responseArgs.t = t
    self._erred = true
    while (self._errorHandlers.length > 0) {
      self._errorHandlers.shift()(resp, msg, t)
    }
    complete(resp)
  }

  this.request = getRequest.call(this, success, error)
}

Reqwest.prototype = {
  abort: function () {
    this._aborted = true
    this.request.abort()
  }

, retry: function () {
    init.call(this, this.o, this.fn)
  }

  /**
   * Small deviation from the Promises A CommonJs specification
   * http://wiki.commonjs.org/wiki/Promises/A
   */

  /**
   * `then` will execute upon successful requests
   */
, then: function (success, fail) {
    success = success || function () {}
    fail = fail || function () {}
    if (this._fulfilled) {
      success(this._responseArgs.resp)
    } else if (this._erred) {
      fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
    } else {
      this._fulfillmentHandlers.push(success)
      this._errorHandlers.push(fail)
    }
    return this
  }

  /**
   * `always` will execute whether the request succeeds or fails
   */
, always: function (fn) {
    if (this._fulfilled || this._erred) {
      fn(this._responseArgs.resp)
    } else {
      this._completeHandlers.push(fn)
    }
    return this
  }

  /**
   * `fail` will execute when the request fails
   */
, fail: function (fn) {
    if (this._erred) {
      fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
    } else {
      this._errorHandlers.push(fn)
    }
    return this
  }
}

function reqwest(o, fn) {
  return new Reqwest(o, fn)
}

// normalize newline variants according to spec -> CRLF
function normalize(s) {
  return s ? s.replace(/\r?\n/g, '\r\n') : ''
}

function serial(el, cb) {
  var n = el.name
    , t = el.tagName.toLowerCase()
    , optCb = function (o) {
        // IE gives value="" even where there is no value attribute
        // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
        if (o && !o.disabled)
          cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text))
      }
    , ch, ra, val, i

  // don't serialize elements that are disabled or without a name
  if (el.disabled || !n) return

  switch (t) {
  case 'input':
    if (!/reset|button|image|file/i.test(el.type)) {
      ch = /checkbox/i.test(el.type)
      ra = /radio/i.test(el.type)
      val = el.value
      // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
      ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
    }
    break
  case 'textarea':
    cb(n, normalize(el.value))
    break
  case 'select':
    if (el.type.toLowerCase() === 'select-one') {
      optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
    } else {
      for (i = 0; el.length && i < el.length; i++) {
        el.options[i].selected && optCb(el.options[i])
      }
    }
    break
  }
}

// collect up all form elements found from the passed argument elements all
// the way down to child elements; pass a '<form>' or form fields.
// called with 'this'=callback to use for serial() on each element
function eachFormElement() {
  var cb = this
    , e, i
    , serializeSubtags = function (e, tags) {
        var i, j, fa
        for (i = 0; i < tags.length; i++) {
          fa = e[byTag](tags[i])
          for (j = 0; j < fa.length; j++) serial(fa[j], cb)
        }
      }

  for (i = 0; i < arguments.length; i++) {
    e = arguments[i]
    if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
    serializeSubtags(e, [ 'input', 'select', 'textarea' ])
  }
}

// standard query string style serialization
function serializeQueryString() {
  return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
}

// { 'name': 'value', ... } style serialization
function serializeHash() {
  var hash = {}
  eachFormElement.apply(function (name, value) {
    if (name in hash) {
      hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
      hash[name].push(value)
    } else hash[name] = value
  }, arguments)
  return hash
}

// [ { name: 'name', value: 'value' }, ... ] style serialization
reqwest.serializeArray = function () {
  var arr = []
  eachFormElement.apply(function (name, value) {
    arr.push({name: name, value: value})
  }, arguments)
  return arr
}

reqwest.serialize = function () {
  if (arguments.length === 0) return ''
  var opt, fn
    , args = Array.prototype.slice.call(arguments, 0)

  opt = args.pop()
  opt && opt.nodeType && args.push(opt) && (opt = null)
  opt && (opt = opt.type)

  if (opt == 'map') fn = serializeHash
  else if (opt == 'array') fn = reqwest.serializeArray
  else fn = serializeQueryString

  return fn.apply(null, args)
}

reqwest.toQueryString = function (o, trad) {
  var prefix, i
    , traditional = trad || false
    , s = []
    , enc = encodeURIComponent
    , add = function (key, value) {
        // If value is a function, invoke it and return its value
        value = ('function' === typeof value) ? value() : (value == null ? '' : value)
        s[s.length] = enc(key) + '=' + enc(value)
      }
  // If an array was passed in, assume that it is an array of form elements.
  if (isArray(o)) {
    for (i = 0; o && i < o.length; i++) add(o[i].name, o[i].value)
  } else {
    // If traditional, encode the "old" way (the way 1.3.2 or older
    // did it), otherwise encode params recursively.
    for (prefix in o) {
      buildParams(prefix, o[prefix], traditional, add)
    }
  }

  // spaces should be + according to spec
  return s.join('&').replace(/%20/g, '+')
}

function buildParams(prefix, obj, traditional, add) {
  var name, i, v
    , rbracket = /\[\]$/

  if (isArray(obj)) {
    // Serialize array item.
    for (i = 0; obj && i < obj.length; i++) {
      v = obj[i]
      if (traditional || rbracket.test(prefix)) {
        // Treat each array item as a scalar.
        add(prefix, v)
      } else {
        buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add)
      }
    }
  } else if (obj && obj.toString() === '[object Object]') {
    // Serialize object item.
    for (name in obj) {
      buildParams(prefix + '[' + name + ']', obj[name], traditional, add)
    }

  } else {
    // Serialize scalar item.
    add(prefix, obj)
  }
}

reqwest.getcallbackPrefix = function () {
  return callbackPrefix
}

// jQuery and Zepto compatibility, differences can be remapped here so you can call
// .ajax.compat(options, callback)
reqwest.compat = function (o, fn) {
  if (o) {
    o.type && (o.method = o.type) && delete o.type
    o.dataType && (o.type = o.dataType)
    o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback
    o.jsonp && (o.jsonpCallback = o.jsonp)
  }
  return new Reqwest(o, fn)
}

reqwest.ajaxSetup = function (options) {
  options = options || {}
  for (var k in options) {
    globalSetupOptions[k] = options[k]
  }
}

return reqwest

});