/**
 * Vodafone Flickr Demo Widget
 * 
 * @version 0.0.1
 * @copyright (c) 2009 Vodafone Labs
 * @since 0.0.1
 * @namespace Flickr
 */
var Flickr = function() {
	
	// Private members
	// ===============
	
	// This is the host the widgets connects to
	// Make sure this host is enabled in the security/access section of the config.xml
	var _host = 'http://m.flickr.com/';
	
	// These are the different resources/URLs the widget calls on the specified host
	var _url = {
		api: _host + 'services/rest/?',
		auth: _host + 'services/auth/?',
		feed: _host + 'services/feeds/'
	};
	
	// Here we store our Flickr API key information
	var _auth = {
		apiKey : '__YOUR_API_KEY__',
		frob: null,
		secret : '__YOUR_API_KEY_SECRET__',
		token: (widget.preferenceForKey('_auth.token') != null ? widget.preferenceForKey('_auth.token') : null)
	};
	
	// To keep the server calls to a minimum, we store information inside the widget.
	// Therefore we create an object that functions as data store and we store all data/information
	// we do not want to load from the server every time into this object.
	var _dataStore = {
		publicPhotos: { },
		search: { },
		user: { }
	};
	
	// Here we store some pseudo language constants
	var _lang = {
		AUTH_NOT_COMPLETED: 'Authentifizierung could not be completed!',
		AUTH_NOT_INIT: 'Authentifizierung could not be initialized!',
		AUTH_START: 'Start Authorization...',
		SEARCH_ERROR: 'Your search could not be performed. Please try again!',
		SEARCH_NO_TERMS_ENTERED: 'Please enter some search criteria'
	};
	
	var _photosOffset = 0;
	var _photosToDisplay = 0;
	
	
	// Private methods
	// ===============
	
	/**
	 * Fire custom events
	 * 
	 * _fireEvent is a little helper method that allows us to fire custom events.
	 * For more on events see: http://www.w3.org/TR/DOM-Level-2-Events/events.html
	 * 
	 * @param string eventType The type of the event, e.g. onMyEvent
	 * @param object DOM_element The DOM element that fires the event
	 * @param object params [optional] Optional parameters to attach to the event that can be accessed within the event listener
	 */
	var _fireEvent = function(eventType, DOM_element, params) {
		var evt = document.createEvent("UIEvents");
		
		// First parameter is our event type
		// Second parameter states the the event should bubble up
		// Third parameter states that the event is cancelable
		evt.initEvent(eventType, true, true);
		
		// If additional parameters were provided, add them to the event
		if (params && (typeof params == 'object')) {
			evt.params = params;
		}
		DOM_element.dispatchEvent(evt);
	};
	
	
	/**
	 * Maximize the widget
	 * 
	 * _maxWidgetSize resizes the widget to the maximum available
	 * screen width and screen height
	 */
	var _maxWidgetSize = function() {
		// Get the actual screen width and screen height
		var screenHeight = window.screen.height;
		var screenWidth = window.screen.width;
		
		// Resize the widget
		window.resizeTo(screenWidth, screenHeight);
	};
	
	
	/**
	 * Display a spinning wheel
	 * 
	 * _loadIndicator returns a short XHTML text that displays a spinning wheel to
	 * indicate to the user that something is happening in the background that
	 * is not visible to him/her.
	 * 
	 * @return string The XHTML text generated to display the spinning wheel
	 */
	var _loadIndicator = function() {
		return '<div class="loadIndicator"><img src="css/icons/ajaxLoading.gif" alt="" /></div>';
	};
	
	
	/**
	 * Check for an widget authorization token
	 * 
	 * Before we can access the user's data on flickr.com, the user needs to authorize
	 * our widget to allow access to his/her data. After the authorization was successful,
	 * we store the token that is needed to make authorized API calls. This method just
	 * checks if that token is already available.
	 * 
	 * @return boolean true: The token needed to make authorized calls to the flickr API is available
	 * 				   false: The token needed to make authorized calls to the flickr API is NOT avilable
	 */
	var _isAuthTokenAvailable = function() {
		// Check for authorization token
		if (_auth.token != '') {
			// Authorization token is available
			return true;
		}
		
		// Authorization token is not available
		return false;
	};
	
	
	/**
	 * Before we can access the user's data on flickr.com, the user needs to authorize
	 * our widget to allow access to his/her data. This method enables a dialog to do
	 * exactly that.
	 */
	var _prepareWidgetAuthorization = function(){
		var DOM_authorizeWidget = document.getElementById('view_authorizeWidget');
		DOM_authorizeWidget.setAttribute('class', 'view');
		
		// Get the frob that is needed to identify the login session (s. _getFrob());
		_getFrob();
	};
	
	
	/**
	 * Get Frob from flickr.com
	 * 
	 * Before we can start the actual authorization process, we need "to request a frob to identify the login
	 * session" (s. http://www.flickr.com/services/api/auth.howto.desktop.html). This method creates an
	 * XMLHttpRequest to request that frob. After the frob was received, we store it in _auth.frob and fire
	 * an event (onFrobReceived) to signal, that the frob was successfully received and stored.
	 * 
	 * For more details on the authorization process s. http://www.flickr.com/services/api/auth.howto.desktop.html
	 */
	var _getFrob = function() {
		// Create API call signature (s. http://www.flickr.com/services/api/auth.spec.html section 8: Signing)
		var api_sig = MD5(_auth.secret + 'api_key' + _auth.apiKey + 'methodflickr.auth.getFrob');
		// Construct URL
		var url = _url.api + 'method=flickr.auth.getFrob&api_key=' + _auth.apiKey + '&api_sig=' + api_sig;
		
		// Create and send new XHR
		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function(){
			if (this.readyState == 4) {
				if (this.status == 200) {
					// XHR was successful
					try {
						// Store the received frob
						_auth.frob = this.responseXML.getElementsByTagName('frob')[0].firstChild.nodeValue;
						// Fire event to signal that the frob was successfully received and stored
						_fireEvent('onFrobReceived', document);
					} catch (e) {
						// The frob was not returned by the XHR
						DOM_view_authorizeWidget_status = document.getElementById('view_authorizeWidget_status');
						DOM_view_authorizeWidget_status.innerHTML = _lang.AUTH_NOT_INIT;
					}
				}
			}
		};
		xhr.open('GET', url, true);
		xhr.send();
	};
	
	
	/**
	 * 
	 */
	var _enableWidgetAuthorization = function(){
		var DOM_btn_startWidgetAuthorization = document.getElementById('btn_startWidgetAuthorization');
		
		// Enable the button so that the user may start the actual authorization process
		DOM_btn_startWidgetAuthorization.innerHTML = _lang.AUTH_START;
		DOM_btn_startWidgetAuthorization.removeAttribute('disabled');
	};
	
	
	/**
	 * Before we can access the user's data on flickr.com, the user needs to authorize
	 * our widget to allow access to his/her data. Therefore, we need to redirect the user to a website
	 * on which he/she grants our widget the right to access his/her data.
	 * 
	 * This method creates the necessary signature for the required API call, then constructs the URL
	 * the user has to visit. To prevent the user from starting the authorization process before we
	 * received the frob from flickr (s. _getFrob()), the button the user has to click to start the
	 * authorization process is disabled by default. So this method enables this particular button and,
	 * after the user clicks the button to start the authorization process, redirects him/her to the
	 * URL generated within this method (the event handler for this click is defined in _addEventListeners).
	 * 
	 * For more details on the authorization process s. http://www.flickr.com/services/api/auth.howto.desktop.html
	 */
	var _processWidgetAuthorization = function() {
		var DOM_btn_startWidgetAuthorization = document.getElementById('btn_startWidgetAuthorization');
		// Hide this button
		DOM_btn_startWidgetAuthorization.setAttribute('class', 'view hide');
			
		// Show the button to complete the authorization process
		// The user has to click this button once he/she granted access to his/ger flickr
		// data to this widget
		DOM_btn_completeAuthorization = document.getElementById('btn_completeWidgetAuthorization');
		DOM_btn_completeAuthorization.setAttribute('class', '');
		
		// Create API call signature
		var api_sig = MD5(_auth.secret + 'api_key' + _auth.apiKey + 'frob' + _auth.frob + 'permsread');
		// Create URL the user has to visit to grant access
		var url = _url.auth + 'api_key=' + _auth.apiKey + '&perms=read&frob=' + _auth.frob + '&api_sig=' + api_sig;
		
		widget.openURL(url);
	};
	
	
	/**
	 * Complete the authorization process
	 * 
	 * After the user has authorized our widget to access his/her data on flickr.com, we need to convert
	 * our frob into a token to make authenticated calls to the flickr API.
	 * 
	 * This method first creates a signature for the required call to the flickr API. It then constructs
	 * the URL to call and sends an XHR to this URL. If everything works out okay, a token is returned from
	 * the flickr server which we will store into _auth.token to make authenticated calls to the flickr API.
	 * 
	 * For more details on the authorization process s. http://www.flickr.com/services/api/auth.howto.desktop.html
	 */
	var _completeWidgetAuthorization = function() {
		// We want to make sure that the user does not hit the button to complete the authorization
		// more than once. Therefore, we disable it.
		DOM_btn_completeAuthorization = document.getElementById('btn_completeWidgetAuthorization');
		DOM_btn_completeAuthorization.setAttribute('disabled', 'disabled');
		
		// Create API call signature
		var api_sig = MD5(_auth.secret + 'api_key' + _auth.apiKey + 'frob' + _auth.frob + 'methodflickr.auth.getToken');
		// Create URL
		var url = _url.api + 'method=flickr.auth.getToken&api_key=' + _auth.apiKey + '&frob=' + _auth.frob + '&api_sig=' + api_sig;
		
		// Create and send new XHR
		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function(){
			if (this.readyState == 4) {
				if (this.status == 200) {
					// XHR was successful
					try {
						// Store the received token
						_auth.token = this.responseXML.getElementsByTagName('token')[0].firstChild.nodeValue;
						widget.setPreferenceForKey(_auth.token, '_auth.token');
						
						// Store user information
						var user = this.responseXML.getElementsByTagName('user')[0];
						_dataStore.user.nsid = user.getAttribute('nsid');
						_dataStore.user.username = user.getAttribute('username');
						
						// Fire an event to signal that the authorization process was successfully completed
						_fireEvent('onWidgetAuthorizationCompleted', document);
					} catch (e) {
						// The token was not returned by the XHR
						DOM_view_authorizeWidget_status = document.getElementById('view_authorizeWidget_status');
						DOM_view_authorizeWidget_status = _lang.AUTH_NOT_COMPLETED;
					}
				}
			}
		};
		xhr.open('GET', url, true);
		xhr.send();
	};
	
	
	/**
	 * Calculate the first photo to show (offset) and the number of photos to display at once
	 * depending on the width and height of the screen/mobile device.
	 * 
	 * Usually, the call to flickr to receive a number of photos, e.g. the public photo stream
	 * or a search result, returns more photos than can be displayed on the screen of a mobile
	 * device at once. Therefore, we calculate the number of photos that can be displayed at once
	 * on the mobile device. Additionally, the first photo (offset) that should be displayed is
	 * calculated.
	 * Furthermore, we can specify an optional parameter called direction. This parameter, if set,
	 * determines whether a user wants to skip through a set of photos and which photos he
	 * "requested". If this parameter is set to 'next', he/she wants the next sub-set of photos, if it
	 * is set to 'previous' he/she wants to see the previous sub-set of photos.
	 * 
	 * @param object dataStore The data store that hold the photos to display
	 * @param object DOM_element The DOM elemnt in which the photos will be displayed
	 * @param string direction [optional] next: get the next sub-set of photos
	 * 									  prvious: get the prvious sub-set of photos
	 */
	var _calculateOffsetAndPhotosToDisplay = function(dataStore, DOM_element, direction) {
		// Get the height and width of the DOM element in which the photos will be displayed
		var height = DOM_element.offsetHeight;
		var width = DOM_element.offsetWidth;
		
		// We divide the available height and width by 85 because:
		// - width of the photos provided by flickr: 75px,
		// - padding of images set in style.css: 2px on each side = 4px alltogeteher
		// - border around images set in style.css: 1px on each side = 2px alltogether
		// - margin-right on images set in style.css: 4px
		// -> 75px + 4px + 2px + 4px = 85px
		var columns = Math.floor(width / 85);
		var rows = Math.floor(height / 85);
		
		switch (direction) {
			case 'next':
			// The next sub-set of photos was requested by the user
				_photosOffset += columns * rows;
				if (_photosOffset + _photosToDisplay > dataStore.length) {
					_photosToDisplay = dataStore.length - _photosOffset;
				}
			break;
			case 'previous':
			// The previuos sub-set of photos was requested by the user
				_photosOffset -= columns * rows;
				if (_photosOffset < 0) {
					_photosOffset = 0;
				}
				_photosToDisplay = columns * rows;
			break;
			default:
			// If no direction was specified, we just set the number of photos that can be
			// displayed at once on the mobile device
				_photosToDisplay = columns * rows;
			break;
		};
	};
	
	
	/**
	 * Reset the offset and the number of photos to display
	 * 
	 * We reset the index of the first photo to display and the number of photos
	 * to display.
	 */
	var _resetOffsetAndPhotosToDisplay = function () {
		_photosOffset = 0;
		_photosToDisplay = 0;
	};
	
	
	/**
	 * Retrieve public photos from Flickr via an XHR call in JSON format
	 * 
	 * To demonstrate how to interact with a server that returns JSON data, we create an XHR
	 * call to flickr.com to receive the public feed that is available at
	 * http://api.flickr.com/services/feeds/photos_public.gne
	 * 
	 * This method creates the URL to receive the public photo stream, then sends an XHR to actually
	 * receive the JSON data from flickr. If the XHR was successful, it removes the call to jsonFlickrFeed
	 * (for more on jsonFlickrfeed s. http://www.wait-till-i.com/2007/10/22/hacking-flickr-the-json-way/),
	 * because we want to call our own callback function. After that we fire a custom event onPublicPhotosLoaded
	 * to signal, that the publicly available photos were received and stored successfully.
	 * 
	 * For more information s. http://www.flickr.com/services/feeds/docs/photos_public/
	 */
	var _getPublicPhotos = function() {
		// Create URL that points to the public photos flickr stream
		var url = _url.feed + 'photos_public.gne?format=json';
		
		// Create and send XHR
		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function() {
			if (this.readyState == 4) {
				if (this.status == 200) {
					// Get rid of jsonFlickrFeed() submited by Flickr
					var responseText = this.responseText.replace(/^jsonFlickrFeed\(/, '').replace(/\)$/, '');
					
					// Store json of the public photos in our data store
					_dataStore.publicPhotos = JSON.parse(responseText);
					
					// Fire an event to signal, that a new set of public photos has been loaded
					_fireEvent('onPublicPhotosLoaded', document);
				}
			}
		};
		xhr.open('GET', url, true);
		xhr.send();
	};
	
	
	/**
	 * Search
	 * 
	 * This method first checks wheteher the user entered some search terms or not. If this is the case,
	 * a signature for the required call to the flickr API. After that, it constructs the URL to call and
	 * sends an XHT to this URL. If the XHR was successful, it removes the call to jsonFlickrApi
	 * (for more on jsonFlickrfeed s. http://www.wait-till-i.com/2007/10/22/hacking-flickr-the-json-way/),
	 * because we want to call our own callback function. After that it fires a custom event onSearchCompleted
	 * to signal, that the search result was successfully received and stored in the data store.
	 */
	var _search = function() {
		var searchterms = document.getElementById('search_terms').value;
		var DOM_search_status = document.getElementById('view_search_status');
		
		// Check whether the user entered some search terms
		if (searchterms == '') {
			// No search terms were provided
			DOM_search_status.innerHTML = _lang.SEARCH_NO_TERMS_ENTERED;
			return false;
		} else {
			DOM_search_status.innerHTML = '';
			
			// Create API call signature
			var api_sig = MD5(_auth.secret + 'api_key' + _auth.apiKey + 'auth_token' + _auth.token + 'formatjsonmethodflickr.photos.searchtags' + searchterms);
			// Create URL
			var url = _url.api + 'api_key=' + _auth.apiKey + '&auth_token=' + _auth.token + '&format=json&method=flickr.photos.search&tags=' + searchterms + '&api_sig=' + api_sig;
			
			// Create and send XHR
			var xhr = new XMLHttpRequest();
			xhr.onreadystatechange = function() {
				if (this.readyState == 4) {
					if (this.status == 200) {
						try {
							// Get rid of jsonFlickrApi() submited by Flickr
							var responseText = this.responseText.replace(/^jsonFlickrApi\(/, '').replace(/\)$/, '');
							
							// Store json of the public photos in our data store
							_dataStore.search = JSON.parse(responseText);
							
							// Fire an event to signal that the search is completed
							_fireEvent('onSearchCompleted', document);
						} catch (e) {
							DOM_search_status.innerHTML = _lang.SEARCH_ERROR;
						}
					}
				}
			};
			xhr.open('GET', url, true);
			xhr.send();
		}
	};

	
	/**
	 * Attach event listeners
	 * 
	 * To keep event registration in one central place, we create this method and attach all
	 * event listeners here. This makes it easier and more efficient to find a specific event
	 * listener instead of searching through all the code.
	 */
	var _addEventListeners = function(){
		// Event: resolution
		// This event is triggered when the screen resolution changes. This happens,
		// for example, when a mobile device is rotated by the user (e.g. from portrait
		// to landscape)
		widget.addEventListener('resolution', function() {
			// Resize the widget to the maximum screen width and height
			_maxWidgetSize();
		}, false);
		
		
		// Events triggered on the main menu
		// =================================
		
		// Demo 1: Authorize Widget
		// This event is triggered when the user clicks on the link
		// to the first demo on the main page
		var DOM_li_demo1 = document.getElementById('demo1');
		DOM_li_demo1.addEventListener('click', function() {
			// We just fire a custom event to start the authorization process
			_fireEvent('beforeWidgetAuthorization', document);
		}, false);
		
		// Demo 2: Show a public photo stream
		// This event is triggered when the user licks on the link
		// to the second demo on the main page
		var DOM_li_demo2 = document.getElementById('demo2');
		DOM_li_demo2.addEventListener('click', function(evt) {
			// Show the public photo stream container
			var DOM_view_publicPhotoStream = document.getElementById('view_publicPhotoStream');
			DOM_view_publicPhotoStream.setAttribute('class', 'view');
			
			// Show load indicator to signal to the user that something is happening in the background
			var DOM_publicPhotos = document.getElementById('view_publicPhotoStream_photos');
			DOM_publicPhotos.innerHTML = _loadIndicator();
			
			// We load a new set of publicly available photos only if there are no previously loaded
			// photos or if we explicitly want to load a new set of photos
			if (_dataStore.publicPhotos.items == null || (evt.params && evt.params.override == true)) {
				_getPublicPhotos();
			} else {
				// Otherwise, we just fire a custom event that signals, that the
				// public photo stream was loaded
				_fireEvent('onPublicPhotosLoaded', document);
			}
		}, false);
		
		// Demo 3: Search
		// This event is triggered when teh user clicks on the link
		// to the third demo on the main page
		var DOM_li_demo3 = document.getElementById('demo3');
		DOM_li_demo3.addEventListener('click', function() {
			// Show the search container
			var DOM_view_search = document.getElementById('view_search');
			DOM_view_search.setAttribute('class', 'view');
		}, false);
		
		
		// Events triggered within the first demo (Widget Authorization)
		// =============================================================
		
		// Event: beforeWidgetAuthorization
		// This event is triggered when the widget authorization is requested 
		document.addEventListener('beforeWidgetAuthorization', _prepareWidgetAuthorization, false);
		
		// Event: onFrobReceived
		// This event is triggered when the frob from flickr was successfully received and stored
		document.addEventListener('onFrobReceived', _enableWidgetAuthorization, false);
		
		// Event: click
		// This event is triggered when the user wants to start the authorization process
		var DOM_btn_startWidgetAuthorization = document.getElementById('btn_startWidgetAuthorization');
		DOM_btn_startWidgetAuthorization.addEventListener('click', _processWidgetAuthorization, false);
		
		// Event: click
		// This event is triggered when the user wants to complete the authorization
		var DOM_btn_completeWidgetAuthorization = document.getElementById('btn_completeWidgetAuthorization');
		DOM_btn_completeWidgetAuthorization.addEventListener('click', _completeWidgetAuthorization, false);
		
		// Event: onWidgetAuthorizationCompleted
		// This event is triggered when the widget was sucessfully authorized by the user
		// access his/her data on flickr.com
		document.addEventListener('onWidgetAuthorizationCompleted', function() {
			var DOM_view_authorizeWidget = document.getElementById('view_authorizeWidget');
			DOM_view_authorizeWidget.setAttribute('class', 'view hide');
		}, false);
		
		
		// Events triggered within the second demo (Show public photo stream)
		// ==================================================================
		
		// Event: onPublicPhotosLoaded
		// This event is triggered after the public photo stream was loadedsuccessfully and
		// the data was stored in the data store
		document.addEventListener('onPublicPhotosLoaded', function(){
			DOM_view_publicPhotoStream_photos = document.getElementById('view_publicPhotoStream_photos');
			Flickr.showPhotos({
				dataStore: _dataStore.publicPhotos.items,
				DOM_element: DOM_view_publicPhotoStream_photos,
				fireThisEvent: 'onPublicPhotosDisplayed'
			});
		}, false);
		
		
		// Event: onPublicPhotosDisplayed
		// This event is triggered after the photos from the public photo stream are displayed
		document.addEventListener('onPublicPhotosDisplayed', function() {
			// Check which navigational elements should be displayed and which should not be displayed
			document.getElementById('view_publicPhotoStream_nav_previous').setAttribute('class', (_photosOffset > 0 ? '' : 'hide'));
			document.getElementById('view_publicPhotoStream_nav_next').setAttribute('class', ((_photosOffset + _photosToDisplay) >= _dataStore.publicPhotos.items.length ? 'hide' : ''));
		}, false);
		
		// Event: click
		// This event is triggered when the user clicks on the arrow on the left hand side
		// of the public photo stream to indicate that he/she wants to see the previous sub-set of photos
		var DOM_publicPhotos_nav_previous = document.getElementById('view_publicPhotoStream_nav_previous');
		DOM_publicPhotos_nav_previous.addEventListener('click', function() {
			DOM_view_publicPhotoStream_photos = document.getElementById('view_publicPhotoStream_photos');
			// Show the photos
			Flickr.showPhotos({
				dataStore: _dataStore.publicPhotos.items,
				DOM_element: DOM_view_publicPhotoStream_photos,
				fireThisEvent: 'onPublicPhotosDisplayed',
				direction: 'previous'
			});
		}, false);
		
		// Event: click
		// This event is triggered when the user clicks on the arrow on the right hand side
		// of the public photo stream to indicate that he/she wants to see the next sub-set of photos
		var DOM_publicPhotos_nav_next = document.getElementById('view_publicPhotoStream_nav_next');
		DOM_publicPhotos_nav_next.addEventListener('click', function() {
			DOM_view_publicPhotoStream_photos = document.getElementById('view_publicPhotoStream_photos');
			// Show the photos
			Flickr.showPhotos({
				dataStore: _dataStore.publicPhotos.items,
				DOM_element: DOM_view_publicPhotoStream_photos,
				fireThisEvent: 'onPublicPhotosDisplayed',
				direction: 'next'  
			});
		}, false);
		
		// Event: click
		// This event is triggered when the user wants to reload a new set of publicly
		// available photos
		var DOM_btn_reloadPublicPhotos = document.getElementById('btn_reloadPublicPhotos');
		DOM_btn_reloadPublicPhotos.addEventListener('click', function() {
			_resetOffsetAndPhotosToDisplay();
			document.getElementById('view_publicPhotoStream_nav_previous').setAttribute('class', 'hide');
			document.getElementById('view_publicPhotoStream_nav_next').setAttribute('class', 'hide');
			_fireEvent('click', DOM_li_demo2, {override: true});
		}, false);
		
		// Event: click
		// This event is triggered when the user wants to close the public photo stream view
		var DOM_btn_closePublicPhotos = document.getElementById('btn_closePublicPhotos');
		DOM_btn_closePublicPhotos.addEventListener('click', function() {
			_resetOffsetAndPhotosToDisplay();
			document.getElementById('view_publicPhotoStream_nav_previous').setAttribute('class', 'hide');
			document.getElementById('view_publicPhotoStream_nav_next').setAttribute('class', 'hide');
			
			var DOM_view_publicPhotoStream = document.getElementById('view_publicPhotoStream');
			DOM_view_publicPhotoStream.setAttribute('class', 'view hide');
		}, false);
		
		
		// Events triggered within the third demo (Search)
		// ===============================================
		
		// Event: click (start search process)
		// This event is triggered when the user clicks on the button to start a search
		var DOM_btn_search = document.getElementById('btn_search');
		DOM_btn_search.addEventListener('click', function() {
			var DOM_view_search_photos = document.getElementById('view_search_photos');
			DOM_view_search_photos.innerHTML = _loadIndicator();
			
			_search();
		}, false);
		
		// Event: onSearchCompleted
		// This event is triggered after a search was completed successfully and
		// the search results were stored in the data store
		document.addEventListener('onSearchCompleted', function() {
			// Display the search results
			var DOM_view_search_photos = document.getElementById('view_search_photos');
			//Flickr.showSearchResults(_dataStore.search, DOM_view_search_photos);
			Flickr.showPhotos({
				dataStore: _dataStore.search.photos.photo,
				DOM_element: DOM_view_search_photos,
				thisIsASearchPhoto: true,
				fireThisEvent: 'onSearchResultsDisplayed'
			});
		}, false);
		
		// Event: onSearchResultsDisplayed
		// This event is triggered after the photos from the search result are displayed
		document.addEventListener('onSearchResultsDisplayed', function() {
			// Check which navigational elements should be displayed and which should not be displayed
			document.getElementById('view_search_nav_previous').setAttribute('class', (_photosOffset > 0 ? '' : 'hide'));
			document.getElementById('view_search_nav_next').setAttribute('class', ((_photosOffset + _photosToDisplay) >= _dataStore.search.photos.photo.length ? 'hide' : ''));
		}, false);
		
		// Event: click
		// This event is triggered when the user clicks on the arrow on the left hand side
		// of the search results to indicate that he/she wants to see the previous sub-set of photos
		var DOM_search_nav_previous = document.getElementById('view_search_nav_previous');
		DOM_search_nav_previous.addEventListener('click', function() {
			DOM_view_search_photos = document.getElementById('view_search_photos');
			// Show the previous sub-set of photos
			Flickr.showPhotos({
				dataStore: _dataStore.search.photos.photo,
				DOM_element: DOM_view_search_photos,
				fireThisEvent: 'onSearchResultsDisplayed',
				direction: 'previous',
				thisIsASearchPhoto: true,
			});
		}, false);
		
		// Event: click
		// This event is triggered when the user clicks on the arrow on the right hand side
		// of the search results to indicate that he/she wants to see the next sub-set of photos
		var DOM_search_nav_next = document.getElementById('view_search_nav_next');
		DOM_search_nav_next.addEventListener('click', function() {
			DOM_view_search_photos = document.getElementById('view_search_photos');
			// Show the next sub-set of photos
			Flickr.showPhotos({
				dataStore: _dataStore.search.photos.photo,
				DOM_element: DOM_view_search_photos,
				fireThisEvent: 'onSearchResultsDisplayed',
				direction: 'next',
				thisIsASearchPhoto: true,
			});
		}, false);
		
		// Event: click
		// This event is triggered when the user wants to close the search view
		var DOM_btn_closeSearch = document.getElementById('btn_closeSearch');
		DOM_btn_closeSearch.addEventListener('click', function() {
			var DOM_view_search = document.getElementById('view_search');
			DOM_view_search.setAttribute('class', 'view hide');
		}, false);
		
		
		// Events triggered within the single photo view
		// =============================================
		
		// Event: click
		// This event is triggered when the user wants to close the single photo view
		var DOM_btn_closeSinglePhoto = document.getElementById('btn_closeSinglePhoto');
		DOM_btn_closeSinglePhoto.addEventListener('click', function() {
			var DOM_view_singlePhoto = document.getElementById('view_singlePhoto');
			DOM_view_singlePhoto.setAttribute('class', 'view hide');
		}, false);
	};
	
	
	
	
	return {
		
		// Public members
		// ==============
		
		
		
		
		// Public methods
		// ==============
		
		/**
		 * Initialize the widget
		 * 
		 * This method is called from within index.html to initialize the widget.
		 */
		init: function() {
			// To make sure that the DOM has been completely loaded before we start to access or manipulate it,
			// we attach an event listener on the DOMContentLoaded event.
			document.addEventListener('DOMContentLoaded', function () {
				// Attach all our event lsteners/handlers
				_addEventListeners();
				
				// Maximize widget
				//_maxWidgetSize();
				
				// Check whether our widget is already authorized to access the user's data on flickr.com
				if (!_isAuthTokenAvailable()) {
					// No authentication token is available, therefore we show a dialog to create
					// a new authentication token
					_fireEvent('beforeWidgetAuthorization', document);
				}
			}, false);
		},
		
		
		/**
		 * Display a set of photos
		 * 
		 * This method displays a set of photos. 
		 * 
		 * @param object params
		 */
		showPhotos: function(params) {
			_calculateOffsetAndPhotosToDisplay(params.dataStore, params.DOM_element, (!params.direction ? '' : params.direction));
			// Remove any existing photos that are currently displayed
			params.DOM_element.innerHTML = '';
			
			// Scan through the JSON data
			for (var i = _photosOffset; i < _photosOffset + _photosToDisplay; i++) {
				// Extract the URL to the photo on flickr (medium size)
				// For more information on the different photos sizes flickr offers
				// s. http://www.flickr.com/services/api/misc.urls.html (section Size Suffixes)
				
				if (params.thisIsASearchPhoto) {
					var photo_url = 'http://farm' + params.dataStore[i].farm + '.static.flickr.com/' + params.dataStore[i].server + '/' + params.dataStore[i].id + '_' + params.dataStore[i].secret + '_s.jpg';
				} else {
					var photo_url = params.dataStore[i].media.m;
				}
				
				// Create a new link element and attach an event listener to it,
				// so that when the user clicks on the image, Flickr.showSinglePhoto is called
				var DOM_a = document.createElement('a');
				DOM_a.href = '#';
				DOM_a.onclick = function(evt) {
					var photo_url_m = evt.target.src.replace(/s\.jpg$/, 'm.jpg');
					Flickr.showSinglePhoto(photo_url_m);
				};
				
				// Create a new img element and set its src attribute to the small version of the image
				// For more information on the different photos sizes flickr offers
				// s. http://www.flickr.com/services/api/misc.urls.html (section Size Suffixes)
				var photo_url_s = photo_url.replace(/m\.jpg$/, 's.jpg');
				var DOM_img = document.createElement('img');
				DOM_img.setAttribute('class', 'photo');
				DOM_img.setAttribute('src', photo_url_s);
				
				// Append everything to the public photos container
				DOM_a.appendChild(DOM_img);
				params.DOM_element.appendChild(DOM_a);
				
				_fireEvent(params.fireThisEvent, document);
			}
		},
		
		
		/**
		 * Display a single photo
		 * 
		 * This method shows a single photo, whose source is specified by url
		 * 
		 * @param string url The URL to the photo
		 */
		showSinglePhoto: function(url) {
			// Activate the single photo container and clear any previously displayed photo
			var DOM_view_singlePhoto = document.getElementById('view_singlePhoto');
			DOM_view_singlePhoto.setAttribute('class', 'view');
			
			// Get the DOM element to which to append the photo
			var DOM_view_singlePhoto_photo = document.getElementById('view_singlePhoto_photo');
			DOM_view_singlePhoto_photo.innerHTML = '';
			
			// Create new img element and set its src attribute to url
			var DOM_img = document.createElement('img');
			DOM_img.setAttribute('class', 'photo');
			DOM_img.setAttribute('src', url);
			
			// Append img element
			DOM_view_singlePhoto_photo.appendChild(DOM_img);
		}
	}
}();
