/*
Uploadify v2.1.0
Release Date: August 24, 2009

Copyright (c) 2009 Ronnie Garcia, Travis Nickels

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

if (jQuery) (
	function (jQuery) {
	    jQuery.extend(jQuery.fn, {
	        uploadify: function (options) {
	            jQuery(this).each(function () {
	                settings = jQuery.extend({
	                    id: jQuery(this).attr('id'), // The ID of the object being Uploadified
	                    uploader: 'uploadify.swf', // The path to the uploadify swf file
	                    script: 'uploadify.php', // The path to the uploadify backend upload script
	                    expressInstall: null, // The path to the express install swf file
	                    folder: '', // The path to the upload folder
	                    height: 30, // The height of the flash button
	                    width: 110, // The width of the flash button
	                    cancelImg: 'cancel.png', // The path to the cancel image for the default file queue item container
	                    wmode: 'opaque', // The wmode of the flash file
	                    scriptAccess: 'sameDomain', // Set to "always" to allow script access across domains
	                    fileDataName: 'Filedata', // The name of the file collection object in the backend upload script
	                    method: 'POST', // The method for sending variables to the backend upload script
	                    queueSizeLimit: 999, // The maximum size of the file queue
	                    simUploadLimit: 1, // The number of simultaneous uploads allowed
	                    queueID: false, // The optional ID of the queue container
	                    displayData: 'percentage', // Set to "speed" to show the upload speed in the default queue item
	                    onInit: function () { }, // Function to run when uploadify is initialized
	                    onSelect: function () { }, // Function to run when a file is selected
	                    onQueueFull: function () { }, // Function to run when the queue reaches capacity
	                    onCheck: function () { }, // Function to run when script checks for duplicate files on the server
	                    onCancel: function () { }, // Function to run when an item is cleared from the queue
	                    onError: function () { }, // Function to run when an upload item returns an error
	                    onProgress: function () { }, // Function to run each time the upload progress is updated
	                    onComplete: function () { }, // Function to run when an upload is completed
	                    onAllComplete: function () { }  // Functino to run when all uploads are completed
	                }, options);
	                var pagePath = location.pathname;
	                pagePath = pagePath.split('/');
	                pagePath.pop();
	                pagePath = pagePath.join('/') + '/';
	                var data = {};
	                data.uploadifyID = settings.id;
	                data.pagepath = pagePath;
	                if (settings.buttonImg) data.buttonImg = escape(settings.buttonImg);
	                if (settings.buttonText) data.buttonText = escape(settings.buttonText);
	                if (settings.rollover) data.rollover = true;
	                data.script = settings.script;
	                data.folder = escape(settings.folder);
	                if (settings.scriptData) {
	                    var scriptDataString = '';
	                    for (var name in settings.scriptData) {
	                        scriptDataString += '&' + name + '=' + settings.scriptData[name];
	                    }
	                    data.scriptData = escape(scriptDataString.substr(1));
	                }
	                data.width = settings.width;
	                data.height = settings.height;
	                data.wmode = settings.wmode;
	                data.method = settings.method;
	                data.queueSizeLimit = settings.queueSizeLimit;
	                data.simUploadLimit = settings.simUploadLimit;
	                if (settings.hideButton) data.hideButton = true;
	                if (settings.fileDesc) data.fileDesc = settings.fileDesc;
	                if (settings.fileExt) data.fileExt = settings.fileExt;
	                if (settings.multi) data.multi = true;
	                if (settings.auto) data.auto = true;
	                if (settings.sizeLimit) data.sizeLimit = settings.sizeLimit;
	                if (settings.checkScript) data.checkScript = settings.checkScript;
	                if (settings.fileDataName) data.fileDataName = settings.fileDataName;
	                if (settings.queueID) data.queueID = settings.queueID;
	                if (settings.onInit() !== false) {
	                    jQuery(this).css('display', 'none');
	                    jQuery(this).after('<div id="' + jQuery(this).attr('id') + 'Uploader"></div>');
	                    swfobject.embedSWF(settings.uploader, settings.id + 'Uploader', settings.width, settings.height, '9.0.24', settings.expressInstall, data, { 'quality': 'high', 'wmode': settings.wmode, 'allowScriptAccess': settings.scriptAccess });
	                    if (settings.queueID == false) {
	                        jQuery("#" + jQuery(this).attr('id') + "Uploader").after('<div id="' + jQuery(this).attr('id') + 'Queue" class="uploadifyQueue"></div>');
	                    }
	                }
	                if (typeof (settings.onOpen) == 'function') {
	                    jQuery(this).bind("uploadifyOpen", settings.onOpen);
	                }
	                jQuery(this).bind("uploadifySelect", { 'action': settings.onSelect, 'queueID': settings.queueID }, function (event, ID, fileObj) {
	                    if (event.data.action(event, ID, fileObj) !== false) {
	                        var byteSize = Math.round(fileObj.size / 1024 * 100) * .01;
	                        var suffix = 'KB';
	                        if (byteSize > 1000) {
	                            byteSize = Math.round(byteSize * .001 * 100) * .01;
	                            suffix = 'MB';
	                        }
	                        var sizeParts = byteSize.toString().split('.');
	                        if (sizeParts.length > 1) {
	                            byteSize = sizeParts[0] + '.' + sizeParts[1].substr(0, 2);
	                        } else {
	                            byteSize = sizeParts[0];
	                        }
	                        if (fileObj.name.length > 20) {
	                            fileName = fileObj.name.substr(0, 20) + '...';
	                        } else {
	                            fileName = fileObj.name;
	                        }
	                        queue = '#' + jQuery(this).attr('id') + 'Queue';
	                        if (event.data.queueID) {
	                            queue = '#' + event.data.queueID;
	                        }
	                        jQuery(queue).append('<div id="' + jQuery(this).attr('id') + ID + '" class="uploadifyQueueItem">\
								<div class="cancel">\
									<a href="javascript:jQuery(\'#' + jQuery(this).attr('id') + '\').uploadifyCancel(\'' + ID + '\')"><img src="' + settings.cancelImg + '" border="0" /></a>\
								</div>\
								<span class="fileName">' + fileName + ' (' + byteSize + suffix + ')</span><span class="percentage"></span>\
								<div id="' + jQuery(this).attr('id') + ID + 'ProgressBar"><!--Progress Bar--></div>\
							</div>');
	                        $("#" + jQuery(this).attr('id') + ID + "ProgressBar").progressbar(
                            {
                                value: 0
                            });
	                    }
	                });
	                if (typeof (settings.onSelectOnce) == 'function') {
	                    jQuery(this).bind("uploadifySelectOnce", settings.onSelectOnce);
	                }
	                jQuery(this).bind("uploadifyQueueFull", { 'action': settings.onQueueFull }, function (event, queueSizeLimit) {
	                    if (event.data.action(event, queueSizeLimit) !== false) {
	                        alert('The queue is full.  The max size is ' + queueSizeLimit + '.');
	                    }
	                });
	                jQuery(this).bind("uploadifyCheckExist", { 'action': settings.onCheck }, function (event, checkScript, fileQueueObj, folder, single) {
	                    var postData = new Object();
	                    postData = fileQueueObj;
	                    postData.folder = pagePath + folder;
	                    if (single) {
	                        for (var ID in fileQueueObj) {
	                            var singleFileID = ID;
	                        }
	                    }
	                    jQuery.post(checkScript, postData, function (data) {
	                        for (var key in data) {
	                            if (event.data.action(event, checkScript, fileQueueObj, folder, single) !== false) {
	                                var replaceFile = confirm("Do you want to replace the file " + data[key] + "?");
	                                if (!replaceFile) {
	                                    document.getElementById(jQuery(event.target).attr('id') + 'Uploader').cancelFileUpload(key, true, true);
	                                }
	                            }
	                        }
	                        if (single) {
	                            document.getElementById(jQuery(event.target).attr('id') + 'Uploader').startFileUpload(singleFileID, true);
	                        } else {
	                            document.getElementById(jQuery(event.target).attr('id') + 'Uploader').startFileUpload(null, true);
	                        }
	                    }, "json");
	                });
	                jQuery(this).bind("uploadifyCancel", { 'action': settings.onCancel }, function (event, ID, fileObj, data, clearFast) {
	                    if (event.data.action(event, ID, fileObj, data, clearFast) !== false) {
	                        var fadeSpeed = (clearFast == true) ? 0 : 250;
	                        jQuery("#" + jQuery(this).attr('id') + ID).fadeOut(fadeSpeed, function () { jQuery(this).remove() });
	                    }
	                });
	                if (typeof (settings.onClearQueue) == 'function') {
	                    jQuery(this).bind("uploadifyClearQueue", settings.onClearQueue);
	                }
	                var errorArray = [];
	                jQuery(this).bind("uploadifyError", { 'action': settings.onError }, function (event, ID, fileObj, errorObj) {
	                    if (event.data.action(event, ID, fileObj, errorObj) !== false) {
	                        var fileArray = new Array(ID, fileObj, errorObj);
	                        errorArray.push(fileArray);
	                        jQuery("#" + jQuery(this).attr('id') + ID + " .percentage").text(" - " + errorObj.type + " Error");
	                        jQuery("#" + jQuery(this).attr('id') + ID).addClass('uploadifyError');
	                    }
	                });
	                jQuery(this).bind("uploadifyProgress", { 'action': settings.onProgress, 'toDisplay': settings.displayData }, function (event, ID, fileObj, data) {
	                    if (event.data.action(event, ID, fileObj, data) !== false) {
	                        $("#" + jQuery(this).attr('id') + ID + "ProgressBar").progressbar('value', data.percentage);
	                        if (event.data.toDisplay == 'percentage') displayData = ' - ' + data.percentage + '%';
	                        if (event.data.toDisplay == 'speed') displayData = ' - ' + data.speed + 'KB/s';
	                        if (event.data.toDisplay == null) displayData = ' ';
	                        jQuery("#" + jQuery(this).attr('id') + ID + " .percentage").text(displayData);
	                    }
	                });
	                jQuery(this).bind("uploadifyComplete", { 'action': settings.onComplete }, function (event, ID, fileObj, response, data) {
	                    if (event.data.action(event, ID, fileObj, unescape(response), data) !== false) {
	                        jQuery("#" + jQuery(this).attr('id') + ID + " .percentage").text(' - Completed');
	                        jQuery("#" + jQuery(this).attr('id') + ID).fadeOut(250, function () { jQuery(this).remove() });
	                    }
	                });
	                if (typeof (settings.onAllComplete) == 'function') {
	                    jQuery(this).bind("uploadifyAllComplete", { 'action': settings.onAllComplete }, function (event, uploadObj) {
	                        if (event.data.action(event, uploadObj) !== false) {
	                            errorArray = [];
	                        }
	                    });
	                }
	            });
	        },
	        uploadifySettings: function (settingName, settingValue, resetObject) {
	            var returnValue = false;
	            jQuery(this).each(function () {
	                if (settingName == 'scriptData' && settingValue != null) {
	                    if (resetObject) {
	                        var scriptData = settingValue;
	                    } else {
	                        var scriptData = jQuery.extend(settings.scriptData, settingValue);
	                    }
	                    var scriptDataString = '';
	                    for (var name in scriptData) {
	                        scriptDataString += '&' + name + '=' + escape(scriptData[name]);
	                    }
	                    settingValue = scriptDataString.substr(1);
	                }
	                returnValue = document.getElementById(jQuery(this).attr('id') + 'Uploader').updateSettings(settingName, settingValue);
	            });
	            if (settingValue == null) {
	                if (settingName == 'scriptData') {
	                    var returnSplit = unescape(returnValue).split('&');
	                    var returnObj = new Object();
	                    for (var i = 0; i < returnSplit.length; i++) {
	                        var iSplit = returnSplit[i].split('=');
	                        returnObj[iSplit[0]] = iSplit[1];
	                    }
	                    returnValue = returnObj;
	                }
	                return returnValue;
	            }
	        },
	        uploadifyUpload: function (ID) {
	            jQuery(this).each(function () {
	                document.getElementById(jQuery(this).attr('id') + 'Uploader').startFileUpload(ID, false);
	            });
	        },
	        uploadifyCancel: function (ID) {
	            jQuery(this).each(function () {
	                document.getElementById(jQuery(this).attr('id') + 'Uploader').cancelFileUpload(ID, true, false);
	            });
	        },
	        uploadifyClearQueue: function () {
	            jQuery(this).each(function () {
	                document.getElementById(jQuery(this).attr('id') + 'Uploader').clearFileUploadQueue(false);
	            });
	        }
	    })
	})(jQuery);
	/*
	* jQuery Autocomplete plugin 1.1
	*
	* Copyright (c) 2009 Jörn Zaefferer
	*
	* Dual licensed under the MIT and GPL licenses:
	*   http://www.opensource.org/licenses/mit-license.php
	*   http://www.gnu.org/licenses/gpl.html
	*
	* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
	*/

	; (function ($) {

	    $.fn.extend({
	        autocomplete: function (urlOrData, options) {
	            var isUrl = typeof urlOrData == "string";
	            options = $.extend({}, $.Autocompleter.defaults, {
	                url: isUrl ? urlOrData : null,
	                data: isUrl ? null : urlOrData,
	                delay: isUrl ? $.Autocompleter.defaults.delay : 10,
	                max: options && !options.scroll ? 10 : 150
	            }, options);

	            // if highlight is set to false, replace it with a do-nothing function
	            options.highlight = options.highlight || function (value) { return value; };

	            // if the formatMatch option is not specified, then use formatItem for backwards compatibility
	            options.formatMatch = options.formatMatch || options.formatItem;

	            return this.each(function () {
	                new $.Autocompleter(this, options);
	            });
	        },
	        result: function (handler) {
	            return this.bind("result", handler);
	        },
	        search: function (handler) {
	            return this.trigger("search", [handler]);
	        },
	        flushCache: function () {
	            return this.trigger("flushCache");
	        },
	        setOptions: function (options) {
	            return this.trigger("setOptions", [options]);
	        },
	        unautocomplete: function () {
	            return this.trigger("unautocomplete");
	        }
	    });

	    $.Autocompleter = function (input, options) {

	        var KEY = {
	            UP: 38,
	            DOWN: 40,
	            DEL: 46,
	            TAB: 9,
	            RETURN: 13,
	            ESC: 27,
	            COMMA: 188,
	            PAGEUP: 33,
	            PAGEDOWN: 34,
	            BACKSPACE: 8
	        };

	        // Create $ object for input element
	        var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

	        var timeout;
	        var previousValue = "";
	        var cache = $.Autocompleter.Cache(options);
	        var hasFocus = 0;
	        var lastKeyPressCode;
	        var config = {
	            mouseDownOnSelect: false
	        };
	        var select = $.Autocompleter.Select(options, input, selectCurrent, config);

	        var blockSubmit;

	        // prevent form submit in opera when selecting with return key
	        $.browser.opera && $(input.form).bind("submit.autocomplete", function () {
	            if (blockSubmit) {
	                blockSubmit = false;
	                return false;
	            }
	        });

	        // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	        $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function (event) {
	            // a keypress means the input has focus
	            // avoids issue where input had focus before the autocomplete was applied
	            hasFocus = 1;
	            // track last key pressed
	            lastKeyPressCode = event.keyCode;
	            switch (event.keyCode) {

	                case KEY.UP:
	                    event.preventDefault();
	                    if (select.visible()) {
	                        select.prev();
	                    } else {
	                        onChange(0, true);
	                    }
	                    break;

	                case KEY.DOWN:
	                    event.preventDefault();
	                    if (select.visible()) {
	                        select.next();
	                    } else {
	                        onChange(0, true);
	                    }
	                    break;

	                case KEY.PAGEUP:
	                    event.preventDefault();
	                    if (select.visible()) {
	                        select.pageUp();
	                    } else {
	                        onChange(0, true);
	                    }
	                    break;

	                case KEY.PAGEDOWN:
	                    event.preventDefault();
	                    if (select.visible()) {
	                        select.pageDown();
	                    } else {
	                        onChange(0, true);
	                    }
	                    break;

	                // matches also semicolon 
	                case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
	                case KEY.TAB:
	                case KEY.RETURN:
	                    if (selectCurrent()) {
	                        // stop default to prevent a form submit, Opera needs special handling
	                        event.preventDefault();
	                        blockSubmit = true;
	                        return false;
	                    }
	                    break;

	                case KEY.ESC:
	                    select.hide();
	                    break;

	                default:
	                    clearTimeout(timeout);
	                    timeout = setTimeout(onChange, options.delay);
	                    break;
	            }
	        }).focus(function () {
	            // track whether the field has focus, we shouldn't process any
	            // results if the field no longer has focus
	            hasFocus++;
	        }).blur(function () {
	            hasFocus = 0;
	            if (!config.mouseDownOnSelect) {
	                hideResults();
	            }
	        }).click(function () {
	            // show select when clicking in a focused field
	            if (hasFocus++ > 1 && !select.visible()) {
	                onChange(0, true);
	            }
	        }).bind("search", function () {
	            // TODO why not just specifying both arguments?
	            var fn = (arguments.length > 1) ? arguments[1] : null;
	            function findValueCallback(q, data) {
	                var result;
	                if (data && data.length) {
	                    for (var i = 0; i < data.length; i++) {
	                        if (data[i].result.toLowerCase() == q.toLowerCase()) {
	                            result = data[i];
	                            break;
	                        }
	                    }
	                }
	                if (typeof fn == "function") fn(result);
	                else $input.trigger("result", result && [result.data, result.value]);
	            }
	            $.each(trimWords($input.val()), function (i, value) {
	                request(value, findValueCallback, findValueCallback);
	            });
	        }).bind("flushCache", function () {
	            cache.flush();
	        }).bind("setOptions", function () {
	            $.extend(options, arguments[1]);
	            // if we've updated the data, repopulate
	            if ("data" in arguments[1])
	                cache.populate();
	        }).bind("unautocomplete", function () {
	            select.unbind();
	            $input.unbind();
	            $(input.form).unbind(".autocomplete");
	        });


	        function selectCurrent() {
	            var selected = select.selected();
	            if (!selected)
	                return false;

	            var v = selected.result;
	            previousValue = v;

	            if (options.multiple) {
	                var words = trimWords($input.val());
	                if (words.length > 1) {
	                    var seperator = options.multipleSeparator.length;
	                    var cursorAt = $(input).selection().start;
	                    var wordAt, progress = 0;
	                    $.each(words, function (i, word) {
	                        progress += word.length;
	                        if (cursorAt <= progress) {
	                            wordAt = i;
	                            return false;
	                        }
	                        progress += seperator;
	                    });
	                    words[wordAt] = v;
	                    // TODO this should set the cursor to the right position, but it gets overriden somewhere
	                    //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
	                    v = words.join(options.multipleSeparator);
	                }
	                v += options.multipleSeparator;
	            }

	            $input.val(v);
	            hideResultsNow();
	            $input.trigger("result", [selected.data, selected.value]);
	            return true;
	        }

	        function onChange(crap, skipPrevCheck) {
	            if (lastKeyPressCode == KEY.DEL) {
	                select.hide();
	                return;
	            }

	            var currentValue = $input.val();

	            if (!skipPrevCheck && currentValue == previousValue)
	                return;

	            previousValue = currentValue;

	            currentValue = lastWord(currentValue);
	            if (currentValue.length >= options.minChars) {
	                $input.addClass(options.loadingClass);
	                if (!options.matchCase)
	                    currentValue = currentValue.toLowerCase();
	                request(currentValue, receiveData, hideResultsNow);
	            } else {
	                stopLoading();
	                select.hide();
	            }
	        };

	        function trimWords(value) {
	            if (!value)
	                return [""];
	            if (!options.multiple)
	                return [$.trim(value)];
	            return $.map(value.split(options.multipleSeparator), function (word) {
	                return $.trim(value).length ? $.trim(word) : null;
	            });
	        }

	        function lastWord(value) {
	            if (!options.multiple)
	                return value;
	            var words = trimWords(value);
	            if (words.length == 1)
	                return words[0];
	            var cursorAt = $(input).selection().start;
	            if (cursorAt == value.length) {
	                words = trimWords(value)
	            } else {
	                words = trimWords(value.replace(value.substring(cursorAt), ""));
	            }
	            return words[words.length - 1];
	        }

	        // fills in the input box w/the first match (assumed to be the best match)
	        // q: the term entered
	        // sValue: the first matching result
	        function autoFill(q, sValue) {
	            // autofill in the complete box w/the first match as long as the user hasn't entered in more data
	            // if the last user key pressed was backspace, don't autofill
	            if (options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE) {
	                // fill in the value (keep the case the user has typed)
	                $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
	                // select the portion of the value not typed by the user (so the next character will erase)
	                $(input).selection(previousValue.length, previousValue.length + sValue.length);
	            }
	        };

	        function hideResults() {
	            clearTimeout(timeout);
	            timeout = setTimeout(hideResultsNow, 200);
	        };

	        function hideResultsNow() {
	            var wasVisible = select.visible();
	            select.hide();
	            clearTimeout(timeout);
	            stopLoading();
	            if (options.mustMatch) {
	                // call search and run callback
	                $input.search(
				function (result) {
				    // if no value found, clear the input box
				    if (!result) {
				        if (options.multiple) {
				            var words = trimWords($input.val()).slice(0, -1);
				            $input.val(words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : ""));
				        }
				        else {
				            $input.val("");
				            $input.trigger("result", null);
				        }
				    }
				}
			);
	            }
	        };

	        function receiveData(q, data) {
	            if (data && data.length && hasFocus) {
	                stopLoading();
	                select.display(data, q);
	                autoFill(q, data[0].value);
	                select.show();
	            } else {
	                hideResultsNow();
	            }
	        };

	        function request(term, success, failure) {
	            if (!options.matchCase)
	                term = term.toLowerCase();
	            var data = cache.load(term);
	            // recieve the cached data
	            if (data && data.length) {
	                success(term, data);
	                // if an AJAX url has been supplied, try loading the data now
	            } else if ((typeof options.url == "string") && (options.url.length > 0)) {

	                var extraParams = {
	                    timestamp: +new Date()
	                };
	                $.each(options.extraParams, function (key, param) {
	                    extraParams[key] = typeof param == "function" ? param() : param;
	                });

	                $.ajax({
	                    // try to leverage ajaxQueue plugin to abort previous requests
	                    mode: "abort",
	                    // limit abortion to this input
	                    port: "autocomplete" + input.name,
	                    dataType: options.dataType,
	                    url: options.url,
	                    data: $.extend({
	                        q: lastWord(term),
	                        limit: options.max
	                    }, extraParams),
	                    success: function (data) {
	                        var parsed = options.parse && options.parse(data) || parse(data);
	                        cache.add(term, parsed);
	                        success(term, parsed);
	                    }
	                });
	            } else {
	                // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
	                select.emptyList();
	                failure(term);
	            }
	        };

	        function parse(data) {
	            var parsed = [];
	            var rows = data.split("\n");
	            for (var i = 0; i < rows.length; i++) {
	                var row = $.trim(rows[i]);
	                if (row) {
	                    row = row.split("|");
	                    parsed[parsed.length] = {
	                        data: row,
	                        value: row[0],
	                        result: options.formatResult && options.formatResult(row, row[0]) || row[0]
	                    };
	                }
	            }
	            return parsed;
	        };

	        function stopLoading() {
	            $input.removeClass(options.loadingClass);
	        };

	    };

	    $.Autocompleter.defaults = {
	        inputClass: "ac_input",
	        resultsClass: "ac_results",
	        loadingClass: "ac_loading",
	        minChars: 1,
	        delay: 400,
	        matchCase: false,
	        matchSubset: true,
	        matchContains: false,
	        cacheLength: 10,
	        max: 100,
	        mustMatch: false,
	        extraParams: {},
	        selectFirst: true,
	        formatItem: function (row) { return row[0]; },
	        formatMatch: null,
	        autoFill: false,
	        width: 0,
	        multiple: false,
	        multipleSeparator: ", ",
	        highlight: function (value, term) {
	            return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
	        },
	        scroll: true,
	        scrollHeight: 180
	    };

	    $.Autocompleter.Cache = function (options) {

	        var data = {};
	        var length = 0;

	        function matchSubset(s, sub) {
	            if (!options.matchCase)
	                s = s.toLowerCase();
	            var i = s.indexOf(sub);
	            if (options.matchContains == "word") {
	                i = s.toLowerCase().search("\\b" + sub.toLowerCase());
	            }
	            if (i == -1) return false;
	            return i == 0 || options.matchContains;
	        };

	        function add(q, value) {
	            if (length > options.cacheLength) {
	                flush();
	            }
	            if (!data[q]) {
	                length++;
	            }
	            data[q] = value;
	        }

	        function populate() {
	            if (!options.data) return false;
	            // track the matches
	            var stMatchSets = {},
			nullData = 0;

	            // no url was specified, we need to adjust the cache length to make sure it fits the local data store
	            if (!options.url) options.cacheLength = 1;

	            // track all options for minChars = 0
	            stMatchSets[""] = [];

	            // loop through the array and create a lookup structure
	            for (var i = 0, ol = options.data.length; i < ol; i++) {
	                var rawValue = options.data[i];
	                // if rawValue is a string, make an array otherwise just reference the array
	                rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;

	                var value = options.formatMatch(rawValue, i + 1, options.data.length);
	                if (value === false)
	                    continue;

	                var firstChar = value.charAt(0).toLowerCase();
	                // if no lookup array for this character exists, look it up now
	                if (!stMatchSets[firstChar])
	                    stMatchSets[firstChar] = [];

	                // if the match is a string
	                var row = {
	                    value: value,
	                    data: rawValue,
	                    result: options.formatResult && options.formatResult(rawValue) || value
	                };

	                // push the current match into the set list
	                stMatchSets[firstChar].push(row);

	                // keep track of minChars zero items
	                if (nullData++ < options.max) {
	                    stMatchSets[""].push(row);
	                }
	            };

	            // add the data items to the cache
	            $.each(stMatchSets, function (i, value) {
	                // increase the cache size
	                options.cacheLength++;
	                // add to the cache
	                add(i, value);
	            });
	        }

	        // populate any existing data
	        setTimeout(populate, 25);

	        function flush() {
	            data = {};
	            length = 0;
	        }

	        return {
	            flush: flush,
	            add: add,
	            populate: populate,
	            load: function (q) {
	                if (!options.cacheLength || !length)
	                    return null;
	                /* 
	                * if dealing w/local data and matchContains than we must make sure
	                * to loop through all the data collections looking for matches
	                */
	                if (!options.url && options.matchContains) {
	                    // track all matches
	                    var csub = [];
	                    // loop through all the data grids for matches
	                    for (var k in data) {
	                        // don't search through the stMatchSets[""] (minChars: 0) cache
	                        // this prevents duplicates
	                        if (k.length > 0) {
	                            var c = data[k];
	                            $.each(c, function (i, x) {
	                                // if we've got a match, add it to the array
	                                if (matchSubset(x.value, q)) {
	                                    csub.push(x);
	                                }
	                            });
	                        }
	                    }
	                    return csub;
	                } else
	                // if the exact item exists, use it
	                    if (data[q]) {
	                        return data[q];
	                    } else
	                        if (options.matchSubset) {
	                            for (var i = q.length - 1; i >= options.minChars; i--) {
	                                var c = data[q.substr(0, i)];
	                                if (c) {
	                                    var csub = [];
	                                    $.each(c, function (i, x) {
	                                        if (matchSubset(x.value, q)) {
	                                            csub[csub.length] = x;
	                                        }
	                                    });
	                                    return csub;
	                                }
	                            }
	                        }
	                return null;
	            }
	        };
	    };

	    $.Autocompleter.Select = function (options, input, select, config) {
	        var CLASSES = {
	            ACTIVE: "ac_over"
	        };

	        var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;

	        // Create results
	        function init() {
	            if (!needsInit)
	                return;
	            element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);

	            list = $("<ul/>").appendTo(element).mouseover(function (event) {
	                if (target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	                    active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
	                    $(target(event)).addClass(CLASSES.ACTIVE);
	                }
	            }).click(function (event) {
	                $(target(event)).addClass(CLASSES.ACTIVE);
	                select();
	                // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
	                input.focus();
	                return false;
	            }).mousedown(function () {
	                config.mouseDownOnSelect = true;
	            }).mouseup(function () {
	                config.mouseDownOnSelect = false;
	            });

	            if (options.width > 0)
	                element.css("width", options.width);

	            needsInit = false;
	        }

	        function target(event) {
	            var element = event.target;
	            while (element && element.tagName != "LI")
	                element = element.parentNode;
	            // more fun with IE, sometimes event.target is empty, just ignore it then
	            if (!element)
	                return [];
	            return element;
	        }

	        function moveSelect(step) {
	            listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
	            movePosition(step);
	            var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
	            if (options.scroll) {
	                var offset = 0;
	                listItems.slice(0, active).each(function () {
	                    offset += this.offsetHeight;
	                });
	                if ((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
	                    list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
	                } else if (offset < list.scrollTop()) {
	                    list.scrollTop(offset);
	                }
	            }
	        };

	        function movePosition(step) {
	            active += step;
	            if (active < 0) {
	                active = listItems.size() - 1;
	            } else if (active >= listItems.size()) {
	                active = 0;
	            }
	        }

	        function limitNumberOfItems(available) {
	            return options.max && options.max < available
			? options.max
			: available;
	        }

	        function fillList() {
	            list.empty();
	            var max = limitNumberOfItems(data.length);
	            for (var i = 0; i < max; i++) {
	                if (!data[i])
	                    continue;
	                var formatted = options.formatItem(data[i].data, i + 1, max, data[i].value, term);
	                if (formatted === false)
	                    continue;
	                var li = $("<li/>").html(options.highlight(formatted, term)).addClass(i % 2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
	                $.data(li, "ac_data", data[i]);
	            }
	            listItems = list.find("li");
	            if (options.selectFirst) {
	                listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
	                active = 0;
	            }
	            // apply bgiframe if available
	            if ($.fn.bgiframe)
	                list.bgiframe();
	        }

	        return {
	            display: function (d, q) {
	                init();
	                data = d;
	                term = q;
	                fillList();
	            },
	            next: function () {
	                moveSelect(1);
	            },
	            prev: function () {
	                moveSelect(-1);
	            },
	            pageUp: function () {
	                if (active != 0 && active - 8 < 0) {
	                    moveSelect(-active);
	                } else {
	                    moveSelect(-8);
	                }
	            },
	            pageDown: function () {
	                if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
	                    moveSelect(listItems.size() - 1 - active);
	                } else {
	                    moveSelect(8);
	                }
	            },
	            hide: function () {
	                element && element.hide();
	                listItems && listItems.removeClass(CLASSES.ACTIVE);
	                active = -1;
	            },
	            visible: function () {
	                return element && element.is(":visible");
	            },
	            current: function () {
	                return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
	            },
	            show: function () {
	                var offset = $(input).offset();
	                element.css({
	                    width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
	                    top: offset.top + input.offsetHeight,
	                    left: offset.left
	                }).show();
	                if (options.scroll) {
	                    list.scrollTop(0);
	                    list.css({
	                        maxHeight: options.scrollHeight,
	                        overflow: 'auto'
	                    });

	                    if ($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
	                        var listHeight = 0;
	                        listItems.each(function () {
	                            listHeight += this.offsetHeight;
	                        });
	                        var scrollbarsVisible = listHeight > options.scrollHeight;
	                        list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight);
	                        if (!scrollbarsVisible) {
	                            // IE doesn't recalculate width when scrollbar disappears
	                            listItems.width(list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")));
	                        }
	                    }

	                }
	            },
	            selected: function () {
	                var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
	                return selected && selected.length && $.data(selected[0], "ac_data");
	            },
	            emptyList: function () {
	                list && list.empty();
	            },
	            unbind: function () {
	                element && element.remove();
	            }
	        };
	    };

	    $.fn.selection = function (start, end) {
	        if (start !== undefined) {
	            return this.each(function () {
	                if (this.createTextRange) {
	                    var selRange = this.createTextRange();
	                    if (end === undefined || start == end) {
	                        selRange.move("character", start);
	                        selRange.select();
	                    } else {
	                        selRange.collapse(true);
	                        selRange.moveStart("character", start);
	                        selRange.moveEnd("character", end);
	                        selRange.select();
	                    }
	                } else if (this.setSelectionRange) {
	                    this.setSelectionRange(start, end);
	                } else if (this.selectionStart) {
	                    this.selectionStart = start;
	                    this.selectionEnd = end;
	                }
	            });
	        }
	        var field = this[0];
	        if (field.createTextRange) {
	            var range = document.selection.createRange(),
			orig = field.value,
			teststring = "<->",
			textLength = range.text.length;
	            range.text = teststring;
	            var caretAt = field.value.indexOf(teststring);
	            field.value = orig;
	            this.selection(caretAt, caretAt + textLength);
	            return {
	                start: caretAt,
	                end: caretAt + textLength
	            }
	        } else if (field.selectionStart !== undefined) {
	            return {
	                start: field.selectionStart,
	                end: field.selectionEnd
	            }
	        }
	    };

	})(jQuery);
