(function() {
    var KEY_CODE_ARROW_LEFT = 37, KEY_CODE_ARROW_RIGHT = 39, KEY_CODE_ARROW_UP = 38, KEY_CODE_ARROW_DOWN = 40, KEY_CODE_ENTER_RETURN = 13, KEY_CODE_HOME = 36, KEY_CODE_END = 35, KEY_CODE_PAGE_UP = 33, KEY_CODE_PAGE_DOWN = 34, KEY_CODE_ESCAPE = 27, KEY_CODE_TAB = 9;
    var _textPadding = 2, _rowHeight = 20, _maxRows = 6;

    function SuggestionField(field, dataSource, method, opts) {
        if (!field.tagName || (field.tagName && !field.type) || (field.tagName && field.type && field.type != 'text'))
            reliance.throwError('[reliance.Exception: SuggestionField] Cannot instantiate given field.');

        if (method && typeof method != 'function')
            reliance.throwError('[reliance.Exception: SuggestionField] Type mismatch: method must be a type of function.');

        reliance.xDomainHttpRequest.create('http://' + reliance.loader.proxyHost() + '/proxy/' + dataSource, this, function(response) { this.data = response }, { silent: true });

        this.field = field;
        this.method = method;
        this.data = [];
        this.suggestionKeystrokeMinimum = opts && opts.suggestionKeystrokeMinimum && typeof opts.suggestionKeystrokeMinimum == 'number' ? opts.suggestionKeystrokeMinimum : 1;
        this.width = opts && opts.expandedWidth && typeof opts.expandedWidth == 'number' ? opts.expandedWidth : field.offsetWidth;
        this.displayInfo = opts && opts.displayInfo && typeof opts.displayInfo == 'boolean' ? opts.displayInfo : false;
        this.infoSize = opts && opts.infoSize && typeof opts.infoSize == 'number' ? opts.infoSize : 35;
        this.dataSource = dataSource;
        initialize.apply(this);
    }
    SuggestionField.prototype.setValue = function(value) {
        this.field.value = value;
    }
    SuggestionField.prototype.getValue = function() {
        return this.field.value;
    }

    function initialize() {
        // The "this" keyword refers to the SuggestionField object
        var f = this.field;
        f.autocomplete = 'off';
        var list = document.createElement('div');
        list.style.cssText = 'float:left; position:absolute; border:#3366cc 1px solid; border-left-color:#a2bae7; border-top-color:#a2bae7; display:none; overflow:auto; width:' + (this.width - _getBorderCorrection()) + 'px';
        document.body.appendChild(list);
        
        this.list = list;

        reliance.event.bindDom(f, 'keyup', this, function(evnt) { return _handleKey.apply(this, ['up', evnt]) });
        reliance.event.bindDom(f, 'keydown', this, function(evnt) { return _handleKey.apply(this, ['down', evnt]) });
        reliance.event.addDomListener(document, 'click', reliance.event.callbackArgs(this, _handleDisplay, 0));
    }

    function _getBorderCorrection() {
        var borderWidth = 1;
        return borderWidth * 2;
    }

    function _handleKey(eventKeyMode, evnt) {
        // The "this" keyword refers to the SuggestionField object
        evnt = reliance.domUtilities.fixEvent(evnt);

        if (evnt.keyCode == KEY_CODE_ESCAPE || evnt.keyCode == KEY_CODE_TAB)
            return this.list.style.display = 'none';

        if (eventKeyMode == 'up') {
            if (evnt.keyCode == KEY_CODE_ARROW_LEFT || evnt.keyCode == KEY_CODE_ARROW_RIGHT || evnt.keyCode == KEY_CODE_ARROW_UP || evnt.keyCode == KEY_CODE_ARROW_DOWN || evnt.keyCode == KEY_CODE_HOME || evnt.keyCode == KEY_CODE_END || evnt.keyCode == KEY_CODE_PAGE_UP || evnt.keyCode == KEY_CODE_PAGE_DOWN) {
                // do nothing
            } else if (evnt.keyCode == KEY_CODE_ENTER_RETURN) {
                if (this.highlightIndex != -1)
                    this.list.childNodes[this.highlightIndex].onclick.apply();
            } else {
                if (eventKeyMode == 'up')
                    _redraw.apply(this);
            }
        }

        if (eventKeyMode == 'down') {
            if (evnt.keyCode == KEY_CODE_ARROW_UP || evnt.keyCode == KEY_CODE_ARROW_DOWN) {
                var x = evnt.keyCode == KEY_CODE_ARROW_UP ? -1 : 1;
                _highlight.apply(this, [this.highlightIndex + x]);
            }
        }
    }

    var _city;

    function _redraw() {
        // The "this" keyword refers to the SuggestionField object
        if (this.getValue().length < 3) {
			reliance.domUtilities.clearChildNodes(this.list);
			this.list.style.display = 'none';
		}

        var value = this.field.value.trim().replace(/^,\s*/g, '');
        value = value.replace(/\|/gi, '\\|');
        if (value.length >= this.suggestionKeystrokeMinimum) {
            var clear = document.createElement('div');
            clear.style.cssText = 'clear:both; overflow:hidden; height:' + (document.compatMode == 'BackCompat' ? 1 : 0) + 'px; background-color:#ffffff';
            clear.appendChild(document.createTextNode('.'));

            var matchCount = 0;

            if (this.data != null) {
				reliance.domUtilities.clearChildNodes(this.list);
                var re = new RegExp('\\b' + value + '.*?\\b', 'gi');
                for (var i = 0; i < this.data.length; i++) {
                    if (this.data[i].suggestion.match(re)) {
                        this.list.appendChild(_createItem.apply(this, [this.data[i], matchCount]));
                        matchCount++;
                    }
                }
                this.list.appendChild(clear);
                _handleDisplay.apply(this, [matchCount]);
            } else {
                var dataSource = this.dataSource + (this.dataSource.indexOf('?') == -1 ? '?' : '&') + 'q=' + value;
                reliance.xDomainHttpRequest.abort(_city);
                _city = reliance.xDomainHttpRequest.create('http://' + reliance.loader.proxyHost() + '/proxy/' + dataSource, this, function(response) {
					reliance.domUtilities.clearChildNodes(this.list);
                    for (var i = 0; i < response.length; i++) {
                        this.list.appendChild(_createItem.apply(this, [response[i], i]));
                    }
                    this.list.appendChild(clear);
                    _handleDisplay.apply(this, [response.length]);
                }, { silent: true });
            }
        }
    }

    function _handleDisplay(matches) {
        // The "this" keyword refers to the SuggestionField object
        var mode = matches > 0 ? 1 : 0;
        var display = { 0: 'none', 1: 'block' };
        this.list.style.display = display[mode];
        this.highlightIndex = -1;
        var height = (matches * _rowHeight);
        if (matches > _maxRows) {
            height = (_maxRows * _rowHeight);
            var scrollCorrection = reliance.browser.scrollbarWidthCorrection();
            for (var i = 0; i < this.list.childNodes.length - 1; i++) {
                var item = this.list.childNodes[i];
                var primaryNode = item.childNodes[0];
                item.style.width = (item.offsetWidth - scrollCorrection) + 'px';
                primaryNode.style.width = (primaryNode.offsetWidth - scrollCorrection) + 'px';
            }
        }
        this.list.style.height = (height + (document.compatMode == 'BackCompat' ? (reliance.browser.isIE() ? 3 : 1) : 0)) + 'px';
        if (mode != 0) {
            var offset = reliance.domUtilities.getOffset(this.field);
            this.list.style.left = offset.x + 'px';
            this.list.style.top = ((offset.y + this.field.offsetHeight) - 1) + 'px';
        }
    }

    function _unhighlight() {
        // The "this" keyword refers to the SuggestionField object
        this.style.backgroundColor = '#ffffff';
        this.childNodes[0].style.color = '#000000';
        if (this.childNodes[1])
            this.childNodes[1].style.color = '#999999';
    }

    function _highlight(displayIndex, mouseEvent) {
        // The "this" keyword refers to the SuggestionField object
        if (displayIndex > this.list.childNodes.length - 2) displayIndex = -1;
        if (displayIndex < -1) displayIndex = this.list.childNodes.length - 2;
        if (this.highlightIndex == -1) {
            this.userText = this.field.value;
        } else {
            _unhighlight.apply(this.list.childNodes[this.highlightIndex]);
        }
        if (displayIndex > -1 && displayIndex < this.list.childNodes.length - 1) {
            var node = this.list.childNodes[displayIndex];
            node.style.backgroundColor = '#3366cc';
            node.childNodes[0].style.color = '#ffffff';
            if (node.childNodes[1])
                node.childNodes[1].style.color = '#adc1ea';
            if (!mouseEvent)
                this.field.value = _getInnerText.apply(node.childNodes[0]);
        } else {
            this.field.value = this.userText;
        }
        if ((displayIndex + 1) * _rowHeight > this.list.scrollTop + (_maxRows * _rowHeight)) {
            this.list.scrollTop = (((displayIndex + 1) - _maxRows) * _rowHeight);
        } else if (displayIndex * _rowHeight < this.list.scrollTop) {
            this.list.scrollTop = (displayIndex * _rowHeight);
        }

        this.highlightIndex = displayIndex;
    }

    function _selectItem(dataItem) {
        // The "this" keyword refers to the SuggestionField object
        this.setValue(dataItem.suggestion);
        _handleDisplay.apply(this, [0]);
        if (this.method) {
            var callback = reliance.event.callbackArgs(null, this.method, dataItem.arguments);
            callback.apply();
        }
    }

    function _createItem(dataItem, displayIndex) {
        // The "this" keyword refers to the SuggestionField object
        
        //var itemWidth = this.width - (_getBorderCorrection() * (document.compatMode == 'BackCompat' ? 2 : 1));
        
        var itemWidth = this.width - _getBorderCorrection();
        if (reliance.browser.isIE()) {
			if (document.compatMode == 'BackCompat') {
				itemWidth -= _getBorderCorrection();
			}
        } else if (!reliance.browser.isFirefox()) {
			itemWidth -= _getBorderCorrection();
        }
        var nodeContentWidth = itemWidth - (_textPadding * 2);
        var primaryInfo = document.createElement('div');
        primaryInfo.style.cssText = 'position:absolute; white-space:nowrap; font-family:Arial, sans-serif; overflow:hidden; text-overflow:ellipsis; font-size:9pt; left:' + _textPadding + 'px; top:2px; zIndex:1; float:left; color:#000000; width:' + nodeContentWidth + 'px';
        primaryInfo.appendChild(document.createTextNode(dataItem.suggestion));

        var item = document.createElement('div');
        item.style.cssText = 'position:relative; float:left; width:' + itemWidth + 'px; height:' + _rowHeight + 'px; background-color:#ffffff; cursor:pointer';
        item.onmouseover = reliance.event.callbackArgs(this, _highlight, displayIndex, true);
        item.onclick = reliance.event.callbackArgs(this, _selectItem, dataItem);
        item.appendChild(primaryInfo);

        if (this.displayInfo && dataItem.info) {
            var secondaryNodeWidth = nodeContentWidth * (this.infoSize / 100);
            primaryInfo.style.width = (nodeContentWidth - secondaryNodeWidth) + 'px'
            var secondaryInfo = primaryInfo.cloneNode(false);
            secondaryInfo.style.fontSize = '7.5pt';
            secondaryInfo.style.left = '';
            secondaryInfo.style.right = _textPadding + 'px';
            secondaryInfo.style.top = '3px';
            secondaryInfo.style.width = secondaryNodeWidth + 'px';
            secondaryInfo.style.zIndex = 0;
            secondaryInfo.style.cssFloat = 'right';
            secondaryInfo.style.styleFloat = 'right';
            secondaryInfo.style.textAlign = 'right';
            secondaryInfo.style.color = '#999999';
            secondaryInfo.appendChild(document.createTextNode(dataItem.info));

            item.appendChild(secondaryInfo);
        }
        return item;
    }

    function _getInnerText() {
        // The "this" keyword refers to the SuggestionField object
        if (this.innerText)
            return this.innerText;
        return this.textContent;
    }

    window.reliance_exportSymbols('reliance.controls', [['SuggestionField', SuggestionField]]);
    reliance.load('domUtilities');
    reliance.load('browser');
    reliance.load('xDomainHttpRequest');
})();