var CKEditors = {};

//// FORM
function FormSend(options) {
    var defaults = {
        form: '#form',
        target: '#response',
        type: 'post',
        validate: true,
        validateHidden: false,
        timeout: 30000,
        beforeSerialize: null,
        beforeSubmit: null,
        onTimeout: null,
        onComplete: null,
        onSuccess: null,
        onError: null,
        onValidateError: null,
        block: true,
        progress: false,
        progressBar: '.progress-bar',
        FormReport: true,
        clearForm: null,
        url: null,
        suffix: SUFFIX,
        preventDblClick: ''
    };
    var params = $.extend({}, defaults, options);
    var form = params.form instanceof jQuery ? params.form : $(params.form);
    if (!form.length) {
        alert('Form not found');
        return false;
    }

    // Prevent Multiple Clicks
    if (params.preventDblClick && !PreventDoubleClick(params.preventDblClick)) {
        return false;
    }

    form.ajaxSubmit({
        target: params.target,
        type: params.type,
        /* url: params.url ? params.suffix + params.url : params.suffix + form.attr('action'), */
        // BEFORE SERIALIZE
        beforeSerialize: function (form, options) {
            // UPDATE CKEDITOR
            if (typeof CKEditors != 'undefined') {
                for (const key in CKEditors) {
                    let editor = CKEditors[key];
                    editor.updateSourceElement();
                }
            }
            if (typeof params.beforeSerialize == 'function') {
                params.beforeSerialize();
            }
            return true;
        },
        // BEFORE SUBMIT
        beforeSubmit: function (fields, form, options) {
            if (typeof params.beforeSubmit == 'function') {
                params.beforeSubmit();
            }
            if (params.validate && typeof params.validate == 'function') {
                if (!params.validate(fields, form, options, params)) {
                    !params.preventDblClick || PreventDoubleClickClear(params.preventDblClick);
                    return false;
                }
            } else if (params.validate) {
                if (!FormValidate(fields, form, options, params)) {
                    !params.preventDblClick || PreventDoubleClickClear(params.preventDblClick);
                    return false;
                }
            }
            if (params.block) {
                block({
                    progress: params.progress
                });
            }
        },
        // SUCCESS
        statusCode: {
            200: function (response, statusText, xhr) {
                if (typeof params.onSuccess == 'function') {
                    params.onSuccess(response, statusText, xhr);
                }

                !params.clearForm || form.trigger('reset');
            }
        },
        // ERROR
        error: function (xhr, textStatus, errorThrown) {
            // Timeout
            if (textStatus == 'timeout') {
                if (typeof params.onTimeout == 'function') {
                    params.onTimeout(xhr, textStatus, errorThrown);
                } else {
                    alert('Request Timeout');
                }
            }
            // Regular
            else {
                if (typeof params.onError == 'function') {
                    params.onError(xhr, textStatus, errorThrown);
                }
                if (params.FormReport && xhr.getResponseHeader('Report')) {
                    FormReport($.parseJSON(xhr.getResponseHeader('Report')), form);
                }
            }
        },
        // COMPLETE
        complete: function (xhr, textStatus) {
            if (typeof params.onComplete == 'function') {
                params.onComplete(xhr, textStatus);
            }
            !params.preventDblClick || PreventDoubleClickClear(params.preventDblClick);
            !params.block || unblock();
        },
        // UPLOAD
        uploadProgress: function (event, position, total, percentComplete) {
            var percentVal = percentComplete + '%';
            $(params.progressBar).width(percentVal).html(percentVal);
        },
        // AJAX METHODS
        timeout: params.timeout
    });
}

function FormReport(json, form) {
    var form = form.jquery ? form : $(form);
    var field;
    // Print Report
    $.each(json, function (name, errors) {
        field = form.find('#' + name);
        if (field.length) {
            $.each(errors, function (i, k) {
                field.addClass('is-invalid').after('<div class="invalid-feedback">' + k.message + '</div>');
            });
        } else {
            alerta(errors);
        }
    });
}

function FormValidate(fields, form, options, params) {
    var form = form instanceof jQuery ? form : $(form);
    form.find('.is-invalid, .is-valid').removeClass('is-invalid is-valid');
    form.find('.invalid-feedback, .valid-feedback').remove();

    var Error = {};
    var name = field = null;
    for (var i = 0; i < fields.length; i++) {
        name = fields[i].name;
        if (name.includes('[]')) {
            continue;
        }
        field = form.find('#' + name);
        // Validar Todos
        if (params.validateHidden) {
            if (field.hasClass('required') && !field.val()) {
                Error[name] = [{message: field.data('required')}];
            }
        }
        // Validar apenas se campo estiver visivel
        else {
            if (field.is(':visible') && field.hasClass('required') && !field.val()) {
                Error[name] = [{message: field.data('required')}];
            }
        }
    }

    if (Object.keys(Error).length) {
        FormReport(Error, form);
        typeof params.onValidateError == 'function' ?
            params.onValidateError(Error, form) : null;
        return false;
    } else {
        return true;
    }
}

function FormValidateAlert(fields, form, options, params) {
    var Error = [];

    for (var i = 0; i < fields.length; i++) {
        var field = $('#' + fields[i].name);
        // Validar Todos
        if (params.validateHidden) {
            if (field.hasClass('required') && !field.val()) {
                Error.push(field.data('required'));
            }
        }
        // Validar apenas se campo estiver visivel
        else {
            if (field.is(':visible') && field.hasClass('required') && !field.val()) {
                Error.push(field.data('required'));
            }
        }
    }

    if (Error.length) {
        alerta(Error.join('<br>'));
        return false;
    } else {
        return true;
    }
}

//// BLOCK
$.blockUI.defaults.css = {};
var blockTimeout = 0;
block = options => {
    let defaults = {
        selector: false,
        title: 'Aguarde',
        text: '',
        addClass: '',
        zIndex: 2000,
        customMsg: false,
        load: true,
        progress: false,
        message: ''
    };
    let params = $.extend({}, defaults, options);
    if (!params.selector && $('.blockOverlay').length) {
        return true;
    }
    if (params.customMsg) {
        params.message = params.customMsg;
    } else {
        if (params.progress) {
            params.message += '<div class="progress mb-2"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div></div>';
        } else if (params.load) {
            params.message += '<div class="css-load"></div>';
        }
        params.message += params.title ? '<h1>' + params.title + '</h1>' : '';
        params.message += params.text ? '<p>' + params.text + '</p>' : '';
    }

    let settings = $.extend({}, params, {
        message: params.message,
        addClass: params.addClass,
        baseZ: params.zIndex ? params.zIndex : 1000
    });

    blockTimeout = setTimeout(function () {
        params.selector ? $(params.selector).block(settings) : $.blockUI(settings);
    }, 250);
}

unblock = selector => {
    clearTimeout(blockTimeout);
    selector ? $(selector).unblock() : $.unblockUI();
}

function CloseReload() {
    if (typeof datatable != 'undefined') {
        datatable.rows().deselect();
        datatable.draw();
    }
    setTimeout(function () {
        unblock();
        closeLastDialog();
    }, 300);
}

closeDialog = element => {
    let selector = element || '.modal';
    $(selector).last().modal('hide');
}


function closeLastDialog(element, twice) {
    var selector = element || '.modal';
    $(selector).last().modal('hide');

    if (twice) {
        $(selector).last().modal('hide');
    }
}

function ShowLoading(element, customClass) {
    var obj = obj instanceof jQuery ? element : $(element);
    var css = customClass || '';
    if (obj.length) {
        obj.html('<div class="css-load-wrap ' + css + '"><div class="css-load align-center"></div></div>');
    }
}

function HideLoading(element, customClass) {
    var obj = obj instanceof jQuery ? element : $(element);
    if (obj.length) {
        obj.html('');
    }
}

function ShowModalButtons(showPrimary) {
    if (showPrimary) {
        $('.modal-footer').show().find('.btn').show();
    } else {
        $('.modal-footer').show().find('.btn').not('.btn-primary').show();
    }
}

function HideModalButtons(onlyPrimary) {
    var all = typeof onlyPrimary == 'undefined' ? true : false;
    if (all) {
        $('.modal-footer').hide().find('.btn').hide();
    } else {
        $('.modal-footer').find('.btn-primary').hide();
    }
}

function GenerateUniqId() {
    function _p8(s) {
        var p = (Math.random().toString(16) + "000000000").substr(2, 8);
        return s ? p.substr(0, 4) + p.substr(4, 4) : p;
    }

    return _p8() + _p8(true);
}

// CEP
getCep = options => {
    let defaults = {
        cep: null,
        wrapper: null,
        fillFunction: null,
        clearFunction: null
    };
    let params = $.extend({}, defaults, options);
    if (!params.cep) {
        return null;
    }

    params.wrapper = params.wrapper ? $(params.wrapper) : $('body');

    $.ajax({
        timeout: 10000,
        url: `https://souzaoliveira.com.br/webservice/cep/${params.cep}`,
        beforeSend: () => {
            if (params.clearFunction !== false) {
                typeof params.clearFunction == 'function' ? params.clearFunction(params) : getCepClear(params);
            }
            block();
        },
        success: (data, xhr) => {
            params.data = data;
            typeof params.fillFunction == 'function' ? params.fillFunction(params) : getCepFill(params);
        },
        error: (xhr, data) => console.log(xhr, data),
        complete: () => unblock()
    });
}

getCepClear = params => {
    let wrapper = params.wrapper;
    wrapper.find('input[name=state]').val('');
    wrapper.find('input[name=city]').val('');
    wrapper.find('input[name=district]').val('');
    wrapper.find('input[name=street]').val('');
    wrapper.find('input[name=number]').val('');
    wrapper.find('input[name=ibge]').val('');
    wrapper.find('input[name=complement]').val('');
    wrapper.find('input[name=reference]').val('');
}

getCepFill = params => {
    let wrapper = params.wrapper;
    let json = params.data;
    wrapper.find('input[name=state]').val(json.Uf);
    wrapper.find('input[name=city]').val(json.Cidade);
    wrapper.find('input[name=district]').val(json.Bairro);
    wrapper.find('input[name=street]').val(json.Logradouro);
    wrapper.find('input[name=ibge]').val(json.cMun);
    wrapper.find('input[name=number]').focus();
}

function ChangePostcode(cep, fillFunction, cleanFunction) {
    if (cep.length < 9) {
        return false;
    }
    GetCep(cep, fillFunction, cleanFunction);
}

function GetCep(cep, fillFunction, cleanFunction) {
    if (cep.length < 9) {
        return false;
    }
    $.ajax({
        timeout: 10000,
        // url: 'https://rafaelfiori.com.br/ws/cep/' + cep,
        url: `https://souzaoliveira.com.br/webservice/cep/${cep}`,
        beforeSend: function (xhr) {
            if (cleanFunction !== false) {
                if (typeof cleanFunction == 'function') {
                    cleanFunction();
                } else {
                    GetCepReset();
                }
            }
            block();
        },
        success: function (data, xhr) {
            if (fillFunction !== false) {
                if (typeof fillFunction == 'function') {
                    fillFunction(data);
                } else {
                    GetCepFill(data);
                }
            }
        },
        error: function (xhr, data) {
            console.log(xhr, data);
        },
        complete: function () {
            unblock();
        }
    });
}

function GetCepReset() {
    $('#state').val('');
    $('#city').val('');
    $('#district').val('');
    $('#street').val('');
    $('#number').val('');
    $('#ibge').val('');
    $('#complement').val('');
    $('#reference').val('');
}

function GetCepFill(json) {
    $('#state').val(json.Uf);
    $('#city').val(json.Cidade);
    $('#district').val(json.Bairro);
    $('#street').val(json.Logradouro);
    $('#ibge').val(json.cMun);
    $('#number').focus();
}

//// HELPERS
function InlineAlert(options) {
    let defaults = {
        parent: 'body',
        title: null,
        size: 'h4',
        message: 'inline-alert',
        sub: null,
        dismiss: true,
        theme: 'warning',
        addClass: null,
        type: 'append'
    };

    let params = $.extend({}, defaults, options);
    let parent = params.parent.jquery ? params.parent : $(params.parent);
    let main = $(`<div class="alert alert-${params.theme} ${params.addClass}" role="alert">${params.message || ''}</div>`);

    if (params.type == 'append') {
        main.appendTo(parent);
    } else if (params.type == 'prepend') {
        main.prependTo(parent);
    } else {
        parent.html(main);
    }

    if (params.title) {
        main.prepend(`<${params.size} class="alert-heading">${params.title}</${params.size}>`);
    }
    if (params.sub) {
        main.append(`<hr class="my-2"/><p class="mb-0 font-14">${params.sub}</p>`);
    }
    if (params.dismiss) {
        main.prepend(`<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>`);
    }
}

function ToggleVisibility(selector, options) {
    let defaults = {
        display: 'd-block',
        onShow: null,
        onHide: null,
    };

    let params = $.extend({}, defaults, options);
    let element = selector.jquery ? selector : $(selector);
    element.toggleClass('d-none ' + params.display);

    let isVisible = !element.hasClass('d-none');
    if (isVisible && typeof params.onShow === 'function') {
        params.onShow();
    } else if (!isVisible && typeof params.onHide === 'function') {
        params.onHide();
    }
}

function ToggleVisibilityShow(selector, display) {
    let element = selector.jquery ? selector : $(selector);
    element.removeClass('d-none').addClass(display || 'd-block');
}

function ToggleVisibilityHide(selector, display) {
    let element = selector.jquery ? selector : $(selector);
    element.removeClass(display || 'd-block').addClass('d-none');
}

function PreventSubmit(event) {
    if (event.which == 13) {
        event.preventDefault();
        return false;
    }
}

function PreventDoubleClick(selector, timeout) {
    var element = selector.jquery ? selector : $(selector);
    if (element.hasClass('prevent-dbl-click')) {
        return false;
    } else {
        if (timeout) {
            setTimeout(function () {
                element.removeClass('prevent-dbl-click');
            }, timeout);
        }
        element.addClass('prevent-dbl-click');
        return true;
    }
}

function PreventDoubleClickClear(selector) {
    if (typeof (selector) == 'undefined') {
        $('.prevent-dbl-click').removeClass('prevent-dbl-click');
    } else {
        var element = selector.jquery ? selector : $(selector);
        element.removeClass('prevent-dbl-click');
    }
}

SerializeCollection = selector => {
    let object = {};
    let element = selector.jquery ? selector : $(selector);
    if (element.length > 0) {
        element.find("input, select, textarea").map(function () {
            let name = $(this).attr('name') || false;
            if (!name) {
                console.info(`Missing attribute name: ${$(this).attr('id')}`);
                return;
            }

            // set value
            let value = $(this).val();
            if ($(this)[0].type == 'checkbox') {
                value = $(this).is(':checked') ? $(this).val() : null;
            }

            // check if its an array
            let check = new RegExp(/\[.*\]$/);
            if (name.search(check) > 0) {
                let real_name = name.substr(0, name.indexOf('['));
                if (typeof object[real_name] === 'undefined') {
                    object[real_name] = [];
                }
                let key = name.substring(name.indexOf('[') + 1, name.indexOf(']'));
                if (key) {
                    object[real_name][key] = value;
                } else {
                    object[real_name].push(value);
                }
            } else {
                object[name] = value;
            }
        });
    }
    return object;
}

ParseHeader = (xhr, param) => {
    return $.parseJSON(xhr.getResponseHeader(param || 'Result'));
}

// PROTOTYPE
String.prototype.killWhiteSpace = function () {
    return this.replace(/\s/g, '');
};
String.prototype.reduceWhiteSpace = function () {
    return this.replace(/\s+/g, ' ');
};
String.prototype.rtrim = function (s) {
    if (s == undefined)
        s = '\\s';
    return this.replace(new RegExp("[" + s + "]*$"), '');
};
String.prototype.ltrim = function (s) {
    if (s == undefined)
        s = '\\s';
    return this.replace(new RegExp("^[" + s + "]*"), '');
};
String.prototype.toNumber = function (c, d, t) {
    var n = this;
    n = n.replace(".", '');
    return Number(n.replace(',', '.'));
};
Number.prototype.toMoney = function (decimals, decimal_sep, thousands_sep) {
    var n = this,
        c = isNaN(decimals) ? 2 : Math.abs(decimals),
        d = decimal_sep || ',',
        t = (typeof thousands_sep === 'undefined') ? '.' : thousands_sep,
        sign = (n < 0) ? '-' : '',
        i = parseInt(n = Math.abs(n).toFixed(c)) + '',
        j = ((j = i.length) > 3) ? j % 3 : 0;
    return sign + (j ? i.substr(0, j) + t : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
};

class MyUploadAdapter {
    constructor(loader, controller) {
        this.loader = loader;
        this.controller = controller;
    }

    // Starts the upload process.
    upload() {
        return this.loader.file
            .then(file => new Promise((resolve, reject) => {
                this._initRequest();
                this._initListeners(resolve, reject, file);
                this._sendRequest(file);
            }));
    }

    // Aborts the upload process.
    abort() {
        if (this.xhr) {
            this.xhr.abort();
        }
    }

    // Initializes the XMLHttpRequest object using the URL passed to the constructor.
    _initRequest() {
        const xhr = this.xhr = new XMLHttpRequest();
        xhr.open('POST', this.controller, true);
        xhr.responseType = 'json';
    }

    // Initializes XMLHttpRequest listeners.
    _initListeners(resolve, reject, file) {
        const xhr = this.xhr;
        const loader = this.loader;
        const genericErrorText = `Couldn't upload file: ${file.name}.`;

        xhr.addEventListener('error', () => reject(genericErrorText));
        xhr.addEventListener('abort', () => reject());
        xhr.addEventListener('load', () => {
            const response = xhr.response;
            if (!response || response.error) {
                return reject(response && response.error ? response.error.message : genericErrorText);
            }

            resolve({
                default: response.url
            });
        });

        // Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
        if (xhr.upload) {
            xhr.upload.addEventListener('progress', evt => {
                if (evt.lengthComputable) {
                    loader.uploadTotal = evt.total;
                    loader.uploaded = evt.loaded;
                }
            });
        }
    }

    // Prepares the data and sends the request.
    _sendRequest(file) {
        // Prepare the form data.
        const data = new FormData();
        data.append('upload', file);

        //data.append('test', 'test');
        // Send the request.
        this.xhr.send(data);
    }
}

function MyCustomUploadAdapterPlugin(editor) {
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
        // Configure the URL to the upload script in your back-end here!
        let controller = SUFFIX + '/controller/file/ckeditor5';
        return new MyUploadAdapter(loader, controller);
    };
}

keyPress = (e, callback) => {
    if (e.which == 13) {
        e.preventDefault();
        callback();
    }
}
// new ajax
// reportForm
reportForm = (errorList, form) => {
    let formElement = form.jquery ? form : $(form);

    $.each(errorList, function (input, message) {
        let element = formElement.find(`#${input}`);
        if (!element.length) {
            let arrayElement = formElement.find(`input[name^="${input}"]`);
            element = arrayElement.first();
        }

        if (element.length) {
            let parent = element.parents('.field');
            parent
                .addClass(`error`)
                .append(`<p class="warning">${message}</p>`)
        }
    })

    // tabs
    let tabsHeader = formElement.find('#tab-header');
    if (tabsHeader.length) {
        // clear badges
        tabsHeader.find('> div').find('.badge').remove();

        let first = true;
        $('#tab-content > div').each(function () {
            let count = $(this).find('p.warning');
            let index = $(this).index();

            // add badges
            if (count.length > 0) {
                tabsHeader.find('> div').eq(index).append(`<div class="badge badge-danger">${count.length}</div>`)

                // change tab
                if (first) {
                    tabsHeader.find('> div').eq(index).trigger('click');
                    first = false;
                }
            }
        })
    }
    // regular
    else {
        let firstError = formElement.find('p.warning:visible').first();
        if (firstError.length) {
            setTimeout(() => {
                let position = firstError.parent('div').position().top;
                $('#site-content').animate({scrollTop: position}, 300);
            }, 300);
            firstError.parent('div').find('input:first:visible').focus();
        }
        // error
        else {
            let messageError = [];
            $.each(errorList, function (input, message) {
                messageError.push(message);
            });
            error(messageError.join('<br>'));
        }
    }
}

saveForm = options => {
    let defaults = {
        form: '#form',
        target: '#response',
        timeout: 60000,
        block: true,
        beforeSerialize: null,
        beforeSubmit: null,
        onTimeout: null,
        onComplete: null,
        onSuccess: null,
        onError: null,
        uploadProgress: null,
        clearForm: null,
        validatehidden: false,
        method: 'post',
        json: false
    };

    let params = Object.assign(defaults, options);
    let formElement = params.form.jquery ? params.form : $(params.form);
    if (!formElement.length) {
        alerta('Elemento do formulário não encontrado.');
        return false;
    }

    // cliente side validation
    let errorList = {};
    formElement.find('div.error').removeClass('error');
    formElement.find('p.warning').remove();
    let collection = params.validateHidden ? formElement.find('input.required, select.required, textarea.required, select.select2') : formElement.find('input.required:visible, select.required:visible, textarea.required, select.select2');
    collection.each(function () {
        let input = $(this);
        if (!input.val()) {
            errorList[input.prop('id')] = input.data('required') || 'Campo obrigatório';
        }
    });
    if (Object.keys(errorList).length) {
        reportForm(errorList, formElement);
        return false;
    }

    // submit
    formElement.ajaxSubmit({
        method: params.method,
        target: params.target,
        timeout: params.timeout,
        data: params.params || null,
        uploadProgress: (event, position, total, percentComplete) => {
            typeof params.uploadProgress == 'function' ? params.uploadProgress(position, total, percentComplete) : null;
        },
        beforeSerialize: (form, options) => {
            // update ckeditor fields
            if (typeof CKEditors != 'undefined') {
                for (const key in CKEditors) {
                    let editor = CKEditors[key];
                    editor.updateSourceElement();
                }
            }
            typeof params.beforeSerialize == 'function' ? params.beforeSerialize() : null;
            return true;
        },
        beforeSubmit: () => {
            params.block ? block() : null;
            typeof params.beforeSubmit == 'function' ? params.beforeSubmit() : null;
        },
        // success
        statusCode: {
            200: (data, status, xhr) => {
                !params.block || unblock();
                if (typeof params.onSuccess == 'function') {
                    let response = params.json ? null : ParseHeader(xhr);
                    params.onSuccess(data, response, formElement, xhr);
                }
            }
        },

        // error
        error: (xhr, text, errorThrown, form) => {
            !params.block || unblock();
            // timeout
            if (text == 'timeout') {
                typeof params.onTimeout == 'function' ? params.onTimeout(xhr, form) : alert('Request Timeout');
            }
            // regular error
            else {
                if (typeof params.onError == 'function') {
                    let response = params.json ? xhr.responseJSON : ParseHeader(xhr);
                    params.onError(response, form, xhr);
                }
            }
        },
        // complete
        complete: (xhr, text, form) => {
            if (typeof params.onComplete == 'function') {
                params.onComplete(xhr, text, form);
            }
        },
        clearForm: params.clearForm || null
    });
}

saveFormClearFeedback = (form) => {
    form.find('.is-invalid').removeClass('is-invalid');
    form.find('.invalid-feedback').remove();
}

saveFormErrorFeedback = (response, form, options) => {
    if (response.form) {
        $.each(response.form, function (name, message) {
            let input = form.find(`#${name}`).addClass('is-invalid');
            if (!input.length) {
                input = form.find(`[name=${name}]`).addClass('is-invalid');
            }
            let parent = input.parents('div.form-group');
            parent.append(`<div class="invalid-feedback">${message}</div:>`);
            form.find('.invalid-feedback').show();
        });

        let firstError = $('.invalid-feedback').first();
        if (firstError.length) {
            firstError.parent('div').find('input:first:visible').focus();
            ScrollTo(firstError, options);
        }

    } else {
        error(response.message);
    }
}

xhrPool = [];
xhrPoolPush = (xhr) => {
    // clear complete
    $.each(xhrPool, function (id, obj) {
        if (obj && obj.readyState != 1) {
            xhrPool.splice(id, 1);
        }
    });

    xhrPool.push(xhr);
}

xhrPoolClear = () => {
    $.each(xhrPool, function (id, xhr) {
        if (xhr && xhr.readyState == 1) {
            xhr.abort();
        }
    });
}

getJSON = options => {
    let defaults = {
        router: null,
        params: {},
        // events
        beforeSubmit: false,
        onError: null,
        onSuccess: null,
        onComplete: null,
    };

    let params = Object.assign(defaults, options);
    let url = `${SUFFIX}/controller/${params.router}`;

    $.ajax({
        url: url,
        method: 'POST',
        dataType: 'json',
        data: params.params,
        beforeSend: (xhr) => {
            xhrPoolPush(xhr);
            typeof params.beforeSubmit === 'function' ? params.beforeSubmit() : null;
        }
    })
        .done((data, textStatus, xhr) => {
            // onSuccess
            typeof params.onSuccess === 'function' ? params.onSuccess(data, xhr, textStatus) : null;
        })
        .fail((xhr, textStatus, errorThrown) => {
            // onAbort
            if (textStatus == 'abort') {
            }
            // onError
            else {
                if (typeof params.onError === 'function') {
                    let json = xhr.responseJSON || null;
                    if (!json) {
                        try {
                            json = $.parseJSON(xhr.responseText);
                        } catch (err) {
                            console.error(err.message);
                            console.error(xhr.responseText);
                        }
                    }
                    params.onError(json, xhr, textStatus);
                }
            }
        })
        .always((data, textStatus, xhr) => {
            // callback user
            if (typeof params.onComplete === 'function') {
                params.onComplete(data, xhr, textStatus);
            }
        });
}

callController = options => {
    let defaults = {
        container: '#response',
        module: null,
        component: null,
        method: null,
        router: null,
        params: {},
        suffix: SUFFIX,
        type: 'data',
        // events
        beforeSubmit: false,
        onError: null,
        onSuccess: null,
        callback: null
    };

    let params = $.extend({}, defaults, options);

    // check router
    if (!params.router && !params.method) {
        error('Method not provided');
        return false;
    }
    if (!params.router && !params.module && !params.controller) {
        error('Router not provided');
        return false;
    }
    if (!params.router) {
        params.router = params.component ? `${params.module}/${params.component}/${params.method}` : `${params.module}/${params.method}`;
    }
    let url = `${params.suffix}/controller/${params.router}`;


    // json
    if (options.type == 'json') {
        $.ajax({
            url: url,
            method: 'POST',
            dataType: 'json',
            data: params.params,
            beforeSend: () => {
                typeof params.beforeSubmit === 'function' ? params.beforeSubmit() : null;
            }
        })
            .done((data, textStatus, xhr) => {
                // onSuccess
                typeof params.onSuccess === 'function' ? params.onSuccess(data, textStatus, xhr) : null;
            })
            .fail((xhr, textStatus, errorThrown) => {
                // onError
                if (typeof params.onError === 'function') {
                    let result = xhr.getResponseHeader('result') ? $.parseJSON(xhr.getResponseHeader('result')) : null;
                    params.onError(result, xhr, textStatus, errorThrown);
                }
            })
            .always((xhr, textStatus, errorThrown) => {
                // callback system
                typeof params.callbackSys === 'function' ? params.callbackSys(xhr, textStatus, errorThrown) : null;

                // callback user
                typeof params.callback === 'function' ? params.callback(xhr, textStatus, errorThrown) : null;

                // parseError
                if (textStatus == 'parsererror' || (xhr.responseText && !xhr.hasOwnProperty('responseJSON'))) {
                    alerta(xhr.responseText, 'Retorno Inesperado');
                }
            });
    }
    // data
    else {
        let container = params.container.jquery ? params.container : $(params.container);
        if (!container.length) {
            error('Container not found');
            return false;
        }

        // beforeSubmit
        typeof params.beforeSubmit == 'function' ? params.beforeSubmit() : null;

        container.load(url, params.params, function (data, text, xhr) {
            // callback system
            typeof params.callbackSys === 'function' ? params.callbackSys(data) : null;

            // onError
            if (xhr.status == 400) {
                if (typeof params.onError === 'function') {
                    let Report = xhr.getResponseHeader('Report') ? $.parseJSON(xhr.getResponseHeader('Report')) : null;
                    params.onError(data, text, xhr, Report);
                }
            }
            // onSuccess
            else {
                typeof params.onSuccess === 'function' ? params.onSuccess(data, text, xhr) : null;
            }

            // callback user
            typeof params.callback === 'function' ? params.callback(data, text, xhr) : null;
        });
    }
}

animateCss = (selector, animation) => {
    let elements = document.querySelectorAll(selector);
    let prefix = 'animate__';

    elements.forEach(element => {
        // clear
        for (let i = element.classList.length - 1; i >= 0; i--) {
            const className = element.classList[i];
            if (className.startsWith(prefix)) {
                element.classList.remove(className);
            }
        }

        // add
        if (animation) {
            animateCssElement(element, animation);
        }
    })
}

animateCssElement = (element, animation) => {
    let prefix = 'animate__';
    let object = element.jquery ? element[0] : element;
    object.classList.add(`${prefix}animated`, `${prefix}${animation}`);
}

customFileChanged = (obj, e) => {
    let label = obj.nextElementSibling;
    let fileName = e.target.value.split('\\').pop();
    if (fileName) {
        label.querySelector('span').innerHTML = fileName;
    }
}