/* fetch.js

	Purpose:

	Description:
		this is a polyfill for Fetch API from https://github.com/github/fetch v0.11.1
	History:
		Wed May 13 12:31:40 CST 2020, Created by leon

Copyright (C) 2020 Potix Corporation. All Rights Reserved.

*/
// support FormData with Ajax upload under the fetch implementation since ZK 10.0.0
(function (self) {
	'use strict';
	if (self.ajaxFetch) {
		return
	}

	function normalizeName(name) {
		if (typeof name !== 'string') {
			name = String(name)
		}
		if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
			throw new TypeError('Invalid character in header field name')
		}
		return name.toLowerCase()
	}

	function normalizeValue(value) {
		if (typeof value !== 'string') {
			value = String(value)
		}
		return value
	}

	function Headers(headers) {
		this.map = {}

		if (headers instanceof Headers) {
			headers.forEach(function (value, name) {
				this.append(name, value)
			}, this)

		} else if (headers) {
			Object.getOwnPropertyNames(headers).forEach(function (name) {
				this.append(name, headers[name])
			}, this)
		}
	}

	Headers.prototype.append = function (name, value) {
		name = normalizeName(name)
		value = normalizeValue(value)
		var list = this.map[name]
		if (!list) {
			list = []
			this.map[name] = list
		}
		list.push(value)
	}

	Headers.prototype['delete'] = function (name) {
		delete this.map[normalizeName(name)]
	}

	Headers.prototype.get = function (name) {
		var values = this.map[normalizeName(name)]
		return values ? values[0] : null
	}

	Headers.prototype.getAll = function (name) {
		return this.map[normalizeName(name)] || []
	}

	Headers.prototype.has = function (name) {
		return this.map.hasOwnProperty(normalizeName(name))
	}

	Headers.prototype.set = function (name, value) {
		this.map[normalizeName(name)] = [normalizeValue(value)]
	}

	Headers.prototype.forEach = function (callback, thisArg) {
		Object.getOwnPropertyNames(this.map).forEach(function (name) {
			this.map[name].forEach(function (value) {
				callback.call(thisArg, value, name, this)
			}, this)
		}, this)
	}

	function consumed(body) {
		if (body.bodyUsed) {
			return Promise.reject(new TypeError('Already read'))
		}
		body.bodyUsed = true
	}

	function fileReaderReady(reader) {
		return new Promise(function (resolve, reject) {
			reader.onload = function () {
				resolve(reader.result)
			}
			reader.onerror = function () {
				reject(reader.error)
			}
		})
	}

	function readBlobAsArrayBuffer(blob) {
		var reader = new FileReader()
		reader.readAsArrayBuffer(blob)
		return fileReaderReady(reader)
	}

	function readBlobAsText(blob) {
		var reader = new FileReader()
		reader.readAsText(blob)
		return fileReaderReady(reader)
	}

	var support = {
		blob: 'FileReader' in self && 'Blob' in self && (function () {
			try {
				new Blob()
				return true
			} catch (e) {
				return false
			}
		})(),
		formData: 'FormData' in self,
		arrayBuffer: 'ArrayBuffer' in self
	}

	function Body() {
		this.bodyUsed = false


		this._initBody = function (body) {
			this._bodyInit = body
			if (typeof body === 'string') {
				this._bodyText = body
			} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
				this._bodyBlob = body
			} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
				this._bodyFormData = body
			} else if (!body) {
				this._bodyText = ''
			} else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) {
				// Only support ArrayBuffers for POST method.
				// Receiving ArrayBuffers happens via Blobs, instead.
			} else {
				throw new Error('unsupported BodyInit type')
			}

			if (!this.headers.get('content-type')) {
				if (typeof body === 'string') {
					this.headers.set('content-type', 'text/plain;charset=UTF-8')
				} else if (this._bodyBlob && this._bodyBlob.type) {
					this.headers.set('content-type', this._bodyBlob.type)
				}
			}
		}

		if (support.blob) {
			this.blob = function () {
				var rejected = consumed(this)
				if (rejected) {
					return rejected
				}

				if (this._bodyBlob) {
					return Promise.resolve(this._bodyBlob)
				} else if (this._bodyFormData) {
					throw new Error('could not read FormData body as blob')
				} else {
					return Promise.resolve(new Blob([this._bodyText]))
				}
			}

			this.arrayBuffer = function () {
				return this.blob().then(readBlobAsArrayBuffer)
			}

			this.text = function () {
				var rejected = consumed(this)
				if (rejected) {
					return rejected
				}

				if (this._bodyBlob) {
					return readBlobAsText(this._bodyBlob)
				} else if (this._bodyFormData) {
					throw new Error('could not read FormData body as text')
				} else {
					return Promise.resolve(this._bodyText)
				}
			}
		} else {
			this.text = function () {
				var rejected = consumed(this)
				return rejected ? rejected : Promise.resolve(this._bodyText)
			}
		}

		if (support.formData) {
			this.formData = function () {
				return this.text().then(decode)
			}
		}

		this.json = function () {
			return this.text().then(JSON.parse)
		}

		return this
	}

	// HTTP methods whose capitalization should be normalized
	var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']

	function normalizeMethod(method) {
		var upcased = method.toUpperCase()
		return (methods.indexOf(upcased) > -1) ? upcased : method
	}

	function Request(input, options) {
		options = options || {}
		var body = options.body
		if (Request.prototype.isPrototypeOf(input)) {
			if (input.bodyUsed) {
				throw new TypeError('Already read')
			}
			this.url = input.url
			this.credentials = input.credentials
			if (!options.headers) {
				this.headers = new Headers(input.headers)
			}
			this.method = input.method
			this.mode = input.mode
			if (!body) {
				body = input._bodyInit
				input.bodyUsed = true
			}
		} else {
			this.url = input
		}

		this.credentials = options.credentials || this.credentials || 'omit'
		if (options.headers || !this.headers) {
			this.headers = new Headers(options.headers)
		}
		this.method = normalizeMethod(options.method || this.method || 'GET')
		this.mode = options.mode || this.mode || null
		this.referrer = null

		if ((this.method === 'GET' || this.method === 'HEAD') && body) {
			throw new TypeError('Body not allowed for GET or HEAD requests')
		}
		this._initBody(body)
	}

	Request.prototype.clone = function () {
		return new Request(this)
	}

	function decode(body) {
		var form = new FormData()
		body.trim().split('&').forEach(function (bytes) {
			if (bytes) {
				var split = bytes.split('=')
				var name = split.shift().replace(/\+/g, ' ')
				var value = split.join('=').replace(/\+/g, ' ')
				form.append(decodeURIComponent(name), decodeURIComponent(value))
			}
		})
		return form
	}

	function headers(xhr) {
		var head = new Headers()
		var pairs = (xhr.getAllResponseHeaders() || '').trim().split('\n')
		pairs.forEach(function (header) {
			var split = header.trim().split(':')
			var key = split.shift().trim()
			var value = split.join(':').trim()
			head.append(key, value)
		})
		return head
	}

	Body.call(Request.prototype)

	function Response(bodyInit, options) {
		if (!options) {
			options = {}
		}

		this.type = 'default'
		this.status = options.status
		this.ok = this.status >= 200 && this.status < 300
		this.statusText = options.statusText
		this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
		this.url = options.url || ''
		this._initBody(bodyInit)
	}

	Body.call(Response.prototype)

	Response.prototype.clone = function () {
		return new Response(this._bodyInit, {
			status: this.status,
			statusText: this.statusText,
			headers: new Headers(this.headers),
			url: this.url
		})
	}

	Response.error = function () {
		var response = new Response(null, {status: 0, statusText: ''})
		response.type = 'error'
		return response
	}

	var redirectStatuses = [301, 302, 303, 307, 308]

	Response.redirect = function (url, status) {
		if (redirectStatuses.indexOf(status) === -1) {
			throw new RangeError('Invalid status code')
		}

		return new Response(null, {status: status, headers: {location: url}})
	}

	self.ajaxFetch = function (input, init) {
		return new Promise(function (resolve, reject) {
			var request
			if (Request.prototype.isPrototypeOf(input) && !init) {
				request = input
			} else {
				request = new Request(input, init)
			}

			var xhr = new XMLHttpRequest()

			function responseURL() {
				if ('responseURL' in xhr) {
					return xhr.responseURL
				}

				// Avoid security warnings on getResponseHeader when not allowed by CORS
				if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
					return xhr.getResponseHeader('X-Request-URL')
				}

				return
			}
			var reqInfo = zAu.ajaxReqInf,
				uploadCallbacks = reqInfo ? reqInfo.uploadCallbacks : [];
			xhr.onload = function () {
				if (uploadCallbacks.length) {
					for (var i = 0, j = uploadCallbacks.length; i < j; i++) {
						if (uploadCallbacks[i]['onload']) {
							uploadCallbacks[i]['onload'].apply(xhr, arguments);
						}
					}
				}
				var status = (xhr.status === 1223) ? 204 : xhr.status
				if (status < 100 || status > 599) {
					reject(new TypeError('Network request failed'))
					return
				}
				var options = {
					status: status,
					statusText: xhr.statusText,
					headers: headers(xhr),
					url: responseURL()
				}
				var body = 'response' in xhr ? xhr.response : xhr.responseText
				resolve(new Response(body, options))
			}

			xhr.onerror = function () {

				if (uploadCallbacks.length) {
					for (var i = 0, j = uploadCallbacks.length; i < j; i++) {
						if (uploadCallbacks[i]['onerror']) {
							uploadCallbacks[i]['onerror'].apply(xhr, arguments);
						}
					}
				}
				reject(new TypeError('Network request failed'))
			}

			if (uploadCallbacks.length) {
				xhr.upload.onprogress = function () {
					for (var i = 0, j = uploadCallbacks.length; i < j; i++) {
						if (uploadCallbacks[i]['onprogress']) {
							uploadCallbacks[i]['onprogress'].apply(xhr, arguments);
						}
					}
				}
			}

			xhr.ontimeout = function () {
				reject(new TypeError('Network request failed'))
			}

			xhr.open(request.method, request.url, true)

			if (request.credentials === 'include') {
				xhr.withCredentials = true
			}

			if ('responseType' in xhr && support.blob) {
				xhr.responseType = 'blob'
			}

			request.headers.forEach(function (value, name) {
				xhr.setRequestHeader(name, value)
			})

			xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
		})
	}

	// old browser like IE9
	if (!self.fetch) {
		self.Headers = Headers
		self.Request = Request
		self.Response = Response
		self.fetch = self.ajaxFetch;
	} else {
		var oldFetch = self.fetch;
		self.fetch = function (input, init) {
			// only FormData with uploadCallbacks uses ajaxFetch, otherwise, uses
			// the browser fetch instead.
			if (init && init.body instanceof FormData) {
				var reqInfo = zAu.ajaxReqInf;
				if (reqInfo && reqInfo.uploadCallbacks && reqInfo.uploadCallbacks.length) {
					return self.ajaxFetch.apply(self, arguments);
				}
			}
			return oldFetch.apply(self, arguments);
		}
	}
	self.ajaxFetch.polyfill = true
})(typeof self !== 'undefined' ? self : this);
