/* -*- coding: utf-8 mode: javascript -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
*/
(function($) {
  $.resetTabindex = function(expr) {
    // Par défaut, prendre tous les champs de saisie *visibles*
    if (expr === undefined) expr = ":input:visible";
    $(expr).attr("tabindex", 0);
  };
  $.initTabindex = function(expr) {
    // Par défaut, prendre tous les champs de saisie *visibles*
    if (expr === undefined) expr = ":input:visible";
    // D'abord calculer la plus grande valeur de tabindex
    var tabindex = 0;
    var $expr = $(expr);
    $expr.each(function() {
      var $this = $(this);
      var ti = parseInt($this.attr("tabindex"));
      if (ti > tabindex) tabindex = ti;
    });
    // Puis initialiser les éléments restants à partir de tabindex + 1
    tabindex += 1;
    $expr.each(function() {
      var $this = $(this);
      var ti = parseInt($this.attr("tabindex"));
      if (!ti) $this.attr("tabindex", tabindex++);
    });
  };
  $.updateTabindex = function(expr) {
    if (expr === undefined) {
      $.resetTabindex(":input");
      $.initTabindex(":input:visible");
    } else {
      $.resetTabindex(expr);
      $.initTabindex(expr);
    }
  };

  // Dans les fonctions suivantes, input est un objet non wrappé par
  // jQuery. $input est le même objet wrappé par jQuery.
  var isdefined = function(value) { return value !== null && value !== undefined; }
  var isempty = function(input) { return $(input).val().length === 0; }
  var hasSelection = function(input) {
    // Tester si l'élément peut contenir une sélection. Typiquement,
    // il s'agit d'un champ de saisie de texte.
    try {
      return isdefined(input.selectionStart)
    } catch (e) {
    }
  };
  var isNavigable = function(input) {
    // Tester si l'élément est navigable avec la touche Entrée
    // Par défaut, on prend tous les éléments de type :input sauf textarea et submit
    var $input = $(input);
    return $input.is(":input") && !$input.is("textarea, :submit, :reset, :button")
  };
  var noSel = function(input) {
    if (! hasSelection(input)) return true;
    return input.selectionStart === input.selectionEnd;
  };
  var allSel = function(input) {
    if (! hasSelection(input)) return false;
    var length = $(input).val().length;
    return input.selectionStart === 0 && input.selectionEnd === length;
  };
  var noSelOrAllSel = function(input) {
    return noSel(input) || allSel(input);
  };
  var focusinput = function(tabindex, select) {
    if (tabindex > 0) {
      var $input = $("[tabindex='" + tabindex + "']");
      $input.focus();
      if (select) $input.select();
    }
  };
  var previnput = function(input, select) {
    var tabindex = $(input).attr("tabindex");
    if (tabindex) focusinput(parseInt(tabindex) - 1, select);
  };
  var nextinput = function(input, select) {
    var tabindex = $(input).attr("tabindex");
    if (tabindex) focusinput(parseInt(tabindex) + 1, select);
  };
  var atbegin = function(input) {
    if (! hasSelection(input)) return true;
    else if (noSel(input)) return input.selectionStart === 0;
    else return false;
  };
  var atend = function(input) {
    if (! hasSelection(input)) return true;
    else if (noSel(input)) return input.selectionEnd === $(input).val().length;
    else return false;
  };
  var hideAutocompleter = function(input) {
    var select = $(input).data("ac.select");
    if (select) select.hide();
  };
  var submitForm = function(input) {
    var $form = $(input).closest("form");
    var found = false;
    if ($form.length) {
      var phase = 0;
      $form.find(":input").each(function() {
        if (phase === 0 && this === input) {
          phase = 1;
        } else if (phase === 1 && $(this).is("input[type='submit'], button[type='submit']")) {
          found = true;
          $(this).click();
          return false;
        }
      });
      if (!found) {
        // si on ne trouve pas de bouton après l'élément, prendre le premier
        var $submit = $form.find("input[type='submit'], button[type='submit']").first();
        if ($submit.length) {
          found = true;
          $submit.click();
        }
      }
      if (!found) {
        // s'il n'y a aucun bouton, demander au formulaire de se soumettre lui-même
        $form.submit();
      }
    }
    return found;
  };

  $.navigate = function(onEnter, selectAfterFocus, initTabindex) {
    // Action par défaut: next
    if (onEnter === undefined) onEnter = "next";
    // Faut-il sélectionner le champ après avoir changé le focus?
    if (selectAfterFocus === undefined) selectAfterFocus = true;
    // Faut-il initialiser tabindex pour les éléments sélectionnés? Sinon,
    // il FAUT spécifier les valeurs de tabindex pour tous les champs
    // concernés par la navigation
    if (initTabindex === undefined) initTabindex = true;

    if (onEnter === "submit") {
      $(":input").off(".navigate").on("keypress.navigate", function(e) {
        if (e.which === 13 && isNavigable(this)) {
          e.preventDefault();
          submitForm(this);
        }
      });
    } else if (onEnter === "next") {
      var willMove = false;
      if (initTabindex) $.initTabindex(":input");
      $(":input").off(".navigate").on("keypress.navigate", function(e) {
        if (e.which === 8 && hasSelection(this) && isempty(this)) {
          // backspace sur un champ vide déplace la sélection sur le champ précédent
          //hideAutocompleter(this);
          e.preventDefault();
          previnput(this);
        } else if (e.which === 13) {
          if (e.ctrlKey) {
            // Ctrl-Entrée soumet le formulaire
            e.preventDefault();
            submitForm(this);
          } else if (isNavigable(this)) {
            // Entrée sur un champ déplace la sélection sur le champ suivant
            e.preventDefault();
            nextinput(this, selectAfterFocus);
          }
        }
      }).on("keydown.navigate", function(e) {
        willMove = false;
        if (e.which === 37 && atbegin(this)) {
          // flèche gauche au début du champ déplace la sélection sur le champ précédent
          if (!hasSelection(this) || isempty(this) || !allSel(this)) {
            willMove = true;
            e.preventDefault();
          }
        } else if (e.which === 39 && atend(this)) {
          // flèche droite à la fin du champ déplace la sélection sur le champ suivant
          if (!hasSelection(this) || isempty(this) || !allSel(this)) {
            willMove = true;
            e.preventDefault();
          }
        }
      }).on("keyup.navigate", function(e) {
        if (e.which === 37 && willMove) {
          // flèche gauche au début du champ déplace la sélection sur le champ précédent
          //hideAutocompleter(this);
          previnput(this, selectAfterFocus);
        } else if (e.which === 39 && willMove) {
          // flèche droite à la fin du champ déplace la sélection sur le champ suivant
          //hideAutocompleter(this);
          nextinput(this, selectAfterFocus);
        }
      });
    } else if (onEnter === "disable" || onEnter === "off") {
      $(":input").off(".navigate");
    }
  };

  // Activer le focus et placer le curseur à la fin de la ligne spécifiée.
  // Par défaut, il s'agit de la première ligne (soit line==0)
  // Si setSelectionRange n'est pas supporté, c'est un NOP
  $.fn.cursorAtEOFL = function(line) {
    if (line === undefined) line = 0;
    return this.each(function() {
      var $this = $(this);
      if (this.setSelectionRange) {
        var text = $this.val();
        var pos = 0;
        while (line-- >= 0) {
          var index = text.search('\r?\n');
          if (index !== -1) {
            // index est la position avant [CR]LF, indexnl la position après [CR]LF
            var indexnl = index;
            if (text.charAt(indexnl) === '\r') indexnl++;
            indexnl++;

            if (line === -1) {
              // à la dernière ligne, considérer index, parce
              // qu'on veut se placer avant [CR]LF
              pos += index;
            } else {
              // avant la dernière ligne, considérer indexnl, parce
              // qu'on veut avoir un compte exact
              pos += indexnl;
            }
            text = text.substr(indexnl);
          } else {
            pos += text.length;
            break;
          }
        }
        this.setSelectionRange(pos, pos);
      }
      $this.focus();
    });
  };
})(jQuery);
