/*
 * Copyright (C) 2009 Paul Uithol, SMARTposition <http://www.smartposition.nl>
 *
 * SmartPosition.Live (de4daagselive)
 */

SmartPosition.routeStyles = {
	foreground: {
		fill: {
			color: '#FFFFFF',
			opacity: 1,
			width: 5
		},
		stroke: {
			color: '#333333',
			opacity: 0.7,
			width: 8
		},
		progress: {
			color: '#5CBB5C',
			opacity: 1,
			width: 5
		}
	},
	background: {
		fill: {
			color: '#E6FBDC',
			opacity: 1,
			width: 4
		},
		stroke: {
			color: '#333333',
			opacity: 0.7,
			width: 6
		}
	}
}

// Object that holds participant data
SmartPosition.participant = SmartPosition.participant || {};

/**
 * Live class
 */
SmartPosition.Live = function() {
	return this.init.apply( this, arguments );
}

SmartPosition.Live.prototype = {
	name: 'SmartPosition.Live',
	
	map: null, // map element
	
	location: null, // Participant location
	live: {
		marker: null,
		accuracyCircle: null
	},
	history: {
		marker: null,
		accuracyCircle: null
	},
	polylines: {
		progress: null,
		route: [],
		platoon: []
	},
	
	status: null, // status element
	
	participantStatus: {
		notStarted: ['INITIAL', 'NOTSTARTED', 'AWAITINGSTART'],
		started: ['STARTED'],
		finished: ['FINISHED'],
		error: ['NOTONOWNROUTE','OUTOFCOMPETITION', 'TOOLATEFORFINISH']
	},
	
	feedLoadOptions: null,
	
	init: function(mapElement, statusElement, historyElement) {
		var dit = this;
		SmartPosition.debug && console.log( this );
		
		this.map = $( mapElement ).gmap( {
			center: [51.84, 5.87], // Nijmegen
			mapType: G_HYBRID_MAP,
			zoom: 11
		});
		this.status = $( statusElement );
		
		SmartPosition.participant.currentDay = parseInt( SmartPosition.participant.currentDay );
		
		// Draw route for current day
		this.addRoute( SmartPosition.participant.distance, SmartPosition.participant.currentDay, true );
		
		// History accordion
		if ( $( '#history dl' ).length > 0 )
			$( '#history' ).accordion({
				active: false,
				change: function(event, ui) {
					for (var date in SmartPosition.marchDays) {
						if ( ui.newHeader.hasClass(date) )
							dit.addRoute( SmartPosition.participant.distance, SmartPosition.marchDays[date].day );
						else if ( SmartPosition.participant.currentDay !== SmartPosition.marchDays[date].day )
							dit.removeRoute( SmartPosition.marchDays[date].day );
					}
				},
				collapsible: true
			});
		
		// Load participant location data
		var options = $.extend({}, SmartPosition.locationFeed, {
			success: this.setLocationData,
			callbackScope: this
		});
		$.loadData( options );
		
		// Show/hide platoon view
		if ( $( '#showPlatoon:checked' ).length > 0 ) {
			this.feedLoadOptions = $.extend({}, SmartPosition.platoonFeed, {
				success: this.showPlatoonProgress,
				callbackScope: this
			});
			$.loadData( this.feedLoadOptions );
		}
		
		// Handle clicks on 'onderweg' locations
		$( '.locationsTraveled a' ).live( 'click', function() {
			var link = $( this );
			var coords = [ link.getUrlParam( 'y' ), link.getUrlParam( 'x' ) ];
			var accuracy = link.getUrlParam( 'accuracy' );
			
			dit.map.gmap( 'clearOverlay', dit.history.marker )
				.gmap( 'addMarker', {
					latLng: coords,
					icon: $.gmap.icons.dot,
					info: '<p>Locatie van ' + link.text() + '</p>'
				}, function( marker ) {
					dit.history.marker = marker;
					dit.map.gmap( 'openInfoWindow', marker );
				})
				.gmap( 'setCenter', {latLng: coords} );
			
			dit.map.gmap( 'clearOverlay', dit.history.accuracyCircle );
			if ( accuracy && accuracy > 0 ) {
				dit.map.gmap( 'addCircle', {
					latLng: coords,
					radius: accuracy
				}, function( accuracy ) {
					dit.history.accuracyCircle = accuracy;
				});
			}
			
			return false;
		});
	},
	
	setLocationData: function(json) {
		// Show noStatus placeholders and exit if there's no location
		if ( !json.point || !json.timestamp ) {
			this.location == null && $('.noStatus').show() && $( 'hasStatus' ).hide();
			
			return false;
		}
		
		// Exit if we currently have a location, but haven't received a new location
		if ( this.location && json.timestamp == this.location.timestamp ) {
			return false;
		}
		
		// If we receive a location for the first time, hide noData, hide noStatus placeholders and show real info
		if ( this.location == null ) {
			$('.noData').hide();
			$('.noStatus').hide();
			$('.hasStatus').show();
		}
		
		var point = [ json.point.y, json.point.x, json.point.z, json.point.srId ]; // Watch out; order is lat,lon,alt (y,x,z)
		
		var timestamp = new Date();
		timestamp.setISO8601( json.timestamp );
		$( '#content span.timestamp' ).text( timestamp.format('H:MM') );

        var address = ( json.address == null ? ' ' : timestamp.format('H:MM') + ' uur, ' + json.address.city );
		
		this.location = $.extend( json, {
			point: point,
            address: address,
			speed: json.speed || 0,
			accuracy: json.accuracy || 5000,
            previousLocations: json.previousLocations,
			distanceTraveled: Math.max( 0, SmartPosition.participant.distance - (json.distanceToFinish || SmartPosition.participant.distance) )
		});
		
		// Fix data in final state
		if (this.hasParticipantState( 'finished' )) {
			this.location.distanceToFinish = 0;
			this.location.distanceTraveled = SmartPosition.participant.distance;
		}
		
		SmartPosition.debug && console.debug('Live.setLocationData: update to (' + point[0] + ',' + point[1] + '), accuracy=' + this.location.accuracy + ' @ ' + this.location.timestamp.replace(/[TZ]/g," "));

		this.updateStatus();
		
		// Avoid drawing polyline when distanceToFinish is still null. distanceTraveled can be > 0 already then, due to variations in actual route length.
		if (json.distanceToFinish !== null) {
			var currentPoint = this.showRouteProgress();
			this.setPoint( currentPoint );
		}
	},
	
	setPoint: function( coords, accuracy ) {
		var dit = this;
		coords = coords || this.location.point;
		accuracy = accuracy || this.location.accuracy;
		
		/*
         * Only set the location if it's not (0.0, 0.0). This is a location
         * with a special meaning: a Participant gets that location when the
         * pardus location is unknown.
         */
		if ( coords[ 0 ] > 0.0 && coords[ 1 ] > 0.0 ) {
			this.map.gmap( 'clearOverlay', this.live.marker )
				.gmap( 'addMarker', {latLng: coords}, function( marker ) {
					dit.live.marker = marker;
				})
				.gmap( 'setCenter', {latLng: coords} );
			
			this.map.gmap( 'clearOverlay', this.live.accuracyCircle );
			if ( accuracy && accuracy > 0 ) {
				dit.map.gmap( 'addCircle', {
					latLng: coords,
					radius: accuracy
				}, function( accuracy ) {
					dit.live.accuracyCircle = accuracy;
				});
			}
		}
	},
	
	showRouteProgress: function() {
		var dit = this;
		var points = [];
		var route = ( SmartPosition.routes[SmartPosition.participant.distance] && SmartPosition.routes[SmartPosition.participant.distance][SmartPosition.participant.currentDay - 1] ) || [];  // Take day - 1 since routes are 0-indexed
		
		// Calculate how many pieces of the route someone has finished based on route.lengt (routes are mostly not exactly equal to SmartPosition.participant.distance)
		var kmTraveled = Math.round( route.length - this.location.distanceToFinish );
		
		for (var km in route) {
			//console.debug( km + ': %o', route[km] );
			
			points = points.concat( route[km] );
			if ( km >= kmTraveled )
				break;
		}
		
		this.hideRouteProgress();
		
		console.debug( 'Drawing route for day=%i and km=%i, with %i points', SmartPosition.participant.currentDay, SmartPosition.participant.distance, points.length );
		
		var style = SmartPosition.routeStyles.foreground.progress;
		this.map.gmap( 'addPolyline', {points: points, strokeColor: style.color, strokeWidth: style.width, strokeOpacity: style.opacity}, function( polyline ) {
			dit.polylines.progress = polyline;
		});
		
		return points.length > 0 ? points[ points.length - 1 ] : null;
	},
	
	hideRouteProgress: function() {
		this.map.gmap( 'clearOverlay', this.polylines.progress );
	},
	
	showPlatoonProgress: function( data ) {
		var dit = this;
		this.hidePlatoonProgress();
		console.debug( data );
		
		/**
		 * Distribute platoon into polylines, based on percentage of participants on a certain stretch (km) of the route
		 */
		var segmentBoundary = 0.03;
		
		var polylines = [], segment = [];
		var route = ( SmartPosition.routes[SmartPosition.participant.distance] && SmartPosition.routes[SmartPosition.participant.distance][SmartPosition.participant.currentDay - 1] ) || [];  // Take day - 1 since routes are 0-indexed
		
		var totalAmount = data.totalAmountOfParticipants;
		var percentage = 0, maxSegmentPercentage = 0, minSegmentPercentage = 1, percentageInBucket = 0, segmentLength = 0;		
		
		for ( var km in route ) {
			// Search for the proper entry in data.platoon, or assume a percentage of 0
			percentage = 0;
			for ( var i in data.platoon ) {
				if ( km == route.length - data.platoon[ i ].distanceToFinish ) {
					percentage = data.platoon[ i ].amountOfParticipants / totalAmount;
					break;
				}
			}
			
			if ( segmentLength > 0 && ( Math.abs( percentage - maxSegmentPercentage ) > segmentBoundary || Math.abs( percentage - minSegmentPercentage ) > segmentBoundary ) ) {
				polylines.push( {percentage: percentageInBucket, segmentLength: segmentLength, points: segment} );
				//console.debug( 'adding segment with points=%i, percentage=%i, segmentLength=%i to polylines', segment.length, percentageInBucket, segmentLength );
				
				// Reset counters
				segment = [];
				segmentLength = 0;
				percentageInBucket = 0;
				maxSegmentPercentage = 0;
				minSegmentPercentage = 1;
			}
			
			maxSegmentPercentage = Math.max( percentage, maxSegmentPercentage );
			minSegmentPercentage = Math.min( percentage, minSegmentPercentage );
			
			//console.debug( 'km=%s; percentage=%i, minSegmentPercentage=%i, maxSegmentPercentage=%i', km, percentage, minSegmentPercentage, maxSegmentPercentage )
			segment = segment.concat( route[ km ] ); // Add points for the correct km to the segment (zero based)
			percentageInBucket += percentage;
			segmentLength += 1;
		}
		
		// Push last segment
		if ( segment.length > 0 ) {
			polylines.push( {percentage: percentageInBucket, segmentLength: segmentLength, points: segment} );
			//console.debug( 'adding segment with points=%i, percentage=%i, segmentLength=%i to polylines', segment.length, percentageInBucket, segmentLength );
		}
		
		// Draw all polylines; color depends on percentage per segment
		var style = SmartPosition.routeStyles.foreground.fill;
		
		for ( var i in polylines ) {
			if ( polylines[ i ].points ) {
				var busyness = polylines[ i ].percentage / polylines[ i ].segmentLength;
				
				if ( busyness > 0.01 ) {
					if ( busyness < 0.10 ) {
						// Rescale 'busyness' to reach from 0 to 1 (instead of 0.01 to 0.10)
						var intensity = 10 * busyness;
						// Small percentage: white to pure green (FFFFFF->00FF00)
						var colorIntensity = Math.round( 255 - ( 255 * intensity ) ).toString( 16 ).lpad( '0', 2 );
						style.color = '#' + colorIntensity + 'FF' + colorIntensity;
					}
					else if ( busyness < 0.25 ) {
						// Rescale 'busyness' to reach from 0 to 1 (instead of 0.10 to 0.25)
						var intensity = ( busyness - 0.10 ) * ( 6 + 2/3 );
						// Medium percentage: pure green to pure yellow (00FF00->FFFF00)
						var colorIntensity = Math.round( 255 * intensity ).toString( 16 ).lpad( '0', 2 );
						style.color = '#' + colorIntensity + 'FF00';
					}
					else {
						// Rescale 'busyness' to reach from 0 to 1 (instead of 0.25 to 1)
						var intensity = ( busyness - 0.25 ) * ( 1 + 1/3 );
						// High percentage: pure yellow to pure red (FFFF00->FF0000)
						var colorIntensity = Math.round( 255 - ( 255 * intensity ) ).toString( 16 ).lpad( '0', 2 );
						style.color = '#FF' + colorIntensity + '00';
					}
					
					console.debug( 'adding line with color=%i for percentage=%i; segmentLength=%i; busyness=%i; intensity=%i; points=%i', style.color, polylines[ i ].percentage, polylines[ i ].segmentLength, busyness, intensity, polylines[ i ].points.length );

					this.map.gmap( 'addPolyline', {points: polylines[ i ].points, strokeColor: style.color, strokeWidth: style.width, strokeOpacity: style.opacity}, function( polyline ) {
						dit.polylines.platoon.push( polyline );
					});

				}
			}
		}

        // TODO
        var  geoXml = new GGeoXml("http://de4daagselive.nl/assets/files/" + SmartPosition.participant.distance + "_" + SmartPosition.participant.currentDay + ".kml");
        console.debug( "http://de4daagselive.nl/assets/files/" + SmartPosition.participant.distance + "_" + SmartPosition.participant.currentDay + ".kml" );
        this.map.gmap( 'addOverlay', geoXml);
	},
	
	hidePlatoonProgress: function() {
		while ( this.polylines.platoon.length ) {
			this.map.gmap( 'clearOverlay', this.polylines.platoon.pop() );
		}
	},
	
	/**
	 * Add a route polyline for a certain day/distance.
	 * @param foreground {boolean} Determines line style; true = foreground (current day), false = background. Default is false.
	 */
	addRoute: function( distance, day, foreground ) {
		var dit = this;
		var points = [];
		foreground = foreground || false;
		var route = ( SmartPosition.routes[distance] && SmartPosition.routes[distance][day - 1] ) || []; // Take day - 1 since routes are 0-indexed
		
		for (var km in route) {
			//console.debug( km + ': %o', route[km] );
			points = points.concat( route[km] );
		}
		//console.debug(points.length + ' %o', points[0]);
		
		if (points.length) {
			if (!dit.polylines.route[day])
				this.polylines.route[day] = [];
			
			var style = foreground ? SmartPosition.routeStyles.foreground.stroke : SmartPosition.routeStyles.background.stroke;
			this.map.gmap( 'addPolyline', {points: points, strokeColor: style.color, strokeWidth: style.width, strokeOpacity: style.opacity}, function(polyline) {
				dit.polylines.route[day].push(polyline);
			});
			
			style = foreground ? SmartPosition.routeStyles.foreground.fill : SmartPosition.routeStyles.background.fill;
			this.map.gmap( 'addPolyline', {points: points, strokeColor: style.color, strokeWidth: style.width, strokeOpacity: style.opacity}, function(polyline) {
				dit.polylines.route[day].push(polyline);
			});
			
			// todo: draw marker at start/finish point?
		}
	},
	
	removeRoute: function( day ) {
		if (!this.polylines.route[day])
			return;
		
		//console.log( this.polylines.route[day] );
		for (overlayIdx in this.polylines.route[day]) {
			this.map.gmap( 'clearOverlay', this.polylines.route[day][overlayIdx] );
		}
	},
	
	updateStatus: function() {
		/* Not started yet ('INITIAL', 'NOTSTARTED', 'AWAITINGSTART') */
		if (this.hasParticipantState( 'notStarted' )) {
			this.status.find('div:not(.notStarted)').hide().end().find('div.notStarted').show();
		}
		/* Started ('STARTED') */
		else if (this.hasParticipantState( 'started' )) {
			var list = this.status.find('div:not(.started)').hide().end().find('div.started').show();
			
			list.find( '.distanceTraveled' ).text( Math.round(this.location.distanceTraveled) );

            if( this.location.previousLocations ) {
                list.find('.locationsTraveled').empty();
                for ( i in this.location.previousLocations ) {
					var loc = this.location.previousLocations[ i ];
                    if ( loc.distanceToFinish ) {
                            var dist = SmartPosition.participant.distance - loc.distanceToFinish;
                    }
                    else {
                            var dist = '?';
                    }
                    dist += '/' + SmartPosition.participant.distance + " km";
					
                    if ( loc.address ) {
                        var prevLocAddress = loc.address.city.city
                    }
                    else {
                        var prevLocAddress = '';
                    }
					
					var ts = new Date();
                    ts.setISO8601( loc.timestamp );
					
                    list.find('.locationsTraveled').append(
                        '<a href="#?x=' + loc.x + '&y=' + loc.y + '&accuracy=' + loc.accuracy + '">' + ts.format( 'H:MM' ) + ', ' + dist + '</a><br />'
                    );
                }

            }
            else {
                list.find('.locationsTraveled').text( 'Geen gegevens' );
            }
			
			if ( this.location.startTime ) {
				var startTime = new Date();
				startTime.setISO8601( this.location.startTime );
				//list.find('.startTime').text( startTime.format('H:MM') );
				list.find('.startTime').text( startTime.format( 'H:MM' ) );
			}
			
			if ( this.location.eta ) {
				var eta = new Date();
				eta.setISO8601( this.location.eta );
				eta.setTime( eta.getTime() + (eta.getTimezoneOffset() * 60 * 1000) ); // parse eta; given in local time by Pardus, so eliminate timezoneOffset 

                // Round eta minutes by 15 minutes
                var minutes = Math.round( eta.getMinutes() / 15 ) * 15;
                var hours = eta.getHours();

                if ( minutes == 0 && eta.getMinutes() > 45 ) {
                    hours += 1;
                } else if ( minutes == 60 ) {
                    hours += 1;
                    minutes = 0;
                }
                hours = hours % 24;
                hours = hours.toString().lpad('0',  2 );
                minutes = minutes.toString().lpad( '0', 2 );

                var endTime = new Date();
                endTime.setHours( 20, 0, 0, 0 );
                
                if ( ( eta.getTime() - endTime.getTime() ) > 0 ) {
                    list.find('.eta').text( 'na 18:00' );
                }
                else {
                    list.find('.eta').text( 'rond ' + hours + ':' + minutes );
                }
				//list.find('.eta').text( eta.getHours() + 1 );
			}
			else {
				list.find('.eta').text('?');
			}

            if ( this.location.address ) {
                var ts = new Date();
                ts.setISO8601( this.location.timestamp );
                list.find('.lastLocation').text( ts.format( 'H:MM' ) + ' uur ' );
            }
		}
		/* Finished ('FINISHED') */
		else if (this.hasParticipantState( 'finished' )) {
                    var list = this.status.find('div:not(.finished)').hide().end().find('div.finished').show();
                    var startTime = new Date();
                    var finishTime = new Date();

                    if (this.location.startTime) {
                            startTime.setISO8601( this.location.startTime );
                            //list.find('.startTime').text( startTime.format('H:MM') );
                            list.find('.startTime').text( startTime.format( 'H:MM' ) );
                    }

                    if (this.location.finishTime) {
                            finishTime.setISO8601( this.location.finishTime );
                            //list.find('.finishTime').text( finishTime.getHours() + 1 );
                            list.find('.finishTime').text( finishTime.format( 'H:MM' ) );
                    }
			
                    if (this.location.startTime && this.location.finishTime) {
                            //var averageSpeed = SmartPosition.participant.distance / ((finishTime - startTime) / 1000 / 3600);
                            var averageSpeed = Math.round( SmartPosition.participant.distance / ((finishTime.getHours() + 1) - startTime.getHours()) );
                            list.find('.averageSpeed').text( (averageSpeed < SmartPosition.participant.maxAverageSpeed) ? averageSpeed + ' km/u' : '?' );
                    }
			
                    if( this.location.previousLocations ) {
                        for( loc in this.location.previousLocations ) {
                            ts = new Date();
                            x = this.location.previousLocations[ loc ].x;
                            y = this.location.previousLocations[ loc ].y;
                            accuracy = this.location.previousLocations[ loc ].accuracy;
                            if ( this.location.previousLocations[ loc ].distanceToFinish ) {
                                historyDistance = SmartPosition.participant.distance - this.location.previousLocations[ loc ].distanceToFinish;
                            }
                            else {
                                historyDistance = '?';
                            }

                            historyDistance += '/' + SmartPosition.participant.distance + " km";
                            ts.setISO8601( this.location.previousLocations[ loc ].timestamp );
                            list.find('.locationsTraveled').append(
                             '<a href="#?x=' + x + '&y=' + y + '&accuracy=' + accuracy + '">' + ts.format( 'H:MM' ) + ', ' + historyDistance + '</a><br />'
                            );
                        }

                    }
                    else {
                        list.find('.locationsTraveled').text( 'Geen gegevens' );
                    }
		}
		/* Error state ('NOTONOWNROUTE', 'OUTOFCOMPETITION', 'TOOLATEFORFINISH') */
		else if (this.hasParticipantState( 'error' )) {
			this.status.find('div:not(.error)').hide().end().find('div.error').show();
		}
	},
	
	/**
	 * Check if a participant is in one of the states defined in this.participantStatus
	 * @param state One of the keys in 'participantStatus'
	 */
	hasParticipantState: function( state ) {
		if (!this.participantStatus.hasOwnProperty( state ) || !this.location || !this.location.status) {
			return false;
		}
		
		return ($.inArray(this.location.status, this.participantStatus[ state ]) >= 0);
	}
}

/**
 * MessageFeed class
 */
SmartPosition.MessageFeed = function() {
	return this.init.apply( this, arguments );
}

SmartPosition.MessageFeed.prototype = {
	name: 'SmartPosition.MessageFeed',
	
	div: null,
	
	feedLoadOptions: null,
	
	init: function( element ) {
		var dit = this;
		SmartPosition.debug && console.log( this );
		
		this.div = $( element );
		
		// Load message feed
		this.feedLoadOptions = $.extend({}, SmartPosition.messageFeed, {
			success: dit.processMessages,
			callbackScope: this
		});
		$.loadData( this.feedLoadOptions );
	},
	
	processMessages: function( data ) {
		console.debug( data );
		
		// Update feed request parameters
		if ( data.refreshData ) {
			for ( var i in data.refreshData ) {
				this.feedLoadOptions.data[ i ] = data.refreshData[ i ];
			}
		}
		
		// Add messages to feed
		if ( data.messages ) {
			for ( var i in data.messages ) {
				this.addMessage( data.messages[ i ] );
			}
		}
		
		// Limit amount of shown messages to 50
		var allMessages = this.div.find( 'div.message:not(.prototype)' );
		for ( var i = allMessages.length - 1; i > 50; i-- ) {
			$( allMessages[ i ] ).slideUp( 'slow', function() { $(this).remove() } );
		}
	},
	
	addMessage: function( options ) {
		var elem = this.div.find( 'div.prototype' ).clone( true ).removeClass( 'prototype' ).hide();
		
		var inserted = this.div.find( 'div.prototype' ).after( elem );
		
		if ( options.profile_image_url ) {
			$( '<img />' ).appendTo( elem.find( '.image' ) ).attr( 'src', options.profile_image_url );
		}
		
		elem.find( '.text' ).text( options.text ? options.text : '' );
		elem.find( '.source' ).text( 'bericht via ' + options.source + ( options.user_name ? ' van ' + options.user_name : '' ) );
		
		if ( options.media && options.media.url_thumbnail && options.url ) {
			$( elem.find( '.media' ) ).append( '<a href="' + options.url + '"><img src="' + options.media.url_thumbnail + '" /></a>' );
		}
		
		elem.slideDown( 'slow' );
	}
}


jQuery(function($) { // shorthand for $(document).ready(), failsafe $ alias.
	$.gmap.markerDefaults.defaultIcon = $.gmap.icons[ 'default' ] = $.gmap.createIcon( {
		image: SmartPosition.baseUrl + '/images/icons/walker_24_5cbb5c.png',
		size: [24,24],
		shadow: SmartPosition.baseUrl + '/images/icons/round_shadow_24.png',
		shadowSize: [46,24]
	});
	$.gmap.icons.dot = $.gmap.createIcon( {
		image: SmartPosition.baseUrl + '/images/icons/dot_12_5cbb5c.png',
		size: [12,12]
	});
	
	$.gmap.polygonDefaults = $.extend( $.gmap.polygonDefaults, {
		strokeColor: '#5cbb5c',
		strokeOpacity: 0.5,
		strokeWeight: 6,
		fillColor: '#5cbb5c',
		fillOpacity: 0.2
	});
	
	SmartPosition.live = new SmartPosition.Live( '#map', '#status', '#history' );
	
	SmartPosition.messageFeed = new SmartPosition.MessageFeed( '#messageFeed' );
});
