/**
 * Controls the behaviour of the search drop-down list when typing into the
 * URL address field.
 *
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * Constructor.
 * @param {PageAssistant} pageAssistant
 */
function UrlSearchController(controller){

	try {
		this.urlSearchListModel = $A();
		this.urlToBookmark = $H();
		this.controller = controller;
		this._urlSeenRecord = $H();
		this._maxSearchResults = 32;		
		this.searchListVisible = true;

		// Out sources of data for the list.
		this.historyStore = new HistoryStore({
			database: Mojo.Controller.appController.assistant.getDatabase()
		});
		this.bookmarkStore = new BookmarkStore({
			database: Mojo.Controller.appController.assistant.getDatabase()
		});

		this.controller.setupWidget('urlSearchScroller', {
			mode: 'vertical'
		});
		
		this._urlSearchScrollerWidget = this.controller.get('urlSearchScroller'); 

		this.controller.setupWidget('urlSearchList', {
			itemTemplate: 'urlsearch/urlsearch-entry',
			listTemplate: 'urlsearch/urlsearch-container',
			itemsCallback: this._getItemsCallback.bind(this),
			renderLimit: 16,
			lookahead: 8
		});

		this._urlSearchListWidget = this.controller.get('urlSearchList');
		this.controller.listen('urlSearchList', Mojo.Event.tap, this._handleSelection.bindAsEventListener(this));
		this._urlSearch = this.controller.get('urlSearch');

		this.matchTemplate = Mojo.View.render({
			object: {
				match: 'ZZZZ'
			},
			template: '../views/urlsearch/matched'
		});
	} catch (e) {
		Mojo.Log.logException(e, "UrlSearchController()");
	}
}

UrlSearchController.SearchProviders = [
	{
		id: 'wikipedia',
		title: $L('Wikipedia'),
		weburl: $L('http://en.m.wikipedia.org/wiki/Special:Search?search=#{query}')
	}];


UrlSearchController.prototype.setup = function(params) {

	this._onEnableSceneScroller = params.onEnableSceneScroller || function(){};
	this._onSelect = params.onSelect || function(){};
	this._setupSearchProvider();
};

UrlSearchController.prototype.cleanup = function() {
	
	this._serviceListRequest.cancel();
	this._serviceDefaultRequest.cancel();
};

/**
 * Show the search list of not already visible.
 */
UrlSearchController.prototype.showSearchList = function() {

	if (this.searchListVisible) {
		return;
	}

	this.searchListVisible = true;
	this._onEnableSceneScroller(false);
};

/**
 * Hide the search list. Can be called multiple times.
 */
UrlSearchController.prototype.hideSearchList = function() {

	if (!this.searchListVisible) {
		return;
	}

	this._urlSearch.hide();
	this.searchListVisible = false;
	this._onEnableSceneScroller(true);
};

UrlSearchController.prototype.isSearchListVisible = function() {

	return this._urlSearch.style.display !== 'none';
};

/**
 * Substitute matching text with the templatetized version of that
 * matched text.
 *
 * @param {String} text Original text to match against. Cannot be HTML markup.
 * @param {String} ucMatchText The text to match (all uppercase)
 * @param {Object} matchTemplate
 *
 * @return {String} The input "text" string transformed if any portions of it match ucMatchText.
 *                  The return string is HTML markup and no longer plain text.
 */
UrlSearchController.prototype._subMatchString = function(text, ucMatchText, matchTemplate) {
	var idx = text.toLocaleUpperCase().indexOf(ucMatchText);
	if (idx != -1) {
		var matchedText = text.substr(idx, ucMatchText.length);
		var matchHLText = matchTemplate.replace('ZZZZ', matchedText);
		return text.replace(matchedText, matchHLText);
	}
	else {
		return text;
	}
};

/**
 * Add all matching items to the search list model.
 *
 * @param {Array} items        The list of UrlReference instances (Bookmark or History) to match against
 * @param {String} iconclass   The icon class to use for matching items.
 */
UrlSearchController.prototype._addToSearchListModel = function(items, iconclass, rowclass) {

	var ucMatchText = this.filterText.toLocaleUpperCase();
	var title, subTitle;
	var seenKey;

	for (var i = 0; i < items.length; i++) {
		var item = items[i];
		if (item instanceof UrlReference) {

			var alreadyInList = false;
			if (item.title) {
				title = item.title;
				subTitle = UrlUtil.cleanup(item.url);
				subTitle = this._subMatchString(subTitle.escapeHTML(), ucMatchText, this.matchTemplate);
				
				seenKey = subTitle;
			} else {
				title = UrlUtil.cleanup(item.url);
				subTitle = '&nbsp;';
				
				seenKey = title;
			}

			title = this._subMatchString(title.escapeHTML(), ucMatchText, this.matchTemplate);
			if (!this._urlSeenRecord.get(seenKey)) {
				this._urlSeenRecord.set(seenKey, 1);

				var modelItem = Mojo.Model.decorate(item);
				modelItem.title = title;
				modelItem.subTitle = subTitle;

				modelItem.iconclass = iconclass;
				modelItem.rowclass = rowclass;
				if (modelItem.iconFile32) {
					modelItem.iconUrl = 'file://' + modelItem.iconFile32;
				}
				else {
					if (iconclass === 'history-image') {
						modelItem.iconUrl = Mojo.appPath + 'images/list-icon-history.png';
					}
					else {
						modelItem.iconUrl = Mojo.appPath + 'images/icon32-missing.png';
					}
				}
				this.urlSearchListModel.push(modelItem);
			}
		}
	}
};

/**
 * Add the customizable searches to the list.
 * @param {String} searchText
 */
UrlSearchController.prototype._addSearchProviders = function(searchText, withDivider) {

	var escapedSearchText = searchText.escapeHTML();
	var encodedUriComponent = encodeURIComponent(searchText);
	
	// Enumerate through the list of chosen providers, adding them to the search dropdown.
	var subModels = [];
	this._chosenProviders.each(function(provider) {
		
		var url   = provider.weburl.interpolate({query: encodedUriComponent});
		var icon  = Mojo.appPath + 'images/list-icon-#{id}.png'.interpolate({id: provider.id});
		var title = $L('#{title} "#{search}"').interpolate({title: provider.title,
								    search: escapedSearchText});
		var item = new UrlReference(url, title);
		
		item.subTitle = $L('Search #{title}').interpolate({title: provider.title});
		item.iconUrl = icon;
		item.iconclass = 'search-image';
		item.rowclass = 'search';
		subModels.push(item);
	});
	
	var length = subModels.length;
	if (length !== 0) {
		if (length === 1) {
			subModels[0].rowclass = withDivider ? 'search first last' : 'search single'; 
		} else {
			subModels[length - 1].rowclass = withDivider ? 'search last' : 'search last only';
			subModels[0].rowclass = 'search first';
		}
	}
	
	// Update the search results with the prefix search providers options.
	this.urlSearchListModel = subModels.concat(this.urlSearchListModel);
};

UrlSearchController.prototype._clearList = function() {

	this._urlSeenRecord = $H();
	this.urlToBookmark = $H();
	this.urlSearchListModel = $A();
};

/**
 * Start filling the search list (which is an asynchronous operation).
 *
 * @param {String} filterText
 */
UrlSearchController.prototype.startFillingList = function(filterText) {

	this.filterText = null;
	if (filterText && filterText.length > 0) {
		this.filterText = filterText;
	}
	
	// Clear any old search list entries and add the
	// search options if the filter looks to be a URL.
	this._clearList();
	
	// Start with bookmarks first because they take higher precedence over history
	this.bookmarkStore.readBookmarks(new BookmarkFolder(), 
		this.filterText, 
		this._onBookmarksReadSuccess.bind(this), 
		this._onBookmarksReadFailure.bind(this), 
		0, 
		this._maxSearchResults);
};

UrlSearchController.prototype._setupSearchProvider = function() {

	this._chosenProviders = [];
	this._searchProviders = [];

	var searchProviderDefault = function(status, response) {
		
		var providers = [];
		if (status && response && response.defaultWebSearch) {
			providers = response.defaultWebSearch;
		} 

		this._chosenProviders = [];
		this._searchProviders.each(function(provider) {

			if (providers.indexOf(provider.id) != -1) {
				this._chosenProviders.push(provider);
			}

		}.bind(this));

		// If after all this we don't have a provider then choose the default 
		if (this._chosenProviders.length === 0) {
			this._chosenProviders.push(PreferencesAssistant.DefaultSearchProvider);
		}

		// Now add the search controllers default providers (eg wikipedia)
		this._chosenProviders = this._chosenProviders.concat(UrlSearchController.SearchProviders);
	};

	var searchProviderList = function(status, response) {
		
		// We refresh the current set of choices so clear the old values.
		this._searchProviders.splice(0);

		if (status && response && response.webSearchList && response.webSearchList[Mojo.Locale.current]) {
			this._searchProviders = response.webSearchList[Mojo.Locale.current];
		}
	};

	// Query the system service for the current set of search options.
	this._serviceListRequest = this.controller.serviceRequest("palm://com.palm.systemservice", {
		method: 'getPreferences',
		parameters: {
			'subscribe': true,
			'keys': ['webSearchList']
		},
		onSuccess: searchProviderList.bind(this, true),
		onFailure: searchProviderList.bind(this, false)
	});


	// Query the system service for the current default search options.
	this._serviceDefaultRequest = this.controller.serviceRequest("palm://com.palm.systemservice", {
		method: 'getPreferences',
		parameters: {
			'subscribe': true,
			'keys': ['defaultWebSearch']
		},
		onSuccess: searchProviderDefault.bind(this, true),
		onFailure: searchProviderDefault.bind(this, false)
	});
};

/**
 * Called
 * @param {BookmarkFolder} bookmarksFolder
 */
UrlSearchController.prototype._onBookmarksReadSuccess = function(bookmarksFolder) {

	bookmarksFolder.contents.each(function(item) {
		if (item instanceof UrlReference) {
			this.urlToBookmark.set(item.url, item);
		}
	}.bind(this));

	this._addToSearchListModel(bookmarksFolder.contents, 'bookmark-image', 'urlsearch-bookmark');
	this._startFillingHistory();
};

UrlSearchController.prototype._onBookmarksReadFailure = function() {

	Mojo.Log.error("Error reading bookmarks.");
	this._startFillingHistory();
};

UrlSearchController.prototype._startFillingHistory = function() {

	var count = this._maxSearchResults - this.urlSearchListModel.length;

	// Only attempt to load some history if we have not
	// met the URL search quota
	if (count > 0) {
		this.historyStore.getHistory(new HistoryList(), 
			this.filterText, 
			this._onHistoryReadSuccess.bind(this), 
			this._onHistoryReadFailure.bind(this), 
			0, 
			count);
	} else {
		this._fillListComplete();
	}
};

UrlSearchController.prototype._onHistoryReadSuccess = function(historyList, offset, limit) {

	for (var d = 0; d < historyList.days.length; d++) {
		var day = historyList.days[d];
		this._addToSearchListModel(day.items, 'history-image', 'urlsearch-history');
	}

	this._fillListComplete();
};

UrlSearchController.prototype._onHistoryReadFailure = function() {

	Mojo.Log.error("Failed to read history");
	this._fillListComplete();
};

/**
 * Called when the list filling operation is finished (good or bad).
 */
UrlSearchController.prototype._fillListComplete = function() {

	var listWidget = this.controller.get('urlSearchList');

	if (!UrlUtil.isWellFormedUrl(this.filterText)) {
		if (this.urlSearchListModel.length > 0) {
			this._addSearchProviders(this.filterText, true);
		} else {
			this._addSearchProviders(this.filterText, false);
		}
	} else {
		if (this.urlSearchListModel.length > 0) {
			firstItem = this.urlSearchListModel[0];
			firstItem.rowclass = 'url first';
			if (this.urlSearchListModel.length == 1) {
				firstItem.rowclass = 'url last';
			}
		}
	}

	// Update the list with the new model and set the new length.
	// Don't show the DIV if we have no entries.
	// On an update move the scroller to 0,0 (fixed style issue?)
	this._urlSearchScrollerWidget.mojo.setScrollPosition({x: 0, y:0});
	listWidget.mojo.setLengthAndInvalidate(this.urlSearchListModel.length);
	listWidget.mojo.revealItem(0, false);
	if ((this.urlSearchListModel.length > 0) && this.searchListVisible) {
		this._urlSearch.show();
	}else {
		// Hide the list.
		this._urlSearch.hide();
	}
};

/**
 * Called when a list selection is made.
 * @param {Object} event
 */
UrlSearchController.prototype._handleSelection = function(event) {
	var targetRow = this.controller.get(event.target);
	while (targetRow !== undefined && !targetRow.hasAttribute('x-app-url')) {
		targetRow = targetRow.up();
	}
	if (targetRow === undefined) {
		Mojo.Log.warn("Can't find row attribute");
	}
	else {
	    var url = targetRow.readAttribute('x-app-url');
		var bookmark = this.urlToBookmark.get(url);	// Will be undefined if item not a bookmark
		if (bookmark !== undefined) {
			this._onSelect(bookmark);
		}
		else {
			this._onSelect(url);
		}
	}
};

UrlSearchController.prototype._formatDefaultSearchUrl = function(query) {
	
	var searchurl = query;
	if (this._chosenProviders[0]) {
		searchurl = this._chosenProviders[0].weburl.interpolate({query: encodeURIComponent(query)});
	}

	return searchurl;
};

/**
 * Get the URL to use for the given URL for the default action (like hitting return key).
 */
UrlSearchController.prototype.getDefaultUrl = function(url) {
	if (UrlUtil.appearsToBeUrl(url)) {
		return url;
	}
	else {
		return this._formatDefaultSearchUrl(url);
	}
};

UrlSearchController.prototype._getItemsCallback = function(listWidget, offset, count) {

	// Use the model to update the list but lazily.
	var model = this.urlSearchListModel.slice(offset, offset+count);
	listWidget.mojo.noticeUpdatedItems(offset, model);
};
