/// <reference path="../../../vendor/jquery/jquery-1.3.2.custom/jquery.js"/>

BaseAutocompleter = function (acUrl, $displayValueInput)
{
    var self = this;
    this.inputClass = "ac_input";
    this.resultsClass = "ac_results";
    this.minChars = 2;
    this.loadingClass = "ac_loading";
    this.moreResultsClass = "ac_moreResults";
    this.maxResults = 20;
    this.firstResult = 0;

    this.lastSetShortEntry = $displayValueInput.val();
    this.prev = $displayValueInput.val();
    this.prevResultsLimited = true;

    this.onChange = function ()
    {
        this.updateTypedText(true);
    }

    this.updateTypedText = function (limitResults)
    {
        var v = $displayValueInput.val();
        this.searchTermChanged = v != this.prev;
        this.clearNumberOfFetchedResults();
        if (v == this.prev && limitResults == this.prevResultsLimited && !this.PageResults()) return;
        this.prev = v;
        this.prevResultsLimited = limitResults;

        if (v.length >= this.minChars)
        {
            this.requestData(v, limitResults);
        }
        else
        {
            if (v.length == 0)
            {
                this.clear();
            }
            $displayValueInput.removeClass(this.loadingClass);
            this.startLoading();
        }
    }

    this.PageResults = function ()
    {
        return false;
    }

    // this method will be overrided in descendants of the BaseAutoCompleter which keep track of the number of results fetched so far
    this.clearNumberOfFetchedResults = function ()
    {
        return;
    }

    this.clear = function ()
    {
    }

    this.startLoading = function ()
    {
        this.clearResults();
    }

    this.clearResults = function ()
    {
        this.results.innerHTML = "";
    }

    this.receiveData = function (q, data, useScrollbar)
    {
        $displayValueInput.removeClass(this.loadingClass);

        var dataValid = (data && data.Entries && data.Entries.length != 0);
        var $dataDom = dataValid ? $(this.resultSetToDom(data, useScrollbar)) : this.formatNoResults(q);

        this.preDataDisplayProcessing();

        this.showResults($dataDom, useScrollbar);
    };

    // normally we clear displayed data, and redisplay everything (since new data includes the existing)
    this.preDataDisplayProcessing = function ()
    {
        this.clearResults();
    }

    this.showMoreResults = function (limitResults)
    {
        this.focusInput();
        this.updateTypedText(limitResults);
    }

    this.focusInput = function ()
    {
        $displayValueInput[0].focus();
    }

    this.lastAjaxCallTime = null;
    this.lastAjaxCall = null;

    this.requestData = function (q, limitResults)
    {
        $displayValueInput.addClass(this.loadingClass);

        var myAjaxCallTime = new Date();
        this.lastAjaxCallTime = new Date();
        this.lastAjaxCallTime.setDate(myAjaxCallTime.getDate());
        this.lastAjaxCallTime.setMonth(myAjaxCallTime.getMonth());
        this.lastAjaxCallTime.setFullYear(myAjaxCallTime.getFullYear());
        this.lastAjaxCallTime.setTime(myAjaxCallTime.getTime());

        var url = this.getUrl(q, limitResults);

        // Abort autocomplete requests whose results we no longer care about
        if (this.lastAjaxCall != null)
            this.lastAjaxCall.abort();

        this.lastAjaxCall = $.getJSON(url, function (data)
        {
            self.lastAjaxCall = null;
            if (self.lastAjaxCallTime.valueOf() == myAjaxCallTime.valueOf())
            {
                var scroll = self.shouldUseScroll(limitResults);
                self.receiveData(q, data, scroll);
            }
            else
            {
                // if there's been no data found, remove the loading class
                $displayValueInput.removeClass(this.loadingClass);
            }
        });
    };

    this.shouldUseScroll = function (limitResults)
    {
        return !limitResults;
    }

    this.getUrl = function (searchString, limitResults)
    {
        return getUrl(acUrl, searchString, limitResults, this.firstResult, this.maxResults);
    }

    this.resultSetToDom = function (resultSet, useScrollbar)
    {
        var entries = resultSet.Entries;

        var $div = this.autoCompleteEntriesToDom(entries);

        if (useScrollbar)
        {
            this.addScrollbarTo($div);
        }

        this.addSeeMoreLinkIfApplicable(resultSet, $div);

        return $div;
    }

    this.addSeeMoreLinkIfApplicable = function (resultSet, $container)
    {
        var entries = resultSet.Entries;
        if (resultSet.TotalResults > entries.length)
        {
            var numAdditionalResults = resultSet.TotalResults - entries.length;
            var $li = this.getSeeMoreLink(numAdditionalResults);
			$li.addClass("moreResultsParent");
            $("ul", $container).append($li);
        }
    }

	this.addBehaviors = function(resultsDom)
	{
		this.addSeeMoreBehaviors(resultsDom);
	}

	this.addSeeMoreBehaviors = function(resultsDom)
	{
		$(".moreResultsParent", resultsDom).clickable({
			doClick: function () { self.showMoreResults(self.PageResults()) },
			addLoader: true,
			loaderAddCallback: function (clickable, loader) {
				var $linkNode = $("a", resultsDom);
				$linkNode.after(loader);
			}
		});
	}

    this.autoCompleteEntriesToDom = function (entries)
    {
        var $div = $("<div/>");
        var $ul = $("<ul/>");
        var lastListItem = null;

        $div.append($ul);

        for (var i = 0; i < entries.length; i++)
        {
            var row = entries[i];
            if (!row) continue;

            var item = this.autoCompleteEntryToDom($ul, row, lastListItem);
            lastListItem = item;
        }

        return $div;
    };

    this.addScrollbarTo = function ($div)
    {
        $div.addClass("ac_scrollable");
    }

    this.formatNoResults = function (query)
    {
        return $("<div>No matching results for '" + query + "'</div>");
    }

    this.getSeeMoreLink = function (numAdditionalResults)
    {
        var $li = $("<li/>");

        var $linkNode = $("<a/>");
        $linkNode.text("(Click to see " + numAdditionalResults + " more results)");
        $li.append($linkNode);
        $li.addClass(this.moreResultsClass);
        return $li;
    }

    this.finalize = function ()
    {
        this.makeResultArea();
        $displayValueInput.keydown(this.keyDown);
    }
}

Autocompleter = function(container, acUrl, $input, $displayValueInput)
{
	var self = this;
	BaseAutocompleter.call(this, acUrl, $displayValueInput);
	
	this.onChange = function()
	{
		$input.val(""); //if we've started typing something new then blank the input value
		this.updateTypedText(true);
	}
	
	container.validate = function()
	{
		var displayValue = $displayValueInput.val();
		
		if (displayValue.length > 0 && $input.val().length > 0)
			return ValidationResult.valid;
		else if (displayValue == null || displayValue == "")
			return ValidationResult.empty;
		else
			return ValidationResult.invalid;
	}
	
	$input[0].isValid = function()
	{
		var result = container.validate();
		return result == ValidationResult.valid || result == ValidationResult.empty;
	}
	
	this.checkValid = function()
	{
		if (this.lastAjaxCallTime && ($displayValueInput.val() != lastSetShortEntry) && ($displayValueInput.val().length >= this.minChars))
		{
			$input.val("");
			lastSetShortEntry = "";
			$displayValueInput.addClass("ac_invalid");
			$displayValueInput.attr("title", "You must choose a matched value");
		}
		else
		{
			$displayValueInput.removeClass("ac_invalid");
		}
	}
	
	this.focusInput = function()
	{
		$displayValueInput[0].focus();
		$displayValueInput[0].CancelHide();
	}
	
	this.startLoading = function()
	{
		this.clearResults();
		$displayValueInput[0].HideDropDown();
	}
	
	this.clear = function()
	{
		this.setValue("", "", "");
	}

	this.setValue = function(value, shortEntry, longEntry)
	{
		$input.val((value == null) ? "" : value);
		$displayValueInput.val((shortEntry == null) ? "" : shortEntry);
		this.prev = $displayValueInput.val();
		lastSetShortEntry = shortEntry;
		$input.change();
	}	

	this.getValue = function()
	{
		return $input.val();
	}

	this.makeResultArea = function()
	{
		this.results = document.createElement("div");

		$displayValueInput.makeDropDown(this.results, {
			contentsClass: "acOptions",
			bindevent: "focus",
			hideDropDownCallback: this.checkValid
		});
	    
		// Apply inputClass if necessary
		$displayValueInput.addClass(this.inputClass)
			.focus(function() {
				// Hack because in a special case (when the page first load) we may want an autocomplete box to have
				// focus, but we don't want the dropdown to show
				if (!firstFocus)
				{
					$displayValueInput[0].ShowDropDown();
					firstFocus = false;
				}
			});

		$(this.results).addClass(this.resultsClass);
	}

	this.autoCompleteEntryToDom = function($list, row, lastListItem)
	{
		var $li = $("<li/>");
		var li = $li[0];
		$li.text(row.LongEntry);
		$list.append($li);
		
		if (row.Children && row.Children.length>0)
		{
			$li.addClass("heading");
			$li.append(this.autoCompleteEntriesToDom(row.Children));
		}
		else
		{
			if (lastListItem)
			{
				lastListItem.nextListItem = li;
				li.prevListItem = lastListItem;
			}
			$li.attr("shortEntry", row.ShortEntry)
				.attr("longEntry", row.LongEntry)
				.attr("filterValue", row.Value);
			
			if (row.IconCssClass)
			{
				var $span = $("<span/>");
				$li.append($span)
				$span.addClass(row.IconCssClass)
			}
		}
		
		return $li[0];
	}

	this.addBehaviors = function(resultsDom)
	{
		this.addSeeMoreBehaviors(resultsDom);
		$("li", resultsDom).each(function () {
			$(this).hover(
				function() { $("li", this.results).removeClass("ac_over"); $(this).addClass("ac_over");  },
				function() { if ($(".ac_over", this.results) >= 2) $(this).removeClass("ac_over"); }
			)
			.click(function(e)
			{
				var clicked = $(this);
				self.setSearchValue(e, clicked);
				e.preventDefault();
				e.stopPropagation();
			});
		});
	}
	
	this.showResults = function(resultsDom, useScrollbar)
	{
		if ($.browser.msie)
		{
			// Set the width of all the list items to the width of the list itself, plus the width
			// of a scrollbar. In Firefox, this all just works with CSS, but in IE, CSS bugs make
			// them either too narrow (just covering the text, leaving a blank area to the right),
			// or one scrollbar-width too wide (causing either text truncation, or a horizontal
			// scrollbar).
			//
			// To get this to work in IE, we have to first display the results in an offscreen (but
			// not hidden) div, and measure the width there. That div can't be in the same part of
			// the DOM as the results actually end up in, because then it would inherit an incorrect
			// width from the parent.
			var $measurementDiv = $('<div class="ac_results"></div>');
			$("#pagebox").append($measurementDiv);
			$measurementDiv.append(resultsDom);

			$measurementDiv.css({ position: "absolute", x: "-2000px", y: "-2000px", display: "block" });
			var liWidth = resultsDom.width();

			$measurementDiv.remove();

			$displayValueInput[0].ShowDropDown();
			$(this.results).append(resultsDom);
			$(this.results).show();

			if (useScrollbar)
			{
				// Using 20px as an approximation of the width of a scrollbar (it's actually
				// narrower), since getting the real scrollbar width is complex and ugly, and a
				// little extra margin on the right won't hurt anything.
				liWidth = liWidth + 20;
			}
			$(this.results).width(liWidth);
			$("li", resultsDom).width(liWidth);
			
			// IE6 doesn't support the max-height property, so set the height of the box directly.
			if (useScrollbar && parseInt(jQuery.browser.version) == 6)
			{
				resultsDom.height(400);
			}
			
			// we put a styled iframe behind the autocomplete results so HTML SELECT elements don't show through 
			var $iframe = $(document.createElement("iframe"));
			$iframe.attr("frameborder", 0).css({height:resultsDom.height(), width: resultsDom.width()});
			resultsDom.before($iframe);
		}
		else
		{
			$displayValueInput[0].ShowDropDown();
			$(this.results).append(resultsDom);
			$(this.results).show();
		}

		this.addBehaviors(resultsDom);
		
		if (!$(".ac_over", this.results).size())
		{
			$("li", this.results).not(".heading").slice(0,1).addClass("ac_over");
		}
	}

	this.setSearchValue = function(event, selectedItem)
	{
		if (selectedItem.length)
		{
			this.setValue(selectedItem.attr("filterValue"), selectedItem.attr("shortEntry"), selectedItem.attr("longEntry"));
			// user has selected a valid value for the filter, remove error message in title
			$displayValueInput.removeAttr("title");

			// If the filter this is in was highlighted as invalid (ie, because the user clicked 'Search'
			// while this autocompleter had an invalid value in it), clear that highlight.
			var $filter = $(container).closest(".filter").each(function () {
				this.clearInvalid();
			});
		}
		$displayValueInput[0].HideDropDown();
		$(this.results).hide();
		return false;
	}
			
	this.keyDown = function(e)
	{
		var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
		switch (key) {
			// tab
			case 9:
			// enter
			case 13:
				if ( $displayValueInput[0].IsDropDownVisible())
				{
					var selected = $(".ac_over", self.results);
					self.setSearchValue(e, selected);
					e.preventDefault();
					e.stopPropagation();
				}
				else
				{
					// let the event bubble
				}
				break;
			// escape
			case 27:
				$displayValueInput[0].HideDropDown();
				break;
			// up arrow
			case 38:
				var current = $(".ac_over", this.results);
				if (current[0].prevListItem) {
					current.add(current[0].prevListItem).toggleClass("ac_over");					
				}
				e.preventDefault();
				e.stopPropagation();
				break;
			// down arrow	
			case 40:
				var current = $(".ac_over", this.results);
				if (current[0].nextListItem) {
					current.add(current[0].nextListItem).toggleClass("ac_over");
				}
				e.preventDefault();
				e.stopPropagation();
				break;
			default:
				// let the input do its thing
				setTimeout(function ()
				{
					self.onChange();
				}, 1);
				break;
		}
	};
		
	this.finalize();

	$input.hide();
	$displayValueInput.show();
	if ($.isFunction($input[0].clear))
	{	
		var oldClear =  $input[0].clear;
		$input[0].clear = function() { oldClear(); self.clear(); };
	}
}

function getUrl (acUrl, searchString, limitResults, firstResult, maxResults)
{
    searchString = searchString.toLowerCase();
    // {q} after url encoding = %7bq%7d
    var url = encodeURI(acUrl.replace("%7bq%7d", searchString));
    url = url + "&firstResult=" + firstResult;
    if (limitResults)
        url = url + "&maxResults=" + maxResults;
    return url;
}

