//******************************************************************************
// OBJET KEYWORD_MANAGER
//******************************************************************************
function KeywordManager(input, debug)
{
    // Initialisation des valeurs par défaut des variables d'instance.
    this._keywords            = [];
    this._keywordsCount       = 0;
    this._selectedKeyword     = null;
    this._caretPosition       = 0;
    
    this._suggestions        = [];
    this._defaultSuggestions = [];
    
    // Initialisation du composant de saisie.
    this._input = $(input);
    this._input.data('manager', this);
    
    // DEBUG
    this.debugdiv = (debug!=null) ? $(debug) : null;

    // Initialisation du détecteur de mots-clés.
    this._input.keyup(this._caretPositionHandler).click(this._caretPositionHandler);
    
    // Initialisation de l'auto-completion.
    this._autocompleteInit();
    
    // Initialisation du formulaire.
    var form = this._input.parent('form');
    var me   = this;
    
    form.submit(function() 
    {
    	/*
    	// Sélection automatique de la première suggestion si possible.
        var autocomplete = me._input.data('autocomplete');
        var menu         = autocomplete.menu;
        
        if (menu.widget().is(':visible')) {
            menu.activate($.Event({type:'mouseenter'}), menu.element.children().first());
            menu.select($.Event({type:'mouseenter'}));
        }    	
    	*/
    
        var keywords = me.getFoundKeywords();
        $(this).find('#keywords').val(JSON.stringify(keywords));
    });
}




//******************************************************************************
// KEYWORD_MANAGER - METHODES UTILITAIRES
//******************************************************************************
KeywordManager.prototype.isEmpty = function()
{
    return (this._input.val().length == 0);
};


KeywordManager.prototype._getInputWithoutKeywords = function(keywords)
{
    var length = keywords.length;
    var last   = (length>0) ? keywords[length-1] : null;
    var start  = (last != null) ? last.getPosition().end+1 : 0;
    var input  = $.trim(this._input.val().substring(start));
    
    return input;
};


KeywordManager.prototype.setSuggestionService = function(url)
{
	this._suggestionServiceUrl = url;
};




//******************************************************************************
// KEYWORD_MANAGER - GESTION DE LA BASE DE MOTS-CLES
//******************************************************************************
KeywordManager.prototype.addKeyword = function(keyword)
{
    var key = '#'+keyword.id+'#'+keyword.type+'#'+keyword.body.length;
    
    if (this._keywords[key] == null)
    {
        this._keywords[key] = keyword;
        this._keywordsCount++;
    }
};


KeywordManager.prototype.addKeywords = function(mixed)
{
    if (mixed instanceof Suggestion) {
        this.addKeywords(mixed._keywords);
    }
    
    else {
        for (var i in mixed) {
            this.addKeyword(mixed[i]);
        }
    }
};


KeywordManager.prototype.cleanupKeywords = function()
{
    // Tant que le cache est inférieure à 50 éléments, pas de nettoyage.
    if (this._keywordsCount < 50)
        return;
    
    this._updateKeywordsPosition();
    
    for (var i in this._keywords)
    {
        var keyword = this._keywords[i];

        if (this._keywords[i].position == -1 &&
        	this._keywords[i].type != 'LOC'	)
        { 
            delete this._keywords[i];
            this._keywordsCount--;
        }
    }
};


KeywordManager.prototype.getFoundKeywords = function()
{
    var found = [];
    this._updateKeywordsPosition();
    
    for (var key in this._keywords)
    {
        var keyword  = this._keywords[key];
        if (keyword.position!=-1)
            found.push(keyword);
    }  

    return found;
};




//******************************************************************************
// KEYWORD_MANAGER - GESTION DU POSITIONNEMENT DES MOTS-CLES
//******************************************************************************
KeywordManager.prototype._caretPositionHandler = function()
{
    var manager = $(this).data('manager');
    
    manager._caretPosition = $(this).caret().start;
    manager._updateKeywordsPosition();
    manager._updateKeywordSelection();
    manager.debug();
};


KeywordManager.prototype._updateKeywordsPosition = function()
{
    var input = this._input.val().toLowerCase();
    
    for (var key in this._keywords) 
    {
        var keyword  = this._keywords[key];
        var position = input.indexOf(keyword.body);
        
        if (position != -1) {
        	var left  = (position==0 || input[position-1]==' ' || input[position-1]==undefined);
        	var right = (input[position+keyword.body.length]==undefined || input[position+keyword.body.length]==' ');
        	
        	if (!left || !right) {
        		position = -1;
        	}
        }
                
        keyword.position = position;
    }
    
    // On cherche si un mot clé n'est pas inclus dans un autre plus grand et présent
    // Exemple "Syndic" et "Syndic de copropriété"
    for (var key in this._keywords)
    {
    	var keyword = this._keywords[key];
    	
    	if (keyword.position == -1)
    		continue;  	
    	
    	for (var key2 in this._keywords)
        {
    		var keyword2 = this._keywords[key2];
    		
    		if (keyword2.position == -1 || keyword2.body.length == keyword.body.length)
    			continue;
    		
    		if (keyword2.body.indexOf(keyword.body) == 0) {
    			keyword.position = -1;
    		}
    		
    		//console.log("  "+keyword2.body+" "+keyword2.position+" "+keyword2.body.indexOf(keyword.body)+" "+keyword.position);
        }
    }
};


KeywordManager.prototype._updateKeywordSelection = function()
{
    this._selectedKeyword = null;
    
    for (var key in this._keywords)
    {
        var keyword  = this._keywords[key];
        var position = keyword.getPosition();
        
        if (position!=null && position.contains(this._caretPosition)) {
            this._selectedKeyword = keyword;
            this._input.caret({start:position.start, end:position.end+1});
        }
    }    
};


KeywordManager.prototype._getKeywordsBeforeCaret = function()
{    
    // Initialisation.
    var keywords = [];
    
    // Récupération des mots-clés situés avant le curseur.
    for (var key in this._keywords)
    {
        var keyword = this._keywords[key];
        
        if (keyword.position!=-1 && keyword.position<=this._caretPosition) {
            keywords.push(keyword);
        }
    }
    
    // Tri des mots-clés en fonction de leur position dans la saisie.
    keywords.sort(function (k1,k2) {return k1.position - k2.position;});
    
    return keywords;
};




//******************************************************************************
// KEYWORD_MANAGER - GESTION DE LA BASE DE SUGGESTIONS
//******************************************************************************
KeywordManager.prototype.addSuggestion = function(suggestion, isDefault)
{
    this._suggestions.push(suggestion);
    
    if (isDefault == true) {
        this._defaultSuggestions.push(suggestion);
    }
    
    return this;
};


KeywordManager.prototype.sortSuggestions = function(request)
{
    this._suggestions.sort(function (s1,s2)
    {        
        // On trie en premier lieu les suggestions par nombre de mots-clés.
        if (s1._keywords.length != s2._keywords.length)
            return s1._keywords.length - s2._keywords.length;
        
        // On trie ensuite les suggestions en fonction de leur libellé si même
        // nombre de mots-clés.
        return s1.label.localeCompare(s2.label); 
    });
};


KeywordManager.prototype._getSuggestionsForRequest = function(request)
{
    var response = [];
    var count    = 0;
    
    // Nettoyage du cache de mots-clés.
    this.cleanupKeywords();
    
    // Si la saisie est vide, comme au moment du focus par exemple,
    // récupération des suggestions définies par défaut.
    if (request=='') {
        for (var i in this._defaultSuggestions) {
            var suggestion = this._defaultSuggestions[i];
            response.push({label:suggestion.label, suggestion:suggestion});
            
            // Mise à jour du cache de mots-clés
            this.addKeywords(suggestion);
        }
    }
    
    // Récupération des suggestions définies en cache dans le cas contraire.
    else {
        for (var i in this._suggestions) {
            var suggestion = this._suggestions[i];
            
            if (suggestion.label.indexOf(request.toLowerCase()) == 0)
            {
                response.push({label:suggestion.label, suggestion:suggestion});
                count++;
                
                // Mise à jour du cache de mots-clés
                this.addKeywords(suggestion);
            }
            
            if (count == 5)
                break;
        }
    }
    
    return response;
};




//******************************************************************************
// KEYWORD_MANAGER - GESTION DE L'AUTOCOMPLETION
//******************************************************************************
KeywordManager.prototype._autocompleteInit = function()
{
    var me = this;
    
    // Initialisation de l'auto-complétion.
    this._input.autocomplete(
    {
        minLength:0,
        source:this._autocompleteSource,
        focus:this._autocompleteFocusEventHandler,
        select:this._autocompleteSelectEventHandler 
    });
    
    // Selection automatique de la première suggestion via la touche tabulation.
    this._input.keydown(function(event)
    { 
        var keyCode = $.ui.keyCode;
        
        if (event.keyCode == keyCode.TAB)
        {
            var autocomplete = me._input.data('autocomplete');
            var menu         = autocomplete.menu;
            
            if (menu.widget().is(':visible')) {
                menu.activate($.Event({type:'mouseenter'}), menu.element.children().first());
                menu.select($.Event({type:'mouseenter'}));
            }
            
            event.preventDefault();
        }
    });
    
    // Affichage de la recherche au moment du focus sur le champ de saisie.
    this._input.focus(function() {if (me.isEmpty()) {me._input.autocomplete('search');}});
};


KeywordManager.prototype._autocompleteFocusEventHandler = function(event, ui)
{
    return false;
};


KeywordManager.prototype._autocompleteSource = function(request, response)
{
    var manager = $(this.element).data('manager');
    
    // 1. Recherche des suggestions en cache.
    var data = manager._getSuggestionsForRequest(request.term);
    if (data.length > 0) {
        response(data);
        return;
    }
    
    // 2. Recherche des suggestions côté serveur.
    // Récupération des mots clés avant curseur.
    var keywords = manager._getKeywordsBeforeCaret();
    var input    = manager._getInputWithoutKeywords(keywords);
    
    // Préparation des données à envoyer.
    var data = []; 
    
    for (var i in keywords) {
        var keyword = keywords[i];
        data.push([keyword.id,keyword.type]);
    }
    
    $.getJSON(manager._suggestionServiceUrl, {'input':input, 'keywords':JSON.stringify(data)}, function(data, status, xhr)
    {
        var result = [];
        
        // Nettoyage du cache de mots-clés.
        manager.cleanupKeywords();
        
        for (var i in data)
        {
            // Duplication du tableau de mots-clés déjà saisis.
            var suggestionKeywords = keywords.slice(0);
            
            // Si réception d'un tableau de mots clés.
            if (data[i] instanceof Array) {
                for (var j in data[i]) {
                    suggestionKeywords.push(new Keyword(data[i][j]));
                }
            }
            
            // Si réception d'un objet mot-clé directement.
            else {
                suggestionKeywords.push(new Keyword(data[i]));
            }

            // Création de la suggestion.
            var suggestion = new Suggestion(suggestionKeywords);  
            result.push({label:suggestion.label, 'suggestion':suggestion});
            
            // Mise à jour du cache de mots-clés
            manager.addKeywords(suggestion);            
        }
        
        response(result); 
    });
};


KeywordManager.prototype._autocompleteSelectEventHandler = function(event, ui)
{
    var manager    = $(event.target).data('manager');
    var suggestion = ui.item.suggestion;
    
    manager.addKeywords(suggestion);
    manager.debug();
};










//******************************************************************************
// KEYWORD_MANAGER - DEBUG
//******************************************************************************
KeywordManager.prototype.debug = function()
{
	if (!this.debugdiv)
		return;
	
    var table = $('<table border="1"><tr><th>ID</th><th>Body</th><th>Type</th><th>Position</th></table>');
    
    for (key in this._keywords) {
        var keyword = this._keywords[key];
        table.append('<tr><td>'+keyword.id+'</td><td>'+keyword.body+'</td><td>'+keyword.type+'</td><td>'+keyword.position+'</td></tr>');
    }
    
    this.debugdiv.html(table);
};




//******************************************************************************
// OBJET KEYWORD
//******************************************************************************
function Keyword(id, body, type)
{
    if (id instanceof Object) {
        body = id.body;
        type = id.type;
        id   = id.id;
    }
    
    this._init(id, body, type);
}


Keyword.prototype._init = function(id, body, type)
{
    this.id       = id;
    this.body     = (body) ? body.toLowerCase() : '';
    this.type     = type;
    this.position = -1;
};


Keyword.prototype.getPosition = function()
{
    if (this.position == -1) {
        return null;
    }
    
    return {
        start:this.position,
        end:this.position+this.body.length-1,
        contains:function(i) {return (i>this.start && i<=this.end);}
    };
};




//******************************************************************************
// OBJET SUGGESTION
//******************************************************************************
function Suggestion(keywords)
{
    if (keywords != null) {
        this._keywords = keywords;
        this.buildLabel();
    }
    else {   
        this.label    = '';
        this._keywords = [];
    }
}


Suggestion.prototype.addKeyword = function(keyword)
{
    this._keywords.push(keyword);
    return this;
}


Suggestion.prototype.buildLabel = function()
{
    var label = '';
    
    for (var i in this._keywords) {
        if (label != '') {label += ' ';}
        label += this._keywords[i].body;
    }    
    
    this.label = label;
    return this;
}
