/* global Overlay,CodeMirror */
/**
 * Objet global définissant l'espace de nom Bdf
 * 
 * @namespace Bdf
 */
var Bdf = {};

/**
 * 
 * Rétrocompatibilité
 */
var BDF = Bdf;

/**
 * Cinq signatures :
 * - une chaine unique : recherche de l'élément avec l'identifiant
 * - un objet : transformation de l'objet en sélecteur CSS
 * - deux chaines : concaténation des deux chaines avec _
 * - un premier argument et un objet : conversion de l'objet en sélecteur CSS et recherche 
 * à partir de l'élément décrit par le premier argument
 * - plusieurs chaines : concaténation des chaines avec _
 * 
 * @returns {JQuery}
 */
function $$(...theArgs) {
    var length = theArgs.length;
    switch(length) {
        case 0:
            throw new Error("No argument");
        case 1: {
            let uniqueArg = theArgs[0];
            if (typeof uniqueArg === "object") {
                return _byProperties(uniqueArg);
            } else {
                return _byId(uniqueArg);
            }
        }
        case 2: {
            let arg1 = theArgs[0];
            let arg2 = theArgs[1];
            if (typeof arg1 === "object") {
                return _find(arg1, arg2);
            } else {
                if (typeof arg2  === "object") {
                    return _find(_byId(arg1), arg2);
                } else {
                    return _byId(arg1 + "_" + arg2);
                }
            }
        }
        default: {
            let lastArg = theArgs[length - 1];
            if (typeof lastArg === "object") {
                return _find(_byId(theArgs.slice(0, length - 1).join("_")), lastArg);
            } else {
                return _byId(theArgs.join("_"));
            }
        }
    }
    
    function _byId(id) {
        return $(document.getElementById(id));
    }
    
    function _byProperties(properties) {
        return $($$.toCssSelector(properties));
    }
    
    function _find(jqArgument, properties) {
        return $$.convert(jqArgument).find($$.toCssSelector(properties));
    }
    
};

/**
 * Convertit l'argument en objet JQuery. S'il s'agit déjà d'un objet JQuery, il
 * est simplement retourné.
 * 
 * @param {JQuery|string|object} jqArgument
 * @returns {JQuery} Objet JQuery résultant
 */
$$.convert = function (jqArgument) {
    if (jqArgument.jquery) {
        return jqArgument;
    } else {
        return $(jqArgument);
    }
};

$$.first = function (...theArgs) {
    var $element = $$.apply(null, theArgs);
    if ($element.length === 0) {
        return null;
    } else {
        return $element[0];
    }
};

$$.one = function (...theArgs) {
    if ((theArgs.length === 1) && (typeof theArgs[0] === "string")) {
        let element = document.getElementById(theArgs[0]);
        if (element) {
            return element;
        } else {
            throw "No element with id: " + theArgs[0];
        }
    }
    var $element = $$.apply(null, theArgs);
    if ($element.length === 0) {
        throw "No element matching: " + theArgs.join(" / ");
    } else {
        return $element[0];
    }
};

$$.toCssSelector = function (properties) {
    var query = "";
    var elementName = false;
    var suffix = "";
    for(let key in properties) {
        let value = properties[key];
        if (!key.startsWith("_")) {
            if (value === true) {
                query += "[" + _toDataAttribute(key) + "]";
            } else {
                query += "[" + _toDataAttribute(key) + "='" + value + "']";
            }
        } else if (key === "_checked") {
            if (value) {
               suffix += ":checked";
           } else {
               suffix += ":not(:checked)";
           }
        } else if (key === "_element") {
            elementName = value;
        } else {
            if (value === true) {
                query += "[" + key.substring(1) + "]";
            } else {
                query += "[" + key.substring(1) + "='" + value + "']";
            }
        }
    }
    if (elementName) {
        query = elementName + query;
    }
    query += suffix;
    return query;
     
    function _toDataAttribute(camelCaseString) {
        return "data-" + camelCaseString.replace(/[A-Z]/g, function (upperLetter) {
            return "-" + upperLetter.toLowerCase();
        });
    }
};

var ID = $$.one;

Bdf.URL = "";
Bdf.WORKING_LANG = "";
Bdf.MAC_PLATFORM = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
Bdf.genid = 0;
Bdf.genidMap = {};
Bdf.jsonDataType = "json";
Bdf.langConfiguration = null;
Bdf.pathConfiguration = null;
Bdf.timer = null;
Bdf.templateInitDone = false;
Bdf.templateHelpers = {
    loc: function() {
            return Bdf.Loc.get.apply(this, arguments);
        },
    genid: function (nameSpace, localKey) {
            return Bdf.generateId(nameSpace, localKey);
        }
};
Bdf.templateTags = {
    colon: function() {
                return Bdf.escape(Bdf.Loc.get("_ label.global.colon"));
        },
    clean: function () {
        return this.tagCtx.render().replace(/\s\s+/gi, "");
    }
};
Bdf.templateConverters = {};
Bdf.changeState = false;
Bdf.changeTestFunctions = [];
Bdf.unloadKey = "_ warning.global.beforeunload";
Bdf.registredObjectMap = new Map();


/*******************************************************************************
* Valeurs à redéfinir pour une utilisation de LocalStorage
*******************************************************************************/

Bdf.BASE_URI = false;
Bdf.MAINSTORAGE_KEY = false;


/*******************************************************************************
* Bdf.Loc localisation du texte (Bdf.Loc.map est remplie par Bdf.Loc.add)
*******************************************************************************/

Bdf.Loc = {};

Bdf.Loc.map = {};

Bdf.Loc.add = function (locs) {
    for(let key in locs) {
        Bdf.Loc.map[key] = locs[key];
    }
};

Bdf.Loc.contains = function (messageKey) {
    return Bdf.Loc.map.hasOwnProperty(messageKey);
};

Bdf.Loc.get = function (messageKey) {
    var argLength = arguments.length;
    if (messageKey === "") {
        let text = "";
        if (argLength > 1) {
            for(let i = 1; i < argLength; i++) {
                let arg = arguments[i];
                if (!arg) {
                    text += arg;
                }
            }
        }
        return text;
    } else if (!Bdf.Loc.map.hasOwnProperty(messageKey)) {
        return "?" + messageKey + "?";
    } else {
        let text = Bdf.Loc.map[messageKey];
        if (argLength > 1) {
            for(let i = 1; i < argLength; i++) {
                let arg = arguments[i];
                if (!arg) {
                    arg = "";
                }
                let p = i-1;
                text = text.replace( "{" + p + "}", arg);
            }
        }
        return text;
    }
};

Bdf.Loc.escape = function () {
    return Bdf.escape(Bdf.Loc.get.apply(this, arguments));
};



/**
 * Lancement à la suite de fonctions.
 * Les arguments doivent être des fonctions ou un tableau de fonctions comme unique argument.
 * Toutes ces fonctions doivent accepter un seul argument une fonction de rappel : 
 * 
 * @param {Array} functionArray
 * @returns {undefined}
 */
Bdf.runSerialFunctions = function (functionArray) {
    var length = arguments.length;
    var functionArray;
    if (length === 0) {
        return;
    } else if ((length === 1) && (Array.isArray(arguments[0]))) {
        functionArray = arguments[0];
    } else {
        functionArray = Array.from(arguments);
    }
    length = functionArray.length;
    if (length > 0) {
        functionArray[0](_getCallback(0));
    }
    
    function _getCallback(index) {
        if (index < (length - 1)) {
            var next = index + 1;
            return function () {
                functionArray[next](_getCallback(next));
            };
        } else {
            return false;
        }
    }
};

/**
 * Retourne une fonction lançant une suite de fonctions.
 * Cette fonction prend comme seul argument une fonction de rappel.
 * Les arguments doivent être des fonctions ou un tableau de fonctions comme unique argument.
 * Toutes ces fonctions doivent accepter un seul argument une fonction de rappel : 
 * 
 * @param {Array} functionArray
 * @returns {Function}
 */
Bdf.serialFunctions = function (functionArray) {
    var length = arguments.length;
    var functionArray;
    if (length > 0) {
        if ((length === 1) && (Array.isArray(arguments[0]))) {
            functionArray = Array.from(arguments[0]);
        } else {
            functionArray = Array.from(arguments);
        }
        length = functionArray.length;
    }
    if (length === 0) {
        return function (callback) {
            if (callback) {
                callback();
            }
        };
    } else {
        return function (callback) {
            functionArray.push(function () {
                if (callback) {
                    callback();
                }
            });
            length = functionArray.length;
            functionArray[0](_getCallback(0));
        };
    }

    
    function _getCallback(index) {
        if (index < (length - 1)) {
            let next = index + 1;
            return function () {
                functionArray[next](_getCallback(next));
            };
        } else {
            return false;
        }
    }
    
};


/*******************************************************************************
* Initialisation de la configuration de la langue
*******************************************************************************/

Bdf.toLabelArray = function (labelMap) {
    var labelArray = new Array();
    for(var i = 0, len = Bdf.langConfiguration.workingLangArray.length; i < len; i++) {
        var lang = Bdf.langConfiguration.workingLangArray[i];
        var value = "";
        if (labelMap.hasOwnProperty(lang)) {
            value = labelMap[lang];
        }
        labelArray.push({
            lang: lang,
            value: value
        });
    }
    return labelArray;
};

/*******************************************************************************
* Timer
*******************************************************************************/

Bdf.startTimer = function (cronFunction, milliseconds) {
    if (!milliseconds) {
        milliseconds = 1000;
    }
    Bdf.clearTimer();
    Bdf.timer = setInterval(cronFunction, milliseconds);
};

Bdf.clearTimer = function () {
    if (Bdf.timer) {
        clearInterval(Bdf.timer);
        Bdf.timer = null;
    }
};

Bdf.hasTimer = function () {
    if (Bdf.timer) {
        return true;
    } else {
        return false;
    }
};


/*******************************************************************************
* Suivi des changements
*******************************************************************************/
Bdf.hasChange = function () {
    if (Bdf.changeState) {
        return true;
    }
    for(let changeTestFunction of Bdf.changeTestFunctions) {
        if (changeTestFunction()) {
            return true;
        }
    }
    return false;
};

Bdf.setChange = function (state) {
    Bdf.changeState = state;
};

Bdf.addChangeTestFunction = function (changeTestFunction) {
    Bdf.changeTestFunctions.push(changeTestFunction);
};

Bdf.confirmUnload = function (needed) {
    var confirmKey = "_ warning.global.beforeunload";
    var finalNeeded = true;
    if (needed) {
        if (typeof needed === 'function') {
            finalNeeded = needed();
        } else {
            finalNeeded = needed;
        }
    } else {
        finalNeeded = Bdf.hasChange();
    }
    if (finalNeeded) {
        var confirmation = confirm(Bdf.Loc.get(confirmKey));
        if (!confirmation) {
            return false;
        }
    }
    return true;
};

Bdf.initBeforeUnload = function (options) {
    var testFunction = Bdf.hasChange;
    if (options) {
        if (options.unloadKey) {
            Bdf.unloadKey = options.unloadKey;
        }
        if (options.testFunction) {
            testFunction = options.testFunction;
        }
    }
    $(window).on('beforeunload', function(e) {
        if (testFunction(e)) {
            var loc = Bdf.Loc.get(Bdf.unloadKey);
            if (loc.length === 0) {
                loc = "Discard changes ?";
            }
            return loc;
        } else {
            return;
        }
    });
};


/*******************************************************************************
* Initialisation des gabarits Jsrender
*******************************************************************************/

Bdf.addTemplateOptions = function (options) {
    if (options.helpers) {
        for(var prop in options.helpers) {
            if (Bdf.templateHelpers.hasOwnProperty(prop)) {
                Bdf.log("Already defined in Bdf.templateHelpers: " + prop);
            }
            Bdf.templateHelpers[prop] = options.helpers[prop];
        }
    }
    if (options.converters) {
        for(var prop in options.converters) {
            if (Bdf.templateConverters.hasOwnProperty(prop)) {
                Bdf.log("Already defined in  Bdf.templateConverters: " + prop);
            }
            Bdf.templateConverters[prop] = options.converters[prop];
        }
    }
    if (options.tags) {
        for(var prop in options.tags) {
            if (Bdf.templateTags.hasOwnProperty(prop)) {
                Bdf.log("Already defined in  Bdf.templateTags: " + prop);
            }
            Bdf.templateTags[prop] = options.tags[prop];
        }
    }
};

Bdf.initTemplates = function () {
    if (!Bdf.templateInitDone) {
        Bdf.templateInitDone = true;
        if ($.templates) {
            $.views.helpers(Bdf.templateHelpers);
            $.views.tags(Bdf.templateTags);
            $.views.converters(Bdf.templateConverters);
            $("script[type='text/x-jsrender']").each(function (index, element) {
                $.templates(element.dataset.name, $(element).html());
            });
        }
        if (Overlay) {
            Overlay.closeTooltip = Bdf.Loc.escape("_ link.global.close");
        }
    }
};

/**
 * See https://www.jsviews.com/#tmplrender
 * 
 * @param {String} name
 * @param {Object||Array} data
 * @param {Object} context
 * @param {Boolean} noIteration
 * @returns {String}
 */
Bdf.render = function (name, data, context, noIteration) {
    if (!Bdf.templateInitDone) {
        Bdf.log("Bdf.initTemplates not called");
        return "";
    }
    var template = $.templates[name];
    if (template) {
        return template.render(data, context, noIteration);
    } else {
        Bdf.log("Unknown template: " + name);
        return "";
    }
};


/*******************************************************************************
* Fonctions utilitaires
*******************************************************************************/

Bdf.log = function (msg) {
    if ((console) && (console.log)) {
        console.log(msg);
    }
};

Bdf.removeInArray = function (array, name) {
    var index = -1;
    for(let i = 0, len = array.length; i < len; i++) {
        if (array[i].name === name) {
            index = i;
            break;
        }
    }
    if (index !== -1) {
        array.splice(index, 1);
    }
};

Bdf.insertInArray = function (array, obj) {
    var objName = obj.name;
    var index = -1;
    for(let i = 0, len = array.length; i < len; i++) {
        if (array[i].name > objName) {
            index = i;
            break;
        }
    }
    if (index !== -1) {
        array.splice(index, 0, obj);
    } else {
        array.push(obj);
    }
};

Bdf.updateInArray = function (array, obj) {
    var objName = obj.name;
    for(let i = 0, len = array.length; i < len; i++) {
        if (array[i].name === objName) {
            array[i] = obj;
            break;
        }
    }
};

Bdf.escape = function (text) {
    text = String(text);
    var result = "";
    for(let i = 0, len = text.length; i < len; i++) {
        let carac = text.charAt(i);
        switch (carac) {
            case '&':
                result += "&amp;";
                break;
            case '"':
                result += "&quot;";
                break;
            case '<':
                result += "&lt;";
                break;
            case '>':
                result += "&gt;";
                break;
            case '\'':
                result += "&#x27;";
                break;
            default:
                result += carac;
        }
    }
    return result;
};

Bdf.checkFormChange = function (jqArgument) {
    var $elements = $$.convert(jqArgument);
    var change = false;
    var $inputs = $elements.find("input");
    for(let i = 0; i < $inputs.length; i++) {
        let input = $inputs[i];
        if (!input.name) {
            continue;
        }
        if ((input.type === 'checkbox') || (input.type === 'radio')) {
            change = (input.checked !== input.defaultChecked);
            
        } else if (input.type === 'file') {
            if (input.value) {
                change = true;
            }
        } else if ((input.type === 'text') || (input.type === 'password')) {
            change = (input.value !== input.defaultValue);
        }
        if (change) {
            return true;
        }
    }
    var $selects = $elements.find("select");
    for(let i = 0; i < $selects.length; i++) {
        let select = $selects[i];
        if (!select.name) {
            continue;
        }
        if (select.options.length === 0) {
            continue;
        }
        let defaultSelectedIndex = 0;
        for(let option of select.options) {
            if (option.defaultSelected) {
                defaultSelectedIndex = option.index;
                break;
            }
        }
        if (select.selectedIndex !== defaultSelectedIndex) {
            change = true;
        }
        if (change) {
            return true;
        }
    }
    var $textareas = $elements.find("textarea");
    for(let i = 0; i < $textareas.length; i++) {
        let textarea = $textareas[i];
        if (!textarea.name) {
            continue;
        }
        if (textarea.value !== textarea.defaultValue) {
            return true;
        }
    }
    return false;
};

Bdf.updateDefaultValues = function (jqArgument) {
    var $elements = $$.convert(jqArgument);
    $elements.find("input").each(function (index, input) {
        if (input.name) {
            if ((input.type === 'checkbox') || (input.type === 'radio')) {
                input.defaultChecked = input.checked;
            } else if (input.type === 'file') {
                input.value = null;
            } else if ((input.type === 'text') || (input.type === 'password')) {
                input.defaultValue = input.value;
            }
        }
    });
    $elements.find("select").each(function (index, select) {
        if (select.name) {
            for(let option of select.options) {
                option.defaultSelected = option.selected;
            }
        }
    });
    $elements.find("textarea").each(function (index, textarea) {
        if (textarea.name) {
            textarea.defaultValue = textarea.value;
        }
        
    });
    return false;
};

Bdf.generateId = function (nameSpace, localKey) {
    var genIdKey = false;
    if ((nameSpace) && (localKey)) {
        genIdKey = nameSpace + ":" + localKey;
        if (Bdf.genidMap.hasOwnProperty(genIdKey)) {
            return Bdf.genidMap[genIdKey];
        }
    }
    var newId = Bdf.genid + 1;
    Bdf.genid = newId;
    var id = "js-";
    if (newId < 10) {
        id += "0";
    } 
    if (newId < 100) {
        id += "0";
    }
    if (newId < 1000) {
        id += "0";
    }
    id = id + newId; 
    if (genIdKey) {
        Bdf.genidMap[genIdKey] = id;
    }
    return id;
};

Bdf.attrMapToString = function (attrMap) {
    var text = "";
    for(var prop in attrMap) {
        text += prop;
        text += "=";
        var array = attrMap[prop];
        for(var i = 0; i < array.length; i++) {
            if (i > 0) {
                text += ";";
            }
            text += _escapeAttrValue(array[i]);
        }
        text += "\n";
    }
    return text;
    
    function _escapeAttrValue(value) {
            var result = "";
        for(var i = 0; i < value.length; i++) {
            carac = value.charAt(i);
            switch (carac) {
                case ';':
                case '\\':
                    result += '\\';
                    break;
            }
            result += carac;
        }
        return result;
    };
};

Bdf.getMatchingCodeMirrorMode = function (path) {
    var extension;
    var idx = path.lastIndexOf(".");
    if (idx === -1) {
        extension = path;
    } else {
        extension = path.substring(idx + 1);
    }
    switch(extension) {
        case "css":
            return "css";
        case "xml":
             return "xml";
         case "xsl":
             return "xml";
         case "js":
            return "javascript";
        case "ini":
            return "properties";
        case "properties":
            return "properties";
        case "html":
            return "xml";
        default:
            return "";
    }
};

Bdf.checkIframeLocation = function (iframe) {
    var outside = false;
    var withBorder;
    var pathName;
    var search;
    try {
        pathName = iframe.contentWindow.location.pathname;
        search = iframe.contentWindow.location.search;
        withBorder = _isWithBorder(pathName, search);
    }  catch(error) {
        outside = true;
        withBorder = true;
    }
    return {
        outsideLocation: outside,
        withBorder: withBorder,
        pathName: pathName,
        queryPart: search
    };
    
    function _isWithBorder(pathName, search) {
        if ((search) && (search.indexOf("bdf-framestyle=border") > -1)) {
            return true;
        }
        let idx = pathName.lastIndexOf("/");
        if (idx > -1) {
            pathName = pathName.substr(idx + 1);
        }
        idx = pathName.indexOf(".");
        if (idx > -1) {
            return true;
        } else {
            return false;
        }
    }
};


/*******************************************************************************
* Classes codeMirror
*******************************************************************************/

Bdf.parseCmSpanArray = function (key) {
    var result = new Array();
    if (key.indexOf("_master_") === 0) {
        result.push({
            text: "_master_",
            cm: "cm-qualifier"
        });
        key = key.substring("_master_".length);
    }
    var idx = key.indexOf("_");
    if (idx === -1) {
        switch(key) {
            case "liage":
            case "parentage":
                return [{
                    text: key,
                    cm: "cm-component-subset"
                }];
            default:
                return [{
                text: key,
                cm: "cm-component-field"
            }];
        }
    }
    var prefix = key.substring(0, idx);
    result.push({
        text: prefix,
        cm: _getCmClass(prefix)
    });
    var name = key.substring(idx);
    var mode, weight = false;
    idx = name.indexOf("_", 1);
    if (idx > 0) {
        mode = name.substring(idx);
        name = name.substring(0, idx);
    }
    result.push({
        text: name,
        cm: "cm-quote"
    });
    if (mode) {
        if ((/^[0-9]+$/.test(mode))) {
            weight = mode;
            mode = false;
        } else {
            idx = mode.indexOf("_");
            if (idx > 0) {
                weight = mode.substring(idx);
                mode = mode.substring(0, idx);
            }
        }
    }
    if (mode) {
        result.push({
            text: mode,
            cm: "cm-def"
        });
    }
    if (weight) {
        result.push({
            text: weight,
            cm: "cm-number"
        });
    }
    return result;
    
    
    function _getCmClass(prefix) {
        switch(prefix) {
            case "corpus":
            case "thesaurus":
            case "addenda":
            case "album":
            case "sphere":
            case "motcle":
            case "fiche":
                return "cm-component-subset";
            default:
                return "cm-component-field";
        }
    }
};

/*******************************************************************************
* Chemins divers
*******************************************************************************/

Bdf.toTmpPath = function (tmpFileName) {
    return "output/tmp/" + tmpFileName;
};

Bdf.getCommandUrl = function (commandKey) {
    return "http://www.fichotheque.net/" + commandKey + ".html";
};


/*******************************************************************************
* Raccourci JQuery
*******************************************************************************/

Bdf.$ = function (jqArgument, properties) {
    if (!properties) {
        properties = jqArgument;
        jqArgument = null;
    }
    var query = $$.toCssSelector(properties);
    if (jqArgument) {
         return $$.convert(jqArgument).find(query);
    } else {
         return $(query);
    }
};

Bdf.getMandatoryAncestor = function (element, properties) {
    var cssQuery = $$.toCssSelector(properties);
    var parents = $(element).parents(cssQuery);
    if (parents.length === 0) {
        throw "No ancestor matching query: " +  cssQuery;
    }
    return parents[0];
};


/*******************************************************************************
* CodeMirror
*******************************************************************************/

Bdf.toCodeMirror = function (textArea, config) {
    if (typeof textArea === "string") {
        textArea = $$.one(textArea);
    }
    var codeMirror = CodeMirror.fromTextArea(textArea, config);
    $(textArea)
            .data("codeMirrorInstance", codeMirror)
            .parents("details").on("toggle", function () {
                codeMirror.refresh();
            });
    return codeMirror;
};

Bdf.getAssociatedCodeMirror = function (textArea) {
    return $(textArea).data("codeMirrorInstance");
};

Bdf.refreshCodeMirror = function (jqArgument) {
    $$.convert(jqArgument).find("textarea").each(function (index, element) {
        let codeMirror = Bdf.getAssociatedCodeMirror(element);
        if (codeMirror) {
            codeMirror.refresh();
        }
    });
};

/*******************************************************************************
* Remplacement d'un champ de texte par trois champs date
*******************************************************************************/

Bdf.replaceWithDateInputs = function (input, type) {
    type = _checkType(type);
    var $input = $$.convert(input);
    var is_number = /^[0-9]+$/;
    var dateArray = Bdf.splitDate($input.val());
    if (!dateArray) {
        return;
    }
    var genId = Bdf.generateId();
    $input.hide();
    $input.data("subinputGenId", genId);
    $input.after(Bdf.render("bdf:dateinputs-" + type, {
        genId: genId,
        year: dateArray[0],
        month: dateArray[1],
        day: dateArray[2]
    }));
    var $yearInput = $$(genId, "year");
    var $monthInput =  $$(genId, "month");
    var $dayInput =  $$(genId, "day");
    $yearInput.on('input', function () {
        let $this = $(this);
        let val = $this.val();
        let val_split = Bdf.splitDate(val);
        if (!val_split) {
            $this.addClass("global-ErrorMessage");
        } else {
            $this.removeClass("global-ErrorMessage");
            if (val_split[1].length > 0) {
                $this.val(val_split[0]);
                $monthInput.val(val_split[1]).trigger('input');
                if (val_split[2].length > 0) {
                    $dayInput.val(val_split[2]).trigger('input');
                }
                $this.change();
            } else if (val_split[0].length === 4) {
                switch(type) {
                    case "iso":
                        $monthInput.focus();
                        break;
                }
            }
        }

    }).keydown(function (event) {
        _keydown(event, this, "year");
    }).keyup(function (event) {
        _keyup(event, this, "year");
    }).change(_updateInput);
    $monthInput.on('input', function () {
        let $this = $(this);
        let val = $this.val();
        if (_test_month(val)) {
            $this.removeClass("global-ErrorMessage");
            if ((val.length === 2) || ((val.length === 1) && (val !== '0') && (val !== '1'))) {
                switch(type) {
                    case "iso":
                       $dayInput.focus();
                       break;
                    case "inv":
                        $yearInput.focus();
                        break;
                    case "usa":
                        $dayInput.focus();
                        break;
                }
            }
        } else {
            $this.addClass("global-ErrorMessage");
        }
    }).keydown(function (event) {
        _keydown(event, this, "month");
    }).keyup(function (event) {
        _keyup(event, this, "month");
    }).change(_updateInput);
    $dayInput.on('input', function () {
        let $this = $(this);
        let val = $this.val();
        if (_test_day(val)) {
            $this.removeClass("global-ErrorMessage");
            if ((val.length === 2) || ((val.length === 1) && (val !== '0') && (val !== '1') && (val !== '2') && (val !== '3'))) {
                switch(type) {
                    case "inv":
                        $monthInput.focus();
                        break;
                    case "usa":
                        $yearInput.focus();
                        break;
                }
            }
        } else {
            $this.addClass("global-ErrorMessage");
        }
    }).keydown(function (event) {
        _keydown(event, this, "day");
    }).keyup(function (event) {
        _keyup(event, this, "day");
    }).change(_updateInput);
    return genId;
    
    function _test_month(month) {
        month = month.trim();
        var monthLength = month.length;
        if (monthLength === 0) {
            return true;
        }
        if (monthLength > 2) {
            return false;
        }
        if (Bdf.testSpecialMonths(month)) {
            return true;
        }
        if (!is_number.test(month)) {
            return false;
        }
        if (parseInt(month) > 12) {
            return false;
        }
        return true;
    }
    
    function _test_day(day) {
        day = day.trim();
        var dayLength = day.length;
        if (dayLength === 0) {
            return true;
        }
        if (dayLength > 2) {
            return false;
        }
        if (!is_number.test(day)) {
            return false;
        }
        if (parseInt(day) > 31) {
            return false;
        }
        return true;
    }
    
    function _updateInput() {
        var newValue = Bdf.joinDate($yearInput.val().trim(), $monthInput.val().trim(), $dayInput.val().trim());
        $input.val(newValue);
        $input.change();
    }
    
    function _checkType(type) {
        if (!type) {
            return "iso";
        }
        switch(type) {
            case "inv":
                return "inv";
            case "usa":
                return "usa";
            default:
                return "iso";
        }
    }
    
    function _keydown(event, currentInput, datePart) {
        var currentSelectionStart = currentInput.selectionStart;
        switch(event.key) {
            case "Backspace":
                if (currentSelectionStart === 0) {
                    let $previous = _getPrevious(datePart);
                    if ($previous) {
                        $previous.focus();
                        $previous[0].selectionStart = $previous.val().length;
                    }
                }
                break;
        }       
    }
    
    function _keyup(event, currentInput, datePart) {
        var currentSelectionStart = currentInput.selectionStart;
        switch(event.key) {
            case "ArrowLeft":
                if (currentSelectionStart === 0) {
                    let $previous = _getPrevious(datePart);
                    if ($previous) {
                        $previous.focus();
                        $previous[0].selectionStart = $previous.val().length;
                    }
                }
                break;
            case "ArrowRight":
                if (currentSelectionStart === (currentInput.value.length)) {
                    let $next = _getNext(datePart);
                    if ($next) {
                        $next.focus();
                        $next[0].selectionStart = 0;
                    }
                }
                break;
        }       
    }
    
    function _getNext(datePart) {
        switch(type) {
            case "iso":
                switch(datePart) {
                    case "month":
                        return $dayInput; 
                    case "year":
                        return $monthInput;
                    default:
                        return null;
                }
            case "inv":
                switch(datePart) {
                    case "day":
                        return $monthInput;
                    case "month":
                        return $yearInput;
                    default:
                        return null;
                }
            case "usa":
                switch(datePart) {
                    case "day":
                        return $yearInput;
                    case "month":
                        return $dayInput;
                     default:
                        return null;
                }
        }
    }
    
    function _getPrevious(datePart) {
        switch(type) {
            case "iso":
                switch(datePart) {
                    case "day":
                        return $monthInput;
                    case "month":
                        return $yearInput; 
                    default:
                        return null;
                }
            case "inv":
                switch(datePart) {
                    case "month":
                        return $dayInput;
                    case "year":
                        return $monthInput;
                    default:
                        return null;
                }
            case "usa":
                switch(datePart) {
                    case "day":
                        return $monthInput;
                    case "year":
                        return $dayInput;
                    default:
                        return null;
                }
        }        
    }
};

Bdf.joinDate = function (year, month, day) {
    year = year + "";
    month = month + "";
    day = day + "";
    var newValue = "";
    if (year.length > 0) {
        newValue = year;
        if (month.length > 0) {
            if (month.length === 1) {
                month = "0" + month;
            }
            newValue = newValue + "-" + month;
            if (day.length > 0) {
                if (day.length === 1) {
                    day = "0" + day;
                }
                newValue = newValue + "-" + day;
            }
        }
    }
    return newValue;
};

Bdf.updateDateInputs = function (input) {
    var $input = $$.convert(input);
    var genId = $input.data("subinputGenId");
    if (!genId) {
        return;
    }
    var dateArray = Bdf.splitDate($input.val());
    if (!dateArray) {
        Bdf.log("Wrong date: " + $input.val());
        return;
    }
    $$(genId, "year").val(dateArray[0]);
    $$(genId, "month").val(dateArray[1]);
    $$(genId, "day").val(dateArray[2]);
};

Bdf.splitDate = function (value) {
    var is_number = /^[0-9]+$/;
    if (value) {
        value = value.trim();
    } else {
        value = "";
    }
    if (value.length === 0) {
        return ["", "", ""];
    }
    if (value === "*") {
        return ["*", "", ""];
    }
    var split = value.split("-");
    if (split.length > 3) {
        return false;
    }
    for(var i = split.length; i < 3; i++) {
        split.push("");
    }
    var year = split[0];
    var month = split[1];
    var day = split[2];
    if (year.length === 0) {
        return false;
    } else if (!is_number.test(year)) {
        return false;
    }
    if (month.length > 0) {
        if (!is_number.test(month)) {
            if (Bdf.testSpecialMonths(month)) {
                if (day.length > 0) {
                    return false;
                }
            } else {
                return false;
            }
        }
    } else if (day.length > 0) {
        if (!is_number.test(day)) {
            return false;
        }
    }
    return [year, month, day];
};

Bdf.testSpecialMonths = function (month) {
    switch(month) {
        case "T1":
        case "T2":
        case "T3":
        case "T4":
        case "S1":
        case "S2":
            return true;
        default:
            return false;
    }
};

Bdf.appendValue = function (element, newValue) {
    let currentValue = element.value;
    if (_testSemicolon(currentValue)) {
        currentValue = currentValue + "; ";
    }
    element.value = currentValue + newValue;
            
    function _testSemicolon(currentText) {
        let size = currentText.length;
        if (size === 0) return false;
        for (let i = size-1;i >= 0;i --) {
            let carac = currentText.charAt(i);
            if (carac !== " ") {
                if (carac === ";") return false;
                else return true;
            }
        }
        return false;
    }
};


/*******************************************************************************
* Bdf.registredObjectMap
*******************************************************************************/

Bdf.registerObject = function (name, objectToRegister) {
    Bdf.registredObjectMap.set(name, objectToRegister);
};

Bdf.getRegistredObject = function (name) {
    return Bdf.registredObjectMap.get(name);
};

Bdf.registerFunction = function (name, functionToRegister) {
    if ((functionToRegister) && (typeof functionToRegister !== "function")) {
        throw "functionToRegister is not a function: " + functionToRegister;
    }
    Bdf.registredObjectMap.set("function:" + name, functionToRegister);
};

Bdf.getRegistredFunction = function (name) {
    var registredFunction = Bdf.getRegistredObject("function:" + name);
    if ((registredFunction) && (typeof registredFunction !== "function")) {
        Bdf.log("registredFunction is not a function: " + registredFunction);
    }
    return registredFunction;
};

Bdf.getCallback = function (callKey) {
    var callback = Bdf.getRegistredObject("callback:" + callKey);
    if ((callback) && (typeof callback !== "function")) {
        Bdf.log("callback is not a function: " + callback);
    }
    return callback;
};

Bdf.registerCallback = function (callKey, callback) {
    if ((callback) && (typeof callback !== "function")) {
        throw "callback is not a function: " + callback;
    }
    Bdf.registerObject("callback:" + callKey, callback);
};


/*******************************************************************************
* LocalStorage
*******************************************************************************/

Bdf.getMainLocalStorage = function () {
    if (!Bdf.MAINSTORAGE_KEY) {
        return false;
    }
    return Bdf.getLocalStorage(Bdf.MAINSTORAGE_KEY);
};

Bdf.getLocalStorage = function (key) {
    if (!localStorage) {
        return false;
    }
    var chaine = localStorage.getItem(Bdf.BASE_URI + "/" + key);
    if (!chaine) {
        return new Object();
    }
    return JSON.parse(chaine);
};

Bdf.putMainLocalStorage = function (data) {
    if (!Bdf.MAINSTORAGE_KEY) {
        return;
    }
    Bdf.putLocalStorage(Bdf.MAINSTORAGE_KEY, data);
};

Bdf.putLocalStorage = function (key, data) {
    if (localStorage) {
        localStorage.setItem(Bdf.BASE_URI + "/" + key, JSON.stringify(data));
    }
};


/*******************************************************************************
* CommandMessage
*******************************************************************************/

Bdf.showCommandMessage = function (commandMessageObj) {
    if (!commandMessageObj) {
        return false;
    }
    var text = "";
    if (commandMessageObj.text) {
        text = text + commandMessageObj.text;
    } else {
        text = text + commandMessageObj.key;
    }
    if (commandMessageObj.type === "error") {
        alert(text);
        return false;
    }
    var html = "<div class='global-CommandMessageBox' style='top: -70px'><div>" + Bdf.escape(text) + "</div></div>";
    $(html).appendTo("body").animate({
        top: -4
    }, "slow", function () {
        $(this).delay(5000).animate({
            top: -70
        }, "slow", function () {$(this).detach();});}
    );
    if (commandMessageObj.type === "done") {
        return true;
    } else {
        return false;
    }
};


/*******************************************************************************
* Traitement du JSON
*******************************************************************************/

Bdf.checkData = function (data, objectNames, callback) {
    if (data.commandMessage) {
        if (!Bdf.checkError(data)) {
            return false;
        }
    }
    var nameArray;
    if (Array.isArray(objectNames)) {
       nameArray = objectNames;
    } else {
        nameArray = objectNames.toString().split(/[,;\s]/);
    }
    var missingArray = new Array();
    var resultArray = new Array();
    for(let name of nameArray) {
        if (name.length > 0) {
            if (!data.hasOwnProperty(name)) {
                missingArray.push(name);
            } else {
                resultArray.push(data[name]);
            }
        }
    }
    if (missingArray.length > 0) {
         $.error("BDF Error: missing properties in json response: " + missingArray.join(","));
        return false;
    } else {
        resultArray.push(data);
        callback.apply(this, resultArray);
        return true;
    }
};

Bdf.checkError = function (data) {
    var alertText = "";
    if (data.commandMessage) {
        if (data.commandMessage.type === "error") {
            if (data.commandMessage.text) {
                alertText = alertText + data.commandMessage.text;
            } else {
                alertText = alertText + data.commandMessage.key;
            }
            if (data.commandMessage.multiErrorMessageArray) {
                let array = data.commandMessage.multiErrorMessageArray;
                let length = array.length;
                let min;
                if (length < 8) {
                    min = length;
                } else {
                    min = 5;
                }
                for(let i = 0; i < min; i++) {
                    let obj = array[i];
                    alertText = alertText + "\n\n      - ";
                    if (obj.line) {
                        alertText = alertText + obj.line + ": ";
                    }
                    if (obj.text) {
                        alertText = alertText + obj.text;
                    } else {
                        alertText = alertText + obj.key;
                    }
                }
                if (length > 7) {
                    let remain = length - 5;
                    alertText = alertText + "\n\n      " + Bdf.Loc.get("_ warning.global.remainingerrors", remain);
                }
                
            }
            alert(alertText);
            return false;
        }
    }
    return true;
};


/*******************************************************************************
* Utilitaires
*******************************************************************************/

Bdf.ensureVisibility = function (viewport, item) {
    let $viewport = $(viewport);
    let $item = $(item);
    let currentTop = $viewport.scrollTop();
    let currentHeight = $viewport.innerHeight();
    let itemTop = $item.position().top;
    let itemBottom = itemTop + $item.innerHeight();
    if (itemTop < 0) {
        $viewport.scrollTop(currentTop + itemTop);
    } else if (itemBottom > currentHeight) {
        $viewport.scrollTop(currentTop + itemBottom - currentHeight + 10);
    }
};