/**
 * Ensemble des fonctions JavaScript appelées sur les formulaires.
 */
function Form() {

    this.regional = new Array();
    this.lastTimeStamp = undefined;
    this.doUpdate = false;

    /**
     * Initialise le composant de formulaires JS.
     */
    this.initialize = function () {
        // Mise en place du "collapse/expand" sur les titres des fieldsets
        // La méthode afterAjax réninitialise le composant Form ce qui peut provoquer de multiples enregistrements de 
        // listener sur l'icône qui permet de plier les fieldset
        orion.event.stopListeningToUsingSelector("click", "span[data-o-role='legend-icon']", orion.form.showHideFieldsetContent);
        orion.event.listenToUsingSelector("click", "span[data-o-role='legend-icon']", orion.form.showHideFieldsetContent);
        orion.event.listenToUsingSelector("click", "[data-toggle=dropdown]", orion.form.defineDropdownPosition);

        if (orion.event.listenToBodyPadding) {
            orion.event.listenToBodyPadding(orion.form.updateAnchorShift);
        }

        this.initializePopOverForInput();
        this.initializeControls();
        this.initializeHasClearOptions();
        this.initializeInputFile();
        this.blockSubmissionThroughEnter();
        this.initializeControlsClass();
        this.initializeSlaveForm();
        this.initializeFieldsets();
        this.initializeManyCheckbox();
    };

    this.blockSubmissionThroughEnter = function () {
        $("[data-o-role='control'] > input").live("keypress", function (event) {
            if (event.keyCode === 10 || event.keyCode === 13) {
                event.preventDefault();
            }
        });
    };

    /**
     * Définit la position (droite ou gauche) des dropdown menu selon l'emplacement du bouton cliqué.
     * @returns {undefined}
     */
    this.defineDropdownPosition = function () {
        $(this).parent().removeClass("o-left-side-dropdown");

        var pos = $(this).offset();

        var bodyWidth = $('body').width();

        // En fonction de l'emplacement du coin suppérieur gauche du boutton cliqué
        // on modifie l'emplacement du dropdown via une classe
        if (pos.left > (bodyWidth / 2)) {
            $(this).parent().addClass("o-left-side-dropdown");
        }
    };

    this.beforeAjax = function () {
        orion.form.doUpdate = true;
    };

    this.afterAjax = function () {
        if (orion.form.doUpdate) {
            orion.form.initialize();

            // Refresh error state
            $("[data-o-role='control']").find("input,select,textarea").each(
                function () {
                    if (!orion.form.hasMessages($(this))) {
                        orion.form.removeErrorState($(this));
                    }
                }
            );

            orion.form.doUpdate = false;
        }

    };

    /**
     * Définit la largeur des champs d'input.
     * Cette taille est hérité par défaut, mais doit impérativement être
     * recalculée en JS. En effet, il faut à postériori enlever la largeur
     * des padding, border et margin (cf Box Model sur w3schools : http://www.w3schools.com/css/css_boxmodel.asp)
     * puis celle nécessaire pour placer l'icône d'erreur à droite du champ.
     */
    this.defineInputWidth = function () {
        var controlSelector = "*[data-o-role='control']";

        $(controlSelector).each(function () {
            var inputElement = $(this).children("input,select,textarea");

            var inputWidth = inputElement.width();
            var outerInputWidth = inputElement.outerWidth(true);

            var iconElement = $(this).find("*[data-o-role='input-tooltip']");
            var iconWidth = iconElement.outerWidth(true)

            var widthDelta = outerInputWidth - inputWidth + iconWidth;
            var newWidth = inputWidth - widthDelta;

            inputElement.width(newWidth);
        });
    };

    /**
     * Met à jour le décalage vers le bas des ancres (associées au champs de saisie
     * et utilisée par les messages d'erreur pour renvoyer vers ces champs), pour quelles
     * ne soient pas masquées derrière le header.
     *
     * @param event : l'évènement qui a déclenché la fonction.
     */
    this.updateAnchorShift = function (event) {
        if (event) {
            // On ajoute un pixel pour être sûr de ne pas être superposé avec la bordure inférieure du header.
            var headerHeight = parseInt(event.headerHeight) + 1;
            headerHeight = "-" + headerHeight + "px";
            $(".o-anchor-top").css("top", headerHeight);
        }
    };

    /**
     * Permet de déclencher un callback lors d'une saisie valide sur un champ de
     * formulaire.
     *
     * @param evt : l'évènement qui déclenche l'action.
     */
    this.onValidInput = function (evt) {
        // Prévenir le saut de ligne qui résulte d'une frappe trop rapide
        var evtTimestamp = evt.timeStamp;
        orion.logger.log("orion.form.onValidInput inspect id " + evt.target.id + " when " + evtTimestamp);
        if (orion.form.lastTimeStamp) {
            var elapsedTimeBetweenTwoStroke = evtTimestamp - orion.form.lastTimeStamp;
            if (elapsedTimeBetweenTwoStroke < 80) {
                orion.logger.log("orion.form.onValidInput reject, elapsedTimeBetweenTwoStroke  " + elapsedTimeBetweenTwoStroke);
                return true;
            }
        }

        orion.form.lastTimeStamp = evtTimestamp;

        evt.preventDefault();
        evt.stopPropagation();

        var input = $(this);
        orion.form.validate(input);
        if (!input[0].validity.valid) {
            return true;
        }

        var callback = input.parent("div").attr("data-o-onvalidinput");

        var shouldPublish = false;

        switch (evt.keyCode) {
            case 40: // down arrow
            case 38: // up arrow
            case 37: // left arrow
            case 39: // right arrow
            case 36: // Begin of line
            case 35: // End of line
            case 27: // escape
            case 16: // shift
            case 17: // ctrl
            case 18: // alt
            case 9: // tab
            case 8: // backspace                
                break;
            default :
                shouldPublish = true;
        }

        if (shouldPublish) {
            eval(callback + "(input)");
        }
        else {
            orion.logger.log("orion.form.onValidInput : input is not valid " + input.attr("id"));
        }
        return true;

    }

    /**
     * Passe le focus au tabIndex suivant.
     *
     * @param currentInput: la saisie qui déclenche l'action.
     */
    this.nextTabIndex = function (currentInput) {
        var input = currentInput;
        var tabIndex = parseInt(input.attr("tabIndex"));
        var nextTabIndex = tabIndex + 1;

        // Si le tabIndex suivant existe
        var inputToFocus = $("input[tabIndex=" + nextTabIndex + "]");
        if (inputToFocus) {
            // On lui définit le focus
            inputToFocus.focus();

            // Et on présélectionne sa valeur pour permettre son remplacement 
            // direct lors de la saisie.
            inputToFocus.select();

            return true;
        }
    }

    /**
     * Passe le focus au prochain input dans le DOM.
     */
    this.next = function (targetInput) {
        var targetInputId = targetInput.attr("id");
        orion.logger.log("orion.form.next from id " + targetInputId);
        var allInputs = $("input[type!='hidden']:not([DISABLED]):not([READONLY])");
        var index = 0;
        for (; index < allInputs.length; index++) {
            var input = allInputs[index];
            var id = $(input).attr("id");
            if ((id === targetInputId) && (index + 1 < allInputs.length)) {
                var nextInput = $(allInputs[index + 1]);
                orion.logger.log("orion.form.next  input found id is " + nextInput.attr("id"));
                nextInput.focus();

                // Et on présélectionne sa valeur pour permettre son remplacement 
                // direct lors de la saisie.
                nextInput.select();

                return;
            }
        }
    };

    /**
     * le contexte this de cette méthode est l'icône !
     * @returns {undefined}
     */
    this.showHideFieldsetContent = function () {
        var fieldsetElem = $(this).parents("fieldset").first();
        var fieldsetBody = fieldsetElem.find('[data-o-role="fieldset-body"]');
        var isHidden = (fieldsetBody[0].style.display === "none");
        if (isHidden) {
            orion.form.showFieldsetContent.apply(this);
        }
        else {
            orion.form.hideFieldsetContent.apply(this);
        }
    };

    this.showFieldsetContent = function () {
        // On modifie la visibilité de l'ensemble des "frères" de la balise legend du fieldset
        var fieldsetElem = $(this).parents("fieldset").first();
        var fieldsetBody = fieldsetElem.find('[data-o-role="fieldset-body"]');
        fieldsetBody.show();
        orion.form.saveFieldsetState(fieldsetElem, false);
        // On modifie l'icône associée
        $(this).children("i").removeClass("fa fa-chevron-down").addClass("fa fa-chevron-up");
    };

    /**
     * Alternativement masque ou affiche le contenu d'un fieldset dans une box.
     */
    this.hideFieldsetContent = function () {
        // On modifie la visibilité de l'ensemble des "frères" de la balise legend du fieldset
        var fieldsetElem = $(this).parents("fieldset").first();
        var fieldsetBody = fieldsetElem.find('[data-o-role="fieldset-body"]');
        fieldsetBody.hide();
        orion.form.saveFieldsetState(fieldsetElem, true);
        // On modifie l'icône associée
        $(this).children("i").removeClass("fa fa-chevron-up").addClass("fa fa-chevron-down");
    };

    /**
     *
     */
    this.initializeFieldsets = function () {
        $("span[data-o-role='legend-icon']").each(function () {
            var fieldsetElem = $(this).parents("fieldset").first();
            var fieldsetHiddenInLocalStorage = orion.form.isFieldsetHiddenInLocalStorage(fieldsetElem);
            if (fieldsetHiddenInLocalStorage !== null) {
                if (fieldsetHiddenInLocalStorage) {
                    orion.form.hideFieldsetContent.apply(this);
                }
                else {
                    orion.form.showFieldsetContent.apply(this);
                }
            }
            else {
                var folded = fieldsetElem.attr("data-o-folded") === 'true';
                if (folded) {
                    orion.form.hideFieldsetContent.apply(this);
                }
                else {
                    orion.form.showFieldsetContent.apply(this);
                }
            }
        });
    };

    /**
     * Soumet le formulaire parent du tag html sur lequel cette fonction javascript est positionné.
     */
    this.submitParentForm = function (elementId) {
        var selector = "[id$='" + elementId + "']";
        $(selector).closest('form').submit();
    };

    /**
     * Permet de positionner le focus sur un champ a partir de son elementId.
     */
    this.setFocus = function (elementId) {
        var selector = "[id$='" + elementId + "']";
        $(selector).focus();
    };

    this.setState = function (input, state) {
        var controlGroup = input.parents("*[data-o-role='form-group']");
        if (controlGroup.length > 0) {
            controlGroup.addClass(state);
        }
        var control = input.parents("*[data-o-role='control']");
        control.addClass(state);
    };

    this.removeState = function (input, state) {
        var controlGroup = input.parents("*[data-o-role='form-group']");
        if (controlGroup.length > 0) {
            controlGroup.removeClass(state);
        }
        var control = input.parents("*[data-o-role='control']");
        control.removeClass(state);
    };

    this.setErrorState = function (input) {
        orion.form.setState(input, "has-error");
    };


    this.removeErrorState = function (input) {
        orion.form.removeState(input, "has-error");
    };

    this.initializeControls = function () {

        orion.logger.log("orion.form.initializeControls start");
        $(document).find("*[data-o-role='control']").each(function () {
            var control = $(this);
            var input = control.find("input,select,textarea");

            // vérification de la taille du tableau
            //car un control peut contenir aussi des champ type outputtext et ne pas contenir 
            //des input,select,textarea
            if (input.length > 0) {
                var inputId = "#" + input.attr("id").replace(/:/g, "\\:");
                var isOnValidInput = control.attr("data-o-onvalidinput") !== undefined;
                var hasPattern = input.attr("pattern") !== undefined;
                if (isOnValidInput) {
                    orion.logger.log("orion.form.initializeControls add onValidInput on " + inputId);
                    orion.event.listenToUsingSelector("keyup", inputId, orion.form.onValidInput);
                }
                else {
                    if (hasPattern) {
                        orion.logger.log("orion.form.initializeControls add validateHandler on " + inputId);
                        orion.event.listenToUsingSelector("change", inputId, orion.form.validateHandler);
                    }
                }

                orion.form.validate(input);
                if (orion.form.hasMessages(input)) {
                    orion.form.setErrorState(input);
                }
            }
        });
        orion.logger.log("orion.form.initializeControls finished");
    };

    this.initializeHasClearOptions = function () {

        $(document).find("*[data-o-role='control']").each(function () {
            var control = $(this);
            var input = control.find("input");
            if (input.hasClass("o-hasclear")) {
                if (input.val() != "") {
                    input.next('span').show();
                }else{
                    input.next('span').hide();
                }
            }
        });

        $(".o-hasclear").keyup(function () {
            var t = $(this);
            t.next('span').toggle(Boolean(t.val()));
        });

        $(".o-clearer").click(function () {
            $(this).prev('input').val('').focus();
            $(this).hide();
        });
    };

    this.initializePopOverForInput = function () {
        $("*[data-o-role='input-tooltip']").popover({
            title: orion.form.regional["orion.form.input.messages.title"],
            content: orion.form.providePopOverForInputContent,
            trigger: "click",
            html: true,
            html_safe: true,
            placement: "bottom"
        });
    };

    this.providePopOverForInputContent = function () {
        var content = "";

        $(this).children("*[data-o-role='input-mesg']").each(function () {
            var sev = $(this).attr("data-o-severity");
            var sevClass = "alert-" + sev;
            if (sev === "error") {
                sev = orion.form.regional["orion.form.input.severity.error"];
            }
            else if (sev === "info") {
                sev = orion.form.regional["orion.form.input.severity.info"];
            }
            else if (sev === "warn") {
                sev = orion.form.regional["orion.form.input.severity.warn"];
                sevClass = "alert";
            }
            else if (sev === "fatal") {
                sev = orion.form.regional["orion.form.input.severity.fatal"];
            }

            var text = $(this).html();
            content = content + "<div class='" + sevClass + "'>"
                + "<strong>"
                + sev
                + "</strong> : "
                + text + "</div>";
        });

        return content;
    };

    this.hasMessages = function (input) {
        var messages = input.siblings().find("*[data-o-role='input-mesg']");
        return (messages.length > 0);
    };

    this.hasPatternMessages = function (input) {
        var messages = input.next().find("*[data-o-origin='client']");
        return (messages.length > 0);
    };

    this.showIconWarning = function (input) {
        var inputTooltip = input.next();
        inputTooltip.removeClass("hide");
    };

    this.hideIconWarning = function (input) {
        var inputTooltip = input.next();
        inputTooltip.addClass("hide");
    };

    this.hideInputMessages = function (input) {
        var tooltip = input.next();
        $(tooltip).popover('hide');
    };

    this.refreshInputMessages = function (input) {
        var tooltip = input.next();
        $(tooltip).popover('hide');
        $(tooltip).popover('show');
    };


    this.validateHandler = function () {
        var input = $(this);
        orion.logger.log("orion.form.validateHandler attempt to validate input " + input.attr("id"));
        orion.form.validate(input);
    };

    this.validate = function (input) {
        var pattern = input.attr("pattern");
        if ((pattern != null) && (pattern != undefined)) {
            if (input[0].validity) {
                if (input[0].validity.valid === false) {
                    orion.logger.log("orion.form.validate " + input.attr("id") + ": not valid");
                    // Nok afficher l'icone d'erreur
                    // ajouter les messages                                
                    orion.form.setErrorState(input);
                    orion.form.showIconWarning(input);
                    if (!orion.form.hasPatternMessages(input)) {
                        orion.logger.log("orion.form.validate " + input.attr("id") + ": add pattern message");
                        var title = input.attr("title");
                        //orion.form.regional["orion.form.input.pattern.notmatch"]
                        orion.form.addPatternMessage(input, title);
                    }
                }
                else {
                    orion.logger.log("orion.form.validate " + input.attr("id") + ": valid");
                    // Ok
                    // vider les erreurs concernants le champs               
                    orion.form.removePatternMessage(input);
                    if (!orion.form.hasMessages(input)) {
                        orion.logger.log("orion.form.validate " + input.attr("id") + ": remove pattern message");
                        orion.form.removeErrorState(input);
                        orion.form.hideIconWarning(input);
                    }
                }
            }
            else {
                orion.logger.log("orion.form.validate input does not support pattern");
            }
            orion.form.hideInputMessages(input);
        }
    };

    this.addPatternMessage = function (input, mesg) {
        var messageZone = $(input).next();
        var patternMessage = messageZone.find("*[data-o-origin='client']");
        if (patternMessage.length === 0) {
            var html = "<label for=\"" + input.attr("id") + "\" class=\"o-invisible\" data-o-origin=\"client\" data-o-severity=\"error\""
                + "data-o-role=\"input-mesg\">" + mesg + "</label>";
            messageZone.append(html);
        }
    };

    this.removePatternMessage = function (input) {
        var messageZone = $(input).next();
        messageZone.find("*[data-o-origin='client']").remove();
    };

    /**
     * Initialisation des composants InputFile de sélection de fichier
     */
    this.initializeInputFile = function () {
        var inputFiles = $(document).find("div[data-o-role='inputfile']");
        if (inputFiles.length > 0) {
            orion.logger.log("orion.form.initializeInputFile start");
            inputFiles.each(function () {
                var inputText = $(this).find('input[type="text"]');
                inputText.attr('data-o-role', 'filepreview');
                inputText.attr('readonly', 'readonly');
                $(this).inputfile($(this).data());
            });
            orion.logger.log("orion.form.initializeInputFile finished");
        }
    };

    /**
     * Initialisation des composants SlaveForm
     */
    this.initializeSlaveForm = function () {
        var slaveForms = $(document).find("div[data-o-role='slaveForm']");
        if (slaveForms.length > 0) {
            orion.logger.log("orion.form.initializeSlaveForm start");
            slaveForms.each(function () {
                var slaveFormId = $(this).attr('id');
                slaveFormId = slaveFormId.replace(new RegExp(":", 'g'), "\\\:");
                slaveFormId = "#" + slaveFormId;
                $(slaveFormId).modal({
                    backdrop: 'static',
                    keyboard: false,
                    show: false
                })
                $(slaveFormId).modal('show');
            });
            orion.logger.log("orion.form.initializeSlaveForm finished");
        }
    };

    this.initializeControlsClass = function () {
        $(document).find("*[data-o-role='control']").each(function () {
            var input = $(this).find("input");
            if (input.length > 0) {
                orion.form.validate(input);
                if (orion.form.hasMessages(input)) {
                    orion.form.setErrorState(input);
                }
            }
        });
    };

    /**
     * Enregistre le fait que le fieldset est masquée.
     */
    this.saveFieldsetState = function (element, state) {
        var currentStorageKey = orion.form.getFieldsetLocalStorageKey(element);
        orion.storeInLocalStorage(currentStorageKey, state);
    };

    /**
     * Récupère la clef permettant de stocker/récupérer l'état (affiché/masqué) du fieldset.
     * @param fieldsetElem le fieldset pour lequel générer la storageKey
     * @returns {String} la clef de stockage permettant d'identifier le fieldset .
     */
    this.getFieldsetLocalStorageKey = function (fieldsetElem) {
        var fieldsetId = fieldsetElem.attr("id");
        var currentStorageKey = 'fieldset_' + fieldsetId + "_hidden";
        return currentStorageKey;
    };

    /**
     * Récupère l'état, affiché ou masqué du fieldset.
     *
     * @return true si le fieldset est masqué.
     */
    this.isFieldsetHiddenInLocalStorage = function (fieldsetElem) {

        var currentStorageKey = orion.form.getFieldsetLocalStorageKey(fieldsetElem);
        var isHidden = orion.getFromLocalStorage(currentStorageKey);
        // Le stockage dans le local n'est pas fait au format booléen mais au format string.
        if (isHidden === "true") {
            return true;
        } else if (isHidden === "false") {
            return false;
        }
        return null;
    };

    this.initializeManyCheckbox = function () {
        this.initializeManyCheckboxViewSize();
        this.initializeManyCheckboxLocalFilter();
        this.initializeManyCheckboxAutoScroll();
    }

    /**
     * Defini la taille max des manyCheckbox en fonction
     * du nombre d'élements qui doit être visible
     * (défini par la metadata data-o-showby)
     * @return true si le fieldset est masqué.
     */
    this.initializeManyCheckboxViewSize = function () {
        $(document).find("ul[data-o-role='manyCheckbox']").each(function () {
            var showBy = $(this).attr('data-o-showby');
            var maxHeight = 40 * showBy + "px";
            $(this).css("max-height", maxHeight);
            $(this).css("min-height", maxHeight);
        });
    };

    /**
     * Initialize le filtre local des manyCheckbox
     * : champ de type texte dont la saisie est utilisée
     * pour filtrer localement les valeurs affichées
     */
    this.initializeManyCheckboxLocalFilter = function () {
        $(document).find("ul[data-o-role='manyCheckbox'][data-o-localfilter='true'][data-o-disabled='false']").each(function () {
            var manyCheckboxLocalfilterLi = $(this).find("li[data-o-role='manyCheckbox-localfilter']");
            if (manyCheckboxLocalfilterLi.length === 0) {
                $(this).prepend('<li data-o-role="manyCheckbox-localfilter"></li>');
                var ulId = $(this).attr('id');
                var placeholderFilter = $(this).attr('data-o-filter-placeceholder');
                var localFilterLi = $(this).find('li[data-o-role="manyCheckbox-localfilter"]');
                localFilterLi.append('<div class="form-group">');
                var divControlGroup = localFilterLi.find('div.form-group');
                divControlGroup.append('<label class="control-label hide" for="' + ulId + '-manyCheckboxLocalfilter">Critère de filtre</label>');
                divControlGroup.append('<div class="controls"><div class="input-group">');
                var divInputPrepend = divControlGroup.find('div.input-group');
                divInputPrepend.append('<span class="input-group-addon"><i class="fa fa-search"></i></span>');
                divInputPrepend.append('<input class="form-control" id="' + ulId + '-manyCheckboxLocalfilter" type="text" name="' + ulId + '-manyCheckboxLocalfilter" title="' + placeholderFilter + '" placeholder="' + placeholderFilter + '">');
            }
        });

        this.activeManyCheckboxLocalFilter();
    };

    /**
     * Initialize l'autoscroll des manyCheckbox en fonction
     * du premier élément coché dans la liste
     * (défini par la metadata data-o-showby)
     */
    this.initializeManyCheckboxAutoScroll = function () {
        $(document).find("ul[data-o-role='manyCheckbox'][data-o-autoscroll='true']").each(function () {
            var manyCheckboxSelectedItems = $(this).find("input[checked='checked']");
            if (manyCheckboxSelectedItems.length > 0) {
                var itemPos = parseInt(manyCheckboxSelectedItems[0].value);
                // 40 = hauteur d'une ligne à sélectionner
                // 34 = hauteur du champ de recherche en haut de la liste
                $(this).scrollTop((itemPos * 40) + 34);
            }
        });
    };

    /**
     * Active la fonctionnalité de filtre local des lignes du tableau des données
     * de la liste
     */
    this.activeManyCheckboxLocalFilter = function () {

        $("li[data-o-role='manyCheckbox-localfilter'] input[type='text']").live("keypress", function (event) {
            if (event.keyCode == 10 || event.keyCode == 13) {
                event.preventDefault();
            }
        });

        /**
         * Ajout d'un handler ecoutant l'événement  key up sur le champ
         * input du filtre local de manyCheckbox de manière à déclencher le filtrage lorsque l'utilisateur
         * saisie une valeur dans le champ
         */
        orion.event.listenToUsingSelector("keyup", "li[data-o-role='manyCheckbox-localfilter'] input[type='text']", function (event) {
            if (event.keyCode == '13') {
                return false;
            }

            orion.form.applyManyCheckboxLocalFilter($(this));
            return false;
        });
    }

    /**
     * Applique le filtre local de manycheckboxsur les itels (li)
     * de la liste de checkbox du composant manyCheckbox
     */
    this.applyManyCheckboxLocalFilter = function (input) {

        var filterValue = input.attr('value');

        // Noeud parent li le plus proche
        var li = input.parents('li[data-o-role="manyCheckbox-localfilter"]');
        // Les li contenant les checkbox
        var checkboxesLi = $(li[0]).siblings();

        if (filterValue == "") {
            checkboxesLi.each(
                function () {
                    $(this).show();
                }
            );
            return;
        }

        for (i = 0; i < checkboxesLi.length; i++) {
            var li = $(checkboxesLi[i]);
            //var piece = orion.tableenhancer.extractTextFromNode($(this));
            var piece = $(li).text();
            if (piece !== 'null') {
                if (piece.toLowerCase().indexOf(filterValue.toLowerCase()) == -1) {
                    li.hide();
                }
                else {
                    li.show();
                }
            }
        }
    };
};

orion.form = new Form();
$(document).ready(function () {
    orion.form.initialize();
    orion.event.listenToBeforeAjax(orion.form.beforeAjax);
    orion.event.listenToAfterAjax(orion.form.afterAjax);
});