// This file is included multiple times, so we've wrapped it in a C-style include guard
if (window.form_validation_was_initialised === undefined) {
    window.form_validation_was_initialised = true;
    require(['jquery', 'cla_moment', 'cla_locale', 'domReady'], function ($, moment, locale, domReady) {
        var focusOnIframe = null;
        var focusedField = null;

        /**
         * This is a sentinel value returned when a field is uneditable.
         * Its only purpose is to be compared against in the checkField method.
         * The actual value of the variable is not used, it just needs to be an
         * object so that any equality comparisons will only return true if the
         * variable being compared against contains this exact object. Objects
         * are compared by address in javascript.
         *
         * TODO: Replace this with a Symbol when we move to ES6
         *
         * @constant
         * @type {{}}
         */
        var NOT_EDITABLE = {};
        var MAX_INT_LENGTH = 14;

        domReady(function () {
            // workaround for the fact that iframes do not generate focus/blur events
            $(window).on("focus", onWindowFocus);
            $(window).on("blur", onWindowBlur);

            // This listener, and the global "focused field" variable, are
            // to work around the fact that the signature pad likes to
            // send blur events to EVERY INPUT ON THE PAGE when it is clicked.
            // Without the check of whether the field had been focused first,
            // this causes every field to check itself for validity, which can
            // cause a lot of spurious errors if there are mandatory fields that
            // have not been filled in yet.
            $("body").on("focus", "[data-field-sym-name]", function (e) {
                focusedField = $(this).attr('data-field-sym-name');
            });

            // This means that validation runs twice for some fields. But it's cheap and idempotent, so this is fine
            $("body").on("blur", "[data-field-sym-name]", validationListener);
            $("body").on("change", "[type=text][data-field-sym-name]", validationListener);

            function validationListener (e) {
                var symName = $(this).attr('data-field-sym-name');
                var target = $(this);

                if (focusedField !== symName)
                    return;

                setTimeout(function () {
                    var active = document.activeElement;
                    var select2Container = $('body > .select2-container')[0];

                    if (select2Container && $.contains(select2Container, active))
                        return;

                    if ($.contains(target[0], active))
                        return;

                    checkFieldAndDecorate(target);
                }, 0);
            }

            $("body").on("cla-user-change", "select", function () {
                var field = $(this).closest("[data-field-sym-name]");

                setTimeout(function () {
                    checkFieldAndDecorate(field);
                }, 0);
            });

            $("form").on("submit", function (event) {
                var action = jQuery('input[name=action_script]').val() || jQuery('input[name=action]').val();

                if (action === "add" || action === "save") {
                    var valid = checkFields($(this));

                    if (!valid) {
                        event.preventDefault();
                        var firstInvalidField = document.getElementsByClassName("invalid-field")[0];
                        firstInvalidField.scrollIntoView({behavior: "smooth", block: "center"});
                        cla.showMessage("Invalid field values", "", true);
                        setTimeout(function () {
                            $('#report_issue').prop('disabled', false);
                            $('#discard_draft').prop('disabled', false);
                            $('#save_draft').prop('disabled', false);
                        }, 0);
                    }
                }
            });

            $("[data-field-sym-name]").tooltip({
                placement: "auto",
                title: "error?",
                trigger: "manual",
            });

            $('html').on('before-form-reload', function(event) {
                var form = event.form;
                var invalid_fields = getDecoratedSymNames();
                var el = $('<input type="hidden">');
                el.attr("name", "___invalid_fields___");
                el.val(invalid_fields.join(","));

                form.append(el);
            });

            $('html').on('form-reload', function(event) {
                event.form.find("[name=___invalid_fields___]").remove();
            });
        });

        function onWindowBlur() {
            setTimeout(function () {
                var el = $(document.activeElement);
                if (el.prop("tagName") === 'IFRAME' && el.prop("className") !== 'cke_panel_frame') {
                    focusOnIframe = el;

                    focusOnIframe.trigger("focus");
                }
            }, 0);
        }

        function onWindowFocus() {
            if (focusOnIframe) {
                focusOnIframe.trigger("blur");
                focusOnIframe = null;
            }
        }

        function checkFields(form) {
            var fields = form.find("[data-field-sym-name]");
            var valid = true;

            for (var i = 0; i < fields.length; i++) {
                var fieldValid = checkFieldAndDecorate($(fields[i]));
                valid = valid && fieldValid;
            }

            return valid;
        }

        function getDecoratedSymNames() {
            return jQuery("[data-field-sym-name].invalid-field").
                map(function() {
                    return jQuery(this).attr("data-field-sym-name");
                }).get();
        }

        function checkFieldAndDecorate(field) {
            var result = checkField(field);
            if (result) {
                field.attr('data-original-title', result)
                    .tooltip('show');

                field.addClass("invalid-field");
            } else {
                field.tooltip('hide');
                field.removeClass("invalid-field");
            }

            return !result;
        }

        function checkField(field) {
            var type = field.attr("data-field-type");
            var required = field.attr("data-field-is-required") === "1";
            var format = field.attr("data-field-format");

            var valueCallback;

            valueCallback = textValueCallback;

            if (type in valueCallbacks)
                valueCallback = valueCallbacks[type];

            var value = valueCallback(field);

            if (value === NOT_EDITABLE)
                return "";

            var empty = !value || (Array.isArray(value) && value.length <= 0);

            // max length for number (string format: integer) in the field type short_string is 14
            var isInvalidIntValue = parseInt(type) === document.cla_forms.field_types.TYPE_SHORT_STRING && format === "1" && value.length > MAX_INT_LENGTH;

            if (isInvalidIntValue)
                return lmsg('infocapture.field_validation.value_no_greater_than') + MAX_INT_LENGTH;

            if (required && empty)
                return lmsg('infocapture.field_validation.mandatory_field');

            if (type in typeCallbacks)
                return typeCallbacks[type](field, value);

            return "";
        }

        var typeCallbacks = {
            "1": textCallback,
            "2": textCallback,
            "3": textCallback,
            "4": textCallback,
            "5": textCallback,
            "6": textCallback,
            "11": dateCallback,
            "20": dateCallback,
            "19": textCallback,
            "16": textCallback,
            "22": textCallback,
            "23": dateCallback,
            "26": textCallback
        };

        var valueCallbacks = {
            "4": textareaValueCallback,
            "5": textareaValueCallback,
            "6": textareaValueCallback,
            "16": htmlValueCallback,
            "17": groupValueCallback,
            "18": roleValueCallback,
            "9": selectValueCallback,
            "10": selectValueCallback,
            "13": selectValueCallback,
            "15": selectValueCallback,
            "14": documentValueCallback,
            "8": radioValueCallback,
            "7": checkboxValueCallback,
            "22": checkboxValueCallback,
            "11": dateThreeSelectValueCallback,
            "20": datePickerValueCallback,
            "23": dateTimePickerValueCallback,
            "24": textareaValueCallback,
            "25": signatureValueCallback,
            "26": textValueCallback
        };

        function textValueCallback(field) {
            var input = field.find("input[type=text]");

            if (!input.length)
                return NOT_EDITABLE;

            return input.val();
        }

        function textareaValueCallback(field) {
            var input = field.find("textarea");

            if (!input.length)
                return NOT_EDITABLE;

            return input.val();
        }

        function htmlValueCallback(field) {
            var input = field.find("textarea");
            var id = input.attr("id");

            if (!input.length)
                return NOT_EDITABLE;

            return CKEDITOR.instances[id].getData();
        }

        function signatureValueCallback(field) {
            var pad = field.find(".sigPad");
            var text = field.find("input[type=text]");

            if (!pad.length)
                return NOT_EDITABLE;

            if (field.find(".js-re-sign-button:visible").length) {
                return "%%unchanged-signature%%"
            }

            if (text.is(":visible"))
                return textValueCallback(field);

            return pad.data('plugin-signaturePad').getSignature();
        }

        function groupValueCallback(field) {
            var input = field.find("input[type=hidden].js-group-field-hidden-value");
            var value = input.val();

            if (!input.length)
                return NOT_EDITABLE;

            if (value === "" || value == null)
                return [];

            return value.split(",")
        }

        function roleValueCallback(field) {
            var input = field.find("input[type=hidden].js-role-field-hidden-value");
            var value = input.val();

            if (!input.length)
                return NOT_EDITABLE;

            if (value === "" || value == null)
                return [];

            return value.split(",")
        }

        function selectValueCallback(field) {
            var input = field.find("select");

            if (!input.length)
                return NOT_EDITABLE;

            return input.val();
        }

        function documentValueCallback(field) {
            var val = selectValueCallback(field);

            if (val === NOT_EDITABLE)
                return val;

            return val[0];
        }

        function dateThreeSelectValueCallback(field) {
            var inputs = field.find("select");

            if (!inputs.length)
                return NOT_EDITABLE;

            var day = parseInt(inputs.filter("[id$=day]").val());
            var month = parseInt(inputs.filter("[id$=month]").val());
            var year = parseInt(inputs.filter("[id$=year]").val());

            var isEmpty = !(year || month || day);
            var date = moment(day + '/' + month + '/' + year, "D/M/YYYY", true);

            return isEmpty ? null : date;
        }

        function datePickerValueCallback(field) {
            var input = field.find("input[type=text]");

            if (!input.length)
                return NOT_EDITABLE;

            var value = input.val();
            return value === "" ? null : moment(value, locale.date_format, true);
        }

        function dateTimePickerValueCallback(field) {
            var date_input = field.find("input[type=text][name$=_date]");

            if (!date_input.length)
                return NOT_EDITABLE;

            var date = date_input.val();
            var time = field.find("input[type=text][name$=_time]").val();

            if (date === "" || time === "")
                return null;

            return moment(date + ' ' + time, locale.date_format + ' ' + locale.time_format, true);
        }

        function checkboxValueCallback(field) {
            var input = field.find("input[type=checkbox]:checked");

            if (!input.length)
                return NOT_EDITABLE;

            return input.map(function () {
                return $(this).attr("value");
            }).get();
        }

        function radioValueCallback(field) {
            var input = field.find('input[type=radio]:checked');

            if (!input.length)
                return NOT_EDITABLE;

            return input.val();
        }

        function dateCallback(field, date) {
            // Avoid a console error when a 3-field date is left on the default "Day", "Month" and "Year" placeholders
            if (date === null)
                return "";

            var min = field.attr("data-field-constraint-more-than");
            var max = field.attr("data-field-constraint-less-than");
            var max_date;
            var min_date;
            var max_format = locale.date_format_long;
            var min_format = locale.date_format_long;
            var max_formatted = null;
            var min_formatted = null;

            if (!date.isValid())
                return "This is not a valid date";

            max_date = claTimestampToMoment(max);
            min_date = claTimestampToMoment(min);

            if (min.length > 8)
                min_format += " " + locale.time_format;

            if (max.length > 8)
                max_format += " " + locale.time_format;

            if (max_date && max_date.isValid() && date.isAfter(max_date))
                max_formatted = max_date.format(max_format);

            if (min_date && min_date.isValid() && date.isBefore(min_date))
                min_formatted = min_date.format(min_format);

            if (max_formatted && min_formatted) {
                return lmsg('infocapture.field_validation.date_between') + min_formatted + lmsg('infocapture.field_validation.and') + max_formatted;
            } else if (min_formatted) {
                return lmsg('infocapture.field_validation.date_after') + min_formatted;
            } else if (max_formatted) {
                return lmsg('infocapture.field_validation.date_before') + max_formatted;
            }

            return "";
        }

        function claTimestampToMoment(timestamp) {
            if (!timestamp)
                return null;

            if (timestamp.length === 14)
                return moment(timestamp, "YYYYMMDDHHmmss", true);

            return moment(timestamp, "YYYYMMDD", true);
        }

        function textCallback(field, value) {
            var format = field.attr("data-field-format");
            var regex = field.attr("data-field-constraint-regex");
            var min = field.attr("data-field-constraint-more-than");
            var max = field.attr("data-field-constraint-less-than");
            var min_len = field.attr("data-field-constraint-bigger-than");
            var max_len = field.attr("data-field-constraint-smaller-than");

            if (field.attr("data-field-type") === "26")
                format = "financial";

            if (!checkStringFormat(format, value))
                return lmsg('infocapture.field_validation.expected_format');

            if (!checkRegex(regex, value))
                return lmsg('infocapture.field_validation.expected_format');

            if (!checkLength(min_len, max_len, value)) {
                if (max_len && min_len)
                    return lmsg('infocapture.field_validation.value_between') + min_len + lmsg('infocapture.field_validation.and') + max_len + lmsg('infocapture.field_validation.characters');
                else if (max_len)
                    return lmsg('infocapture.field_validation.value_no_more_than') + max_len + lmsg('infocapture.field_validation.characters');
                else if (min_len)
                    return lmsg('infocapture.field_validation.value_at_least') + min_len + lmsg('infocapture.field_validation.characters');
            }

            if (!checkRange(min, max, value)) {
                if (max && min)
                    return lmsg('infocapture.field_validation.value_between') + min + lmsg('infocapture.field_validation.and') + max;
                else if (max)
                    return lmsg('infocapture.field_validation.value_no_greater_than') + max;
                else if (min)
                    return lmsg('infocapture.field_validation.value_no_less_than') + min;
            }

            return "";
        }

        function checkRange(min, max, value) {
            if (value.length === 0 || min === "" || max === "")
                return true;

            value = parseInt(value);
            return value <= parseInt(max) && value >= parseInt(min);
        }

        function checkRegex(regex, value) {
            if (value.length === 0 || regex === "")
                return true;

            regex = new RegExp(regex);
            var matches = regex.exec(value);

            return matches !== null
        }

        function checkLength(min, max, value) {
            var len = value.length;

            // empty fields are always valid, unless marked required
            if (len === 0 || min === "" || max === "")
                return true;

            return len <= max && len >= min;
        }

        var integerRegex = /^[+\-]?\d+$/;
        var emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
        var floatRegex = /^[+\-]?\d+(?:\.\d+)?$/;
        var financialRegex = /^(?:-*\d+|-*\d{1,3}(?:,\d{3})*)(?:\.\d{1,2})?$/;

        function checkStringFormat(format, value) {
            if (value.length === 0)
                return true;

            switch (format) {
                case "1": //integer
                    return integerRegex.exec(value) !== null;
                case "2": //float
                    return floatRegex.exec(value) !== null;
                case "4": //email
                    return emailRegex.exec(value) !== null;
                case "financial":
                    return financialRegex.exec(value) !== null;
                default:
                    return true;
            }
        }
    });
}
