AJAX = new (function AJAX() {
		
	function newXHR() {
		return !!self.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
	}

    function serialize(obj, prefix) {
        var str = [];
        for (var p in obj) {
          if (obj.hasOwnProperty(p)) {
            var k = prefix ? prefix + "[" + p + "]" : p,
                  v = obj[p];
              str.push(typeof v == "object" ? serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
          }
        }
        return str.join("&");
    }
	
	function decodeResponse(type, response) {
		try {			
			switch (type) {
				case "application/json":
					return JSON.parse(response);
				default:
					return response;
			}
		} catch(e) {
			
		}
		return response;
	}
	
	function doReject(strict, result){
		return !!strict ? Promise.reject(result) : Promise.resolve(result);
	}

    function doRequest(url, method, params, strict) {
        return new Promise(function(resolve, reject) {
			try {
				if (typeof url != 'string') {
					return doReject(strict, {
						success: false,
						response: 'url parameter must be string',
						status: 0
					}).then(resolve, reject);
				}
				if (typeof method != 'string') {
					return doReject(strict, {
						success: false,
						response: 'method parameter must be string',
						status: 0
					}).then(resolve, reject);
				}
				method = method.toUpperCase();
				var xhr = newXHR();
				xhr.onload = function(e) {
					if (e.target.status == 200) {
						var response = decodeResponse(e.target.getResponseHeader("Content-Type"), e.target.response);
						resolve({
							success: true,
							response: response,
							status: e.target.status
						});	
					} else {
						var statusText = e.target.statusText;
						if (!statusText.length) statusText = "unexpected error";
						doReject(strict, {
							success: false,
							response: statusText, 
							status: e.target.status
						}).then(resolve, reject);
					}
				}
				xhr.onerror = function(e) {
					var statusText = e.target.statusText;
					if (!statusText.length) statusText = "unexpected error";
					doReject(strict, {
						success: false,
						response: statusText,
						status: e.target.status
					}).then(resolve, reject);
				}
				xhr.onabort = function(e) {
					doReject(strict, {
						success: false,
						response: 'download aborted',
						status: 0
					}).then(resolve, reject);
				}
				params = typeof params == 'object' ? serialize(params) : '';
				if (method === 'GET') {
					if (!!params.length) params = "?" + params;
					xhr.open(method, url+params);
					xhr.send();
				} else {
					xhr.open(method, url);
					xhr.send(!!params.length ? params : undefined);
				}
			} catch(e) {
					doReject(strict, {
						success: false,
						response: e.message,
						status: 0
					}).then(resolve, reject);
			}
        });
    }
	
	this.request = function(requests, strict) {
		if (typeof strict != 'boolean') strict = true;
		try {
			if (typeof requests == 'string') {
				return doRequest(requests, 'GET', [], strict);
			} else {
				if (typeof requests == 'object') {
					if (Array.isArray(requests)) {
						var proms = [];
						for (var i = 0; i < requests.length; ++i)
							proms.push(this.request(requests[i], strict));
						return Promise.all(proms);
					} else {
						return doRequest(requests.url, requests.method, requests.params, strict);
					}
				}
				throw new Error('wrong type (string/array/dict expected, '+(typeof requests)+' detected)');
			}
		} catch(e) {
			return doReject(strict, {
				success: false,
				response: e.message,
				status: 0
			});
		}
	}

	return Object.freeze(this);
	
});