//****************************************************************************
// Copyright (c) 2006, Coveo Solutions Inc.
//****************************************************************************

function CNL_AutoCompleteTextBox() {

// The timer we use
var m_Timer = null;
// The element in which we display our completions
var m_DropDown = null;
// The items that the user can select
var m_Items = null;
// The currently selected item index
var m_Selected = -1;

//****************************************************************************
// Event handler for the onkeypress event.
//****************************************************************************
this.OnKeyPress = function(p_Event)
{
    var code = p_Event.keyCode ? p_Event.keyCode : p_Event.which;

    if (code == 13) {
        // Enter
        this.OnBlur();
    } else if (code == 40) {
        // Down arrow
        this.CancelUpdate();
        if (m_Items != null) {
            this.SelectNext();
        }
    } else if (code == 38) {
        // Up arrow
        this.CancelUpdate();
        if (m_Items != null) {
            this.SelectPrevious();
        }
    } else if (code == 27) {
        // Escape
        this.CancelUpdate();
        this.SelectItem(-1);
        CNL_CancelEvent(p_Event);
    } else if (code == 32) {
        // Space
        this.CancelUpdate();
        if (m_Selected == -1) {
            this.ArmServerUpdate();
        } else {
            this.AcceptSelected();
            this.ArmServerUpdate();
            CNL_CancelEvent(p_Event);
        }
    } else if (code == 8) {
        // Backspace
        this.CancelUpdate();
        this.ArmServerUpdate();
    } else {
        this.CancelUpdate();
        this.ArmServerUpdate();
    }
}

//****************************************************************************
// Event handler for the onblur event.
//****************************************************************************
this.OnBlur = function()
{
    this.CancelUpdate();

    // Grab any currently selected value
    if (m_Selected != -1) {
        this.AcceptSelected();
    }

    // Remove the dropdown. Must wait a little to let the time to the onclick event to eventually fire.
    var myself = this;
    function HideDropDown() {
        myself.RemoveDropDown();
    }
    m_Timer = setTimeout(HideDropDown, 300);
}

//****************************************************************************
// Arms the timer for updating the completions from the server.
//****************************************************************************
this.ArmServerUpdate = function()
{
    var myself = this;
    m_Timer = setTimeout(function() { myself.LaunchServerUpdate(); }, this.m_UpdateDelay);
}

//****************************************************************************
// Launches a request to update the completions from the server.
//****************************************************************************
this.LaunchServerUpdate = function()
{
    var myself = this;
    // Script# assumes that a delegate object implements a method named 'invoke()'.
    // We have to implement such a method for the following anonymous function
    // because we want it to be called by Script#'s generated code (PartialPostBack.cs)
    // even if we write it manually (instead of having Script# to generate it).
    myself.invoke = function(p_Xml) {
                        myself.ServerUpdateCallback(p_Xml);
                    };
    this.ServerUpdateAutoComplete(
        myself.m_TextBox.value,
        myself // Script# will call 'invoke()' on 'myself'.
    );
}

//****************************************************************************
// Cancels any pending update.
//****************************************************************************
this.CancelUpdate = function()
{
    clearTimeout(m_Timer);
    CNL_AjaxManager.cancelPendingOperations();
}

//****************************************************************************
// Removes the dropdown if it is present.
//****************************************************************************
this.RemoveDropDown = function()
{
    if (m_DropDown != null) {
        m_DropDown.parentNode.removeChild(m_DropDown);
        m_DropDown = null;
    }
    m_Items = null;
    m_Selected = -1;
}

//****************************************************************************
// Callback function for server updates.
// p_Xml - The xml returned by the server.
//****************************************************************************
this.ServerUpdateCallback = function(p_Xml)
{
    var corrections = CNL_SelectNodes(p_Xml, '//Correction');
    var completions = CNL_SelectNodes(p_Xml, '//Completion');
    if (corrections.length != 0 || completions.length != 0) {
        // Create the dropdown if not already done
        if (m_DropDown == null) {
            m_DropDown = document.createElement('div');
            m_DropDown.style.position = 'absolute';
            m_DropDown.style.zIndex = 999;
            m_DropDown.style.border = '1px solid black';
            m_DropDown.style.fontFamily = 'Tahoma';
            m_DropDown.style.fontSize = '8pt';
            m_DropDown.style.backgroundColor = 'white';
            this.m_TextBox.parentNode.appendChild(m_DropDown);
            var size = CNL_GetSize(this.m_TextBox);
            m_DropDown.style.width = (size.m_Width - 3) + 'px';
            CNL_PositionObject(m_DropDown, this.m_TextBox, 'BelowLeft');
        } else {
            m_DropDown.innerHTML = '';
        }

        // Create elements for corrections
        m_Items = new Array();
        m_Selected = -1;
        for (var i = 0; i < corrections.length; ++i) {
            var myself = this;
            var node = corrections[i];
            var from = node.getAttribute('from');
            var to = node.getAttribute('to');
            var elem = document.createElement('div');
            elem.m_Index = m_Items.length;
            elem.m_IsCorrection = true;
            elem.m_Correction = to;
            elem.style.padding = '2px';
            elem.style.borderBottom = '1px solid silver';
            elem.style.backgroundColor = 'whitesmoke';
            elem.style.cursor = 'pointer';
            elem.onmouseover = function() { myself.SelectItem(this.m_Index); };
            elem.onmouseout = function() { myself.SelectItem(-1); };
            elem.onclick = function() { myself.ServerEnter(); };
            m_DropDown.appendChild(elem);
            m_Items.push(elem);

            elem.innerHTML = '<b>Did you mean:</b> ' + to; // TODO: Localize
        }

        // Create elements for completions
        for (var i = 0; i < completions.length; ++i) {
            var myself = this;
            var node = completions[i];
            var elem = document.createElement('div');
            var completion = CNL_GetTextContent(node);
            elem.m_Index = m_Items.length;
            elem.m_IsCompletion = true;
            elem.m_Completion = completion;
            elem.innerHTML = this.GetCompletionDisplay(this.m_TextBox.value, completion);
            elem.style.paddingLeft = '2px';
            elem.style.paddingRight = '2px';
            elem.style.paddingTop = '1px';
            elem.style.paddingBottom = '1px';
            elem.style.cursor = 'pointer';
            elem.onmouseover = function() { myself.SelectItem(this.m_Index); };
            elem.onmouseout = function() { myself.SelectItem(-1); };
            elem.onclick = function() { myself.ServerEnter(); };
            m_DropDown.appendChild(elem);
            m_Items.push(elem);
        }
    } else {
        this.RemoveDropDown();
    }
}

//****************************************************************************
// Selects the next item.
//****************************************************************************
this.SelectNext = function()
{
    this.SelectItem((m_Selected + 1) % m_Items.length);
}

//****************************************************************************
// Selects the previous item.
//****************************************************************************
this.SelectPrevious = function()
{
    if (m_Selected == 0) {
        this.SelectItem(m_Items.length - 1);
    } else {
        this.SelectItem(m_Selected - 1);
    }
}

//****************************************************************************
// Changes the selected index.
// p_Index - The index of the item to select.
//****************************************************************************
this.SelectItem = function(p_Index)
{
    if (m_Selected != -1) {
        var elem = m_Items[m_Selected];
        elem.style.color = '';
        elem.style.backgroundColor = elem.m_Background;
    }

    if (p_Index != -1) {
        var elem = m_Items[p_Index];
        elem.style.color = 'white';
        elem.m_Background = elem.style.backgroundColor;
        elem.style.backgroundColor = 'cornflowerblue';
    }

    m_Selected = p_Index;
}

//****************************************************************************
// Retrieves the display value for a completion.
// p_Current    - The current value.
// p_Completion - The completion for which the display value is needed.
//****************************************************************************
this.GetCompletionDisplay = function(p_Current, p_Completion)
{
    // Highlight the current part in the string
    var disp;
    var pos = p_Completion.toLowerCase().indexOf(p_Current.toLowerCase());
    if (pos == 0) {
        disp = '<b>' + p_Completion.substring(0, p_Current.length) + '</b>' + p_Completion.substring(p_Current.length, p_Completion.length);
    } else {
        disp = p_Completion;
    }

    // Remove the insertion point delimiter, and put the sample value in italics
    var first = disp.indexOf('$');
    var last = disp.lastIndexOf('$');
    if (first != -1) {
        if (first == last) {
            disp = disp.replace('$', '');
        } else {
            disp = disp.substring(0, first) + '<u>' + disp.substring(first + 1, last) + '</u>' + disp.substring(last + 1, disp.length);
        }
    }

    return disp;
}

//****************************************************************************
// Accepts the selected item, putting it into the textbox.
//****************************************************************************
this.AcceptSelected = function()
{
    var elem = m_Items[m_Selected];
    if (elem.m_IsCompletion) {
        var first = elem.m_Completion.indexOf('$');
        var last = elem.m_Completion.lastIndexOf('$');
        if (first != -1) {
            if (first == last) {
                this.m_TextBox.value = elem.m_Completion.replace('$', '');
                CNL_SetSelectedRange(this.m_TextBox, first, first);
            } else {
                this.m_TextBox.value = elem.m_Completion.substring(0, first) + elem.m_Completion.substring(first + 1, last) + elem.m_Completion.substring(last + 1, elem.m_Completion.length);
                CNL_SetSelectedRange(this.m_TextBox, first, last - 1);
            }
        } else {
            this.m_TextBox.value = elem.m_Completion;
        }
    } else {
        this.m_TextBox.value = elem.m_Correction;
    }

}

} // Constructor

