text: "use strict";
  
  const gm = require("./gm");
  
  
  class FetchError extends Error {
    constructor(message, response) {
      super(message);
  		this.name = "FetchError";
  		this.response = response;
    }
  }
  
  class Response {
  	constructor(readyState, responseHeaders, responseText, status, statusText) {
  		this.readyState = readyState;
  		this.responseHeaders = responseHeaders;
  		this.responseText = responseText;
  		this.status = status;
  		this.statusText = statusText;
  	}
  }
  
  class ProgressEvent {
    constructor(lengthComputable, loaded, total) {
  		this.lengthComputable = lengthComputable;
  		this.loaded = loaded;
  		this.total = total;
    }
  }
  
  
  function getResponseHeaderMap(allHeaders) {
  	const responseHeaders = {};
  
  	const re = /\s*(.*)\s*:\s*(.*)\s*/;
  	for (const line of allHeaders.replace(/\r\n\s*$/, "").split("\r\n")) {
  		const m = re.exec(line);
  		if (m !== null) {
  			responseHeaders[m[1].toLowerCase()] = m[2];
  		}
  	}
  
  	return responseHeaders;
  }
  
  function convertXhrResponse(xhr) {
  	return new Response(
  		xhr.readyState,
  		getResponseHeaderMap(xhr.getAllResponseHeaders()),
  		xhr.responseText,
  		xhr.status,
  		xhr.statusText);
  }
  
  function requestXhrInternal(method, url, options) {
  	const data = options.data;
  	//const binary = options.binary;
  	const headers = options.headers;
  	const timeout = options.timeout || 0;
  	const onprogress = options.onprogress;
  	const overrideMimeType = options.overrideMimeType;
  
  	return new Promise((resolve, reject) => {
  		const xhr = new XMLHttpRequest();
  
  		xhr.timeout = timeout;
  		if (typeof(overrideMimeType) === "string") {
  			xhr.overrideMimeType(overrideMimeType);
  		}
  		if (headers !== null && typeof(headers) === "object") {
  			for (const k in headers) {
  				if (!Object.prototype.hasOwnProperty.call(headers, k)) { continue; }
  				xhr.setRequestHeader(k, headers[k]);
  			}
  		}
  
  		xhr.addEventListener("load", () => resolve(convertXhrResponse(xhr)));
  		xhr.addEventListener("error", () => reject(new FetchError(`Request error: ${xhr.statusText} (${xhr.status})`, convertXhrResponse(xhr))));
  		xhr.addEventListener("abort", () => reject(new FetchError("Request aborted", convertXhrResponse(xhr))));
  		xhr.addEventListener("timeout", () => reject(new FetchError("Timeout reached", convertXhrResponse(xhr))));
  
  		if (typeof(onprogress) === "function") {
  			xhr.addEventListener("progress", (e) => onprogress(new ProgressEvent(e.lengthComputable, e.loaded, e.total)));
  		}
  
  		xhr.open(method, url, true);
  		xhr.send(data);
  	});
  }
  
  
  function convertGmResponse(response) {
  	return new Response(
  		response.readyState,
  		getResponseHeaderMap(response.responseHeaders),
  		response.responseText,
  		response.status,
  		response.statusText);
  }
  
  function requestGmInternal(method, url, options) {
  	const data = options.data;
  	const binary = options.binary;
  	const headers = options.headers;
  	const timeout = options.timeout || 0;
  	const onprogress = options.onprogress;
  	const overrideMimeType = options.overrideMimeType;
  
  	return new Promise((resolve, reject) => {
  		const details = {
  			method: method,
  			url: url,
  			headers: headers,
  			overrideMimeType: overrideMimeType,
  			data: data,
  			binary: binary,
  			synchronous: false,
  			timeout: timeout
  		};
  
  		details.onload = (e) => resolve(convertGmResponse(e));
  		details.onerror = (e) => reject(new FetchError(`Request error: ${e.statusText} (${e.status})`, convertGmResponse(e)));
  		details.onabort = (e) => reject(new FetchError("Request aborted", convertGmResponse(e)));
  		details.ontimeout = (e) => reject(new FetchError("Timeout reached", convertGmResponse(e)));
  
  		if (typeof(onprogress) === "function") {
  			details.onprogress = (e) => onprogress(new ProgressEvent(e.lengthComputable, e.loaded, e.total));
  		}
  
  		gm.xmlHttpRequest(details);
  	});
  }
  
  
  function isGmSupported(useGm) {
  	return (useGm && typeof(gm.xmlHttpRequest) === "function") ? true : false;
  }
  
  
  function request(options) {
  	if (options === null || typeof(options) !== "object") {
  		return Promise.reject(new Error("Invalid options"));
  	}
  
  	const method = options.method;
  	const url = options.url;
  	return isGmSupported(options.gm) ? requestGmInternal(method, url, options) : requestXhrInternal(method, url, options);
  }
  
  function get(options) {
  	if (options === null || typeof(options) !== "object") {
  		return Promise.reject(new Error("Invalid options"));
  	}
  
  	const method = "GET";
  	const url = options.url;
  	return isGmSupported(options.gm) ? requestGmInternal(method, url, options) : requestXhrInternal(method, url, options);
  }
  
  function post(options) {
  	if (options === null || typeof(options) !== "object") {
  		return Promise.reject(new Error("Invalid options"));
  	}
  
  	const method = "POST";
  	const url = options.url;
  	return isGmSupported(options.gm) ? requestGmInternal(method, url, options) : requestXhrInternal(method, url, options);
  }
  
  
  function requestGm(options) {
  	if (options === null || typeof(options) !== "object") {
  		return Promise.reject(new Error("Invalid options"));
  	}
  
  	const method = options.method;
  	const url = options.url;
  	return isGmSupported(true) ? requestGmInternal(method, url, options) : Promise.reject(new Error("GM not supported"));
  }
  
  function getGm(options) {
  	if (options === null || typeof(options) !== "object") {
  		return Promise.reject(new Error("Invalid options"));
  	}
  
  	const method = "GET";
  	const url = options.url;
  	return isGmSupported(true) ? requestGmInternal(method, url, options) : Promise.reject(new Error("GM not supported"));
  }
  
  function postGm(options) {
  	if (options === null || typeof(options) !== "object") {
  		return Promise.reject(new Error("Invalid options"));
  	}
  
  	const method = "POST";
  	const url = options.url;
  	return isGmSupported(true) ? requestGmInternal(method, url, options) : Promise.reject(new Error("GM not supported"));
  }
  
  
  module.exports = {
  	request: request,
  	get: get,
  	post: post,
  	gm: {
  		request: requestGm,
  		get: getGm,
  		post: postGm,
  	}
  };
  