YUI.add('aui-autocomplete-deprecated', function (A, NAME) {

/**
 * The AutoComplete Utility
 *
 * @module aui-autocomplete
 */

var Lang = A.Lang,
    isArray = Lang.isArray,
    isString = Lang.isString,
    isNull = Lang.isNull,
    isFunction = Lang.isFunction,

    getClassName = A.getClassName,

    KeyMap = A.Event.KeyMap,

    ALERT = 'alert',
    CONTENT = 'content',
    HELPER = 'helper',
    HIDDEN = 'hidden',
    ICON = 'icon',
    ITEM = 'item',
    LIST = 'list',
    LOADING = 'loading',
    NAME = 'autocomplete',
    RESET = 'reset',
    RESULTS = 'results',
    SELECTED = 'selected',

    ICON_DEFAULT = 'icon-circle-arrow-down',
    ICON_ERROR = ALERT,
    ICON_LOADING = LOADING,

    CSS_HIGLIGHT = getClassName(NAME, SELECTED),
    CSS_HIDDEN = getClassName(HELPER, HIDDEN),
    CSS_LIST_ITEM = getClassName(NAME, LIST, ITEM),
    CSS_RESULTS_LIST = getClassName(HELPER, RESET),
    CSS_RESULTS_OVERLAY = getClassName(NAME, RESULTS),
    CSS_RESULTS_OVERLAY_CONTENT = getClassName(NAME, RESULTS, CONTENT),

    BACKSPACE = 'BACKSPACE',
    TAB = 'TAB',
    ENTER = 'ENTER',
    ALT = 'ALT',
    ESC = 'ESC',
    UP = 'UP',
    DOWN = 'DOWN',
    RIGHT = 'RIGHT',
    WIN_IME = 'WIN_IME',

    OVERLAY_ALIGN = {
        node: null,
        points: ['tl', 'bl']
    },

    BOUNDING_BOX = 'boundingBox',
    CONTENT_BOX = 'contentBox';

/**
 * <p><img src="assets/images/aui-autocomplete/main.png"/></p>
 *
 * A base class for AutoComplete, providing:
 * <ul>
 *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 *    <li>Presenting users choices based on their input</li>
 *    <li>Separating selected items</li>
 *    <li>Keyboard interaction for selecting items</li>
 * </ul>
 *
 * Quick Example:<br/>
 *
 * <pre><code>var instance = new A.AutoCompleteDeprecated({
 *	dataSource: [['AL', 'Alabama', 'The Heart of Dixie'],
 * 	['AK', 'Alaska', 'The Land of the Midnight Sun'],
 *	['AZ', 'Arizona', 'The Grand Canyon State']],
 *	schema: {
 *		resultFields: ['key', 'name', 'description']
 *	},
 *	matchKey: 'name',
 *	delimChar: ',',
 *	typeAhead: true,
 *	contentBox: '#myAutoComplete'
 * }).render();
 * </code></pre>
 *
 * Check the list of <a href="AutoComplete.html#configattributes">Configuration Attributes</a> available for
 * AutoComplete.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class AutoComplete
 * @constructor
 * @extends Component
 */

var AutoComplete = A.Component.create({

    /**
     * Static property provides a string to identify the class.
     *
     * @property AutoComplete.NAME
     * @type String
     * @static
     */

    NAME: NAME,

    /**
     * Static property used to define the default attribute
     * configuration for the AutoComplete.
     *
     * @property AutoComplete.ATTRS
     * @type Object
     * @static
     */

    ATTRS: {
        /**
         * Always show the results container, instead of only showing when the
         * user is requesting them.
         *
         * @attribute alwaysShowContainer
         * @default false
         * @type Boolean
         */
        alwaysShowContainer: {
            value: false
        },

        /**
         * Automatically highlight the first item in the list when the results are
         * made visible.
         *
         * @attribute autoHighlight
         * @default true
         * @type Boolean
         */
        autoHighlight: {
            value: true
        },

        /**
         * If set to true, the <a href="AutoComplete.html#method_filterResults">filterResults</a>
         * method will be run on the response from the data source.
         *
         * @attribute applyLocalFilter
         * @default true
         * @type Boolean
         */
        applyLocalFilter: {
            value: null
        },

        /**
         * To use a button
         *
         * @attribute button
         * @default true
         * @type Boolean
         * @deprecated
         */
        button: {
            value: true
        },

        /**
         * The data source that results will be read from. This can either be
         * an existing <a href="DataSource.html">DataSource</a> object, or it can be a
         * value that would be passed to <a href="DataSource.html">DataSource</a>.
         *
         * @attribute dataSource
         * @default null
         * @type Object | String | Function | Array
         */
        dataSource: {
            value: null
        },

        /**
         * The type of the data source passed into <a href="AutoComplete.html#config_dataSource">dataSource</a>.
         * This can be used to explicitly declare what kind of <a href="DataSource.html">DataSource</a> object will
         * be created.
         *
         * @attribute dataSourceType
         * @default null
         * @type String
         */
        dataSourceType: {
            value: null
        },

        /**
         * The character used to indicate the beginning or ending of a new value. Most commonly used
         * is a ",".
         *
         * @attribute delimChar
         * @default null
         * @type String
         */
        delimChar: {
            value: null,
            setter: function(value) {
                if (isString(value) && (value.length > 0)) {
                    value = [value];
                }
                else if (!isArray(value)) {
                    value = A.Attribute.INVALID_VALUE;
                }

                return value;
            }
        },

        /**
         * If <a href="AutoComplete.html#config_typeAhead">typeAhead</a> is true, this
         * will clear a selection when the overlay closes unless a user explicitly selects an item.
         *
         * @attribute forceSelection
         * @default false
         * @type Boolean
         */
        forceSelection: {
            value: false
        },

        /**
         * The input field which will recieve the users input.
         *
         * @attribute input
         * @default null
         * @type String | Node
         */
        input: {
            value: null
        },

        /**
         * The key or numeric index in the schema to match the result against.
         *
         * @attribute matchKey
         * @default 0
         * @type String | Number
         */
        matchKey: {
            value: 0
        },

        /**
         * The maximum number of results to display.
         *
         *
         * @attribute maxResultsDisplayed
         * @default 10
         * @type Number
         */
        maxResultsDisplayed: {
            value: 10
        },

        /**
         * The minimum number of characters required to query the data source.
         *
         * @attribute minQueryLength
         * @default 1
         * @type Number
         */
        minQueryLength: {
            value: 1
        },

        /**
         * The amount of time in seconds to delay before submitting the query.
         *
         * @attribute queryDelay
         * @default 0.2
         * @type Number
         */
        queryDelay: {
            value: 0.2,
            getter: function(value) {
                return value * 1000;
            }
        },

        /**
         * When IME usage is detected or interval detection is explicitly enabled,
         * AutoComplete will detect the input value at the given interval and send a
         * query if the value has changed.
         *
         * @attribute queryInterval
         * @default 0.5
         * @type Number
         */
        queryInterval: {
            value: 0.5,
            getter: function(value) {
                return value * 1000;
            }
        },

        /**
         * When <a href="AutoComplete.html#config_applyLocalFilter">applyLocalFilter</a> is true,
         * setting this to true will match only results with the same case.
         *
         * @attribute queryMatchCase
         * @default false
         * @type Boolean
         */
        queryMatchCase: {
            value: false
        },

        /**
         * When <a href="AutoComplete.html#config_applyLocalFilter">applyLocalFilter</a> is true,
         * setting this to true will match results which contain the query anywhere in the text,
         * instead of just matching just items that start with the query.
         *
         * @attribute queryMatchContains
         * @default false
         * @type Boolean
         */
        queryMatchContains: {
            value: false
        },

        /**
         * For IO DataSources, AutoComplete will automatically insert a "?" between the server URI and
         * the encoded query string. To prevent this behavior, you can
         * set this value to false. If you need to customize this even further, you
         * can override the <a href="AutoComplete.html#method_generateRequest">generateRequest</a> method.
         *
         * @attribute queryQuestionMark
         * @default true
         * @type Boolean
         */
        queryQuestionMark: {
            value: true
        },

        /**
         * A valid configuration object for any of <a href="module_datasource.html">DataSource</a> schema plugins.
         *
         * @attribute schema
         * @default null
         * @type Object
         */
        schema: {
            value: null
        },

        /**
         * A valid type of <a href="module_datasource.html">DataSource</a> schema plugin, such as array, json, xml, etc.
         *
         * @attribute schemaType
         * @default array
         * @type String
         */
        schemaType: {
            value: '',
            validator: isString
        },

        /**
         * Whether or not the input field should be updated with selections.
         *
         * @attribute suppressInputUpdate
         * @default false
         * @type Boolean
         */
        suppressInputUpdate: {
            value: false
        },

        /**
         * If <a href="AutoComplete.html#config_autoHighlight">autoHighlight</a> is enabled, whether or not the
         * input field should be automatically updated with the first result as the user types,
         * automatically selecting the portion of the text the user has not typed yet.
         *
         * @attribute typeAhead
         * @default false
         * @type Boolean
         */
        typeAhead: {
            value: false
        },

        /**
         * If <a href="AutoComplete.html#config_typeAhead">typeAhead</a> is true, number of seconds
         * to delay before updating the input. In order to prevent certain race conditions, this value must
         * always be greater than the <a href="AutoComplete.html#config_queryDelay">queryDelay</a>.
         *
         * @attribute typeAheadDelay
         * @default 0.2
         * @type Number
         */
        typeAheadDelay: {
            value: 0.2,
            getter: function(value) {
                return value * 1000;
            }
        },

        /**
         * The unique ID of the input element.
         *
         * @attribute uniqueName
         * @default null
         * @type String
         */
        uniqueName: {
            value: null
        }
    },

    prototype: {
        /**
         * Construction logic executed during AutoComplete instantiation. Lifecycle.
         *
         * @method initializer
         * @protected
         */
        initializer: function(config) {
            var instance = this;

            instance._overlayAlign = A.mix({}, OVERLAY_ALIGN);

            instance._createDataSource();
        },

        /**
         * Create the DOM structure for the AutoComplete. Lifecycle.
         *
         * @method renderUI
         * @protected
         */
        renderUI: function() {
            var instance = this;

            instance._renderInput();
            instance._renderOverlay();
        },

        /**
         * Bind the events on the AutoComplete UI. Lifecycle.
         *
         * @method bindUI
         * @protected
         */
        bindUI: function() {
            var instance = this;

            var button = instance.button;
            var inputNode = instance.inputNode;

            instance.dataSource.on('request', A.bind(button.set, button, ICON, ICON_LOADING));

            button.on('click', instance._onButtonMouseDown, instance);

            inputNode.on('blur', instance._onTextboxBlur, instance);
            inputNode.on('focus', instance._onTextboxFocus, instance);
            inputNode.on('keydown', instance._onTextboxKeyDown, instance);
            inputNode.on('keypress', instance._onTextboxKeyPress, instance);
            inputNode.on('keyup', instance._onTextboxKeyUp, instance);

            var overlayBoundingBox = instance.overlay.get(BOUNDING_BOX);

            overlayBoundingBox.on('click', instance._onContainerClick, instance);
            overlayBoundingBox.on('mouseout', instance._onContainerMouseout, instance);
            overlayBoundingBox.on('mouseover', instance._onContainerMouseover, instance);
            overlayBoundingBox.on('scroll', instance._onContainerScroll, instance);

            /**
             * Handles the containerCollapse event. Fired when the container is hidden.
             *
             * @event containerCollapse
             * @param {Event.Facade} event The containerCollapse event.
             */
            instance.publish('containerCollapse');

            /**
             * Handles the containerExpand event. Fired when the container is shown.
             *
             * @event containerExpand
             * @param {Event.Facade} event The containerExpand event.
             */
            instance.publish('containerExpand');

            /**
             * Handles the containerPopulate event. Fired when the container is populated.
             *
             * @event containerPopulate
             * @param {Event.Facade} event The containerPopulate event.
             */
            instance.publish('containerPopulate');

            /**
             * Handles the dataError event. Fired when there is an error accessing the data.
             *
             * @event dataError
             * @param {Event.Facade} event The dataError event.
             */
            instance.publish('dataError');

            /**
             * Handles the dataRequest event. Fired when ever a query is sent to the data source.
             *
             * @event dataRequest
             * @param {Event.Facade} event The dataRequest event.
             */
            instance.publish('dataRequest');

            /**
             * Handles the dataReturn event. Fired when data successfully comes back from the data request.
             *
             * @event dataReturn
             * @param {Event.Facade} event The dataReturn event.
             */
            instance.publish('dataReturn');

            /**
             * Handles the itemArrowFrom event. Fired when the user navigates via the keyboard away from
             * a selected item.
             *
             * @event itemArrowFrom
             * @param {Event.Facade} event The itemArrowFrom event.
             */
            instance.publish('itemArrowFrom');

            /**
             * Handles the itemArrowTo event. Fired when the user navigates via the keyboard to a selected item.
             *
             * @event itemArrowTo
             * @param {Event.Facade} event The itemArrowTo event.
             */
            instance.publish('itemArrowTo');

            /**
             * Handles the itemMouseOut event. Fired when the user mouses away from an item.
             *
             * @event itemMouseOut
             * @param {Event.Facade} event The itemMouseOut event.
             */
            instance.publish('itemMouseOut');

            /**
             * Handles the itemMouseOver event. Fired when the user mouses over an item.
             *
             * @event itemMouseOver
             * @param {Event.Facade} event The itemMouseOver event.
             */
            instance.publish('itemMouseOver');

            /**
             * Handles the itemSelect event. Fired when an item in the list is selected.
             *
             * @event itemSelect
             * @param {Event.Facade} event The itemSelect event.
             */
            instance.publish('itemSelect');

            /**
             * Handles the selectionEnforce event. Fired if <a href="Autocomplete.html#config_forceSelection">forceSelection</a>
             * is enabled and the users input element has been cleared because it did not match one of the results.
             *
             * @event selectionEnforce
             * @param {Event.Facade} event The selectionEnforce event.
             */
            instance.publish('selectionEnforce');

            /**
             * Handles the textboxBlur event. Fired when the user leaves the input element.
             *
             * @event textboxBlur
             * @param {Event.Facade} event The textboxBlur event.
             */
            instance.publish('textboxBlur');

            /**
             * Handles the textboxChange event. Fired when the value in the input element is changed.
             *
             * @event textboxChange
             * @param {Event.Facade} event The textboxChange event.
             */
            instance.publish('textboxChange');

            /**
             * Handles the textboxFocus event. Fired when user moves focus to the input element.
             *
             * @event textboxFocus
             * @param {Event.Facade} event The textboxFocus event.
             */
            instance.publish('textboxFocus');

            /**
             * Handles the textboxKey event. Fired when the input element receives key input.
             *
             * @event textboxKey
             * @param {Event.Facade} event The textboxKey event.
             */
            instance.publish('textboxKey');

            /**
             * Handles the typeAhead event. Fired when the input element has been pre-filled by the type-ahead feature.
             *
             * @event typeAhead
             * @param {Event.Facade} event The typeAhead event.
             */
            instance.publish('typeAhead');

            /**
             * Handles the unmatchedItemSelect event. Fired when a user selects something that does
             * not match any of the displayed results.
             *
             * @event unmatchedItemSelect
             * @param {Event.Facade} event The unmatchedItemSelect event.
             */
            instance.publish('unmatchedItemSelect');

            instance.overlay.after('visibleChange', instance._realignContainer, instance);
        },

        /**
         * Sync the AutoComplete UI. Lifecycle.
         *
         * @method syncUI
         * @protected
         */
        syncUI: function() {
            var instance = this;

            instance.inputNode.setAttribute('autocomplete', 'off');
        },

        /**
         * Destructor lifecycle implementation for the Autocomplete class.
         * Purges events attached to the node (and all child nodes).
         *
         * @method destructor
         * @protected
         */
        destructor: function() {
            var instance = this;

            instance.overlay.destroy();
        },

        /**
         * An overridable method that is executed before the result container is shown.
         * The method can return false to prevent the container from being shown.
         *
         * @method doBeforeExpandContainer
         * @param {String} query The query that was submitted to the data source
         * @param {Object} allResults The parsed results
         * @return {Boolean}
         */
        doBeforeExpandContainer: function() {
            return true;
        },

        /**
         * An overridable method that is executed before the result overlay is loaded with results.
         *
         * @method doBeforeLoadData
         * @param {EventFacade} event
         * @return {Boolean}
         */
        doBeforeLoadData: function(event) {
            return true;
        },

        /**
         * Executed by the data source as a mechanism to do simple client-side
         * filtering of the results.
         *
         * @method filterResults
         * @param {EventFacade} event
         * @return {Object} Filtered response object
         */
        filterResults: function(event) {
            var instance = this;

            var callback = event.callback;
            var query = event.request;
            var response = event.response;

            if (callback && callback.argument && callback.argument.query) {
                query = callback.argument.query;
            }

            if (query) {
                var dataSource = instance.dataSource;
                var allResults = response.results;
                var filteredResults = [];
                var matchFound = false;
                var matchKey = instance.get('matchKey');
                var matchCase = instance.get('queryMatchCase');
                var matchContains = instance.get('queryMatchContains');
                var showAll = (query == '*');

                var fields = instance.get('schema.resultFields');

                for (var i = allResults.length - 1; i >= 0; i--) {
                    var result = allResults[i];

                    var strResult = null;

                    if (isString(result)) {
                        strResult = result;
                    }
                    else if (isArray(result)) {
                        strResult = result[0];
                    }
                    else if (fields) {
                        strResult = result[matchKey || fields[0]];
                    }

                    if (isString(strResult)) {
                        var keyIndex = -1;

                        if (matchCase) {
                            keyIndex = strResult.indexOf(decodeURIComponent(query));
                        }
                        else {
                            keyIndex = strResult.toLowerCase().indexOf(decodeURIComponent(query).toLowerCase());
                        }

                        if (
                            (showAll) ||
                            (!matchContains && (keyIndex === 0)) ||
                            (matchContains && (keyIndex > -1))
                        ) {
                            filteredResults.unshift(result);
                        }
                    }
                }

                response.results = filteredResults;
            }

            return response;
        },

        /**
         * An overridable method for formatting the result of the query before it's displayed in the overlay.
         *
         * @method formatResult
         * @param {Object} result The result data object
         * @param {String} request The current query string
         * @param {String} resultMatch The string from the results that matches the query
         * @return {String}
         */
        formatResult: function(result, request, resultMatch) {
            return resultMatch || '';
        },

        /**
         * An overridable method that creates an object to be passed to the sendRequest
         * method of the data source object. Useful to overwrite if you wish to create
         * a custom request object before it's sent.
         *
         * @method generateRequest
         * @param {String} query The string currently being entered
         * @return {Object}
         */
        generateRequest: function(query) {
            return {
                request: query
            };
        },

        /**
         * Handles the response for the display of the results. This is a callback method
         * that is fired by the sendRequest method so that results are ready to be accessed.
         *
         * @method handleResponse
         * @param {EventFacade} event
         */
        handleResponse: function(event) {
            var instance = this;

            instance._populateList(event);

            var iconClass = ICON_DEFAULT;

            if (event.error) {
                iconClass = ICON_ERROR;
            }

            instance.button.set(ICON, iconClass);
        },

        /**
         * Sends a query request to the data source object.
         *
         * @method sendQuery
         * @param {String} query Query string
         */
        sendQuery: function(query) {
            var instance = this;

            instance.set('focused', null);

            var newQuery = query;

            if (instance.get('delimChar')) {
                query = instance.inputNode.get('value') + query;
            }

            instance._sendQuery(newQuery);
        },

        /**
         * Clears the query interval
         *
         * @method _clearInterval
         * @private
         */
        _clearInterval: function() {
            var instance = this;

            if (instance._queryIntervalId) {
                clearInterval(instance._queryIntervalId);

                instance._queryIntervalId = null;
            }
        },

        /**
         * When <a href="Autocomplete.html#config_forceSelection">forceSelection</a> is true and
         * the user tries to leave the input element without selecting an item from the results,
         * the user selection is cleared.
         *
         * @method _clearSelection
         * @protected
         */
        _clearSelection: function() {
            var instance = this;

            var delimChar = instance.get('delimChar');
            var extraction = {
                previous: '',
                query: instance.inputNode.get('value')
            };

            if (delimChar) {
                extraction = instance._extractQuery(instance.inputNode.get('value'));
            }

            instance.fire('selectionEnforce', extraction.query);
        },

        /**
         * Creates the data source object using the passed in <a href="Autocomplete.html#config_dataSource">dataSource</a>,
         * and if it is a string, will use the <a href="Autocomplete.html#config_dataSourceType">dataSourceType</a> to
         * create a new <a href="module_datasource.html">DataSource</a> object.
         *
         * @method _createDataSource
         * @protected
         * @return {String}
         */
        _createDataSource: function() {
            var instance = this;

            instance._queryTask = A.debounce(instance.sendQuery, instance.get('queryDelay'), instance);

            var dataSource = instance.get('dataSource');
            var data = dataSource;

            var dataSourceType = instance.get('dataSourceType');

            if (!(dataSource instanceof A.DataSource.Local)) {
                if (!dataSourceType) {
                    dataSourceType = 'Local';

                    if (isFunction(data)) {
                        dataSourceType = 'Function';
                    }
                    else if (isString(data)) {
                        dataSourceType = 'IO';
                    }
                }

                dataSource = new A.DataSource[dataSourceType]({
                    source: data
                });
            }

            dataSource.on('error', instance.handleResponse, instance);
            dataSource.after('response', instance.handleResponse, instance);

            dataSourceType = dataSource.name;

            if (dataSourceType == 'dataSourceLocal') {
                instance.set('applyLocalFilter', true);
            }

            instance.set('dataSource', dataSource);
            instance.set('dataSourceType', dataSourceType);

            instance.dataSource = dataSource;

            var schema = instance.get('schema');

            if (schema) {
                if (schema.fn) {
                    instance.dataSource.plug(schema);
                }
                else {
                    var schemaType = instance.get('schemaType');

                    var schemaTypes = {
                        array: A.Plugin.DataSourceArraySchema,
                        json: A.Plugin.DataSourceJSONSchema,
                        text: A.Plugin.DataSourceTextSchema,
                        xml: A.Plugin.DataSourceXMLSchema
                    };

                    schemaType = schemaType.toLowerCase() || 'array';

                    instance.dataSource.plug({
                        fn: schemaTypes[schemaType],
                        cfg: {
                            schema: schema
                        }
                    });
                }
            }

            instance.set('schema', schema);
        },

        /**
         * Enables query interval detection for IME support.
         *
         * @method _enableIntervalDetection
         * @protected
         */
        _enableIntervalDetection: function() {
            var instance = this;

            var queryInterval = instance.get('queryInterval');

            if (!instance._queryIntervalId && queryInterval) {
                instance._queryInterval = setInterval(A.bind(instance._onInterval, instance), queryInterval);
            }
        },

        /**
         * Extracts the right most query from the delimited string in the input.
         *
         * @method _extractQuery
         * @param {String} query String to parse
         * @protected
         * @return {String}
         */
        _extractQuery: function(query) {
            var instance = this;

            var delimChar = instance.get('delimChar');
            var delimIndex = -1;
            var i = delimChar.length - 1;

            var newIndex, queryStart, previous;

            for (; i >= 0; i--) {
                newIndex = query.lastIndexOf(delimChar[i]);

                if (newIndex > delimIndex) {
                    delimIndex = newIndex;
                }
            }

            if (delimChar[i] == ' ') {
                for (var j = delimChar.length - 1; j >= 0; j--) {
                    if (query[delimIndex - 1] == delimChar[j]) {
                        delimIndex--;

                        break;
                    }
                }
            }

            if (delimIndex > -1) {
                queryStart = delimIndex + 1;

                while (query.charAt(queryStart) == ' ') {
                    queryStart += 1;
                }

                previous = query.substring(0, queryStart);

                query = query.substring(queryStart);
            }
            else {
                previous = '';
            }

            return {
                previous: previous,
                query: query
            };
        },

        /**
         * Focuses the input element.
         *
         * @method _focus
         * @protected
         */
        _focus: function() {
            var instance = this;

            setTimeout(
                function() {
                    instance.inputNode.focus();
                },
                0
            );
        },

        /**
         * If there is a currently selected item, the right arrow key will select
         * that item and jump to the end of the input element, otherwise the container is closed.
         *
         * @method _jumpSelection
         * @protected
         */
        _jumpSelection: function() {
            var instance = this;

            if (instance._elCurListItem) {
                instance._selectItem(instance._elCurListItem);
            }
            else {
                instance._toggleContainer(false);
            }
        },

        /**
         * Triggered by the up and down arrow keys, changes the currently selected list element item, and scrolls the
         * container if necessary.
         *
         * @method _moveSelection
         * @param {Number} keyCode The numeric code of the key pressed
         * @protected
         */
        _moveSelection: function(keyCode) {
            var instance = this;

            if (instance.overlay.get('visible')) {
                var elCurListItem = instance._elCurListItem;
                var curItemIndex = -1;

                if (elCurListItem) {
                    curItemIndex = Number(elCurListItem.getAttribute('data-listItemIndex'));
                }

                var newItemIndex = curItemIndex - 1;

                if (KeyMap.isKey(keyCode, DOWN)) {
                    newItemIndex = curItemIndex + 1;
                }

                if (newItemIndex == -1) {
                    newItemIndex = instance._displayedItems - 1;
                }

                if (newItemIndex >= instance._displayedItems) {
                    newItemIndex = 0;
                }

                if (newItemIndex < -2) {
                    return;
                }

                if (elCurListItem) {
                    instance._toggleHighlight(elCurListItem, 'from');

                    instance.fire('itemArrowFrom', elCurListItem);
                }

                if (newItemIndex == -1) {
                    if (instance.get('delimChar')) {
                        instance.inputNode.set('value', instance._pastSelections + instance._currentQuery);
                    }
                    else {
                        instance.inputNode.set('value', instance._currentQuery);
                    }

                    return;
                }

                if (newItemIndex == -2) {
                    instance._toggleContainer(false);

                    return;
                }

                var elNewListItem = instance.resultList.get('childNodes').item(newItemIndex);

                var elContent = instance.overlay.get(CONTENT_BOX);

                var contentOverflow = elContent.getStyle('overflow');
                var contentOverflowY = elContent.getStyle('overflowY');

                var scrollOn = (contentOverflow == 'auto') || (contentOverflow == 'scroll') || (contentOverflowY ==
                    'auto') || (contentOverflowY == 'scroll');

                if (scrollOn &&
                    (newItemIndex > -1) &&
                    (newItemIndex < instance._displayedItems)) {

                    var newScrollTop = -1;
                    var liTop = elNewListItem.get('offsetTop');
                    var liBottom = liTop + elNewListItem.get('offsetHeight');

                    var contentHeight = elContent.get('offsetHeight');
                    var contentScrollTop = elContent.get('scrollTop');
                    var contentBottom = contentHeight + contentScrollTop;

                    if (KeyMap.isKey(keyCode, DOWN)) {
                        if (liBottom > contentBottom) {
                            newScrollTop = (liBottom - contentHeight);
                        }
                        else if (liBottom < contentScrollTop) {
                            newScrollTop = liTop;
                        }
                    }
                    else {
                        if (liTop < contentHeight) {
                            newScrollTop = liTop;
                        }
                        else if (liTop > contentBottom) {
                            newScrollTop = (liBottom - contentHeight);
                        }
                    }

                    if (newScrollTop > -1) {
                        elContent.set('scrollTop', newScrollTop);
                    }
                }

                instance._toggleHighlight(elNewListItem, 'to');

                instance.fire('itemArrowTo', elNewListItem);

                if (instance.get('typeAhead')) {
                    instance._updateValue(elNewListItem);
                }
            }
        },

        /**
         * Called when the user mouses down on the button element in the combobox.
         *
         * @method _onButtonMouseDown
         * @param {EventFacade} event
         * @protected
         */
        _onButtonMouseDown: function(event) {
            var instance = this;

            event.halt();

            instance._focus();

            instance._sendQuery(instance.inputNode.get('value') + '*');
        },

        /**
         * Handles when a user clicks on the container.
         *
         * @method _onContainerClick
         * @param {EventFacade} event
         * @protected
         */
        _onContainerClick: function(event) {
            var instance = this;

            var target = event.target;
            var tagName = target.get('nodeName').toLowerCase();

            event.halt();

            while (target && (tagName != 'table')) {
                switch (tagName) {
                    case 'body':
                        return;

                    case 'li':
                        instance._toggleHighlight(target, 'to');
                        instance._selectItem(target);
                        return;

                    default:
                        break;
                }

                target = target.get('parentNode');

                if (target) {
                    tagName = target.get('nodeName').toLowerCase();
                }
            }
        },

        /**
         * Handles when a user mouses out of the container.
         *
         * @method _onContainerMouseout
         * @param {EventFacade} event
         * @protected
         */
        _onContainerMouseout: function(event) {
            var instance = this;

            var target = event.target;
            var tagName = target.get('nodeName').toLowerCase();

            while (target && (tagName != 'table')) {
                switch (tagName) {
                    case 'body':
                        return;

                    case 'li':
                        instance._toggleHighlight(target, 'from');
                        instance.fire('itemMouseOut', target);
                        break;

                    case 'ul':
                        instance._toggleHighlight(instance._elCurListItem, 'to');
                        break;

                    case 'div':
                        if (target.hasClass(CSS_RESULTS_OVERLAY)) {
                            instance._overContainer = false;

                            return;
                        }
                        break;

                    default:
                        break;
                }

                target = target.get('parentNode');

                if (target) {
                    tagName = target.get('nodeName').toLowerCase();
                }
            }
        },

        /**
         * Handles when a user mouses over the container.
         *
         * @method _onContainerMouseover
         * @param {EventFacade} event
         * @protected
         */
        _onContainerMouseover: function(event) {
            var instance = this;

            var target = event.target;
            var tagName = target.get('nodeName').toLowerCase();

            while (target && (tagName != 'table')) {
                switch (tagName) {
                    case 'body':
                        return;

                    case 'li':
                        instance._toggleHighlight(target, 'to');
                        instance.fire('itemMouseOut', target);
                        break;

                    case 'div':
                        if (target.hasClass(CSS_RESULTS_OVERLAY)) {
                            instance._overContainer = true;
                            return;
                        }
                        break;

                    default:
                        break;
                }

                target = target.get('parentNode');

                if (target) {
                    tagName = target.get('nodeName').toLowerCase();
                }
            }
        },

        /**
         * Handles the container scroll events.
         *
         * @method _onContainerScroll
         * @param {EventFacade} event
         * @protected
         */
        _onContainerScroll: function(event) {
            var instance = this;

            instance._focus();
        },

        /**
         * Enables the query to be triggered based on detecting text input via intervals instead of via
         * key events.
         *
         * @method _onInterval
         * @protected
         */
        _onInterval: function() {
            var instance = this;

            var curValue = instance.inputNode.get('value');
            var lastValue = instance._lastValue;

            if (curValue != lastValue) {
                instance._lastValue = curValue;

                instance._sendQuery(curValue);
            }
        },

        /**
         * Handles the input element losing focus.
         *
         * @method _onTextboxBlur
         * @param {EventFacade} event
         * @protected
         */
        _onTextboxBlur: function(event) {
            var instance = this;

            if (!instance._overContainer || KeyMap.isKey(instance._keyCode, TAB)) {
                if (!instance._itemSelected) {
                    var elMatchListItem = instance._textMatchesOption();

                    var overlayVisible = instance.overlay.get('visible');

                    if (!overlayVisible || (overlayVisible && isNull(elMatchListItem))) {
                        if (instance.get('forceSelection')) {
                            instance._clearSelection();
                        }
                        else {
                            instance.fire('unmatchedItemSelect', instance._currentQuery);
                        }
                    }
                    else {
                        if (instance.get('forceSelection')) {
                            instance._selectItem(elMatchListItem);
                        }
                    }
                }

                instance._clearInterval();

                instance.blur();

                if (instance._initInputValue !== instance.inputNode.get('value')) {
                    instance.fire('textboxChange');
                }

                instance.fire('textboxBlur');

                instance._toggleContainer(false);
            }
            else {
                instance._focus();
            }
        },

        /**
         * Handles the input element gaining focus.
         *
         * @method _onTextboxFocus
         * @param {EventFacade} event
         * @protected
         */
        _onTextboxFocus: function(event) {
            var instance = this;

            if (!instance.get('focused')) {
                instance.inputNode.setAttribute('autocomplete', 'off');
                instance.focus();
                instance._initInputValue = instance.inputNode.get('value');

                instance.fire('textboxFocus');
            }
        },

        /**
         * Handles the keydown events on the input element for functional keys.
         *
         * @method _onTextboxKeyDown
         * @param {EventFacade} event
         * @protected
         */
        _onTextboxKeyDown: function(event) {
            var instance = this;

            var keyCode = event.keyCode;

            if (instance._typeAheadDelayId != -1) {
                clearTimeout(instance._typeAheadDelayId);
            }

            if (event.isKey(TAB)) {
                if (instance._elCurListItem) {
                    if (instance.get('delimChar') && instance._keyCode != keyCode) {
                        if (instance.overlay.get('visible')) {
                            event.halt();
                        }
                    }

                    instance._selectItem(instance._elCurListItem);
                }
                else {
                    instance._toggleContainer(false);
                }
            }
            else if (event.isKey(ENTER)) {
                if (instance._elCurListItem) {
                    if (instance._keyCode != keyCode) {
                        if (instance.overlay.get('visible')) {
                            event.halt();
                        }
                    }

                    instance._selectItem(instance._elCurListItem);
                }
                else {
                    instance._toggleContainer(false);
                }
            }
            else if (event.isKey(ESC)) {
                instance._toggleContainer(false);
            }
            else if (event.isKey(UP)) {
                if (instance.overlay.get('visible')) {
                    event.halt();

                    instance._moveSelection(keyCode);
                }
            }
            else if (event.isKey(RIGHT)) {
                instance._jumpSelection();
            }
            else if (event.isKey(DOWN)) {
                if (instance.overlay.get('visible')) {
                    event.halt();

                    instance._moveSelection(keyCode);
                }
            }
            else {
                instance._itemSelected = false;
                instance._toggleHighlight(instance._elCurListItem, 'from');

                instance.fire('textboxKey', keyCode);
            }

            if (event.isKey(ALT)) {
                instance._enableIntervalDetection();
            }

            instance._keyCode = keyCode;
        },

        /**
         * Handles the key press events of the input element.
         *
         * @method _onTextboxKeyPress
         * @param {EventFacade} event
         * @protected
         */
        _onTextboxKeyPress: function(event) {
            var instance = this;

            var keyCode = event.keyCode;

            if (event.isKey(TAB)) {
                if (instance.overlay.get('visible')) {
                    if (instance.get('delimChar')) {
                        event.halt();
                    }

                    if (instance._elCurListItem) {
                        instance._selectItem(instance._elCurListItem);
                    }
                    else {
                        instance._toggleContainer(false);
                    }
                }
            }
            else if (event.isKey(ENTER)) {
                if (instance.overlay.get('visible')) {
                    event.halt();

                    if (instance._elCurListItem) {
                        instance._selectItem(instance._elCurListItem);
                    }
                    else {
                        instance._toggleContainer(false);
                    }
                }
            }

            if (event.isKey(WIN_IME)) {
                instance._enableIntervalDetection();
            }
        },

        /**
         * Handles the keyup events of the input element.
         *
         * @method _onTextboxKeyUp
         * @param {EventFacade} event
         * @protected
         */
        _onTextboxKeyUp: function(event) {
            var instance = this;

            var input = instance.inputNode;

            var value = input.get('value');

            if (event.isSpecialKey() && !event.isKey(BACKSPACE)) {
                return;
            }

            instance._queryTask(value);
        },

        /**
         * Populates the container with list items of the query results.
         *
         * @method _populateList
         * @param {EventFacade} event
         * @protected
         */
        _populateList: function(event) {
            var instance = this;

            if (instance._typeAheadDelayId != -1) {
                clearTimeout(instance._typeAheadDelayId);
            }

            var query = event.request;
            var response = event.response;
            var callback = event.callback;
            var showAll = (query == '*');

            if (callback && callback.argument && callback.argument.query) {
                event.request = query = callback.argument.query;
            }

            var ok = instance.doBeforeLoadData(event);

            if (ok && !event.error) {
                instance.fire('dataReturn', event);

                var focused = instance.get('focused');

                if (showAll || focused || focused === null) {
                    var currentQuery = decodeURIComponent(query);

                    instance._currentQuery = currentQuery;
                    instance._itemSelected = false;

                    var allResults = event.response.results;
                    var itemsToShow = Math.min(allResults.length, instance.get('maxResultsDisplayed'));
                    var fields = instance.get('schema.resultFields');
                    var matchKey = instance.get('matchKey');

                    if (!matchKey && fields) {
                        matchKey = fields[0];
                    }
                    else {
                        matchKey = matchKey || 0;
                    }

                    if (itemsToShow > 0) {
                        var allListItemEls = instance.resultList.get('childNodes');

                        allListItemEls.each(
                            function(node, i, nodeList) {
                                if (i < itemsToShow) {
                                    var result = allResults[i];

                                    var resultMatch = '';

                                    if (isString(result)) {
                                        resultMatch = result;
                                    }
                                    else if (isArray(result)) {
                                        resultMatch = result[0];
                                    }
                                    else {
                                        resultMatch = result[matchKey];
                                    }

                                    node._resultMatch = resultMatch;

                                    node._resultData = result;
                                    node.html(instance.formatResult(result, currentQuery, resultMatch));

                                    node.removeClass(CSS_HIDDEN);
                                }
                                else {
                                    node.addClass(CSS_HIDDEN);
                                }
                            }
                        );

                        instance._displayedItems = itemsToShow;

                        instance.fire('containerPopulate', query, allResults);

                        if (query != '*' && instance.get('autoHighlight')) {
                            var elFirstListItem = instance.resultList.get('firstChild');

                            instance._toggleHighlight(elFirstListItem, 'to');
                            instance.fire('itemArrowTo', elFirstListItem);

                            instance._typeAhead(elFirstListItem, query);
                        }
                        else {
                            instance._toggleHighlight(instance._elCurListItem, 'from');
                        }

                        ok = instance.doBeforeExpandContainer(query, allResults);

                        instance._toggleContainer(ok);
                    }
                    else {
                        instance._toggleContainer(false);
                    }

                    return;
                }

            }
            else {
                instance.fire('dataError', query);
            }
        },

        /**
         * Realigns the container to the input element.
         *
         * @method _realignContainer
         * @param {EventFacade} event
         * @protected
         */
        _realignContainer: function(event) {
            var instance = this;

            var overlayAlign = instance._overlayAlign;

            if (event.newVal) {
                instance.overlay._uiSetAlign(overlayAlign.node, overlayAlign.points);
            }
        },

        /**
         * Handles the rendering of the input element.
         *
         * @method _renderInput
         * @protected
         */
        _renderInput: function() {
            var instance = this;

            var contentBox = instance.get(CONTENT_BOX);
            var input = instance.get('input');

            var comboConfig = {
                field: {
                    labelText: false
                },
                icons: [
                    {
                        icon: 'icon-circle-arrow-down',
                        id: 'trigger',
                        handler: {
                            fn: instance._onButtonMouseDown,
                            context: instance
                        }
      }
     ]
            };

            var inputReference = null;
            var inputParent = null;

            if (input) {
                input = A.one(input);

                comboConfig.field.node = input;

                inputReference = input.next();
                inputParent = input.get('parentNode');
            }

            var comboBox = new A.Combobox(comboConfig).render(contentBox);

            if (inputParent) {
                var comboBoundingBox = comboBox.get('boundingBox');

                inputParent.insertBefore(comboBoundingBox, inputReference);
            }

            instance.inputNode = comboBox.get('node');
            instance.button = comboBox.icons.item(0);

            instance.set('uniqueName', A.stamp(instance.inputNode));
        },

        /**
         * Pre-populates the container with the
         * <a href="Autocomplete.html#config_maxResultsDisplayed">maxResultsDisplayed</a>
         * number of list items.
         *
         * @method _renderListElements
         * @protected
         */
        _renderListElements: function() {
            var instance = this;

            var maxResultsDisplayed = instance.get('maxResultsDisplayed');

            var resultList = instance.resultList;

            var listItems = [];

            while (maxResultsDisplayed--) {
                listItems[maxResultsDisplayed] = '<li class="' + CSS_HIDDEN + ' ' + CSS_LIST_ITEM +
                    '" data-listItemIndex="' + maxResultsDisplayed + '"></li>';
            }

            resultList.html(listItems.join(''));
        },

        /**
         * Handles the creation of the overlay where the result list will be displayed.
         *
         * @method _renderOverlay
         * @protected
         */
        _renderOverlay: function() {
            var instance = this;

            var overlayAlign = instance._overlayAlign;

            overlayAlign.node = instance.inputNode;

            var overlay = new A.OverlayBase({
                align: overlayAlign,
                bodyContent: '<ul></ul>',
                visible: false,
                width: instance.inputNode.get('offsetWidth'),
                zIndex: 1
            });

            var contentBox = overlay.get(CONTENT_BOX);

            overlay.get(BOUNDING_BOX).addClass(CSS_RESULTS_OVERLAY);

            contentBox.addClass(CSS_RESULTS_OVERLAY_CONTENT);

            overlay.render();

            overlay.addTarget(instance);

            instance.overlay = overlay;
            instance.resultList = contentBox.one('ul');

            instance.resultList.addClass(CSS_RESULTS_LIST);

            instance._renderListElements();
        },

        /**
         * Selects a list item from the query results.
         *
         * @method _selectItem
         * @param {Node} elListItem The list item to select
         * @protected
         */
        _selectItem: function(elListItem) {
            var instance = this;

            instance._itemSelected = true;

            instance._updateValue(elListItem);

            instance._pastSelections = instance.inputNode.get('value');

            instance._clearInterval();

            instance.fire('itemSelect', elListItem, elListItem._resultData);

            instance._toggleContainer(false);
        },

        /**
         * Makes a query request to the data source.
         *
         * @method _sendQuery
         * @param {String} query The query string
         * @protected
         */
        _sendQuery: function(query) {
            var instance = this;

            if (instance.get('disabled')) {
                instance._toggleContainer(false);

                return;
            }

            var delimChar = instance.get('delimChar');
            var minQueryLength = instance.get('minQueryLength');

            if (delimChar) {
                var extraction = instance._extractQuery(query);

                query = extraction.query;

                instance._pastSelections = extraction.previous;
            }

            if ((query && (query.length < minQueryLength)) || (!query && minQueryLength > 0)) {
                instance._queryTask.cancel();

                instance._toggleContainer(false);

                return;
            }

            query = encodeURIComponent(query);

            if (instance.get('applyLocalFilter')) {
                instance.dataSource.on('response', instance.filterResults, instance);
            }

            var request = instance.generateRequest(query);

            instance.fire('dataRequest', query, request);

            instance.dataSource.sendRequest(request);
        },

        /**
         * Checks to see if the value typed by the user matches any of the
         * query results.
         *
         * @method _textMatchesOption
         * @protected
         */
        _textMatchesOption: function() {
            var instance = this;

            var elMatch = null;
            var displayedItems = instance._displayedItems;
            var listItems = instance.resultList.get('childNodes');

            for (var i = 0; i < displayedItems.length; i++) {
                var elListItem = listItems.item(i);

                var match = ('' + elListItem._resultMatch).toLowerCase();

                if (match == instance._currentQuery.toLowerCase()) {
                    elMatch = elListItem;

                    break;
                }
            }

            return elMatch;
        },

        /**
         * Toggles the display of the results container.
         *
         * @method _toggleContainer
         * @param {Boolean} show Flag to force the showing or hiding of the container
         * @protected
         */
        _toggleContainer: function(show) {
            var instance = this;

            var overlay = instance.overlay;

            if (instance.get('alwaysShowContainer') && overlay.get('visible')) {
                return;
            }

            if (!show) {
                instance._toggleHighlight(instance._elCurListItem, 'from');

                instance._displayedItems = 0;
                instance._currentQuery = null;
            }

            if (show) {
                overlay.show();
                instance.fire('containerExpand');
            }
            else {
                overlay.hide();
                instance.fire('containerCollapse');
            }
        },

        /**
         * Toggles the highlighting of a list item, and removes the highlighting from the previous item
         *
         * @method _toggleHighlight
         * @param {Node} elNewListItem The item to be highlighted
         * @param {String} action Whether we are moving to or from an item. Valid values are "to" or "from".
         * @protected
         */
        _toggleHighlight: function(elNewListItem, action) {
            var instance = this;

            if (elNewListItem) {
                if (instance._elCurListItem) {
                    instance._elCurListItem.removeClass(CSS_HIGLIGHT);
                    instance._elCurListItem = null;
                }

                if (action == 'to') {
                    elNewListItem.addClass(CSS_HIGLIGHT);

                    instance._elCurListItem = elNewListItem;
                }
            }
        },

        /**
         * Updates in the input element with the first result as the user types,
         * selecting the text the user has not typed yet.
         *
         * @method _typeAhead
         * @param {Node} elListItem The selected list item
         * @param {String} query The query string
         * @protected
         */
        _typeAhead: function(elListItem, query) {
            var instance = this;

            if (!instance.get('typeAhead') || KeyMap.isKey(instance._keyCode, BACKSPACE)) {
                return;
            }

            var inputEl = A.Node.getDOMNode(instance.inputNode);

            if (inputEl.setSelectionRange || inputEl.createTextRange) {
                instance._typeAheadDelayId = setTimeout(
                    function() {
                        var value = inputEl.value;

                        var start = value.length;

                        instance._updateValue(elListItem);

                        var end = inputEl.value.length;

                        instance.inputNode.selectText(start, end);

                        var prefill = inputEl.value.substr(start, end);

                        instance.fire('typeAhead', query, prefill);
                    },
                    instance.get('typeAheadDelay')
                );
            }
        },

        /**
         * Updates the input element with the selected query result. If
         * <a href="Autocomplete.html#config_delimChar">delimChar</a> has been set,
         * then the value gets appended with the delimiter.
         *
         * @method _updateValue
         * @param {Node} elListItem The selected list item
         * @protected
         */
        _updateValue: function(elListItem) {
            var instance = this;

            if (!instance.get('suppressInputUpdate')) {
                var input = instance.inputNode;
                var resultMatch = elListItem._resultMatch;

                var delimChar = instance.get('delimChar');

                delimChar = (delimChar && delimChar[0]) || delimChar;

                var newValue = '';

                if (delimChar) {
                    newValue = instance._pastSelections;

                    newValue += resultMatch + delimChar;

                    if (delimChar != ' ') {
                        newValue += ' ';
                    }
                }
                else {
                    newValue = resultMatch;
                }

                input.set('value', newValue);

                if (input.get('type') == 'textarea') {
                    input.set('scrollTop', input.get('scrollHeight'));
                }

                var end = newValue.length;

                input.selectText(end, end);

                instance._elCurListItem = elListItem;
            }
        },

        _currentQuery: null,
        _displayedItems: 0,
        _elCurListItem: null,
        _initInputValue: null,
        _itemSelected: false,
        _keyCode: null,
        _lastValue: null,
        _overContainer: false,
        _pastSelections: '',
        _typeAheadDelayId: -1
    }
});

A.AutoCompleteDeprecated = AutoComplete;


}, '3.1.0-deprecated.110', {
    "requires": [
        "aui-base-deprecated",
        "aui-overlay-base-deprecated",
        "datasource",
        "dataschema",
        "aui-form-combobox-deprecated"
    ],
    "skinnable": true
});
