(function ($) {
    $.fn.ajaxSubmit = function (options) {
        if (!this.length) {
            log("ajaxSubmit: skipping submit process - no element selected");
            return this;
        }
        if (typeof options == "function") {
            options = {
                success: options
            };
        }
        var url = $.trim(this.attr("action"));
        if (url) {
            url = (url.match(/^([^#]+)/) || [])[1];
        }
        url = url || window.location.href || "";
        options = $.extend({
            url: url,
            type: this.attr("method") || "GET",
            iframeSrc: /^https/i.test(window.location.href || "") ? "javascript:false" : "about:blank"
        }, options || {});
        var veto = {};
        this.trigger("form-pre-serialize", [this, options, veto]);
        if (veto.veto) {
            log("ajaxSubmit: submit vetoed via form-pre-serialize trigger");
            return this;
        }
        if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
            log("ajaxSubmit: submit aborted via beforeSerialize callback");
            return this;
        }
        var a = this.formToArray(options.semantic);
        if (options.data) {
            options.extraData = options.data;
            for (var n in options.data) {
                if (options.data[n] instanceof Array) {
                    for (var k in options.data[n]) {
                        a.push({
                            name: n,
                            value: options.data[n][k]
                        });
                    }
                } else {
                    a.push({
                        name: n,
                        value: options.data[n]
                    });
                }
            }
        }
        if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
            log("ajaxSubmit: submit aborted via beforeSubmit callback");
            return this;
        }
        this.trigger("form-submit-validate", [a, this, options, veto]);
        if (veto.veto) {
            log("ajaxSubmit: submit vetoed via form-submit-validate trigger");
            return this;
        }
        var q = $.param(a);
        if (options.type.toUpperCase() == "GET") {
            options.url += (options.url.indexOf("?") >= 0 ? "&" : "?") + q;
            options.data = null;
        } else {
            options.data = q;
        }
        var $form = this,
            callbacks = [];
        if (options.resetForm) {
            callbacks.push(function () {
                $form.resetForm();
            });
        }
        if (options.clearForm) {
            callbacks.push(function () {
                $form.clearForm();
            });
        }
        if (!options.dataType && options.target) {
            var oldSuccess = options.success ||
            function () {};
            callbacks.push(function (data) {
                $(options.target).html(data).each(oldSuccess, arguments);
            });
        } else if (options.success) {
            callbacks.push(options.success);
        }
        options.success = function (data, status) {
            for (var i = 0, max = callbacks.length; i < max; i++) {
                callbacks[i].apply(options, [data, status, $form]);
            }
        };
        var files = $("input:file", this).fieldValue();
        var found = false;
        for (var j = 0; j < files.length; j++) {
            if (files[j]) {
                found = true;
            }
        }
        var multipart = false;
        if (files.length && options.iframe !== false || options.iframe || found || multipart) {
            if (options.closeKeepAlive) {
                $.get(options.closeKeepAlive, fileUpload);
            } else {
                fileUpload();
            }
        } else {
            $.ajax(options);
        }
        this.trigger("form-submit-notify", [this, options]);
        return this;

        function fileUpload() {
            var form = $form[0];
            if ($(":input[name=submit]", form).length) {
                alert("Error: Form elements must not be named \"submit\".");
                return;
            }
            var opts = $.extend({}, $.ajaxSettings, options);
            var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
            var id = "jqFormIO" + (new Date).getTime();
            var $io = $("<iframe id=\"" + id + "\" name=\"" + id + "\" src=\"" + opts.iframeSrc + "\" />");
            var io = $io[0];
            $io.css({
                position: "absolute",
                top: "-1000px",
                left: "-1000px"
            });
            var xhr = {
                aborted: 0,
                responseText: null,
                responseXML: null,
                status: 0,
                statusText: "n/a",
                getAllResponseHeaders: function () {},
                getResponseHeader: function () {},
                setRequestHeader: function () {},
                abort: function () {
                    this.aborted = 1;
                    $io.attr("src", opts.iframeSrc);
                }
            };
            var g = opts.global;
            if (g && !$.active++) {
                $.event.trigger("ajaxStart");
            }
            if (g) {
                $.event.trigger("ajaxSend", [xhr, opts]);
            }
            if (s.beforeSend && s.beforeSend(xhr, s) === false) {
                s.global && $.active--;
                return;
            }
            if (xhr.aborted) {
                return;
            }
            var cbInvoked = 0;
            var timedOut = 0;
            var sub = form.clk;
            if (sub) {
                var n = sub.name;
                if (n && !sub.disabled) {
                    options.extraData = options.extraData || {};
                    options.extraData[n] = sub.value;
                    if (sub.type == "image") {
                        options.extraData[name + ".x"] = form.clk_x;
                        options.extraData[name + ".y"] = form.clk_y;
                    }
                }
            }
            setTimeout(function () {
                var t = $form.attr("target"),
                    a = $form.attr("action");
                form.setAttribute("target", id);
                if (form.getAttribute("method") != "POST") {
                    form.setAttribute("method", "POST");
                }
                if (form.getAttribute("action") != opts.url) {
                    form.setAttribute("action", opts.url);
                }
                if (!options.skipEncodingOverride) {
                    $form.attr({
                        encoding: "multipart/form-data",
                        enctype: "multipart/form-data"
                    });
                }
                if (opts.timeout) {
                    setTimeout(function () {
                        timedOut = true;
                        cb();
                    }, opts.timeout);
                }
                var extraInputs = [];
                try {
                    if (options.extraData) {
                        for (var n in options.extraData) {
                            extraInputs.push($("<input type=\"hidden\" name=\"" + n + "\" value=\"" + options.extraData[n] + "\" />").appendTo(form)[0]);
                        }
                    }
                    $io.appendTo("body");
                    io.attachEvent ? io.attachEvent("onload", cb) : io.addEventListener("load", cb, false);
                    form.submit();
                } finally {
                    form.setAttribute("action", a);
                    t ? form.setAttribute("target", t) : $form.removeAttr("target");
                    $(extraInputs).remove();
                }
            }, 10);
            var domCheckCount = 50;

            function cb() {
                if (cbInvoked++) {
                    return;
                }
                io.detachEvent ? io.detachEvent("onload", cb) : io.removeEventListener("load", cb, false);
                var ok = true;
                try {
                    if (timedOut) {
                        throw "timeout";
                    }
                    var data, doc;
                    doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
                    var isXml = opts.dataType == "xml" || doc.XMLDocument || $.isXMLDoc(doc);
                    log("isXml=" + isXml);
                    if (!isXml && (doc.body == null || doc.body.innerHTML == "")) {
                        if (--domCheckCount) {
                            cbInvoked = 0;
                            setTimeout(cb, 100);
                            return;
                        }
                        log("Could not access iframe DOM after 50 tries.");
                        return;
                    }
                    xhr.responseText = doc.body ? doc.body.innerHTML : null;
                    xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
                    xhr.getResponseHeader = function (header) {
                        var headers = {
                            'content-type': opts.dataType
                        };
                        return headers[header];
                    };
                    if (opts.dataType == "json" || opts.dataType == "script") {
                        var ta = doc.getElementsByTagName("textarea")[0];
                        if (ta) {
                            xhr.responseText = ta.value;
                        } else {
                            var pre = doc.getElementsByTagName("pre")[0];
                            if (pre) {
                                xhr.responseText = pre.innerHTML;
                            }
                        }
                    } else if (opts.dataType == "xml" && !xhr.responseXML && xhr.responseText != null) {
                        xhr.responseXML = toXml(xhr.responseText);
                    }
                    data = $.httpData(xhr, opts.dataType);
                } catch (e) {
                    ok = false;
                    $.handleError(opts, xhr, "error", e);
                }
                if (ok) {
                    opts.success(data, "success");
                    if (g) {
                        $.event.trigger("ajaxSuccess", [xhr, opts]);
                    }
                }
                if (g) {
                    $.event.trigger("ajaxComplete", [xhr, opts]);
                }
                if (g && !--$.active) {
                    $.event.trigger("ajaxStop");
                }
                if (opts.complete) {
                    opts.complete(xhr, ok ? "success" : "error");
                }
                setTimeout(function () {
                    $io.remove();
                    xhr.responseXML = null;
                }, 100);
            }
            function toXml(s, doc) {
                if (window.ActiveXObject) {
                    doc = new ActiveXObject("Microsoft.XMLDOM");
                    doc.async = "false";
                    doc.loadXML(s);
                } else {
                    doc = (new DOMParser).parseFromString(s, "text/xml");
                }
                return doc && doc.documentElement && doc.documentElement.tagName != "parsererror" ? doc : null;
            }
        }
    };
    $.fn.ajaxForm = function (options) {
        return this.ajaxFormUnbind().bind("submit.form-plugin", function () {
            $(this).ajaxSubmit(options);
            return false;
        }).bind("click.form-plugin", function (e) {
            var target = e.target;
            var $el = $(target);
            if (!$el.is(":submit,input:image")) {
                var t = $el.closest(":submit");
                if (t.length == 0) {
                    return;
                }
                target = t[0];
            }
            var form = this;
            form.clk = target;
            if (target.type == "image") {
                if (e.offsetX != undefined) {
                    form.clk_x = e.offsetX;
                    form.clk_y = e.offsetY;
                } else if (typeof $.fn.offset == "function") {
                    var offset = $el.offset();
                    form.clk_x = e.pageX - offset.left;
                    form.clk_y = e.pageY - offset.top;
                } else {
                    form.clk_x = e.pageX - target.offsetLeft;
                    form.clk_y = e.pageY - target.offsetTop;
                }
            }
            setTimeout(function () {
                form.clk = form.clk_x = form.clk_y = null;
            }, 100);
        });
    };
    $.fn.ajaxFormUnbind = function () {
        return this.unbind("submit.form-plugin click.form-plugin");
    };
    $.fn.formToArray = function (semantic) {
        var a = [];
        if (this.length == 0) {
            return a;
        }
        var form = this[0];
        var els = semantic ? form.getElementsByTagName("*") : form.elements;
        if (!els) {
            return a;
        }
        for (var i = 0, max = els.length; i < max; i++) {
            var el = els[i];
            var n = el.name;
            if (!n) {
                continue;
            }
            if (semantic && form.clk && el.type == "image") {
                if (!el.disabled && form.clk == el) {
                    a.push({
                        name: n,
                        value: $(el).val()
                    });
                    a.push({
                        name: n + ".x",
                        value: form.clk_x
                    }, {
                        name: n + ".y",
                        value: form.clk_y
                    });
                }
                continue;
            }
            var v = $.fieldValue(el, true);
            if (v && v.constructor == Array) {
                for (var j = 0, jmax = v.length; j < jmax; j++) {
                    a.push({
                        name: n,
                        value: v[j]
                    });
                }
            } else if (v !== null && typeof v != "undefined") {
                a.push({
                    name: n,
                    value: v
                });
            }
        }
        if (!semantic && form.clk) {
            var $input = $(form.clk),
                input = $input[0],
                n = input.name;
            if (n && !input.disabled && input.type == "image") {
                a.push({
                    name: n,
                    value: $input.val()
                });
                a.push({
                    name: n + ".x",
                    value: form.clk_x
                }, {
                    name: n + ".y",
                    value: form.clk_y
                });
            }
        }
        return a;
    };
    $.fn.formSerialize = function (semantic) {
        return $.param(this.formToArray(semantic));
    };
    $.fn.fieldSerialize = function (successful) {
        var a = [];
        this.each(function () {
            var n = this.name;
            if (!n) {
                return;
            }
            var v = $.fieldValue(this, successful);
            if (v && v.constructor == Array) {
                for (var i = 0, max = v.length; i < max; i++) {
                    a.push({
                        name: n,
                        value: v[i]
                    });
                }
            } else if (v !== null && typeof v != "undefined") {
                a.push({
                    name: this.name,
                    value: v
                });
            }
        });
        return $.param(a);
    };
    $.fn.fieldValue = function (successful) {
        for (var val = [], i = 0, max = this.length; i < max; i++) {
            var el = this[i];
            var v = $.fieldValue(el, successful);
            if (v === null || typeof v == "undefined" || v.constructor == Array && !v.length) {
                continue;
            }
            v.constructor == Array ? $.merge(val, v) : val.push(v);
        }
        return val;
    };
    $.fieldValue = function (el, successful) {
        var n = el.name,
            t = el.type,
            tag = el.tagName.toLowerCase();
        if (typeof successful == "undefined") {
            successful = true;
        }
        if (successful && (!n || el.disabled || t == "reset" || t == "button" || (t == "checkbox" || t == "radio") && !el.checked || (t == "submit" || t == "image") && el.form && el.form.clk != el || tag == "select" && el.selectedIndex == -1)) {
            return null;
        }
        if (tag == "select") {
            var index = el.selectedIndex;
            if (index < 0) {
                return null;
            }
            var a = [],
                ops = el.options;
            var one = t == "select-one";
            var max = one ? index + 1 : ops.length;
            for (var i = one ? index : 0; i < max; i++) {
                var op = ops[i];
                if (op.selected) {
                    var v = op.value;
                    if (!v) {
                        v = op.attributes && op.attributes.value && !op.attributes.value.specified ? op.text : op.value;
                    }
                    if (one) {
                        return v;
                    }
                    a.push(v);
                }
            }
            return a;
        }
        return el.value;
    };
    $.fn.clearForm = function () {
        return this.each(function () {
            $("input,select,textarea", this).clearFields();
        });
    };
    $.fn.clearFields = $.fn.clearInputs = function () {
        return this.each(function () {
            var t = this.type,
                tag = this.tagName.toLowerCase();
            if (t == "text" || t == "password" || tag == "textarea") {
                this.value = "";
            } else if (t == "checkbox" || t == "radio") {
                this.checked = false;
            } else if (tag == "select") {
                this.selectedIndex = -1;
            }
        });
    };
    $.fn.resetForm = function () {
        return this.each(function () {
            if (typeof this.reset == "function" || typeof this.reset == "object" && !this.reset.nodeType) {
                this.reset();
            }
        });
    };
    $.fn.enable = function (b) {
        if (b == undefined) {
            b = true;
        }
        return this.each(function () {
            this.disabled = !b;
        });
    };
    $.fn.selected = function (select) {
        if (select == undefined) {
            select = true;
        }
        return this.each(function () {
            var t = this.type;
            if (t == "checkbox" || t == "radio") {
                this.checked = select;
            } else if (this.tagName.toLowerCase() == "option") {
                var $sel = $(this).parent("select");
                if (select && $sel[0] && $sel[0].type == "select-one") {
                    $sel.find("option").selected(false);
                }
                this.selected = select;
            }
        });
    };

    function log() {
        if ($.fn.ajaxSubmit.debug && window.console && window.console.log) {
            window.console.log("[jquery.form] " + Array.prototype.join.call(arguments, ""));
        }
    }
})(jQuery);
