/* Reference to map object */
var map = undefined, directionsService, directionsDisplay;
/* Namespaces definition */
var cycleapp = {};

/* 
 * Closure for cycleapp.
 */
(function () {
	cycleapp.utils = {};
	cycleapp.loader = {};
	cycleapp.pagecontent = {};
	cycleapp.pagecontent.About = {};	
	cycleapp.pagecontent.MyRoutes = {};
	cycleapp.pagecontent.SearchRoutes = {};	

	cycleapp.mapcore = {};
	cycleapp.mapcore.mapquestapi = {};
	cycleapp.mapcore.googlemapsapi = {};
	cycleapp.mapcore.cloudmade = {};
	cycleapp.mapcore.openlayers = {};	

	cycleapp.model = {};
	cycleapp.model.Location = {};
	cycleapp.model.Profile = {};	

	cycleapp.widgets = {};
	cycleapp.widgets.Controls = {};
	cycleapp.widgets.WaypointsPanel = {};
	cycleapp.widgets.DistancePanel = {};
	cycleapp.widgets.Alert = {};
	cycleapp.widgets.Search = {};
	cycleapp.widgets.Profile = {};
	cycleapp.widgets.Signin = {};	
	cycleapp.widgets.BottomPane = {};
	cycleapp.widgets.HeaderRightLinks = {};
	cycleapp.widgets.StaticMapGenerator = {};	

/* Global vars */
var __ca = cycleapp, 
	__model = cycleapp.model,
	// Ability to change between Mapquest API and Google Maps API
	__map = cycleapp.mapcore.googlemapsapi, 
	//__map = __ca.mapcore.mapquestapi, 
	//__map = cycleapp.mapcore.cloudmade,
	//__map = cycleapp.mapcore.openlayers, 
	__wid = __ca.widgets,
	__utils = __ca.utils,
	__pages = __ca.pagecontent,
	__alert = __wid.Alert,
	__search = __wid.Search,
	_unit = 'm',
	_distance = 0,
	_time,
	_cloudmadeApiKey = 'fdb8465660e144ea9e3817d0c2ca0b58',
	_startIcon = '/images/start.png',
	_endIcon = '/images/finish.png',
	_googleSigninUrl = undefined,
	_googleSignoutUrl = undefined,
	_profile,
	_signinPopup,
	controlsWidget,
	distanceWidget,
	searchWidget,
	bottomWidget,
	_idRoute = 0,
	_route;

// Profile widget
(function () {

	__wid.Profile = function() {
		
		var _getProfile = function() {
			$.get('/get_profile', function(json) {
				if(json.logedin == 'true') {
					// Set profile into global var
					_profile = new __model.Profile({
						name: json.user, 
						nickname: json.user, 
						email: json.email, 
						signoutUrl: json.logout_url,
						dataProvider: 'google'});
					_googleSignoutUrl = json.logout_url;
				} else {
					_profile = 'anonymous';
					_googleSigninUrl = json.login_url;
				}
				
				// Publish event 'profile_read'
				$.Topic('profile_read').publish(_profile);
			});
			
		};
		
		return {
			// Returns the Profile model if the user is logged in
			getProfile: _getProfile			
		}		
	}
	
})();

// Signin widget
(function () {
	
	var openGoogleSiginPopup = function() {
		window.open(_googleSigninUrl, 'Login', 'height=500, width=500');
	},
	
	_handleLinks = function() {
		var liclass = $(this).attr('class');
		var pageInstance;
		
		if (liclass == 'myroutes') {
			pageInstance = new __pages.MyRoutes();
		}
		if(__pages.isOpen()) {
			__pages.hidePage();
		} else {
			__pages.openPage(liclass, pageInstance, {load_main: true});
		}
	};
	
	__wid.Signin = {
		
		attach: function() {
			var isLoggedIn = (_profile !== 'anonymous' && _profile.get().email) ? true : false,
				json,
				interval;
				
			if(isLoggedIn)
				json = {loggedin: isLoggedIn, name: _profile.get().name};
			else
				json = {loggedin: isLoggedIn};
				
			__ca.utils.renderTemplate('signin_header.html', json, function(html) {
				$('#signin_placeholder').html(html);
				if(isLoggedIn) {
					$('#logout').click(function() {
						$.post(_googleSignoutUrl);
						setTimeout(function() {
							__wid.Profile().getProfile();
							_profile = 'anonymous';
							__alert.showAlert({type: 'success', msg: 'You are now loged out.'});
						}, 1200);
					});
					$('.header_profile li').click(_handleLinks);
				} else {
					$('#google_signin').click(function() {
						openGoogleSiginPopup();
					});
				}
			});
		},
		
		openGoogleSigninPopup: function() {
			openGoogleSiginPopup();
		}
	};
	
})();

// Search widget
(function () {
	
	__wid.Search = __search = function() {
		
		var _isOpen,
		
		_attach = function(open) {
			__ca.utils.renderTemplate('search.html', null, function(html) {
				$('#search_placeholder').html(html);
				if(open === true) {
					_open();
				} else {
					_close();
				}
				$('#search_placeholder .searchTab').click(function() {
					_open();
				});			
				
				$('#search_placeholder .hidesearch').click(function() {
					_close();
				});
				$('#search_btn').click(function() {
					_search($('#search_key').attr('value'));
				});
				$('#search_key').keydown(function(evt) {
					if(evt.keyCode == '13') {
						_search($('#search_key').attr('value'));
					}
				});
			});
		},
		
		_close = function() {
			$('#search_placeholder .search_box .content').fadeOut(500);
			$('#search_placeholder .search_box').slideUp(500, function() {
				$('#search_placeholder .searchTab').slideDown(500, function() {
					$('#search_placeholder .searchTab .content').fadeIn(500);
				});				
			});
			_isOpen = false;
		},
		
		_open = function() {
			$('#search_placeholder .searchTab .content').fadeOut(500);
			$('#search_placeholder .searchTab').slideUp(500, function() {
				$('#search_placeholder .search_box').slideDown(500, function(){
					$('#search_placeholder .search_box .content').fadeIn(500);
				});				
			});
			_isOpen = true;
		},
		
		_search = function(query) {
			_geocoding({reverse: false, search: query});
			_close();
		},
		
		_showSearchResults = function(locations) {
			var jsonlocs = [], json;
			for(var i=0; i<locations.length; i++) {
				jsonlocs.push({label: locations[i].label, lat: locations[i].lat, lng: locations[i].lng});
			}
			json = {locations: jsonlocs};
			__ca.utils.renderTemplate('search_results.html', json, function(html) {
				__utils.openModal(html, _bindEventsToModal);
			});
		},
		
		_bindEventsToModal = function() {
			$('#simplemodal-container li a').click(function() {
				var lat = $(this).attr('lat');
				var lng = $(this).attr('lng');
				$.modal.close();

				__map.setCenter(new __model.Location({lat: lat, lng: lng}), 13);
			});
		},
		
		_isOpen = function() {
			return _isOpen;
		},
		
		/*
		 *	Reverse Geocoding function.
		 *  @param params {
		 *  	loc: {Location},
		 *		search: {String},
		 *		reverse: {Boolean}
		 *  }
		 *  @param callback function reveiving one parameter
		 */
		_geocoding = function(params, callback) {
			var loc = params.loc,
				reverse = params.reverse,
				search = params.search,
				params,
				geocoder = new google.maps.Geocoder(),
				location,
				list = [],
				latLng;

			if(reverse) {
				latLng = new google.maps.LatLng(loc.lat, loc.lng);
				params = {'latLng': latLng};						
			} else {
				params = {'address': search};
			}

			geocoder.geocode(params, function(results, status) {
				if (status == google.maps.GeocoderStatus.OK) {
					if(search) {
						if(results.length > 1) {
							for(var i=0; i<results.length; i++) {
								location = results[i];
								list.push(__model.Location({
									lat: location.geometry.location.lat(),
									lng: location.geometry.location.lng(),
									label: location.formatted_address}));
							}
							_showSearchResults(list);
						} else if(results.length == 1) {
							location = results[0].geometry.location;
							__map.setCenter(new __model.Location({lat: location.lat(), lng: location.lng()}), 14);								
						}								
					} else {
						callback(results);
					}
				} else {
					__alert.showAlert({type: 'error', msg: "Geocode was not successful for the following reason: " + status});
				}
			});
		},
		
		// Handles the result from google geocoding and returns street name 
		// if found or city from Location object.
		_getLabelLocation = function(location) {
			var place, labelReturn;

			for(var i=0; i<location.length; i++) {
				labelReturn = location[i].formatted_address;
				if(labelReturn && labelReturn.substr(0, labelReturn.indexOf(' ')) * 1) {
					// Eliminates the number from address
					labelReturn = $.trim(labelReturn.substr(labelReturn.indexOf(' ')));
				}
				if(labelReturn.indexOf(',') >= 0) {
					labelReturn = $.trim(labelReturn.substr(0, labelReturn.indexOf(',')));
				}
				break;
			}

			return labelReturn;
		};
		
		return {
			attach: _attach,
			open: _open,
			close: _close,
			isOpen: _isOpen,
			geocoding: _geocoding,
			getLabelLocation: _getLabelLocation,
			bindEventsToModal: _bindEventsToModal
		}
	}
	
})();

// Closure alert widget
(function () {
	var template = "alert.html";
	
	__wid.Alert = __alert = {
		/* Shows alert
		 * @params {
		 *    type {String} alert / success / error
		 *	  msg  {String}
		 * }
		 */
		showAlert : function(params) {
			var json = {type: params.type, msg: params.msg},
				elem;
			__ca.utils.renderTemplate(template, json, function(html) {
				elem = $('#alert_placeholder');
				
				$(elem).html(html);
				//$(elem).css('right', (($(window).width() / 2) + 140) + 'px');
				$('#alert_placeholder .alert').addClass(params.type).slideDown(500);

				setTimeout(function() {
					$('#alert_placeholder .alert').slideUp(500, function(){
						//$('#alert_placeholder').empty();	
					});
				}, 4000);
			});			
		}
	};
})();

// Pages widget
(function () {
	var _selector = '#map_canvas, #controls_placeholder, #distance_placeholder',
		_open = false,
	
	_callback = function(html, pageInstance) {	
		$('#page_placeholder').html(html);
		pageInstance.bindEvents();
	},
	
	_signinPage = function(html) {
		$('#page_placeholder').html(html);
		var interval = setInterval(function() {
			console.info('Interval running');
			if(!window.frames[0].location.href.endsWith('continue=/callback')) {
				window.clearInterval(interval);
				// TODO: validate if login is successful and load user's data
				__wid.Profile().getProfile();
			}
		}, 1000);
	},
	
	_openPage = function(page, pageInstance, page_params) {
		$(_selector).css('display', 'none');
		$('body').css('background-color', '#382D2C');
		$('#header').animate({height: '100%'}, 1000, function() {
			$('#page_placeholder').empty();
			__ca.utils.renderTemplate(page + '_page.html', page_params, _callback, pageInstance);
		});
		_open = true;
	},

	_hidePage = function() {
		$('.sprite.backtomap').css('display', 'none');
		$('#header').animate({height: '38px'}, 1000, function() {
			$('#page_placeholder').empty();
			$('#page_placeholder .main_content').empty();
			$('body').css('background-color', 'white');
			$(_selector).css('display', 'block');
		});
		_open = false;
		__map.adjustMapSize();
	},
	
	_isOpen = function() { 
		return _open; 
	};
	
	__ca.pagecontent = __pages = {
		openPage: _openPage,
		hidePage: _hidePage,
		isOpen: _isOpen
	};
})();

// Location closure
(function () {
	
	__model.Location = function(location) {
		var lat = location.lat,
			lng = location.lng,
			label = location.label,
			index = location.index;
			
		return {
			lat: lat,
			lng: lng,
			label: label,
			index: index,
			getLat: function() {
				return lat;
			},
			getLng: function() {
				return lng;
			},
			setLabel: function(value) {
				label = value;
			},			
			getLabel: function() {
				return label;
			},
			getIndex: function() {
				return index;
			},
			equals: function(other) {
				return (other.getLat() == lat && other.getLng() == lng && other.getIndex() == index);
			}
		}
	};
	
})();

// Profile model
(function () {
	
	__model.Profile = function(params) {
		var name,
			nickname,
			email,
			login_url,
			logout_url,
			// Provider can be: google, facebook
			dataProvider;
		
		var _set = function(params) {
			if(params) {
				if(params.name)
					name = params.name;
				if(params.nickname)
					nickname = params.nickname;
				if(params.email)
					email = params.email;
				if(params.dataProvider)
					dataProvider = params.dataProvider;
				if(params.login_url)
					login_url = params.login_url;
				if(params.logout_url)
					logout_url = params.logout_url;
			}
		},
		
		_get = function() {
			var loggedIn = (name ? true : false);
			return {name: name, nickname: nickname, email: email, dataProvider: dataProvider, loggedIn: loggedIn};
		};
		
		_set(params);
		
		return {
			set: _set,
			get: _get,
			isLoggedIn: function() {
				return (name !== 'anonymous' && email);
			}
		}
	}
	
})();

/* Controls widget closure */
(function () {
	
	__wid.Controls = function() {
		// Can be "start", "end" or undefined
		var _activeIcon = undefined,
			_mouseEventBinding,
		
		_getActiveIcon = function() {
			return _activeIcon;
		},
		
		_setLabelStart = function(text) {
			$('#cancel_start_point_label').html(text);
		},

		_setLabelEnd = function(text) {
			$('#cancel_end_point_label').html(text);
		},
		
		_showViapoints = function(locations) {
			var json, locs = [];
			
			if(locations) {
				for(var i=0; i<locations.length; i++) {
					locs.push({viapoint: locations[i].label, id: locations[i].index});
				}
				
				json = {viapoints: locs};
				
				__ca.utils.renderTemplate('waypoint_panel.html', json, function(html) {
					$('.controls li.viapoints').remove();
					if(html && html.length > 0) {
						$('.start_li').after(html);
						$('.controls li div.delete').click(function() {
							__ca.mapcore.mapquestapi.removeViapoint($(this).attr('id'));
						});
						$('#controls ul li:nth-child(2)').addClass('viapoints_firstelem');
					}
				});
			}
		},
		
		/* Set icon state.
		   @param state - 'onmap'/'cancel'
		   @param iconName = 'start'/'end'
		*/
		_setIconState = function(params) {
			var state = params.state,
				iconName = params.iconName;
			
			if(_mouseEventBinding)
				_mouseEventBinding.unbind();
			
			// Unbind keydown events (ESC event)
			$(document).unbind('keydown');
			
			if(state && state === 'onmap') {
				if(iconName === 'start') {
					$('#add_start_point').empty();
					$('#start_icon').css('display', 'none');
				} else if(iconName === 'end') {
					$('#add_end_point').empty();
					$('#stop_icon').css('display', 'none');
				}
				_activeIcon = undefined;
			} else if (state && state === 'cancel') {
				if(iconName === 'start') {
					$('#start_icon').css('display', 'none');
					$('#add_start_point').css('display', 'block');
					$('#cancel_start_point_label').empty();
				} else if(iconName === 'end') {
					$('#stop_icon').css('display', 'none');
					$('#add_end_point').css('display', 'block');
					$('#cancel_end_point_label').empty();
				}
				_activeIcon = undefined;
			}
		},
		
		// Init controls' events
		_init = function() {
			$('#add_start_point').click(function() {
				$('#add_start_point').css('display', 'none');
				$('#start_icon').css('display', 'block');
				$('#cancel_start_point_label').html('ESC to cancel');
				_mouseEventBinding = $('#map_canvas').mousemove(function(event) {
					$('#start_icon').css('top', event.pageY - 47);
					$('#start_icon').css('left', event.pageX - 15);
				});
				_activeIcon = "start";
				// Attach event listener for esc keypress
				$(document).keydown(function(event){
					if(event.keyCode == 27) {
						_setIconState({state: 'cancel', iconName: 'start'});
					}
				});
			});
			$('#add_end_point').click(function() {
				if(_getActiveIcon() === 'start') {
					alert('Add a Start point first.');
					return;
				}
				$('#add_end_point').css('display', 'none');
				$('#stop_icon').css('display', 'block');
				$('#cancel_end_point_label').html('ESC to cancel');
				_mouseEventBinding = $('#map_canvas').mousemove(function(event) {
					$('#stop_icon').css('top', event.pageY - 47);
					$('#stop_icon').css('left', event.pageX - 15);
				});
				_activeIcon = "end";
				// Attach event listener for esc keypress
				$(document).keydown(function(event){
					if(event.keyCode == 27) {
						_setIconState({state: 'cancel', iconName: 'end'});
					}
				});						
			});
			$('#clearmap').click(function() {
				__map.clear();
			});
			
			$('#save_button').click(function() {
				if(!_profile || _profile == 'anonymous') {
					// Open signin popup
					// TODO: After siginin, redirect to save map
					__wid.Signin.openGoogleSigninPopup();
				} else {
					_showSaveDialog();
				}
			});
		},
		
		_getRouteJson = function() {
			var name = document.getElementById('route_name').value;
			var waypoints = [];
			var type = 's';
			var route;
			var centerLoc = __map.getCenter();
			
			if(directions && directions.waypoints) {
				for(var i=0; i<directions.waypoints.length; i++) {
					waypoints.push({lat: directions.waypoints[i].lat(),
						lng: directions.waypoints[i].lng(),
						type: type,
						index: i});
				}
				
				route = {id: _idRoute, 
						name: name, 
						distance: directions.getDistance(),
						distance_unit: _unit,
						waypoints: waypoints,
						path: _getCompletePath(),
						center: centerLoc.lat+'|'+centerLoc.lng};
			}
			return route;
		},
		
		_getCompletePath = function() {
			var path = '',
				route = directions.getRoute(0),
				coordinate,
				coordinates = [];
			
			for(var step=0; step<route.steps.length; step++) {
				for(var latlng=0; latlng<route.steps[step].latlngs.length; latlng++) {
					coordinate = route.steps[step].latlngs[latlng];
					path = coordinate.lat() + ',' + coordinate.lng();
					coordinates.push(path);
				}
			}
			
			return coordinates.join('|');
		},
		
		_bindSaveEvents = function() {
			$('#save_route_button').click(function() {
				_route = _getRouteJson();
				$.post('/route/new', {route: JSON.stringify(_route)}, function(data) {
					__utils.closeModal();
					if(data.status == 'success') {
						_idRoute = data.id;
						$('#controls .route_name').html(_route.name);
						__alert.showAlert({type: 'success', msg: 'Route successfully saved.'});
					} else {
						__alert.showAlert({type: 'error', msg: 'Sorry, we could not save the route. Please, try again.'});
					}
				}, "json");
			});
		},
		
		_showSaveDialog = function() {
			__ca.utils.renderTemplate('savedialog.html', {route_name: (_route) ? _route.name : ''}, function(html) {
				__utils.openModal(html, _bindSaveEvents);
			});
		},
		
		_attach = function(params) {
			var template = "controls.html",
				json;
			
			// Init vars
			_activeIcon = undefined;
			_mouseEventBinding = undefined;
			
			__ca.utils.renderTemplate(template, json, function(html) {
				$('#controls_placeholder').html(html);
				_init();
			});
		}
		
		return {
			attach: _attach,
			getActiveIcon: _getActiveIcon,
			setIconState: _setIconState,
			setLabelStart: _setLabelStart,
			setLabelEnd: _setLabelEnd,
			showViapoints: _showViapoints
		}
	};	

})();

/* WaypointsPanel widget closure */
(function () {
	
	__wid.WaypointsPanel = function() {
		var instance;
		var _removePanel = function() {
			$('#page_placeholder').empty();
		},
		
		/*
			@param data {
				locations	{Array}
				geoCaching	{Array}
			}
		*/
		_update = function(data) {
			var tmpl = "waypoint_panel.html",
				locations = data.locations,
				geoCaching = data.geoCaching,
				json,
				html = Mustache.to_html(tmpl, data);

			json = JSON.stringify(locations);
			Utils.renderTemplate(tmpl, json, function(html) { 
				$('#page_placeholder').html(html); 
				$('#info #remove_all').click(function(event) {
					__map.drawRoute();
				});					
			});
		}
		
		return {
			removePanel: _removePanel,
			update: _update
		}
	};
	
})();

/* DistancePanel widget closure */
(function () {
	
	__wid.DistancePanel = function() {
		var unitLabel;
		
		/*
		 * Updates the distance panel.
		 */
		var _updateUnitLabel = function() {
			if(_unit === 'k')
				unitLabel = ' km';
			else
				unitLabel = ' mi';
		},
		
		_getOtherUnit = function() {
			if(_unit === 'k')
				return 'm';
			else
				return 'k';
		},
		
		_update = function() {
			var otherUnit;
			
			_updateUnitLabel();
			$("#distance #value").html(__ca.utils.roundNumber(_distance, 2) + unitLabel);
			
			if(_getOtherUnit() === 'k')
				otherUnit = 'km';
			else 
				otherUnit = 'mi';
			
			$("#switch").html(otherUnit);
		},
		
		// Switch units, from km to mi or vice-versa, and updates the distance panel
		_switchUnit = function() {
			// km to mi
			if(!_distance) _distance = 0;
			
			if(_unit === 'k') {
				_unit = 'm';
				_distance = _distance / 1.609344;
			} else {
				_unit = 'k';
				_distance = _distance * 1.609344;
			}
			_update();
		},
		
		_attach = function() {
			var otherUnit;
			
			_updateUnitLabel();
			
			if(_getOtherUnit() === 'k')
				otherUnit = 'km';
			else 
				otherUnit = 'mi';
				
			__ca.utils.renderTemplate("distance_panel.html", {unit: unitLabel, otherUnit: otherUnit}, function(html) { 
				$("#distance_placeholder").html(html);
				$("#switch").click(function() {
					_switchUnit();
				});
			});
		};
		
		_updateUnitLabel();
		
		return {
			attach: _attach,
			update: _update,
			switchUnit: _switchUnit
		}
	};
	
})();

/* Utils namespace closure */
(function () {
	String.prototype.startsWith = function(prefix) {
	    return this.indexOf(prefix) === 0;
	}

	String.prototype.endsWith = function(suffix) {
	    return this.match(suffix+"$") == suffix;
	};
	
	var loadTemplate = function(templateName, callback) {
        // This loads template
        $.get('/templates/' + templateName, callback);
    };
	
	__ca.utils = __utils = {
				
		roundNumber: function roundNumber(rnum, rlength) {
			var newnumber = Math.round(rnum*Math.pow(10,rlength))/Math.pow(10,rlength);
			return parseFloat(newnumber);
		},
		
	    renderTemplate: function(templateName, data, callback, opt1) {
	        loadTemplate(templateName, function (template) {
	            callback(Mustache.to_html(template, data), opt1);
	        });
	    },

		openModal: function(html, callback) {
			// Append html to DOM
			$('#modal_placeholder').html(html)
			$("#modal_placeholder").modal({overlayClose:true});
			$('#simplemodal-container').addClass('round carbonfibre');
			$('#simplemodal-container .close').click(function() {
				$.modal.close();
			});	
			if(callback)
				callback();
		},
		
		closeModal: function() {
			$.modal.close();
		},
		
		resizeMapCanvas: function() {
			var divHeight = $('#map_canvas').height();
			var divWidth = $('#map_canvas').width();
			if(divWidth !== $(window).width()-2 || divHeight !== $(window).height()-48) {
				$('#map_canvas').css('height', $(window).height() - 48+'px');
				$('#map_canvas').css('width', $(window).width() - 2+'px');
			}
		},
		
		validateEmail: function(email) { 
		    var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\.+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
		    return re.test(email);
		}

	};
})();

/* CloudMade closure */
(function () {
	
	var pois = [];
	
	var _initialize = function() {
		var cloudmade = new CM.Tiles.CloudMade.Web({key: 'fdb8465660e144ea9e3817d0c2ca0b58',  styleId: 51814});
	    map = new CM.Map('map_canvas', cloudmade);
	    map.setCenter(new CM.LatLng(53.335161,-6.301572), 6);
	
		_bindEventsToMap();
	},
	
	_addMapControls = function() {
		map.addControl(new CM.LargeMapControl());	
	},
	
	_bindEventsToMap = function() {
		CM.Event.addListener(map, 'click', function(latlng){
			var loc = __model.Location({lat: latlng.lat(), lng: latlng.lng()}),
				activeIcon = controlsWidget.getActiveIcon(),
				icon,
				poi;
							
			if((activeIcon === "start" || activeIcon === "end") && pois.length <= 2) {
				if(activeIcon === 'start') {
					icon = _startIcon;
				} else {
					icon = _endIcon;
				}
				
				_addPOI({location: loc, img: icon, key: activeIcon});
				controlsWidget.setIconState({state: 'onmap', iconName: activeIcon});
				if(pois.length == 2) {
					_drawRoute();
				}
			}
		});
	},
	
	_loadExternalRoute = function(params) {
		var startLoc = params.startLoc,
			endLoc = params.endLoc,
			path = params.path;
		
		_clear();
		
		_addPOI({location: startLoc, img: _startIcon, key: 'start'});
		_addPOI({location: endLoc, img: _endIcon, key: 'end'});		
		controlsWidget.setIconState({state: 'onmap', iconName: 'start'});
		controlsWidget.setIconState({state: 'onmap', iconName: 'end'});		
		_drawRoute();
	},
	
	_addPOI = function(params) {
		var location = params.location,
			title = params.title,
			content = params.content,
			img = params.img,
			key = params.key,
			icon,
			poi;
		
		icon = new CM.Icon();
		icon.image  = img;
		icon.iconSize = new CM.Size(32, 32);
		icon.iconAnchor = new CM.Point(16, 32);
		
		poi = new CM.Marker(new CM.LatLng(location.lat, location.lng), 
			{icon: icon,
			 title: title,
			 draggable: true,
			 clickable: true,
			 hoverable: true});

		// Sets location for POI on controls panel
		_setLocationControlPanel({loc: location, key: key});
		
		// Handle mouseup. Reloads address on controls panel
		CM.Event.addListener(poi, 'dragend', function() {
			var newLoc = new __model.Location({lat: this.getLatLng().lat(), lng: this.getLatLng().lng()});
			_setLocationControlPanel({loc: newLoc, key: key});
			if(pois.length >= 2) {
				_drawRoute();
			}
		});
		
		map.addOverlay(poi);
		pois.push(poi);
		
		return poi;
	},
	
	// Sets location for start and end POI's.
	_setLocationControlPanel = function(params) {
		var loc = params.loc,
			key = params.key;
			
		searchWidget.geocoding({loc: loc, reverse: true}, function(data) {
			if(key === 'start')
				controlsWidget.setLabelStart(searchWidget.getLabelLocation(data));
			else
				controlsWidget.setLabelEnd(searchWidget.getLabelLocation(data));
		});
	},
			
	_clear = function() {
		map.clearOverlays();
		pois = [];
		_idRoute = 0;
		_distance = 0;
		_time = undefined;
		// Re-attach reset widgets
		controlsWidget.attach();
		distanceWidget.attach();		
	},
	
	
	/*
	 *	Calculates directions on map.
	 */
	_drawRoute = function() {
		var latlngs = [];
		var unit = (_unit == 'k' ? 'km' : 'miles');
		directions = new CM.Directions(map, null, _cloudmadeApiKey);
				
		for(var i=0; i<pois.length; i++) {
			latlngs.push(new CM.LatLng(pois[i].getLatLng().lat(), pois[i].getLatLng().lng()));
		}

		// Remove previous routes
		for(var z=0; z<map._overlays.length; z++) {
			if(map._overlays[z].hasOwnProperty('weight')) {
				try {
					map._overlays[z].remove();
				} catch(err) {
					// Calling the remove function usually an error, but can be ignored.
				}
			}
		}
		
		directions.loadFromWaypoints(latlngs, 
			{draggableWaypoints: true,
				travelMode: 'bicycle',
				units: unit});

		for(var z=0; z<directions._markers.length; z++) {
			directions._markers[z].hide();
		}

		var interv = setInterval(function() {
			try {
				if(directions.getNumRoutes() > 0 || directions.getErrorMessages().length > 0) {					
					_distance = directions.getDistance() / 1000;
					distanceWidget.update();
					
					// Remove standard POIs from route
					for(var i=0; i<directions._markers.length; i++) {
						map.removeOverlay(directions._markers[i]);
					}
					window.clearInterval(interv);
				}
			} catch(err) {}
		}, 500);

	},
	
	_removeViapoint = function() {},
	
	_adjustMapSize = function() {
		var divHeight = $('#map_canvas').height();
		var divWidth = $('#map_canvas').width();
		if(divWidth !== $(window).width()-2 || divHeight !== $(window).height()-48) {
			$('#map_canvas').css('height', $(window).height() - 48+'px');
			$('#map_canvas').css('width', $(window).width() - 2+'px');
		}
		map.checkResize();
	},
	
	_adjustPageSize = function() {
		$('#header').animate({height: $(window).height()-70+'px'}, 500);
	},
	
	_setCenter = function(location, zoom) {
		map.setCenter(new CM.LatLng(location.lat, location.lng), zoom);
	},
	
	_getCenter = function() {
		return new __model.Location({lat: map.getCenter().lat(), lng: map.getCenter().lng() });
	};
	
	__map = cycleapp.mapcore.cloudmade = {		
		initialize: _initialize,
		loadExternalRoute: _loadExternalRoute,
		addMapControls: _addMapControls,
		clear: _clear,
		removeViapoint: _removeViapoint,
		adjustMapSize : _adjustMapSize,
		adjustPageSize: _adjustPageSize,
		setCenter: _setCenter,
		getCenter: _getCenter
	};
	
})();

/* Mapcore closure (Using Mapquest API) */
(function () {
	
	var routeController,
		pois,
		sessionId,
		mapState,
		viapointsCache = [],
		io,

	// Private vars/functions
	_initialize = function() {

		var options = { 
			elt:document.getElementById('map_canvas'),
			zoom:3,
			latLng:{lat:34.044954, lng:-31.890104},
			mtype:'map',
			bestFitMargin:0,
			zoomOnDoubleClick:true
		};

		// Construct an instance of MQA.TileMap with the options object
		window.map = new MQA.TileMap(options);
				
		// Bind events to map
		_bindEventsToMap();
		
		_createShapeCollection();		
	},

	_createShapeCollection = function() {
		// Create shape collection for poi storing
		MQA.withModule('shapes', function() {
			pois = new MQA.ShapeCollection();
			pois.collectionName = 'custom_pois';
			//pois.maxZoomLevel=17;
			map.addShapeCollection(pois);
		}); 		
	},

	_addMapControls = function() {
		MQA.withModule('largezoom', 'mousewheel', function() {

			map.addControl(
				new MQA.LargeZoom(),
				new MQA.MapCornerPlacement(MQA.MapCorner.TOP_LEFT, new MQA.Size(5,5))
			);

			map.enableMouseWheelZoom();
		});
		
	},

	_bindEventsToMap = function() {
		MQA.EventManager.addListener(map, 'click', function(evt){		
			var loc = __model.Location({lat: evt.ll.lat, lng: evt.ll.lng}),
				activeIcon = controlsWidget.getActiveIcon(),
				icon,
				poi;
				
			if((activeIcon === "start" || activeIcon === "end") && pois.getSize() <= 2) {
				if(activeIcon === 'start') {
					icon = _startIcon;
					poi = _addPOI({location: loc, img: icon, key: activeIcon});
				} else {
					icon = _endIcon;
					poi = _addPOI({location: loc, img: icon, key: activeIcon});
				}
				
				controlsWidget.setIconState({state: 'onmap', iconName: activeIcon});
				if(pois.getSize() == 2) {
					_drawRoute();
				}
			}
		});
	},
	
	_addPOI = function(params) {
		var location = params.location,
			title = params.title,
			content = params.content,
			img = params.img,
			icon,
			poi = new MQA.Poi({lat: location.lat, lng: location.lng});
			
		if(title) poi.setInfoTitleHTML(title);
		if(content) poi.setInfoContentHTML(content);
		if(img) {
			icon = new MQA.Icon(img, 37, 37);
			poi.setIcon(icon);
		}
		if(params.key) poi.setKey(params.key);
		poi.setDraggable(true);
		poi.setMaxZoomLevel(15);
		poi.setIconOffset(new MQA.Point(-18,-37));
		poi.maxZoomLevel = 18;
		poi.minZoomLevel = 1;
		// Sets location for POI on controls panel
		_setLocationControlPanel({loc: location, key: poi.getKey()});
		
		// Handle mouseup. Reloads address on controls panel
		MQA.EventManager.addListener(poi, 'mouseup', function(evt) {
			var key = evt.srcObject.getKey(),
				latLng = evt.srcObject.getLatLng(),
				newLoc = new __model.Location({lat: latLng.lat, lng: latLng.lng});
				
			_setLocationControlPanel({loc: newLoc, key: key});
		});
		
		map.addShape(poi);
		pois.add(poi);
		
		return poi;
	},
	
	// Sets location for start and end POI's.
	_setLocationControlPanel = function(params) {
		var loc = params.loc,
			key = params.key;
			
		searchWidget.geocoding({loc: loc, reverse: true}, function(data) {
			if(key === 'start')
				controlsWidget.setLabelStart(_getLabelLocation(data, false));
			else
				controlsWidget.setLabelEnd(_getLabelLocation(data, false));
		});
	},
		
	// Returns street name if found or city from Location object
	_getLabelLocation = function(location, isMapquest) {
		var place, labelReturn;
		
		if(isMapquest) {
			place = location.street;
			labelReturn = place;
			if(place) {
				// If the first segment is number, ignore it and return just the street name
				try {
					if(place.substr(0, place.indexOf(' ')) * 1) {
						labelReturn = $.trim(place.substr(place.indexOf(' ')));
					}
				} catch (err){}
			} else if(location.adminArea5) {
				labelReturn = location.adminArea5;
			} else {
				labelReturn = '<No name found>';
			}	
		} else {
			for(var i=0; i<location.length; i++) {
				labelReturn = location[i].formatted_address;
				if(labelReturn && labelReturn.substr(0, labelReturn.indexOf(' ')) * 1) {
					// Eliminates the number from address
					labelReturn = $.trim(labelReturn.substr(labelReturn.indexOf(' ')));
				}
				if(labelReturn.indexOf(',') >= 0) {
					labelReturn = $.trim(labelReturn.substr(0, labelReturn.indexOf(',')));
				}
				break;
			}			
		}
		
		return labelReturn;
	},
	
	_getCachedViapoint = function(params) {
		if(viapointsCache && viapointsCache.length > 0) {
			for(var i=0; i<viapointsCache.length; i++) {
				if(viapointsCache[i].lat === params.lat && viapointsCache[i].lng === params.lng) {
					return viapointsCache[i];
				}
			}
		}
	},
	
	/*
	 *	Calculates directions on map.
	 */
	_drawRoute = function() {
		(function() {		
			MQA.withModule('route','routeio', function() {
			/*Creates an MQA.RouteIO instance giving the endpoint of our new Directions Service as the
			first parameter and true that we will not use a proxy as the second (enables JSONP support to
			avoid server side proxies for route request and results communication).*/
			io = new MQA.RouteIO(MQROUTEURL,true);

			/*Creates an MQA.RouteDelegate for defining default route appearance and behavior.*/
			delegate = new MQA.Route.RouteDelegate();

			/*The delegate is also required to customize the pois, in this sample adds rollover content*/
			delegate.customizePoi = function(thePoi) {
				var icon;
				if(thePoi.location.type === 's') {
					if(thePoi.stopNumber === 1) {
						icon = new MQA.Icon(_startIcon,37,37);
						thePoi.setKey('start');
						thePoi.setRolloverContent("Start Point");
					} else {
						icon = new MQA.Icon(_endIcon,37,37);
						thePoi.setKey('end');
						thePoi.setRolloverContent("End Point");
					}
					thePoi.setIcon(icon);		
				}
			};
			delegate.recomputeChangedRoute = function(newLocations) {
				_route(io, newLocations);
				controlsWidget.showViapoints(_getViapointsFromMapLocations(newLocations));
			};
			delegate.customizeRibbon = function(ribbonLineOverlay) {
				ribbonLineOverlay.color = "#6C2DC7";
				ribbonLineOverlay.fillColor = "#6C2DC7";
				ribbonLineOverlay.colorAlpha = .8;
				ribbonLineOverlay.borderWidth = 14;
			};
			
			routeController = map.createRoute(delegate, io);
			routeController.draggable = true;
			routeController.poidrag = true;

 			routeController.onPoiDrop = function(poi, cancelled) {
				var newLocations = routeController.routeData.locations,
					loc = new __model.Location({lat: poi.latLng.lat, lng: poi.latLng.lng});
					
				newLocations[poi.locationIndex].latLng = poi.latLng;
				_route(io, newLocations);
				
				if(poi.location.type === 'v') {
				//	controlsWidget.showViapoints(_getViapointsFromMapLocations(routeController.routeData.locations));				
				} else {
					searchWidget.geocoding({loc: loc, reverse: true}, function(data) {					
						if(poi.getKey() === 'start')
							controlsWidget.setLabelStart(_getLabelLocation(data));
						else
							controlsWidget.setLabelEnd(_getLabelLocation(data));						
					});					
				}
			};
			
			_route(io);
			});
		})();
	},
	
	_getViapointsFromMapLocations = function(newLocations) {
		var viapoints = [], cachedViapoint;
		for(var i=0; i<newLocations.length; i++) {
			if(newLocations[i].type === 'v') {
				cachedViapoint = _getCachedViapoint({lat: newLocations[i].latLng.lat, lng: newLocations[i].latLng.lng});
				if(cachedViapoint) {
					cachedViapoint.index = newLocations[i].linkId;
					viapoints.push(cachedViapoint);
				} else {
					cachedViapoint = __model.Location({
						label: _getLabelLocation(newLocations[i], true), 
						lat: newLocations[i].latLng.lat, 
						lng: newLocations[i].latLng.lng, 
						index: newLocations[i].linkId});
					viapoints.push(cachedViapoint);
					viapointsCache.push(cachedViapoint);						
				}
			}
		}
		
		return viapoints;
	},
	
	_removeViapoint = function(linkId) {
		var mapLocations = routeController.routeData.locations,
			newLocations = [];
		for(var i=0; i<mapLocations.length; i++) {
			if(mapLocations[i].linkId != linkId) {
				newLocations.push(mapLocations[i]);
			}
		}
		
		_route(io, newLocations);
		controlsWidget.showViapoints(_getViapointsFromMapLocations(newLocations));
	},
	
	_route = function(io, newLocations) {
		var firstParam,
			directionsLocations,
			tmpMapState;

		directionsLocations = _getDirectionsLocations(newLocations);
		
		if(!newLocations) {
			tmpMapState = routeController.delegate.virtualMapState(map);
		} else {
			tmpMapState = mapState;
		}
		
		firstParam = {locations: directionsLocations, 
			mapState: tmpMapState, 
			options: {shapeFormat: 'cmp6', unit: _unit, routeType: 'shortest', narrativeType: 'none'},
		};

		io.route(
			firstParam,
			{timeout: 10000},
			function(results){
				var status = results.info.statuscode;
				if(status == 0) {
					sessionId = results.route.sessionId;
					_distance = results.route.distance;
					mapState = results.route.mapState;
					_time = results.route.time; // in seconds
					distanceWidget.update();
					routeController.setRouteData(results.route);
					// Remove initial poi's
					pois.removeAll();
					//map.bestFit();				
				} else {
					__alert.showAlert({type: 'error', msg: results.info.messages[0]});
				}
			});
	},
	
	_getDirectionsLocations = function(newLocations) {
		var arrayReturn = [], latLng, locations = [];
		if(!newLocations) {
			for(var i=0; i< pois.getSize(); i++) {
				latLng = pois.getAt(i).latLng;
				locations.push({latLng: {lat: latLng.lat, lng: latLng.lng}});
			}
		} else {
			for(var i=0; i<newLocations.length; i++) {
				latLng = newLocations[i].latLng;
				locations.push({
					linkId: newLocations[i].linkId,
					latLng: {lat: latLng.lat, lng: latLng.lng},
					dragPoint: newLocations[i].dragPoint,
					type: newLocations[i].type
				});
			}
		}
		return locations;
	},
	
	/*
	 * Clears the map.
	 */
	_clear = function() {
		if(routeController) routeController.dispose();
		if(pois) pois.removeAll();
		_distance = 0;
		_time = undefined;
		// Re-attach reset widgets
		controlsWidget.attach();
		distanceWidget.attach();
	},
	
	_adjustMapSize = function() {
		var size = map.getSize();
		if(size.getWidth() !== $(window).width()-2 || size.getHeight() !== $(window).height()-48) {
			map.setSize(new MQA.Size($(window).width()-2, $(window).height() - 48));
		}
	},
	
	_adjustPageSize = function() {
		$('#header').animate({height: $(window).height()-70+'px'}, 500);
	},

	_setCenter = function(location, zoom) {
		map.setCenter({lat: location.lat, lng: location.lng});
		map.setZoomLevel(zoom);
	};

	// Everything inside the namespace is visible from outside
	__ca.mapcore.mapquestapi = {
		initialize: _initialize,
		addMapControls: _addMapControls,
		clear: _clear,
		removeViapoint: _removeViapoint,
		adjustMapSize: _adjustMapSize,
		adjustPageSize: _adjustPageSize,
		setCenter: _setCenter
	};
	
})();

/* Openlayers */
(function() {
	var pois, poisFeature;
    var SHADOW_Z_INDEX = 10;
    var MARKER_Z_INDEX = 11;
    
    var DIAMETER = 200;
    var NUMBER_OF_FEATURES = 15;
	
	var _initialize = function() {
		map = new OpenLayers.Map('map_canvas', {allOverlays: true});
	    map.addControl(new OpenLayers.Control.LayerSwitcher());

	    // the SATELLITE layer has all 22 zoom level, so we add it first to
	    // become the internal base layer that determines the zoom levels of the
	    // map.
	    var gsat = new OpenLayers.Layer.Google(
	        "Google Satellite",
	        {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22, visibility: false}
	    );
	    var gphy = new OpenLayers.Layer.Google(
	        "Google Physical",
	        {type: google.maps.MapTypeId.TERRAIN, visibility: false}
	    );
	    var gmap = new OpenLayers.Layer.Google(
	        "Google Streets", // the default
	        {numZoomLevels: 20}
	    );
	    var ghyb = new OpenLayers.Layer.Google(
	        "Google Hybrid",
	        {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 22, visibility: false}
	    );
		
		// allow testing of specific renderers via "?renderer=Canvas", etc
        var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
        renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
		
		pois = new OpenLayers.Layer.Vector("Markers",
		{
            styleMap: new OpenLayers.StyleMap({
                // Set the external graphic and background graphic images.
                externalGraphic: _startIcon,
                backgroundGraphic: "./img/marker_shadow.png",
                
                // Makes sure the background graphic is placed correctly relative
                // to the external graphic.
                backgroundXOffset: 0,
                backgroundYOffset: -7,
                
                // Set the z-indexes of both graphics to make sure the background
                // graphics stay in the background (shadows on top of markers looks
                // odd; let's not do that).
                graphicZIndex: MARKER_Z_INDEX,
                backgroundGraphicZIndex: SHADOW_Z_INDEX,
                
                pointRadius: 10
            }),
            isBaseLayer: true,
            rendererOptions: {yOrdering: true},
            renderers: renderer
        });
		/*
		poisFeature = new OpenLayers.Feature.Vector(
		 new OpenLayers.Geometry.Point(-6.301572, 53.335161),
		 {some:'data'},
		 {externalGraphic: _startIcon, graphicHeight: 21, graphicWidth: 16});
		pois.addFeatures(poisFeature);
		*/
	    map.addLayers([gsat, gphy, gmap, ghyb, pois]);

		// Add a drag feature control to move features around.
        var dragFeature = new OpenLayers.Control.DragFeature(pois);
		map.addControl(dragFeature);
		
		_bindEventsToMap();
	
		_setCenter(__model.Location({lat: 48.9, lng: 10.2}));

	},
	
	_bindEventsToMap = function() {
		//http://www.peterrobins.co.uk/it/olvectors.html
		map.events.register("click", map, function(e) {
			var latLon = map.getLonLatFromViewPortPx(e.xy),
				icon = _startIcon;
			//if(pois.markers.length > 0)
			//	icon = _endIcon;
				
			//if(pois.markers.length < 2) {
			//	_addPOI({latLon: latLon, img: icon});				
			//}
			//var markerLayer = new OpenLayers.Layer.Vector("Markers2");
			var feature = new OpenLayers.Feature.Vector(
			 new OpenLayers.Geometry.Point(latLon.lon, latLon.lat),
			 {some:'data'},
			 {externalGraphic: _startIcon});
			pois.addFeatures(feature);
			//map.addLayer(markerLayer);
		});
	},
	
	_addPOI = function(params) {
		var img = params.img,
			size = new OpenLayers.Size(32,37),
			offset = new OpenLayers.Pixel(-(size.w/2), -size.h),
			icon = new OpenLayers.Icon(img, size, offset);
		
			//marker = new OpenLayers.Marker(params.latLon, icon);
		//pois.addMarker(marker);
		//var point = new OpenLayers.Geometry.Point(params.latLon.lon, params.latLon.lat);
		//var feature = new OpenLayers.Layer.Vector("test");
		//map.addLayer(feature);
		//pois.addFeature(feature);
	},
	
	_drawRoute = function() {
		
	},
	
	_addMapControls = function() {
		
	},
	
	_adjustMapSize = function() {
		__utils.resizeMapCanvas();
	},
	
	_reverseGeocode = function() {
		
	},
	
	_setCenter = function(loc) {
		var location = new OpenLayers.LonLat(loc.lng, loc.lat);
	    // Google.v3 uses EPSG:900913 as projection, so we have to
	    // transform our coordinates
	    map.setCenter(location.transform(
	        new OpenLayers.Projection("EPSG:4326"),
	        map.getProjectionObject()
	    ), 5);
	},
	
	_getCenter = function() {
		
	};
	
	__map = cycleapp.mapcore.openlayers = {
		initialize: _initialize,
		drawRoute: _drawRoute,
		addMapControls: _addMapControls,
		adjustMapSize : _adjustMapSize,
		reverseGeocode: _reverseGeocode,
		setCenter: _setCenter,
		getCenter: _getCenter		
	};
	
});

/* Google Maps API */
(function () {
	
	var _originDestination = [],
		distanceService,
		geocoder,
	 	_markerImage,
		original_position,
		_markers = [],
		_waypoints = [],
		_locations = [],
		_origin,
		_destination,
		_cacheLocations = [],
		_startMarker,
		_endMarker,
	
	_initialize = function() {
		var latlng,
			geocoder = new google.maps.Geocoder(),
			latlng = new google.maps.LatLng(53.3330556, -6.2488889),

		myOptions = {
			zoom: 14,
			center: latlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP,
			streetViewControl: true,
			overviewMapControl: true,
			mapTypeControl: false
		};

		map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
		
		//var bikeLayer = new google.maps.BicyclingLayer();
		//bikeLayer.setMap(map);
		
		_markerImage = new google.maps.MarkerImage('images/sprite_map_items.png',
		  // This marker is 20 pixels wide by 32 pixels tall.
	      new google.maps.Size(11, 11),
	      // The origin for this image is 0,0.
	      new google.maps.Point(0,0),
	      // The anchor for this image is the base of the flagpole at 0,32.
	      new google.maps.Point(5, 5));

		_startMarker = new google.maps.MarkerImage('images/start.png',
	      new google.maps.Size(32, 37),
	      new google.maps.Point(0,0),
	      new google.maps.Point(16, 37));

		_endMarker = new google.maps.MarkerImage('images/finish.png',
	      new google.maps.Size(32, 37),
	      new google.maps.Point(0,0),
	      new google.maps.Point(16, 37));

		directionsService = new google.maps.DirectionsService();
		directionsDisplay = new google.maps.DirectionsRenderer({
			draggable: true, 
			clickable:	true, 
			suppressMarkers: true, 
			markerOptions: {icon: _markerImage, 
							animation: 'BOUNCE', 
							title: 'Click to remove'}});
		distanceService = new google.maps.DistanceMatrixService();

		directionsDisplay.setMap(map);

		_bindEventsToMap();

		controlsWidget.attach();
	},

	_bindEventsToMap = function() {
		google.maps.event.addListener(map, 'mousemove', function(event) {
			//console.debug('mouse moving map ' + event.latLng.lat());
		});		

		//Add click event on map
		google.maps.event.addListener(map, 'click', function(event) {
			var activeIcon = controlsWidget.getActiveIcon();
			if((activeIcon === "start" || activeIcon === "end") && _originDestination.length < 2) {
				_markers.push(_addMarker(event.latLng, activeIcon));
				controlsWidget.setIconState({state: 'onmap', iconName: activeIcon});

				//_addOriginDestination({location: event.latLng});
				
				if(_markers.length > 1) {
					_drawRoute();
				}
			}
		});
		/*
		// Listener for dragging event in directions
		google.maps.event.addListener(directionsDisplay, 'directions_changed', function() {
			var waypoints = _getWaypointsArray();

			if(waypoints && waypoints.length > 0) {
				_loadCaching(function(){
					waypointsWidget.update({geoCaching: _cacheLocations,locations: waypoints});
				});
			} else {
				waypointsWidget.removePanel();
			}

			// Updates origin and destination array
			if(_originDestination.length == 2) {
				_originDestination[0] = _getOrigin();
				_originDestination[1] = _getDestination();
			}

			distanceWidget.update({directionResult: directionsDisplay.getDirections()});
		});*/
	},

	_isWaypoint = function(lat,lng) {
		var waypts = _getWaypointsArray();
		if(waypts) {
			for(var i=0; i<waypts.length; i++) {
				if(waypts[i].lat == lat && waypts[i].lng == lng)
					return true
			}	
		}		
		return false;
	},

	_addOriginDestination = function(config) {
		_originDestination.push(config.location);
	},


	_addMarker = function(location, iconName) {
		var myMarker, loc;
		
		loc = new __model.Location({lat: location.lat(), lng: location.lng()});
		
		if(iconName === 'start') {
			myMarker = _startMarker;
		} else {
			myMarker = _endMarker;
		}
		_setLocationControlPanel({loc: loc, key: iconName});

		var marker = new google.maps.Marker({
			title: iconName,
			position: location,
			map: map,
			icon: myMarker,
			draggable: true
		});
		
		// Attach events
		google.maps.event.addListener(marker, 'mouseup', function(event) {
			var loc = new __model.Location({lat: event.latLng.lat(), lng: event.latLng.lng()});
			if(this.title === 'start'){
				_originDestination[0] = event.latLng;
			} else {
				_originDestination[1] = event.latLng;
			}
			_setLocationControlPanel({loc: loc, key: this.title});
			if(_markers.length > 1) {
				_drawRoute();
			}
		});
		
		return marker;
	},
	
	// Sets location for start and end POI's.
	_setLocationControlPanel = function(params) {
		var loc = params.loc,
			key = params.key;
			
		searchWidget.geocoding({loc: loc, reverse: true}, function(data) {
			if(key === 'start')
				controlsWidget.setLabelStart(searchWidget.getLabelLocation(data));
			else
				controlsWidget.setLabelEnd(searchWidget.getLabelLocation(data));
		});
	},

	_getWaypointsArray = function() {
		var waypoints = [], return_waypoints = [], location, lat, lng, streetName;

		if(directionsDisplay.directions && directionsDisplay.directions.routes) {
			waypoints = directionsDisplay.directions.routes[0].legs[0].via_waypoints;
			if(waypoints && waypoints.length > 0) {
				for(var i=0; i<waypoints.length; i++) {
					lat = waypoints[i].lat();
					lng = waypoints[i].lng();
					location = new types.Location({index: i,lat: lat, lng: lng});
					return_waypoints.push(location);
				}				
			}
		}
		return return_waypoints;
	},

	_loadCaching = function(callback) {
		var includeInCache = true,
			waypoints = _getWaypointsArray();

		if(waypoints && waypoints.length > 0) {
			for(var i=0; i<waypoints.length; i++) {
				_reverseGeocode(waypoints[i].lat, waypoints[i].lng, callback);
			}
		}	
	},

	/*
		Caches geolocation and street names.
	*/
	_reverseGeocode = function(lat, lng, callback) {
		var latlng = new google.maps.LatLng(parseFloat(lat), parseFloat(lng));

		geocoder = new google.maps.Geocoder();

		geocoder.geocode({'latLng': latlng}, function(results, status) {
			if (status == google.maps.GeocoderStatus.OK) {
				results.__proto__.latLng = latlng;
				_cacheLocations.push(results);
				callback();
			} else {
				alert('erro');
			}
		});
	},

	_getOrigin = function() {
		if(_markers) {
			return _markers[0].position;	
		}
	},

	_getDestination = function() {
		if(_markers) {
			return _markers[_markers.length-1].position;
		}
	},

	_getTotalDistance = function() {
		var routes = directionsDisplay.getDirections().routes,
			route, leg, distance = 0;
			
		for(var r=0; r<routes.length; r++) {
			route = routes[r];
			for(var l=0; l<route.legs.length; l++)  {
				leg = route.legs[l];
				distance = distance + leg.distance.value;
			}
		}
		
		return distance/1000;
	}

	/*
		Calculates directions on map.
	*/
	_drawRoute = function() {
		directionsService.route(_getDirectionsRequestObject(), function(result, status) {
			if(status == google.maps.DirectionsStatus.OK){
				directionsDisplay.setDirections(result);
				//distanceWidget.update({directionResult: result});
				_distance = _getTotalDistance();
				distanceWidget.update();
			}
		});
	},

	_adjustMapSize = function() {
		var divHeight = $('#map_canvas').height();
		var divWidth = $('#map_canvas').width();
		if(divWidth !== $(window).width()-2 || divHeight !== $(window).height()-48) {
			$('#map_canvas').css('height', $(window).height() - 48+'px');
			$('#map_canvas').css('width', $(window).width() - 2+'px');
		}
		google.maps.event.trigger(map, 'resize');
	},

	_addMapControls = function() {
		
	},

	/*
		Returns the DirectionsRequest with all the waypoints.
		@return {Object}
	*/
	_getDirectionsRequestObject = function() {
		var request, waypoints = [];

		request = {
			origin: _markers[0].position,
			destination: _markers[_markers.length-1].position,
			travelMode: google.maps.TravelMode.DRIVING,
			avoidTolls: true,
			avoidHighways: true,
			unitSystem: google.maps.UnitSystem.METRIC,
			waypoints: waypoints
		};

		return request;
	};
	
	__map = __ca.mapcore.googlemapsapi = {
		initialize: _initialize,
		drawRoute: _drawRoute,
		bindEventsToMap: _bindEventsToMap,
		addMapControls: _addMapControls,
		adjustMapSize : _adjustMapSize,
		reverseGeocode: _reverseGeocode
	};
	
})();

/* Header Right Links */
(function () {
	
	__wid.HeaderRightLinks = function() {
		
		function _attach() {
			var loggedIn;
			try {
				loggedIn = isLoggedIn = (_profile !== 'anonymous' && _profile.get().email) ? true : false;
			} catch (err) {
				loggedIn = false;
			}
			var json = {loggedIn: loggedIn};
			__ca.utils.renderTemplate('headerrightlinks.html', json, function(html) {
				$('#header_right_links_placeholder').html(html);
				// About button
				$('#navbar li#li_about, #navbar li div#about').click(function() {
					if(__pages.isOpen())
						__pages.hidePage();
					else
						__pages.openPage('about', new __pages.About());
				});				
			});
		}		
		
		return {
			attach: _attach
		}
		
	}
	
})();

/* Bottom Pane */
(function() {
	
	__wid.BottomPane = function() {
		var isOpen = false;
		
		function _attach() {
			__ca.utils.renderTemplate('bottompane.html', null, function(html) {
				$('#bottom_placeholder').html(html);
				isOpen = true;
				_bindEvents();
			});
		}
		
		function _openPage(page) {
			isOpen = true;
		}
		
		function _closePane() {
			$('#bottom_pane *').fadeOut(200);
			$('#bottom_pane').animate({height: '0px'}, 1000, function() {
				$('#bottom_placeholder').empty();
			});
			isOpen = false;
		}
		
		function _isOpen() {
			return isOpen;
		}
		
		function _bindEvents() {
			$('#send_email').click(function() {
				var email = document.getElementById('email').value;
				
				if(email && email.length > 0) {
					if(__utils.validateEmail(email)) {
						$.post('/newsletter/new', {email: document.getElementById('email').value}, function(data) {
							__alert.showAlert({type: 'success', msg: 'Thank you. You will receive the updates from CyclePlanner.'});
							document.getElementById('email').value = "";
						}, "json");						
					} else {
						__alert.showAlert({type: 'error', msg: 'Email address seems to be wrong. Please, enter it again.'});
					}
				} else {
					__alert.showAlert({type: 'error', msg: 'Please, fill in your email address.'});
				}
			});
			$('#close_news').click(function() {
				//_closePane();
			});
		}
		
		return {
			attach: _attach,
			isOpen: _isOpen,
			close: _closePane
		}
	};
	
})();

// About Page
(function() {
	
	__pages.About = function() {};
	
	__pages.About.prototype.bindEvents = function() {
		$('.sprite.backtomap').css('display', 'block');
		$('.sprite.backtomap').click(function(){
			__pages.hidePage('about');
		});
	}
	
})();

// MyRoutes Page
(function() {
	
	var _getWaypoints = function(route_id) {
		var i, coord, result = [];
		
		$.get('/waypoints', {route_id: route_id}, function(data) {
			for(i=0; i<data.coordinates.length; i++) {
				coord = data.coordinates[i];
				result.push(new __model.Location({lat: coord.lat, lng: coord.lng, index: coord.index}));
			}
			if(result) {
				__pages.hidePage()
				__map.loadExternalRoute({startLoc: result[0], endLoc: result[1]});
			}
		}, "json");
	};
	
	__pages.MyRoutes = function() {};
	
	__pages.MyRoutes.prototype.bindEvents = function() {
		var routes, waypoints;
		
		if(_profile && _profile.isLoggedIn()) {
			$.get('/myroutes', function(data) {
				if(data.status == 'success') {
					routes = data.routes;
					__ca.utils.renderTemplate('myroutes_page.html', {load_partial: true, routes: data.routes}, function(html) {
						$('#page_placeholder .main_content .content').html(html);
						$('.myroutes .routetitle').click(function() {
							var id;
							$(this).siblings().each(function() {
								if($(this).hasClass('id')) {
									id = $(this).attr('value');
								} 
							});
							_getWaypoints(id);
						});
					});
				}
				else
					__alert.showAlert({type: 'error', msg: 'Error while loading routes. Try again.'});
			});
		} else {
			
		}
	}
	
})();

// Search Routes Page
(function() {
	
	__pages.SearchRoutes = function() {};
	
	__pages.SearchRoutes.prototype.bindEvents = function() {
		alert('Binding events for search routes');
	}
	
})();

/* Static maps */
(function() {

	__wid.StaticMapGenerator = function() {
		var _generate = function(params) {
			var route = params.route,
				zoom = params.zoom,
				iconStart = params.iconStart,
				iconFinish = params.iconFinish,
				center = _getCenter(route),
				startFinish = _getStartFinish(route.path),
				url;
				
			url = 'http://staticmaps.cloudmade.com/' + _cloudmadeApiKey +
			'/staticmap?styleid=51814&zoom='+ zoom +'&' +
			'center='+ center +'&size=700x700&' +
			'path=color:blue|weight:5|opacity:1.0|' + route.path +
			'&marker=url:'+ iconStart + '|size:mid|label:A|'+ startFinish.start +'&' +
			'marker=url:'+ iconFinish + '|size:mid|label:B|'+ startFinish.finish;
			
			console.debug(url);	
			
			$.post('http://staticmaps.cloudmade.com/' + _cloudmadeApiKey + '/staticmap', {
				styleid: 51814,
				zoom: zoom,
				center: center,
				size: '700x700',
				path: 'color:blue|weight:5|opacity:1.0|' + route.path,
				marker: 'url:'+ iconStart + '|size:mid|label:A|'+ startFinish.start,
				marker: 'url:'+ iconFinish + '|size:mid|label:B|'+ startFinish.finish
			}, function(data) {
				alert(data);
			});
		},
		
		_getStartFinish = function(path) {
			var coordinates = path.split('|'),
				len = coordinates.length;
				last = coordinates[len-1];
			if($.trim(last).length == 0) {
				last = coordinates[len-2];
			}
			return {start: coordinates[0], finish: last};
		},
		
		_getCenter = function(route) {
			var c = route.center.split('|');
			return c[0] + "," + c[1];
		};
		
		return {
			generate: _generate
		}
	}
	
})();

// Cycleapp closure
(function () {
	
	controlsWidget = new __wid.Controls();
	distanceWidget = new __wid.DistancePanel();
	searchWidget = new __wid.Search();
	bottomWidget = new __wid.BottomPane();
	
	function _handleProfileEvent(data) {
		__wid.Signin.attach(data);
		__wid.HeaderRightLinks().attach();
		if(_profile && _profile.isLoggedIn) {
			if(bottomWidget && bottomWidget.isOpen()) {
				bottomWidget.close();
			}
		} else {
			bottomWidget.attach();
		}
		if(__pages.isOpen()) {
			__pages.hidePage();
		}
	}
	
	function _handleAdjustMap(data) {
		if(__pages.isOpen()) {
			//__utils.adjustPageSize();
		} else {
			__map.adjustMapSize();	
		}
	}
	
	__ca.loader = {
		init: function() {	
			// Added here just for landing page
			bottomWidget.attach();
					
			// Init map
			//__map.initialize();

			// Attach Controls widget
			//controlsWidget.attach();

			// Attach Distance panel widget
			//distanceWidget.attach();

			// Atach Search widget
			//searchWidget.attach(false);

			//__map.adjustMapSize();
			
			//__map.addMapControls();

			// Section to subscription to events
			//$.Topic('profile_read').subscribe(_handleProfileEvent);
			//$.Topic('window_resized').subscribe(_handleAdjustMap);
			
			// When window is resized, publish event 'window_resized'
			//$(window).resize(function() {
			//	setTimeout(function(){$.Topic('window_resized').publish();}, 500);
			//});
						
			// Trigger function to retrieve profile (if any)
			//__wid.Profile().getProfile();
		}
	};

	__ca.loader.init();
})();

}());
