/**
 * Base class for ajax dispatcher
 * Uses jquery ajaxSubmit and loadmask plugins
 * 
 * @param url string
 * @param options object (containerEl, loadingMessage)
 */
function AjaxDispatcher(url, options) {
    this.url = url;
    this.containerEl = (options.containerEl) ? options.containerEl : "body";

    if (options.loadingMessage == null) {
        this.loadingMessage = null;
    } else {
        this.loadingMessage = (options.loadingMessage) ? options.loadingMessage : "Loading...";
    }
}

AjaxDispatcher.prototype.dispatch = function() {
}

AjaxDispatcher.prototype.maskContainer = function() {
    if (this.loadingMessage != null) {
        $(this.containerEl).mask(this.loadingMessage);
    } else {
        $(this.containerEl).mask();
    }
}

AjaxDispatcher.prototype.unmaskContainer = function() {
    $(this.containerEl).unmask();
}



/**
 * Class for dispatching action requests
 * 
 * @param url string
 * @param options object (data, messageEl, successCallback, errorCallback)
 */
function AjaxActionDispatcherJson(url, options) {
    AjaxActionDispatcherJson.superclass.constructor.call(this, url, options);
    
    this.data = (options.data) ? options.data : null;
    this.messageEl = (options.messageEl) ? options.messageEl : null;
    this.successCallback = (options.successCallback) ? options.successCallback : null;
    this.errorCallback = (options.errorCallback) ? options.errorCallback : null;
}

extendClass(AjaxActionDispatcherJson, AjaxDispatcher);

AjaxActionDispatcherJson.prototype.dispatch = function() {
    var t = this;
    
    $.ajax({
        url: this.url,
        data: this.data,
        dataType: 'json',
        success: function(data) {
            t.unmaskContainer();

            if (!data || !data.response || !data.response.status) {
                showTooltipMessage(Config.generalErrorMessage);
                return;
            }

            if (data.response.status == Config.ajaxStatusOk) {
                if ($.isFunction(t.successCallback)) {
                    t.successCallback.call(t, data.response);
                }
            } else if (data.response.status == Config.ajaxStatusError && data.response.message) {
                if ($.isFunction(t.errorCallback)) {
                    this.errorCallback.call(t, data.response);
                } else {
                    if (t.messageEl) {
                        $(t.messageEl).empty().show().html(data.response.message);
                    } else {
                        showTooltipMessage(data.response.message);
                    }
                }
            } else {
                showTooltipMessage(Config.generalErrorMessage);
            }
        },
        error: function() {
            t.unmaskContainer();
            showTooltipMessage(Config.generalErrorMessage);
        }
    });
    
    this.maskContainer();
}



/**
 * Class for dispatching form free post requests
 *
 * @param url string
 * @param options object properties: fields, messagesEl, fieldErrorsEl, invalidFieldClass, errorsContainerClass;
 *                       callbacks: errorsCallback, fieldsErrorsCallback
 */
function AjaxFormFreeDispatcherJson(url, options) {
    AjaxFormFreeDispatcherJson.superclass.constructor.call(this, url, options);

    // properties
    this.fields = (options.fields) ? options.fields : [];
    this.messagesEl = (options.messagesEl) ? options.messagesEl : null;
    this.fieldsErrorsEl = (options.fieldsErrorsEl) ? options.fieldsErrorsEl : null;
    this.invalidFieldClass = (options.invalidFieldClass) ? options.invalidFieldClass : "invalid";
    this.errorsContainerClass = (options.errorsContainerClass) ? options.errorsContainerClass : "error-messages";

    // callback functions
    this.errorsCallback = (options.errorsCallback) ? options.errorsCallback : null;
    this.fieldsErrorsCallback = (options.fieldsErrorsCallback) ? options.fieldsErrorsCallback : null;
}

extendClass(AjaxFormFreeDispatcherJson, AjaxActionDispatcherJson);

AjaxFormFreeDispatcherJson.prototype.dispatch = function() {
    var t = this;

    $.ajax({
        url: this.url,
        data: this.data,
        dataType: 'json',
        success: function(data) {
            t.unmaskContainer();

            if (!data || !data.response || !data.response.status) {
                showTooltipMessage(Config.generalErrorMessage);
                return;
            }

            if (data.response.status == Config.ajaxStatusOk) {
                if ($.isFunction(t.successCallback)) {
                    t.successCallback.call(t, data.response);
                }
            } else if (data.response.status == Config.ajaxStatusError
                && (data.response.message || data.response.errors || data.response.fieldsErrors)) {
                if (data.response.message) {
                    if ($.isFunction(t.errorCallback)) {
                        t.errorCallback.call(t, data.response.message);
                    } else {
                        if (t.messageEl) {
                            $(t.messageEl).html(data.response.message);
                        } else {
                            showTooltipMessage(data.response.message);
                        }
                    }
                } else if (data.response.errors) {
                    if ($.isFunction(t.errorsCallback)) {
                        this.errorsCallback.call(t, data.response.errors);
                    } else {
                        var html = t.getErrorsHtml(data.response.errors);

                        if (t.errorsEl) {
                            $(t.errorsEl).empty().show().html(html);
                        } else {
                            showTooltipMessage(html);
                        }
                    }
                } else if (data.response.fieldsErrors) {
                    if (t.fieldsErrorsEl) {
                        var html = t.getFieldsErrorsHtml(data.response.fieldsErrors);
                        $(t.fieldsErrorsEl).empty().show().html(html);
                    }
                }
            } else {
                showTooltipMessage(Config.generalErrorMessage);
            }
        },
        error: function() {
            t.unmaskContainer();
            showTooltipMessage(Config.generalErrorMessage);
        }
    });

    this.maskContainer();
    this.resetFormErrors();
}

AjaxFormFreeDispatcherJson.prototype.resetFormErrors = function() {
    if (this.errorsEl) {
        $(this.errorsEl).empty().hide();
    }

    if (this.fieldsErrorsEl) {
        $(this.fieldsErrorsEl).empty().hide();
    }

    for (var i in this.fields) {
        $(this.containerEl + " [name=\"" + this.fields[i] + "\"]").removeClass(this.invalidFieldClass);
    }
}

AjaxFormFreeDispatcherJson.prototype.getErrorsHtml = function(errors) {
    var html = "<ul class=\"" + this.errorsContainerClass + "\">";

    for (var i in errors) {
        html += "<li>" + errors[i] + "</li>";
    }

    html += "</ul>";

    return html;
}

AjaxFormFreeDispatcherJson.prototype.getFieldsErrorsHtml = function(errors) {
    var separator = "; ";
    var html = "<ul class=\"" + this.errorsContainerClass + "\">";

    for (var i in errors) {
        var error = errors[i];

        $(this.containerEl + " [name=\"" + error.name + "\"]").addClass(this.invalidFieldClass);
        html += "<li>" + error.label + ": ";

        if ($.isArray(error.messages)) {
            html += error.messages.join(separator);
        } else if ($.isPlainObject(error.messages)) {
            var messages = "";

            for (var i in error.messages) {
                if (messages.length > 0) {
                    messages += separator;
                }

                messages += error.messages[i];
            }

            html += messages;
        } else {
            html += error.messages;
        }

        html += "</li>";

    }

    html += "</ul>";

    return html;
}



/**
 * Class for dispatching form post requests
 * 
 * @param url string
 * @param options object properties: formEl, messagesEl, fieldErrorsEl, invalidFieldClass, errorsContainerClass;
 *                       callbacks: errorsCallback, fieldsErrorsCallback
 */
function AjaxFormDispatcherJson(url, options) {
    AjaxFormDispatcherJson.superclass.constructor.call(this, url, options);
    
    // properties
    this.formEl = (options.formEl) ? options.formEl : null;
    this.messagesEl = (options.messagesEl) ? options.messagesEl : null;
    this.fieldsErrorsEl = (options.fieldsErrorsEl) ? options.fieldsErrorsEl : null;
    this.invalidFieldClass = (options.invalidFieldClass) ? options.invalidFieldClass : "invalid";
    this.errorsContainerClass = (options.errorsContainerClass) ? options.errorsContainerClass : "error-messages";
    
    // callback functions
    this.errorsCallback = (options.errorsCallback) ? options.errorsCallback : null;
    this.fieldsErrorsCallback = (options.fieldsErrorsCallback) ? options.fieldsErrorsCallback : null;
    this.resetErrorsCallback = (options.resetErrorsCallback) ? options.resetErrorsCallback : null;
}

extendClass(AjaxFormDispatcherJson, AjaxActionDispatcherJson);

AjaxFormDispatcherJson.prototype.dispatch = function() {
    var t = this;

    $(this.formEl).ajaxSubmit({
        url: this.url,
        dataType: 'json',
        success: function(data, statusText, xhr, $form) {
            t.unmaskContainer();

            if (!data || !data.response || !data.response.status) {
                showTooltipMessage(Config.generalErrorMessage);
                return;
            }

            if (data.response.status == Config.ajaxStatusOk) {
                if (data.response.action && data.response.action == 'redirect'
                    && data.response.actionData && data.response.actionData.url) {
                    redirect(data.response.actionData.url);
                } else if ($.isFunction(t.successCallback)) {
                    t.successCallback.call(t, data.response);
                }
            } else if (data.response.status == Config.ajaxStatusError
                && (data.response.message || data.response.errors || data.response.fieldsErrors)) {
                if (data.response.message) {
                    if ($.isFunction(t.errorCallback)) {
                        t.errorCallback.call(t, data.response.message);
                    } else {
                        if (t.messageEl) {
                            $(t.messageEl).html(data.response.message);
                        } else {
                            showTooltipMessage(data.response.message);
                        }
                    }
                } else if (data.response.errors) {
                    if ($.isFunction(t.errorsCallback)) {
                        t.errorsCallback.call(t, data.response.errors);
                    } else {
                        var html = t.getErrorsHtml(data.response.errors);

                        if (t.errorsEl) {
                            $(t.errorsEl).empty().show().html(html);
                        } else {
                            showTooltipMessage(html);
                        }
                    }
                } else if (data.response.fieldsErrors) {
                    if ($.isFunction(t.fieldsErrorsCallback)) {
                        t.fieldsErrorsCallback.call(t, data.response.fieldsErrors);
                    } else {
                        if (t.fieldsErrorsEl) {
                            var html = t.getFieldsErrorsHtml(data.response.fieldsErrors);
                            $(t.fieldsErrorsEl).empty().show().html(html);
                        }
                    }
                }
            } else {
                showTooltipMessage(Config.generalErrorMessage);
            }
        },
        error: function() {
            t.unmaskContainer();
            showTooltipMessage(Config.generalErrorMessage);
        }
    });
    
    this.maskContainer();
    this.resetFormErrors();
}

AjaxFormDispatcherJson.prototype.resetFormErrors = function() {
    if (this.errorsEl) {
        $(this.errorsEl).empty().hide();
    }

    if (this.fieldsErrorsEl) {
        $(this.fieldsErrorsEl).empty().hide();
    }

    var form = $(this.formEl).get(0);

    if (form) {
        for (var i = 0; i < form.elements.length; i++) {
            var el = form.elements[i];

            if (el.type == 'hidden') {
                continue;
            }

            $(el).removeClass(this.invalidFieldClass);
        }

        if ($.isFunction(this.resetErrorsCallback)) {
            this.resetErrorsCallback.call(this, form.elements);
        }
    }
}

AjaxFormDispatcherJson.prototype.getErrorsHtml = function(errors) {
    var html = "<ul class=\"" + this.errorsContainerClass + "\">";

    for (var i in errors) {
        html += "<li>" + errors[i] + "</li>";
    }

    html += "</ul>";
    
    return html;
}

AjaxFormDispatcherJson.prototype.getFieldsErrorsHtml = function(errors) {
    var separator = "; ";
    var html = "<ul class=\"" + this.errorsContainerClass + "\">";

    for (var i in errors) {
        var error = errors[i];

        $(this.containerEl + " [name=\"" + error.name + "\"]").addClass(this.invalidFieldClass);
        html += "<li>" + error.label + ": ";

        if ($.isArray(error.messages)) {
            html += error.messages.join(separator);
        } else if ($.isPlainObject(error.messages)) {
            var messages = "";
            
            for (var i in error.messages) {
                if (messages.length > 0) {
                    messages += separator;
                }
                
                messages += error.messages[i];
            }
            
            html += messages;
        } else {
            html += error.messages;
        }

        html += "</li>";

    }

    html += "</ul>";
    
    return html;
}

