﻿/**!
* project-site: http://plugins.jquery.com/project/AjaxManager
* repository: http://github.com/aFarkas/Ajaxmanager
* @author Alexander Farkas
* @version 3.12
* Copyright 2010, Alexander Farkas
* Dual licensed under the MIT or GPL Version 2 licenses.
*/

(function ($) {
    "use strict";
    var managed = {},
		cache = {}
	;
    $.manageAjax = (function () {
        function create(name, opts) {
            managed[name] = new $.manageAjax._manager(name, opts);
            return managed[name];
        }

        function destroy(name) {
            if (managed[name]) {
                managed[name].clear(true);
                delete managed[name];
            }
        }


        var publicFns = {
            create: create,
            destroy: destroy
        };

        return publicFns;
    })();

    $.manageAjax._manager = function (name, opts) {
        this.requests = {};
        this.inProgress = 0;
        this.name = name;
        this.qName = name;

        this.opts = $.extend({}, $.manageAjax.defaults, opts);
        if (opts && opts.queue && opts.queue !== true && typeof opts.queue === 'string' && opts.queue !== 'clear') {
            this.qName = opts.queue;
        }
    };

    $.manageAjax._manager.prototype = {
        add: function (url, o) {
            if (typeof url == 'object') {
                o = url;
            } else if (typeof url == 'string') {
                o = $.extend(o || {}, { url: url });
            }
            o = $.extend({}, this.opts, o);

            var origCom = o.complete || $.noop,
				origSuc = o.success || $.noop,
				beforeSend = o.beforeSend || $.noop,
				origError = o.error || $.noop,
				strData = (typeof o.data == 'string') ? o.data : $.param(o.data || {}),
				xhrID = o.type + o.url + strData,
				that = this,
				ajaxFn = this._createAjax(xhrID, o, origSuc, origCom)
			;
            if (o.preventDoubleRequests && o.queueDuplicateRequests) {
                if (o.preventDoubleRequests) {
                    o.queueDuplicateRequests = false;
                }
                setTimeout(function () {
                    throw ("preventDoubleRequests and queueDuplicateRequests can't be both true");
                }, 0);
            }
            if (this.requests[xhrID] && o.preventDoubleRequests) {
                return;
            }
            ajaxFn.xhrID = xhrID;
            o.xhrID = xhrID;

            o.beforeSend = function (xhr, opts) {
                var ret = beforeSend.call(this, xhr, opts);
                if (ret === false) {
                    that._removeXHR(xhrID);
                }
                xhr = null;
                return ret;
            };
            o.complete = function (xhr, status) {
                that._complete.call(that, this, origCom, xhr, status, xhrID, o);
                xhr = null;
            };

            o.success = function (data, status, xhr) {
                that._success.call(that, this, origSuc, data, status, xhr, o);
                xhr = null;
            };

            //always add some error callback
            o.error = function (ahr, status, errorStr) {
                var httpStatus = '',
					content = ''
				;
                if (status !== 'timeout' && ahr) {
                    httpStatus = ahr.status;
                    content = ahr.responseXML || ahr.responseText;
                }
                if (origError) {
                    origError.call(this, ahr, status, errorStr, o);
                } else {
                    setTimeout(function () {
                        throw status + '| status: ' + httpStatus + ' | URL: ' + o.url + ' | data: ' + strData + ' | thrown: ' + errorStr + ' | response: ' + content;
                    }, 0);
                }
                ahr = null;
            };

            if (o.queue === 'clear') {
                $(document).clearQueue(this.qName);
            }

            if (o.queue || (o.queueDuplicateRequests && this.requests[xhrID])) {
                $.queue(document, this.qName, ajaxFn);
                if (this.inProgress < o.maxRequests && (!this.requests[xhrID] || !o.queueDuplicateRequests)) {
                    $.dequeue(document, this.qName);
                }
                return xhrID;
            }
            return ajaxFn();
        },
        _createAjax: function (id, o, origSuc, origCom) {
            var that = this;
            return function () {
                if (o.beforeCreate.call(o.context || that, id, o) === false) { return; }
                that.inProgress++;
                if (that.inProgress === 1) {
                    $.event.trigger(that.name + 'AjaxStart');
                }
                if (o.cacheResponse && cache[id]) {
                    if (!cache[id].cacheTTL || cache[id].cacheTTL < 0 || ((new Date().getTime() - cache[id].timestamp) < cache[id].cacheTTL)) {
                        that.requests[id] = {};
                        setTimeout(function () {
                            that._success.call(that, o.context || o, origSuc, cache[id]._successData, 'success', cache[id], o);
                            that._complete.call(that, o.context || o, origCom, cache[id], 'success', id, o);
                        }, 0);
                    } else {
                        delete cache[id];
                    }
                }
                if (!o.cacheResponse || !cache[id]) {
                    if (o.async) {
                        that.requests[id] = $.ajax(o);
                    } else {
                        $.ajax(o);
                    }
                }
                return id;
            };
        },
        _removeXHR: function (xhrID) {
            if (this.opts.queue || this.opts.queueDuplicateRequests) {
                $.dequeue(document, this.qName);
            }
            this.inProgress--;
            this.requests[xhrID] = null;
            delete this.requests[xhrID];
        },
        clearCache: function () {
            cache = {};
        },
        _isAbort: function (xhr, status, o) {
            if (!o.abortIsNoSuccess || (!xhr && !status)) {
                return false;
            }
            var ret = !!((!xhr || xhr.readyState === 0 || this.lastAbort === o.xhrID));
            xhr = null;
            return ret;
        },
        _complete: function (context, origFn, xhr, status, xhrID, o) {
            if (this._isAbort(xhr, status, o)) {
                status = 'abort';
                o.abort.call(context, xhr, status, o);
            }
            origFn.call(context, xhr, status, o);

            $.event.trigger(this.name + 'AjaxComplete', [xhr, status, o]);

            if (o.domCompleteTrigger) {
                $(o.domCompleteTrigger)
					.trigger(this.name + 'DOMComplete', [xhr, status, o])
					.trigger('DOMComplete', [xhr, status, o])
				;
            }

            this._removeXHR(xhrID);
            if (!this.inProgress) {
                $.event.trigger(this.name + 'AjaxStop');
            }
            xhr = null;
        },
        _success: function (context, origFn, data, status, xhr, o) {
            var that = this;
            if (this._isAbort(xhr, status, o)) {
                xhr = null;
                return;
            }
            if (o.abortOld) {
                $.each(this.requests, function (name) {
                    if (name === o.xhrID) {
                        return false;
                    }
                    that.abort(name);
                });
            }
            if (o.cacheResponse && !cache[o.xhrID]) {
                if (!xhr) {
                    xhr = {};
                }
                cache[o.xhrID] = {
                    status: xhr.status,
                    statusText: xhr.statusText,
                    responseText: xhr.responseText,
                    responseXML: xhr.responseXML,
                    _successData: data,
                    cacheTTL: o.cacheTTL,
                    timestamp: new Date().getTime()
                };
                if ('getAllResponseHeaders' in xhr) {
                    var responseHeaders = xhr.getAllResponseHeaders();
                    var parsedHeaders;
                    var parseHeaders = function () {
                        if (parsedHeaders) { return; }
                        parsedHeaders = {};
                        $.each(responseHeaders.split("\n"), function (i, headerLine) {
                            var delimiter = headerLine.indexOf(":");
                            parsedHeaders[headerLine.substr(0, delimiter)] = headerLine.substr(delimiter + 2);
                        });
                    };
                    $.extend(cache[o.xhrID], {
                        getAllResponseHeaders: function () { return responseHeaders; },
                        getResponseHeader: function (name) {
                            parseHeaders();
                            return (name in parsedHeaders) ? parsedHeaders[name] : null;
                        }
                    });
                }
            }
            origFn.call(context, data, status, xhr, o);
            $.event.trigger(this.name + 'AjaxSuccess', [xhr, o, data]);
            if (o.domSuccessTrigger) {
                $(o.domSuccessTrigger)
					.trigger(this.name + 'DOMSuccess', [data, o])
					.trigger('DOMSuccess', [data, o])
				;
            }
            xhr = null;
        },
        getData: function (id) {
            if (id) {
                var ret = this.requests[id];
                if (!ret && this.opts.queue) {
                    ret = $.grep($(document).queue(this.qName), function (fn, i) {
                        return (fn.xhrID === id);
                    })[0];
                }
                return ret;
            }
            return {
                requests: this.requests,
                queue: (this.opts.queue) ? $(document).queue(this.qName) : [],
                inProgress: this.inProgress
            };
        },
        abort: function (id) {
            var xhr;
            if (id) {
                xhr = this.getData(id);

                if (xhr && xhr.abort) {
                    this.lastAbort = id;
                    xhr.abort();
                    this.lastAbort = false;
                } else {
                    $(document).queue(
						this.qName, $.grep($(document).queue(this.qName), function (fn, i) {
						    return (fn !== xhr);
						})
					);
                }
                xhr = null;
                return;
            }

            var that = this,
				ids = []
			;
            $.each(this.requests, function (id) {
                ids.push(id);
            });
            $.each(ids, function (i, id) {
                that.abort(id);
            });
        },
        clear: function (shouldAbort) {
            $(document).clearQueue(this.qName);
            if (shouldAbort) {
                this.abort();
            }
        }
    };
    $.manageAjax._manager.prototype.getXHR = $.manageAjax._manager.prototype.getData;
    $.manageAjax.defaults = {
        beforeCreate: $.noop,
        abort: $.noop,
        abortIsNoSuccess: true,
        maxRequests: 1,
        cacheResponse: false,
        async: true,
        domCompleteTrigger: false,
        domSuccessTrigger: false,
        preventDoubleRequests: true,
        queueDuplicateRequests: false,
        cacheTTL: -1,
        queue: false // true, false, clear
    };

    $.each($.manageAjax._manager.prototype, function (n, fn) {
        if (n.indexOf('_') === 0 || !$.isFunction(fn)) { return; }
        $.manageAjax[n] = function (name, o) {
            if (!managed[name]) {
                if (n === 'add') {
                    $.manageAjax.create(name, o);
                } else {
                    return;
                }
            }
            var args = Array.prototype.slice.call(arguments, 1);
            managed[name][n].apply(managed[name], args);
        };
    });

})(jQuery);
