
/**
 * Constructor for QuerySuggester.
 */
function QuerySuggester() {
    this.id = querySuggesters.length;
    querySuggesters[this.id] = this;
}

QuerySuggester.prototype.useInlineSuggestion = true;                // Whether or not to display the first suggestion as marked text within the search-box
QuerySuggester.prototype.clearInlineSuggestionBeforeSubmit = false; // Whether or not to remove inline suggestions before submitting

// Public functions

/**
 * Initialize the query-suggester.
 *
 * @param suggestionUrl the url to fetch suggestions from, will append the query string to it
 * @param formId        the id of the form that should be submitted if the user clicks a suggestion
 * @param tooltipId     the id of the div used to display the suggestsions
 */
QuerySuggester.prototype.initialize = function(suggestionUrl, formId, tooltipId) {

    this.__enabled = new BackgroundLoader().isEnabled();
    
    if (this.__enabled) {
        this.__suggestionUrl = suggestionUrl;
        this.__form = this.byId(formId);
        if (!this.__form) {
            this.__enabled = false;
            this.debug("Couldn't find the form, disabling.");
        } else {
            this.__tooltip = this.byId(tooltipId);
/*
            this.__tooltip = document.createElement("div");
            this.__tooltip.style.position = "absolute";
            this.__tooltip.style.top = "0px";
            this.__tooltip.style.left = "0px";
            this.__tooltip.style.visibility = "hidden";
            this.__tooltip.style.border = "1px solid black";
            this.__tooltip.style.backgroundColor = "white";
            this.__tooltip.style.textAlign = "left";
            this.__form.appendChild(this.__tooltip);
*/
        }
    } else {
        this.debug("Unable to instantiate XMLHttpRequest, disabling.");
    }
};

/**
 * Set the id of the textarea where debug-output is written. Initially, there is no such
 * textarea assigned to the suggester.
 *
 * @param debugAreaID the id of the textarea to print debug messages to, or boolean false
                      to disable debugging
 */
QuerySuggester.prototype.setDebugAreaId = function(debugAreaId) {
    this.__debugAreaId = debugAreaId;
};

/**
 * Set the id of the input-field to do completion for. Not really neccessary to call, as
 * the id is sent to the key handling methods.
 *
 * @param queryId the id of the search box
 */
QuerySuggester.prototype.setQueryId = function(queryId) {
    this.__queryId = queryId;
    this.__query = this.byId(queryId);
};

// Overridable functions

/**
 * Returns the url to visit to obtain suggestions for a query. By default appends the
 * query to the suggestionUrl member. Override for different behavior.
 *
 * @param quer the query for which to fetch suggestions
 * @return the url that will return the suggestions
 */
QuerySuggester.prototype.getSuggestionUrl = function(query) {
    return this.__suggestionUrl + encodeURIComponent(query);
};

/**
 * Called when the search form should be submitted (usually because of mouse clik on one
 * of the suggestions in the list). By default submits the form specified by the formId
 * parameter of the initialize() function. Override for different behavior.
 */
QuerySuggester.prototype.submitForm = function() {
    //this.__form.submit();
    this.hide();
};

// Input event handlers

/**
 * Event handler for key up events.
 */
QuerySuggester.prototype.keyUp = function(event, field) {
    if (!this.__enabled) return;
    else if (field != this.__queryId) this.setQueryId(field);
    //resetIframe();
    if (event) {
        if (this.__timeout) {
            clearTimeout(this.__timeout);
            this.__timeout = false;
        }
        if (event.ctrlKey || event.altKey) {
            return;
        }
        var timeout = 100;
        this.__deletePressed = false;
        switch (event.keyCode) {
            case 9 : // Tab
            case 27 : // Escape
                this.hide();
                return;
            case 8 : // Backspace
            case 46 : // Delete
                this.__deletePressed = true;
                timeout = 200;
                break;
            case 13 : // Enter
            case 16 : // Shift
            case 17 : // Ctrl
            case 18 : // Alt
            case 20 : // Caps Lock
            case 33 : // Page up
            case 34 : // Page down
            case 35 : // End
            case 36 : // Home
            case 37 : // Arrow left
            case 38 : // Arrow up
            case 39 : // Arrow right
            case 40 : // Arrow down
            case 45 : // Insert
                return;
            default :
                timeout = 100;
                break;
        }
        this.__index = -1;
        var qc = this;
        this.__timeout = setTimeout(function() {qc.fetchAndDisplaySuggestions();}, timeout);
    }
};

/**
 * Some keys also require keyDown handlers, because they never give a key up event. For
 * instance, pressing the 'tab' key will typically cause focus to leave the search input
 * box, causing the key up event to be sent to another component.
 */
QuerySuggester.prototype.keyDown = function(event, field) {
    if (!this.__enabled) return;
    else if (field != this.__queryId) this.setQueryId(field);
    //resetIframe();
    if (event && event.keyCode) {
        switch (event.keyCode) {
            case 9 : // Tab
                this.hide();
                break;
            case 13 : // Enter
                if (this.__index == -1) this.clearInlineSuggestion();
                break;
            case 38 : // Up-arrow
                if (this.__index >= 0) this.updateSuggestions(this.__index--, -2);
                break;
            case 40 : // Down-arrow
                if (this.__index < this.__terms.length - 1) this.updateSuggestions(this.__index++, -2);
                break;
        }
    }
};

QuerySuggester.prototype.mouseOver = function(index) {
    if (!this.__enabled) return;
    var previous = this.__mouseIndex >= 0 ? this.__mouseIndex : this.__index;
    this.__mouseIndex = index;
    this.updateSuggestions(-2, previous);
};

QuerySuggester.prototype.mouseOut = function(index) {
    if (!this.__enabled) return;
    var previous = this.__mouseIndex;
    this.__mouseIndex = -1;
    this.updateSuggestions(-2, previous);
};

QuerySuggester.prototype.mouseClick = function(index) {
    if (!this.__enabled) return;
    if (index >= 0) {
        this.__query.value = this.__terms[index];
        this.submitForm();
    } else {
        this.hide();
    }
};

// Functions for retrieving suggestions

QuerySuggester.prototype.fetchAndDisplaySuggestions = function() {
    var val = this.__query.value;
    if (this.canHandleRanges()) {
        val = val.substring(0, this.getCaretPosition());
    }
    if (val.length == 0) {
        this.hide();
        this.__prev = val;
        return;
    } else {
        val = val.replace(new RegExp("\\\\", "g"), "\\\\");
    }
    this.__prev = val;
    if (this.__cache[this.getSuggestionUrl(val)]) {
        this.debug("cache: " + val);
        this.displaySuggestions(this.__cache[this.getSuggestionUrl(val)], this.__prev);
    } else {
        this.debug("query: '" + val + "'");
        this.fetchSuggestion(val);
    }
};

QuerySuggester.prototype.fetchSuggestion = function(query) {
    var qc = this;
    var bl = new BackgroundLoader();
    bl.setLoadedCallback(function(content){qc.parseSuggestions(content);});
    bl.setErrorCallback(function(error){qc.debug("Couldn't get suggestions:\n" + error);});
    bl.loadUrl(this.getSuggestionUrl(query));
};

// Functions for displaying suggestions

QuerySuggester.prototype.parseSuggestions = function(matches) {
    if (matches == "") return;
    var params = eval(matches);
    if (params.length > 2) {
        this.__cache[this.getSuggestionUrl(params[0])] = params;
    }
    this.displaySuggestions(params, params.length > 2 ? params[0] : "");
};

QuerySuggester.prototype.displaySuggestions = function(matches, query) {
    if (matches.length <= 2) {
        this.noSuggestions();
        return;
    }
    if (this.__tooltip && this.__query) {
        this.__tooltip.innerHTML = "";
        this.buildSuggestionsHtml(matches);
        this.show();
        this.showInlineSuggestions(query);
    }
};

QuerySuggester.prototype.buildSuggestionsHtml = function(matches) {
    var text = "";
    var length = matches.length / 2 - 1;
    this.__terms = new Array(length);
    for (var i = 0; i < length; ++i) {
        text += "<div class=\"ajaxSuggest\" id=\"tooltip_" + i + "\" onmouseover=\"mouseOver(" + this.id + "," + i + ")\" onmouseout=\"mouseOut(" + this.id + "," + i + ")\" onclick=\"mouseClick(" + this.id + "," + i + ")\" style=\"cursor: pointer\">" +
                matches[i * 2 + 2] +
                "</div>\n";
        this.__terms[i] = matches[i * 2 + 2];
    }   
    this.__tooltip.innerHTML = text;
//    var iframeelem = document.getElementById(this.__tooltip.id + "_iframe");
//    iframeelem.style.height = (this.__terms.length * 13) + "px";
};

QuerySuggester.prototype.showInlineSuggestions = function(query) {
    if (this.__terms.length > 0 && this.__query.value == query && !this.__deletePressed && this.useInlineSuggestion) {
        this.__original = this.__query.value;
        if (this.canHandleRanges()) {
            this.__query.value = this.__terms[0];
            this.selectRange(this.__original.length, this.__query.value.length);
        }
    }
};

QuerySuggester.prototype.updateSuggestions = function(previousIndex, previousMouseIndex) {
    var previous = this.byId("tooltip_" + previousIndex);
    var previousMouse = this.byId("tooltip_" + previousMouseIndex);
    var current = this.byId("tooltip_" + this.__index);
    var currentMouse = this.byId("tooltip_" + this.__mouseIndex);
    if (previous) {
        if (previousIndex != this.__mouseIndex) {
            previous.style.backgroundColor = "#fff";
            previous.style.color = "#808080";
        }
    }
    if (current) {       
        this.__query.value = this.__terms[this.__index];
        current.style.backgroundColor = "#00c";
        current.style.color = "#fff";
        if (this.canHandleRanges()) {
            this.selectRange(this.__original.length, this.__query.value.length);
        }
    }
    if (previousMouse && previousMouseIndex != this.__index) {
        previousMouse.style.backgroundColor = "#fff";
        previousMouse.style.color = "#808080";
    }
    if (currentMouse) {
        currentMouse.style.backgroundColor = "#00c";
        currentMouse.style.color = "#fff";
    }
};

/**
 * Called when the list of matches returned is empty. Default implementation will simply
 * hide the list of suggestions. Override for custom behavior.
 */
QuerySuggester.prototype.noSuggestions = function() {
    this.hide();
};

QuerySuggester.prototype.show = function() {
    if (this.__tooltip && this.__query) {
        this.__tooltip.style.left = this.findPosX(this.__query) + "px";
        this.__tooltip.style.top = this.findPosY(this.__query) + this.__query.offsetHeight + "px";
        this.__tooltip.style.width = this.__query.offsetWidth + "px";
        this.__tooltip.style.visibility = "visible";
//        var iframeelem = document.getElementById(this.__tooltip.id + "_iframe");
//        iframeelem.style.left = this.findPosX(this.__query) - 1 + "px";
//        iframeelem.style.top = this.findPosY(this.__query) + this.__query.offsetHeight + "px";
//        iframeelem.style.width = this.__query.offsetWidth + "px";
//        iframeelem.style.visibility = "visible";//TODO er fjernet af db
    }
};

QuerySuggester.prototype.hide = function() {
    if (this.__tooltip) {
        this.__terms = new Array();
        this.__tooltip.style.visibility = "hidden";
//        var iframeelem = this.byId(this.__tooltip.id + "_iframe");
//        iframeelem.style.visibility = "hidden";        
    }
};

// Text-selection helper-functions

QuerySuggester.prototype.canHandleRanges = function() {
      return false;
//    return this.__query.createTextRange || this.__query.setSelectionRange;
};

QuerySuggester.prototype.selectRange = function(from, to) {
    if (this.__query.createTextRange) {
        var t = this.__query.createTextRange();
        t.moveStart("character", from);
        t.select();
    } else if (this.__query.setSelectionRange) {
        this.__query.setSelectionRange(from, to);
    } else {
        this.debug("Couldn't select range.");
    }
};

QuerySuggester.prototype.getCaretPosition = function() {
    if (document.selection) {
        var range = document.selection.createRange().duplicate();
        range.collapse(true);
        range.moveStart("character", -1000);
        return range.text.length;
    } else if (this.__query.setSelectionRange) {
        return this.__query.selectionStart;
    } else {
        this.debug("Couldn't find caret position.");
        return this.__query.value.length;
    }
};

QuerySuggester.prototype.clearInlineSuggestion = function() {
    if (this.__query && this.canHandleRanges() && this.clearInlineSuggestionBeforeSubmit) {
        this.__query.value = this.__query.value.substring(0, this.getCaretPosition());
    }
};

// General helper-functions

QuerySuggester.prototype.findPosX = function(obj) {
    var curleft = 0;
    if (obj.offsetParent) {
        while (obj.offsetParent) {
            curleft += obj.offsetLeft;
            obj = obj.offsetParent;
        }
    } else if (obj.x)
        curleft += obj.x;
    return curleft;
};

QuerySuggester.prototype.findPosY = function(obj) {
    var curtop = 0;
    if (obj.offsetParent) {
        while (obj.offsetParent) {
            curtop += obj.offsetTop
            obj = obj.offsetParent;
        }
    } else if (obj.y) {
        curtop += obj.y;
    }
    return curtop;
};

QuerySuggester.prototype.byId = function(id) {
    var element = document.getElementById ? document.getElementById(id) : false;
    return element && element != null ? element : false;
};

QuerySuggester.prototype.debug = function(message) {
    if (this.__debugAreaId) {
        var err = this.byId(this.__debugAreaId);
        if (err) {
            err.value += message + "\n";
        }
    }
};

// Private members

QuerySuggester.prototype.__suggestionUrl = false;   // The url to fetch suggestions from
QuerySuggester.prototype.__form = false;            // The form
QuerySuggester.prototype.__query = false;           // The query input field
QuerySuggester.prototype.__queryId = false;         // The id of the query input field
QuerySuggester.prototype.__tooltip = false;         // The tooltip div
QuerySuggester.prototype.__debugAreaId = true;     // Text-area for debugging

QuerySuggester.prototype.__timeout = false;         // The current timeout
QuerySuggester.prototype.__prev = "";               // The previously sent term
QuerySuggester.prototype.__original = "";           // The term before panning into the suggestions
QuerySuggester.prototype.__index = -1;              // The index of the text cursor in the list of suggestions
QuerySuggester.prototype.__mouseIndex = -1;         // The position of the mouse in the list of suggestions
QuerySuggester.prototype.__terms = new Array();     // The list of suggestions
QuerySuggester.prototype.__cache = new Array();     // A result-cache
QuerySuggester.prototype.__enabled = false;
QuerySuggester.prototype.__deletePressed = false;   // Delete or backspace has been pressed



// Global stuff


// Global array of all instantiated query-suggesters
var querySuggesters = new Array();

// Global mouse-handling function
function mouseOver(id, index) {
    if (id >= 0 && id < querySuggesters.length) {
        querySuggesters[id].mouseOver(index);
    }
}

// Global mouse-handling function
function mouseOut(id, index) {
    if (id >= 0 && id < querySuggesters.length) {
        querySuggesters[id].mouseOut(index);
    }
}

// Global mouse-handling function
function mouseClick(id, index) {
    if (id >= 0 && id < querySuggesters.length) {
        querySuggesters[id].mouseClick(index);
    } else if (id == -1 && index == -1) {
        for (var i = 0; i < querySuggesters.length; ++i) {
            querySuggesters[i].mouseClick(index);
        }
    }
}

// Resets the iframe
function resetIframe() {
    if (this.__tooltip) {
        var iframeelem = this.byId(this.__tooltip.id + "_iframe");
        iframeelem.style.left = "0px";
        iframeelem.style.top = "0px";
        iframeelem.style.width = "0px";
        iframeelem.style.visibility = "hidden";
    }
}