//use json in the cookie
$.cookie.json=true;

//jquery reverse function
jQuery.fn.reverse = [].reverse;

var SERVERNAME=(location.href.indexOf("vision.sdsu.edu/hdma/")!=-1)?"vision":"ec2";
//global varible
var app={
	map:null,
	bookmarkMap:null,
	initCenterLatLng :[32.915, -117.0],
	initZoom:9,
	socket:io.connect("/socket-geoviewer",{resource:((SERVERNAME=='vision')?"hdma":"ec2") + "/socket/socket.io"}),
	layer:{
		markerCluster:null,
		heatmap:null,
		markerGroup:null,
		queryBoundary:null,
		maskCanvas:null,
		geocodeMarker:null,
	},
	layerStyle:{
		stroke: true,
		color: '#f06eaa',
		weight: 4,
		opacity: 0.5,
		fill: true,
		fillColor: null, //same as color by default
		fillOpacity: 0.2,
	},
	popup:null,
	maxTweetNum:100,
	drawHandler:null,
	control:{
		draw:null
	},
	blacklists:["Longdickya9","vincenvi619","vme7413", "uruguaydiablo", "just_sanch","bhaluPnMC","Sunnyrajkumar1","MoneyOrtega","lilcthebakedgod","BixberNavy","genitalia666","EarneyHarisson", "juanesp64250381", "skylerhavenxxx", "debmootoo"],
	user:{},
	icon:{
		getActive:function(type){
			type=type || ".icon-active";
			var $iconDefault=$("#mapPopup_markerGroup img"+type),
				src=$iconDefault.attr("src"),
				width=$iconDefault.attr("markerwidth"),
				height=$iconDefault.attr("markerheight");

				return new L.icon({
					iconUrl : src,
					iconSize : [width, height], //[12.5, 21],
					iconAnchor : [width / 2, height / 2]// [6.25, 10.5]
				});
		},
		geocodeMarker: new L.icon({
			iconUrl:"images/1428463247_add_cross_new_plus_create-128.png",
			iconSize: [15,15],
			iconAnchor: [10,10]
		})
	},
	ribbonClasses:$("#popup_tag #tag-color button").map(function(i,obj){
		var $obj=$(obj);
		return {className:$obj.data("class"), label: $obj.attr('title'), level:i}
	}),
	sentimentClasses:(function(){
		var output={};
			$("#popup_tag #tag-sentiment input").map(function(i, obj){
				var $obj=$(obj);
				output[$obj.val()]=$obj.siblings("i.fa").attr("class").split(" ")[1]
			})
		return output
	})(),
	tagTopics:{
		"disaster":{
			name:"Disaster",
			icon:"glyphicon-warning-sign", //"glyphicon-alert",
			labels:["None", "Safe","Info", "MedicalHelp", "Warning", "Danger"]
		},
		"election":{
			name:"Election",
			icon:"glyphicon-book",
			labels:["None", "Safe","Info", "Primary", "Warning", "Danger"]
		}
	},
	geomask:{
		enable:true,
		meter:100
	},
	geocoder:new google.maps.Geocoder(),
	filter:{},
	streaming:{
		num:0,
		$streamingBadge:$("ul.searchToolbar li.tbx-count span.badge"),
		$masonry:$(".searchResult .masonry").masonry({columnWidth:'.masonry-gridSize', itemSelector: 'li.tweetBox'}).on("click",".tweetBox", function(){}),
		options:{
			playPause:$(".searchToolbar .play-pause.active").attr("value") || "play",
			sort:"prepend",
			refreshTime:"realtime"
		},
		tweetsOnHold:[],
		$tweetsOnHold:$("searchToolbar span.onHoldTweets"),
		timeInterval:null,
		onMaxHoldTweets:null,
		sectionSearchWidth:$(".section-search").width(),
		minWidth:920
	},
	search:{
		results:null
	},
	heatmap:{
		radius:2000, //default heatmap radius (unit: meter)
		max:5,
		slider:null,
		webGL:false
	},
	reloadTwitterWidgetInteval:null,
	basemaps : hdma.layer.basemaps,
	topic:{},
	devMode:(location.href.indexOf("/dev/")!=-1)?true:false
}



//trigger when ready
$(function(){
	//test webgl
	app.heatmap.webGL=run.webgl_detect();

	//show intro modal
	$("#popup_intro").modal("show")

	//init
	$.each(init, function(k,v){
		v()
	})

});




//init
var init={
	//ui
	ui:function(){
		//init login
		//this.login();

		//dev mode
		if(!app.devMode){
			var hides=[
				//"nav .navbar-nav li a[data-value='tour']",  //tour
				".searchToolbar > li:nth-child(2)"          //analysis toolbar
			];
			$(hides.join(", ")).addClass("devMode");
		}





		//window resize
		$(window).on("resize", function(){
			$('.mainContent').outerHeight($(window).outerHeight() - $('.header').outerHeight());
			$('.searchResult').outerHeight($('.section-search').outerHeight() - $('.searchMenu').outerHeight() - $('.searchToolbar').outerHeight())

			//app.map.invalidateSize();
		});


		//when mouse click on other places >> hide
		$(document).mouseup(function(e) {
			var $container = $(".mapPopupWidget,#basemapWidget");
			if (!$container.is(e.target) && $container.has(e.target).length === 0) {
				$container.hide();
			}
		});

		//search result height
		$('.mainContent').outerHeight($(window).outerHeight() - $('.header').outerHeight());
		$('.searchResult').outerHeight($('.section-search').outerHeight() - $('.searchMenu').outerHeight() - $('.searchToolbar').outerHeight())


		//privacy policy
		$(".popup-content").click(function(){
			var target=$(this).attr('data-target');
			if(target&&target!=''){
				$(target).modal({});
			}
		})



		//sidebar menu click event
		$(".sidebar-menu a.sectionSwitch").click(function(){
			var target=$(this).attr("data-target");

			//hide all content
			$("section.content").removeClass('content-active');
			$("section#"+target).addClass("content-active").find("> iframe").height($(window).height()-60).css({"width":"100%", "border":"0px"});

			//resize map
			if(target=='contentMap'){
				app.map.invalidateSize();
			}
		});





		//search button
		$("#btn-search").click(function(){
			//run spatial query
			run.spatialQuery()
		})
		$("#btn-restore").click(function(){
			if(app.layer.queryBoundary){app.map.removeLayer(app.layer.queryBoundary); app.layer.queryBoundary=null;}
			$("#search-keyword, #search-startDate, #search-endDate, #search-geolocation").val("")
			run.spatialQuery()
		})
		$("#search-keyword").keydown(function(e){
			if(e.keyCode==13){run.spatialQuery()}
		})


		//search start and end data
		var mStartDate=moment("2014-12-06","YYYY-MM-DD"),
			mToday=moment(),
			dateFormat="YYYY-MM-DD";
		$("#search-startDate").attr({
			"placeHolder": "Only real-time tweets", //mStartDate.format(dateFormat),
			"value":"" //mStartDate.format(dateFormat)
		}).change(function(){
			if(this.value!=""&&$("#search-endDate").val()==""){
				$("#search-endDate").val(moment().format(dateFormat))
			}
		});
		$("#search-endDate").attr({
			"placeHolder": "Only real-time tweets",//mToday.format(dateFormat),
			"value":""//mToday.format(dateFormat)
		}).change(function(){
			if(this.value!=""&&$("#search-startDate").val()==""){
				$("#search-startDate").val(moment(this.value).subtract(1,"days").format(dateFormat))
			}
		});

		//search view mode
		$(".search-viewMode input[type='radio']").change(function(){
			run.spatialQuery();
		})



		//datepicker
		$(".datepicker").datepicker({
			format:"yyyy-mm-dd",
			todayBtn:"linked",
			endDate:new Date(),
			startDate:new Date(2014,11,6)
		});

		//boostrap-slider for heatmap
		app.heatmap.slider=$("#heatmap_slider").slider({
			min:100,
			max:5100,
			step: (5100-100)/100,
			value: app.heatmap.radius,
			enabled:true
		}).on({
			"slideStop": function(e){
				//clear heatmap layer
				var layer=app.layer.heatmap,
					value=parseInt(e.value);

				//change heatmap radius
				if(app.heatmap.webGL){
					layer.options.size=value;
					layer._plot();
				}else{
					value=value/111000;
					layer.changeRadius(value);
				}


				//layer.setData({radius:parseInt(e.value)/111000})
				//if(layer._heatmap){
					//layer._heatmap.repaint();
				//}

			}
		})




		//split the div
		var $mainContent=$(".mainContent");
		$(".section-search").resizable({
			handles:"e",
			minWidth:615, //$mainContent.width()*0.3,
			maxWidth:$mainContent.width()*0.7,
			resize: function(e, ui){
				var remainingSpace = $(this).parent().width() - $(this).outerWidth(true);
				var divTwo = $(this).next();
				var divTwoWidth = remainingSpace - ( divTwo.outerWidth(true) - divTwo.width() );
				divTwo.css( 'width', divTwoWidth + 'px' );
			},
			stop: function(e, ui){
				//resize map
				app.map.invalidateSize(false);

				//refresh heatmap layer
				var heatmapLayer=app.layer.heatmap;
				if(heatmapLayer&&!app.heatmap.webGL){
					//it may need to wait a while until the app.map finishes resizing.(not sure!!!!)
					setTimeout(function(){
						var size=app.map.getSize();
						heatmapLayer._width = size.x;
						heatmapLayer._height = size.y;
						heatmapLayer._el.style.width = size.x + 'px';
						heatmapLayer._el.style.height = size.y + 'px';
						heatmapLayer._heatmap._width = size.x;
						heatmapLayer._heatmap._height = size.y;
						heatmapLayer._heatmap._renderer.setDimensions(size.x, size.y);
						heatmapLayer._update();
					}, 10)
				}else{
					//refresh heatmap webgl size
					setTimeout(function(){
						heatmapLayer.resize();
						heatmapLayer.update();
					}, 10)
				}

				//detect section section width before showing the tweet
				run.setSearchSectionWidth();


				//refresh masonry
				$css=$(".masonry li.tweetBox, .masonry-gridSize")
				$css.css("width","33%")
				if($(".section-search").width()<app.streaming.minWidth){$css.css("width","50%")}
				app.streaming.$masonry.masonry("layout")
			}
		});


		//navbar
		$(".navbar-nav li").click(function(){
			var $this=$(this),
				value=$this.find("a").data('value');

			show.popup(value);


		})


		//click event for mapPopupWidget markerGroup
		$("#mapPopup_markerGroup .mapPopupWidget_content ul li").click(function() {
			var $img = $(this).find("img"), img_src = $img.attr("src"), width = $img.attr("markerWidth"), height = $img.attr("markerHeight");

			//change marker icon
			run.changeMarkerIcon(img_src, width, height);

			//add icon-default
			$(this).siblings().find('img').removeClass('icon-active');
			$img.addClass('icon-active')
		});

		//searchToolbar dropdown active
		$(".searchToolbar .dropdown-menu").on("click", " > li", function(){
			var $this=$(this),
				$a=$this.find(">a"),
				value=$a.attr("value"),
				type=$a.attr("type");

			//set style
			$this.find(">a").addClass("active")
			$this.siblings().find(">a").removeClass('active');

			//run
			run.toolbar(value, type, $this.find(">a"))
		})


		//select location
		$(".search-location .input-group-btn").click(function(){
			var $this=$(this),
				$btn=$this.find("button"),
				$input=$(".search-location .search-placename, .search-location .search-radius"),
				value=$btn.data('value');

			if(value&&value!=""){
				if(app.drawHandler&&app.drawHandler.enabled()){app.drawHandler.disable();}
				if(app.layer.queryBoundary){app.map.removeLayer(app.layer.queryBoundary); app.layer.queryBoundary=null;}
				if(app.layer.geocodeMarker){app.map.removeLayer(app.layer.geocodeMarker); app.layer.geocodeMarker=null;}


				//change layout
				$btn.addClass("btn-primary")
				$this.siblings().find(".btn").removeClass('btn-primary')


				switch(value){
					case "polygon":
						app.drawHandler=new L.Draw.Polygon(app.map, app.control.draw.options.polygon)
						app.drawHandler.enable();
					break;
					case "rectangle":
						app.drawHandler=new L.Draw.Rectangle(app.map, app.control.draw.options.rectangle)
						app.drawHandler.enable();
					break;
					case "remove":
						$btn.removeClass("btn-primary")
						$this.siblings().find(".btn").removeClass('btn-primary')

						//clear place name and radius
						$input.val("");

						//spatial query
						run.spatialQuery();
					break;
				}
			}
		});
		$(".search-location input").keyup(function(e){
			if(e.keyCode==13){
				var $placename=$(".search-location .search-placename")
					placename=$placename.val(),
					$radius=$(".search-location .search-radius"),
					radius=$radius.val();

				//default radius=20 mile
				if(!radius || radius=="" || isNaN(radius)){radius=10; $radius.val(radius)}
				if(radius>100){radius=100; $radius.val(radius)}

				//remove btn layout
				$(".search-location .input-group-btn .btn").removeClass("btn-primary")

				if(placename&&placename!=""){
					run.geocoding(placename, function(results, status){
						if(status=='OK'){
							var result=results[0],
								latlng=result.geometry.location,
								lat=latlng.lat(),
								lng=latlng.lng();

							//show formatted address
							if(result.formatted_address){$placename.val(result.formatted_address)}

							//show marker
							if(app.layer.geocodeMarker){app.map.removeLayer(app.layer.geocodeMarker); app.layer.geocodeMarker=null;}
							app.layer.geocodeMarker=new L.marker([lat, lng], {
								icon:app.icon.geocodeMarker,
								title: "Your Place: '" + (result.formatted_address || placename) +"'"
							}).addTo(app.map);

							//show circle based on the radius
							if(app.layer.queryBoundary){app.map.removeLayer(app.layer.queryBoundary); app.layer.queryBoundary=null;}
							radius=radius*1609.34; //convert mile to meter
							var diff=radius/111000;

							app.layer.queryBoundary=new L.rectangle([[lat-diff, lng-diff], [lat+diff, lng+diff]], app.layerStyle).addTo(app.map);

							//zoom to the geocoded marker
							app.map.fitBounds(app.layer.queryBoundary)

							//spaital query
							run.spatialQuery();
						}
					})
				}else{
					$(".search-location .search-drawRemove").trigger("click")
				}

			}

		})


		//#popup_tour
		$("#popup_tour .thumbnail").click(function(){
			show.tour($(this).data('tour'));
		})
		//addon click
		$(".input-group-addon[data-tour]").attr("title","Click to see the instruction!").click(function(){
			var $this=$(this);
			show.tour($this.data('tour'), $this.data('tourstep'), "click")
		})


		//popup intro
		$("#popup_intro").on({
			'hidden.bs.modal': function(e){
				var value=$(this).data('clickEvent');
				if(value=='tutorial'){$("#popup_tour").modal("show");}
			}
		}).find('.btn').click(function(){
			var value=$(this).attr("value")
			if(value&&value!=""){
				$("#popup_intro").data('clickEvent', value).modal("hide");
			}
		});


		//popup tour
		$("#popup_tour").on({
			"hidden.bs.modal":function(e){
				//stop iframe
				run.toggleVideo('hide', $(this))
			}
		});

		//popup bookmark
		$("#bookmark-name").keydown(function(){
			$(this).removeClass('error')
		})


		//popup tag
		//tag select topic
		$("#popup_tag").on('change', ".selectpicker", function(){
			var $selected=$(this).find("option:selected"),
				l=$selected.attr('data-label'),
				icon=$selected.attr('data-icon');

			if(l&&l!=""){
				l=l.split(",")
				$("#popup_tag").find("#tag-color button").each(function(i,item){$(item).attr({"title": l[i], "icon":icon}); if(i==1){$(item).trigger('click')}})
			}
		}).on("click", "#tag-color button", function(){
			var $this=$(this),
				$ribbon=$("#popup_tag .github-fork-ribbon"),
				level=$this.data("level"),
				label=$this.attr("title");

			//change value
			$this.html("<i class='fa fa-check'></i>").siblings().html("");
			$this.parent().parent().find("label > span").html(label)

			//change ribbon
			run.updateRibbon($ribbon, {level:level, label:label, icon: $this.attr("icon")});
		}).on("click", "#tag-sentiment label.btn", function(){
			var $this=$(this),
				value=$this.find('> input').val();
			if(value&&app.sentimentClasses[value]){
				$('#popup_tag .tweet-sentiment').html("<i class='fa "+app.sentimentClasses[value]+"'></i>").show();
			}
		}).on("keyup", "#tag-tags", function(e){
			//$("#popup_tag .github-fork-ribbon a").html($(this).val())

			if(e.keyCode==13){run.tag()}
		})



	},





	//socket
	socket: function(){
		app.socket.on("connected", function(data){
			console.log(data)

			if(data){
				//searchArea
				if(data.topic){
					var topic=app.topic=data.topic;

					//check valid
					if(topic.endDate&&moment(topic.endDate)<=moment()){
						$("#popup_intro .alertmsg").html("<div class='alert alert-danger' role='alert'>Due to our server capacity limitation, we stop collecting real-time tweets in this area <br>after "+topic.endDate.split("T")[0]+".<p>Please <a target='_blank' href='https://mail.google.com/mail/?view=cm&fs=1&to=hdmasdsu@gmail.com&su=Why GeoViewer stop collecting tweets in "+topic.name+"?'>contact us</a> for further information.</p></div>");
					}

					//bbox
					if(topic.bbox&&topic.bbox!=""){
						var bbox=topic.bbox.split(",").map(function(o){return parseFloat(o)}),
							latlngBounds=new L.latLngBounds(new L.latLng(bbox[1],bbox[0]), new L.latLng(bbox[3],bbox[2]));

						//zoom to bounds
						app.map.fitBounds(latlngBounds)//.zoomOut(1)

						//bbox layer
						app.layer.bbox=new L.rectangle(latlngBounds)

						//change heatmap radius and zoom scale
						if(bbox[3]-bbox[1]<=0.05 || bbox[2]-bbox[0]<=0.05){
							var num=500;
							if(bbox[3]-bbox[1]<=0.025 || bbox[2]-bbox[0]<=0.025){num=250}
							//if(bbox[3]-bbox[1]<=0.01 || bbox[2]-bbox[0]<=0.01){num=100}

							app.heatmap.radius=num //meter

							if(app.heatmap.webGL){
								app.layer.heatmap.options.size=num;
								app.layer.heatmap.update();
							}else{
								app.layer.heatmap.changeRadius(num/111000)
							}

							app.heatmap.slider.slider('setValue', num)
							app.map.zoomOut(1);
						}

					}

					//set start date and end date on the calendar
					if(topic.startDate || topic.endDate){
						if(topic.startDate&&topic.startDate!=""){
							$('.datepicker').datepicker('setStartDate', moment(topic.startDate.split('T')[0]).toDate())
						}

						if(topic.endDate&&topic.endDate!=""&&moment(topic.endDate)<=moment()){
							$('.datepicker').datepicker('setEndDate', moment(topic.endDate.split('T')[0]).toDate())
						}
					}


					//if topic.name='japan-tokyoUniversity', 'aag2015', switch base map
					switch(topic.name){
						case "japan-tokyoUniversity":
							run.switchBaseLayer("ESRI Street Map", app.map)
						break;
						case "aag2015":
							run.switchBaseLayer("ESRI Imagery Map", app.map)
						break;
						case "nepal-kathmandu":
						case "saipan":
							run.switchBaseLayer("OpenStreet Map", app.map)

							//hide other basemap
							$("#basemapWidget ul.basemap li").not("[title='OpenStreet Map'], [title='ESRI Imagery Map']").hide();
						break;
					}
				}


				//cache tweet
				if(data.cache&&data.cache.length>0){
					//results are ordered from new to old >> need to reorder to old to new
					var results=data.cache.reverse();
					run.showAllTweets(results, {addClass:"tweet-cache"})
				}

				//hide intro loading
				$("#popup_intro .modal-body .loadingmsg").html("<p></p>");

				//get layers
				run.getLayers();
			}
		})


		//only retun gps tweet (filter in the nodejs geoviewer.js)
		app.socket.on("newTweet", function(t){
			//if(app.streaming.num>20){return;}

			//create hdma obj
			//only gps-tweeet
			var bboxes=app.topic.bbox.split(",").map(function(k){return parseFloat(k)}),
				coordinates=t.coordinates;

			if(coordinates&&coordinates.coordinates.length==2&&app.blacklists.indexOf(t.user.screen_name)==-1){
				var coords=coordinates.coordinates;

				//if within the bbox
				if(coords[0]>=bboxes[0]&&coords[0]<=bboxes[2]&&coords[1]>=bboxes[1]&&coords[1]<=bboxes[3]){
					t=run.createHDMA(t, "streaming");

					//check filter
					var validType=run.filterTweet(t),
						insertPosition=app.streaming.options.sort,
						$li,
						streamingOptions=app.streaming.options,
						tweetsOnHold=app.streaming.tweetsOnHold,
						$tweetsOnHold=app.streaming.$tweetsOnHold;

					//check streaming options
					if(streamingOptions.playPause=='pause'){
						tweetsOnHold.push(t);
						console.log('onHold', tweetsOnHold.length)

						//show the number of tweetsOnHold
						$tweetsOnHold.html(tweetsOnHold.length).show();


						//callback when app.streaming.onMaxHoldTweets
						if(app.streaming.onMaxHoldTweets){app.streaming.onMaxHoldTweets();}
					}else{
						app.streaming.num++;

						//show tweet
						$li=show.tweet(t,{validType:validType, popupOnMap:false, id: app.streaming.num, masonry:{isShow:true, isAddItem:true, insertPosition:insertPosition}});

						//update tweet count
						if(validType=="true"){
							var showNum=parseInt(app.streaming.$streamingBadge.html())+1;
							app.streaming.$streamingBadge.html(showNum)
						}
					}



					//reset app.streaming.tweets
					if(app.streaming.num!=0&&app.streaming.num>100&&app.streaming.num%app.maxTweetNum==0){
						//delete old 100 tweets
						var half=app.maxTweetNum,//2,
							start=(insertPosition=='append')?0:half,
							end=(insertPosition=='append')?half:app.maxTweetNum;

						//console.log(slices)
						var $masonry=app.streaming.$masonry;
						$masonry.find('li.tweetBox').slice(start, end).remove();
						//refresh masonry layout
						$masonry.imagesLoaded(function(){
							$masonry.masonry("layout");
						});


						//clear layers >> keep collecting and showing tweets on the map
						//run.clearLayers()
						console.log('over app.maxTweetnum='+app.maxTweetNum, 'tweetBox.length='+$masonry.find('li.tweetBox').length);
					}

				}//end check tweet is within bbox
			}

		});



	},

	//map
	map: function(){
		var basemap=app.basemaps["ESRI National Geographic Map"]().setOpacity(0.7);

		app.map = L.map("map", {
		  zoom: app.initZoom,
		  center: app.initCenterLatLng,
		  layers: [basemap],
		  zoomControl: false,
		  attributionControl: true
		});

		//bookmark map
		var basemap_bookmark=app.basemaps["ESRI National Geographic Map"]().setOpacity(0.7);
		app.bookmarkMap=L.map("bookmark-map", {
		  zoom: app.initZoom,
		  center: app.initCenterLatLng,
		  layers: [basemap_bookmark],
		  zoomControl: false,
		  attributionControl: true
		});

		//set up current basemap
		app.map.currentBasemap=basemap;
		app.bookmarkMap.currentBasemap=basemap_bookmark;

		//heatmap layer
		app.layer.heatmap=(app.heatmap.webGL)?run.createWebGLHeatmapLayer({visible:true}):run.createHeatmapLayer({visible:true})
		app.map.addLayer(app.layer.heatmap)


		//markerCluster layer
		app.layer.markerCluster=run.createMarkerClusterLayer({visible:true, showKeywordCount:false}).addTo(app.map)

		//markerGroup layer
		app.layer.markerGroup=new L.featureGroup();
		app.layer.markerGroup.options={visible:false, layerType:"markerGroup"};


		//zoom control
		var zoomControl = L.control.zoom({
		  position: "bottomright"
		}).addTo(app.map);


		//Initialise the draw control and pass it the FeatureGroup of editable layers
		app.control.draw = new L.Control.Draw({
			draw:{
				//polyline:false,
				polygon:{
					shapeOptions:{}
				},
				//circle:false,
				//marker:false,
				rectangle:{
					shapeOptions:{}
				}
			}
		}).addTo(app.map);


		//map events
		app.map.on({
			"draw:created":	function(e){
				var layer;

				if(e.layerType=='polygon' || e.layerType=='rectangle'){
					app.layer.queryBoundary=layer=e.layer.addTo(app.map)

					//show coordiantes
					$("#search-geolocation").val(run.layerToBBOX(layer).join(", "));

					//show mask canvas layer
					/**
					if(app.layer.maskCanvas){app.map.removeLayer(app.layer.maskCanvas); app.layer.maskCanvas=null;}
					app.layer.maskCanvas=new L.TileLayer.MaskCanvas({
					     radius: 1000,  // radius in pixels or in meters (see useAbsoluteRadius)
					     useAbsoluteRadius: true,  // true: r in meters, false: r in pixels
					     color: '#000',  // the color of the layer
					     opacity: 0.3,  // opacity of the not coverted area
					})
					var data=layer.toGeoJSON().geometry.coordinates[0].map(function(lnglat){
						return [lnglat[1], lnglat[0]]
					})
					app.layer.maskCanvas.setData(data);
					console.log(app.layer.maskCanvas)
					app.map.addLayer(app.layer.maskCanvas)
					*/

					//do spatial query
					run.spatialQuery();
				}

			}
		});


		//controls
		var controls={
			mapGallery : L.Control.extend({
				options : {
					"collapsed" : true,
					"position" : 'topright',
					"text" : 'Map Gallery'
				},
				initialize : function(options) {
					L.Util.setOptions(this, options);
				},
				onAdd : function(map) {
					// create the control container with a particular class name
					var container = L.DomUtil.create('div', 'leaflet-control-mapGallery');
					var html = "<ul>"+
								"<li title='Showing markers' layer='markerGroup' style=''><img src='images/marker-icon.png' /></li>"+
								"<li title='Cluster Map' layer='markerCluster' class='active'><img src='images/gallery-cluster.png' /></li>"+
								"<li title='Hotspots' layer='heatmap' class='active'><img src='images/gallery-heatmap.png' /></li>"+
							   "</ul>";


					//click map gallery event
					$(container).html(html).find("ul > li").on({
						click : function() {
							var $this = $(this),
								value = $this.attr("layer"),
								layer=app.layer[value];

							//if this layer is already shown on the map, hide the layer and change the color
							if(layer){
								if (layer.options.visible) {
									app.map.removeLayer(layer)

		//							app.map.removeLayer(layer);
									//$this.css({"background-color" : ''});
									$this.removeClass("active")

									layer.options.visible=false;
								} else {
									app.map.addLayer(layer)
									//layer.addTo(app.map)

									//make the markerclusterlayer more priority
									if(value=='markerCluster'){
										layer.bringToFront();
										layer.on("clusterclick", run.clusterClickHandler);
									}

									//$this.css({"background-color" : "#3071A9"});
									$this.addClass("active")

									layer.options.visible=true;
								}
							}

						},
						mouseover : function() {
							$(".mapPopupWidget, #basemapWidget, #mapPopup_heatmap").hide();
							var $this = $(this),
								value = $this.attr("layer"),
								layer=app.layer[value];


							//only the layer is activated
							if (layer.options.visible) {
								//show map popup window
								$("#mapPopup_" + value).show();
							}
						},
						mouseleave : function() {

						}
					});
					return container
				}
			}),
			tocThumbnail: L.Control.extend({
				options : {
					"position" : 'topright',
					"text" : 'Change Base Maps'
				},
				initialize : function(options) {L.Util.setOptions(this, options);},
				onAdd : function(map) {
					//create div element
					var mainContent=L.DomUtil.create('div', 'leaflet-control-tocThumbnail'),
						html="<a class='leaflet-control-layers-toggle' href='#' title='Layers'></a>"+ $("#basemapWidget")[0].outerHTML;

					$(mainContent).html(html)
						.addClass('leaflet-control-layers')
						.on({
							"mouseover":mouseoverEvent,
							"click": mouseoverEvent
						})
						.find("ul.basemap li").click(function(){
							run.switchBaseLayer($(this).attr('title'), app.map);
						});


					//monuseoverEvent on the basemapWidget
					function mouseoverEvent(e){
						$("#basemapWidget").show().on({
							"mouseleave":function(){
								//setTimeout(function(){
									//$(this).hide();
								//}, 100)
							}
						});
					}

					return mainContent;
				}
			})
		}

		//add map gallery control
		$.each(controls, function(k,v){
			app.map.addControl(new v());
		})


		//slider for basemap transparency
		app.map.basemapSlider=$("#basemap_slider").slider({
			min:0,
			max:1,
			step:0.1,
			value:0.7,
			enable:true
		}).on({
			"slideStop": function(e){
				var layer=app.map.currentBasemap;
				if(layer instanceof L.LayerGroup){
					layer.eachLayer(function(l){l.setOpacity(e.value)})
				}else{
					layer.setOpacity(e.value)
				}
			}
		})

	},



	//init login
	login: function(){
		//login
		$("#formLogin").submit(function(e){
			var $this=$(this),
				username=$this.find("#formUsername").val() || "",
				password=$this.find("#formPassword").val() || "",
				rememberme=$this.find(".icheckbox_minimal").hasClass('checked');

			//show loading
			$("#loginLoading").show();
			$("#loginMsg").hide();

			//post
			if(username&&password&&username!=""&&password!=""){
				app.socket.emit("login", {username:username, password:$.md5(password)}, function(result){
					run.afterLogin(result)
				})
			}

			e.preventDefault();
		});
	}

}



//show
var show={
	tweet: function(t, options){
		//console.log(t);
		//options
		if(!options){options={}}
		options.targetLayer=app.layer;
		options.validType=options.validType || "true";
		options.popupOnMap=options.popupOnMap || false;
		options.addClass=options.addClass || null;
		options.id=options.id || null;
		options.showOnMap=options.showOnMap || true;
		options.masonry=options.masonry || {};
		options.updateLayer=(options.updateLayer==null)?true:options.updateLayer;


		//masonry options
		var optionsMasonry=options.masonry
		optionsMasonry.isShow=(optionsMasonry.isShow==null)?true:optionsMasonry.isShow;
		optionsMasonry.$target=optionsMasonry.$target || app.streaming.$masonry;
		optionsMasonry.addClass=optionsMasonry.addClass || null;
		optionsMasonry.isAddItem=(optionsMasonry.isAddItem==null)?false:optionsMasonry.isAddItem;
		optionsMasonry.insertPosition=optionsMasonry.insertPosition || app.streaming.options.sort;


		//if tweet is gps-tweet
		if(t&&t.coordinates){
			var obj=t.hdma,
				output=null;

			//show marker
			if(obj&&obj.marker){
				if(options.validType=='true'&&options.updateLayer){
					//console.time("show.tweet[layer]");
					//cluster layer
					options.targetLayer.markerCluster.addLayer(obj.marker);

					//heatmap
					if(app.heatmap.webGL){
						options.targetLayer.heatmap.addDataPoint(((app.geomask.enable)?obj.latlng_geomasking.lat: lat), ((app.geomask.enable)?obj.latlng_geomasking.lng:lng), 100)
					}else{
						//[bugs]seems like the heatmap took the most time (avg time= 30ms)
						options.targetLayer.heatmap.addData({lat:((app.geomask.enable)?obj.latlng_geomasking.lat: lat), lng:((app.geomask.enable)?obj.latlng_geomasking.lng:lng),value:1})
					}


					//marker group
					options.targetLayer.markerGroup.addLayer(obj.marker);

					//popup on map
					if(options.popupOnMap){
						app.popup=obj.marker.getPopup().setLatLng(obj.marker.getLatLng());
						app.popup.options.autoPan=false;
						app.popup.openOn(app.map)
					}
					//console.timeEnd("show.tweet[layer]");
				}


				//show tweet in the list
				if(optionsMasonry.isShow){
					//console.time("show.tweet[masonry]");
					var html=obj.popupHtml,
						$li=$("<li class='tweetBox' data-tweetID='"+t.id_str+"' data-id='"+options.id+"' data-sourceType='' data-hasMedia='"+obj.hasMedia+"' style='display:"+((options.validType=='true')?"block":"none")+"'>"+html+"</li>");
						//$li=$("<li class='tweetBox' data-tweetID='"+t.id_str+"' data-id='"+options.id+"' data-sourceType='' data-hasMedia='"+obj.hasMedia+"'>"+html+"</li>");

					if(optionsMasonry.addClass&&optionsMasonry.isAddItem==false){$li.addClass(optionsMasonry.addClass)}

					//give tweet data into $li and write click event
					$li.data("tweet", t).on("click", ".tweet-actions a", function(){
						var $that=$(this),
							type=$that.attr("class");

						switch(type){
							case "tweet-mapit":
								show.mapPopup($that.parents('li.tweetBox'))
							break;
							case "tweet-note":
								show.tagPopup($that.parents('li.tweetBox'))
							break;
						}
					}).on("click", ".github-fork-ribbon", function(){
						var $that=$(this);
						show.tagPopup($that.parents('li.tweetBox'))
					})

					//check section-search width()
					var temp=app.streaming;
					if(temp.sectionSearchWidth <= temp.minWidth){$li.css("width","50%")}


					//insert $li
					var insert=(optionsMasonry.insertPosition=="prepend")?"prepend":"append";
					optionsMasonry.$target[insert]($li)


					/** refresh masonry layout*/
					if(options.validType=='notMatch'){$li.addClass("tweet-notMatch")}
					if(optionsMasonry.isAddItem){
						$li.hide().imagesLoaded(function(){
							$li.show();
							optionsMasonry.$target.masonry(insert+"ed", $li)
						})
					}


					output=$li;

					//console.timeEnd("show.tweet[masonry]");
				}


				return output;

			}//end obj.marker

		}//end if(t)

	},



	//show spatial query results
	spatialQueryResult: function(results){
		//clear search result
		var $masonry=app.streaming.$masonry;
		$masonry.find(">li.tweetBox.searchResult").remove();

		//parse results
		if(results&&results.length>0){
			//remove draw rectangle
			//app.map.removeLayer(app.layer.queryBoundary)

			//time
			console.time("show.spatialQueryResult")

			//show tweets
			run.showAllTweets(results.reverse(), {addClass:"searchResult", callback:function(){
				//callback: after show masonry
				$(".search-msg").html("");

				console.timeEnd("show.spatialQueryResult")
			}});

		}else{
			console.log("no spatial query result!")
			$(".search-msg").html("Found 0 results. Please search again by other criteria")
		}


	},


	//show hightlight keyword
	highlightKeyword: function(keyword){
		console.log(keyword)

	},


	//show map popup
	mapPopup:function($this){
		var t=$this.data("tweet");

		if(t&&t.hdma){
			var obj=t.hdma;
			app.popup=obj.marker.getPopup().setLatLng(obj.marker.getLatLng());
			app.popup.options.autoPan=true;
			app.popup.openOn(app.map)
		}

	},

	//note popup
	tagPopup:function($this){
		var t=$this.data("tweet");

		if(t&&t.hdma){
			var $target=$("#popup_tag").data("tweet", t);

			//show tweet
			$target.find("#tweetContent").html(t.hdma.popupHtml);


			var $ribbon=$target.find(".github-fork-ribbon"),
				$tags=$target.find("#tag-tags"),
				$level=$target.find("#tag-color")
				$sentiment=$target.find("#tag-sentiment")
				$previewSentiment=$target.find('.tweet-sentiment').show(),
				$topic=$target.find(".selectpicker"),
				currentTopic=(app.user)?app.user.currentTopic:null,
				notes=t.hdma.note,
				note=(notes&&currentTopic)?notes[currentTopic]:null


			//current topic
			if(currentTopic&&currentTopic!=""){
				$topic.val(currentTopic).trigger('change')
			}


			//show note
			if(note){
				//show ribbon
				if(note.level&&note.level>0){
					run.updateRibbon($ribbon, {level:note.level})
					$level.find("button").eq(note.level).html("<i class='fa fa-check'></i>").siblings().html("");
				}

				//show tags
				if(note.tags&&note.tags!=""){
					$tags.val(note.tags);
				}

				//show sentiment
				if(note.sentiment&&note.sentiment!=''&&note.sentiment!='none'){
					$sentiment.find("label.btn input[value='"+note.sentiment+"']").prop("checked", true).parent().trigger("click");
					$previewSentiment.html("<i class='fa "+app.sentimentClasses[note.sentiment]+"'></i>");
				}
			}else{
				//default level
				var level=1,
					$btn=$level.find("button").eq(level);

				$btn.html("<i class='fa fa-check'></i>").siblings().html("");
				run.updateRibbon($ribbon, {level:level, icon:$btn.attr("icon"), label:$btn.attr("title")});
				$tags.val("");
				$sentiment.find("label.btn input[value='none']").prop("checked", true).parent().trigger("click");
				$previewSentiment.html("<i class='fa "+app.sentimentClasses["none"]+"'></i>");
			}


			show.popup("tag")
		}

	},


	//show tag info
	tagInfo: function($tweet){
		var t=$tweet.data('tweet'),
			$tags=$tweet.find(".tweet-tags").hide().html(""),
			$sentiment=$tweet.find('.tweet-sentiment').hide().html(""),
			$ribbon=$tweet.find(".github-fork-ribbon").hide();

		//reset ribbon
		run.updateRibbon($ribbon)

		if(t&&t.hdma.note){
			var notes=t.hdma.note,
				currentTopic=app.user.currentTopic,
				note=notes[currentTopic] || null,
				tagTopic=app.tagTopics[currentTopic];

			//update ribbon
			if(note){
				if(note.level){
					run.updateRibbon($ribbon, {level: note.level, label:tagTopic.labels[note.level], icon:tagTopic.icon})
				}

				//show tags
				if(note.tags){
					$tags.html((note.tags)?note.tags.split(",").map(function(item){return "<span>"+item.trim()+"</span>"}):"").show();
				}

				//need to update sentiment
				if(note.sentiment&&app.sentimentClasses[note.sentiment]){
					$sentiment.html("<i class='fa "+app.sentimentClasses[note.sentiment]+"'></i>").show();
				}
			}



			//update masonry layout
			app.streaming.$masonry.masonry("layout");
		}
	},



	//show alert
	alert: function(html, callback){
		$(".alert-success").html(html).slideDown(500, function(){
			var $that=$(this);

			if(callback){callback();}


			setTimeout(function(){
				$that.slideUp(500)
			}, 2000)
		})
	},


	//popup windows
	popup: function(type){
		if(type&&type!=""){
			$("#popup_"+type).modal("show");
		}
	},

	//help
	tour: function(type, step, action){
		if(type&&app.introStep[type]&&app.introStep[type].length>0){
			//hide tour popup
			$("#popup_tour").modal("hide");

			//start introjs
			var intro=introJs().setOptions({
				steps: app.introStep[type],
				//showStepNumbers:true,
				//"showProgress":true,
				//overlayOpacity:0.8
			});


			if(!action){
				intro.onexit(function(){
					$("#popup_tour").modal("show");
				}).oncomplete(function(){
					$("#popup_tour").modal("show");
				})
			}

			if(step&&step!=""){
				intro.goToStep(parseInt(step)).start();
			}else{
				intro.start();
			}


		}

	},


	//show bookmark
	bookmark: function(bookmarks){
		var $target=$("#nav-bookmark ul.dropdown-menu"), $li, m_date,
			region=app.topic.name;

		$.each(bookmarks, function(i,item){
			if(item.region==region){
				m_date=moment(item.date).local();
				$li=$("<li><a href='#'>"+item.name+"<span class='date'>"+m_date.format("MM-DD-YYYY HH:mm")+"</span></a><button type='button' class='close' aria-label='Close' title='Remove the Saved Search'><i class='fa fa-times-circle'></i></button></li>").data('bookmark',item);

				$li.find("a").click(function(){
					var data=$(this).parent().data('bookmark');
					run.bookmark("click", data)
				})

				$li.find("button.close").click(function(){
					var $l=$(this).closest("li")

					run.confirm("bookmark-remove", $l)
				});

				$target.prepend($li)
			}
		})
	}


}





var run={
	//detect search section width
	setSearchSectionWidth: function(){
		app.streaming.sectionSearchWidth=$(".section-search").width();
	},


	//tag
	tag: function(type){
		var $target=$("#popup_tag"),
			level=$target.find("button:has(i.fa.fa-check)").data("level"),
			tags=$target.find("#tag-tags").val() || "",
			data=$target.data('tweet'),
			$tweet=$("ul.masonry li.tweetBox[data-tweetid='"+data.id_str+"']"),
			updateCache= $tweet.hasClass('tweet-cache'),
			$loading=$target.find(".loading"),
			sentiment=$target.find("#tag-sentiment label.btn.active input").val() || "none",
			topic=$target.find(".selectpicker").val();


		if(data){
			var params={
				id_str:data.id_str,
				level:level,
				tags:tags,
				sentiment:sentiment,
				region:(app.topic.name=='sanDiego')?"San Diego region": app.topic.name,
				topic:topic,
				note:data.note
			}

			//show loading
			$loading.show();


			//update
			app.socket.emit("update", {username:app.user.username, token:app.user.token, type:"tag", value:params, updateCache: updateCache}, function(result){
				if(result.status=='error'){alert("Please log in first!"); console.log("[ERROR] run.tag: "+ result.msg); return; }

				//update the current tweet's ribbon
				var t=$tweet.data("tweet");

				//update the tweet
				if(t&&t.hdma){
					t.hdma.note=result.value;
					$tweet.data("tweet",t);

					//update app.user.note_geoviewer
					if(app.user&&app.user.username&&app.user.note_geoviewer){
						app.user.note_geoviewer[params.id_str]=t.hdma.note;
					}


					//update tweetbox
					show.tagInfo($tweet);


					//hide loading
					$loading.hide();

					//show result
					show.alert("Tag succeed!", function(){
						//hide popup tag
						$("#popup_tag").modal("hide");
					});
				}else{
					console.log("[ERROR] run.tag: no $tweet.data('tweet')")
				}

			});


		}
	},


	//set tag info to each $tweet.data
	setTagInfo: function(options){
		//options
		if(!options){options={}}
		options.logout=(options.logout==true)?true:false;

		if(app.user&&app.user.username&&app.user.note_geoviewer){
			var notes=app.user.note_geoviewer,
				$masonry=app.streaming.$masonry,
				$tweet, data;

			$.each(notes, function(id, v){
				$tweet=$masonry.find(">li[data-tweetid='"+id+"']");
				data=$tweet.data('tweet');

				if(data&&data.hdma){
					data.hdma.note=v

					//if logout
					if(options.logout){delete data.hdma.note}

					$tweet.data("tweet", data)
					show.tagInfo($tweet)
				}
			})
		}


	},



	//update ribbon
	updateRibbon:function($this, options){
		//options
		if(!options){options={}}
		options.level=options.level || 0;
		var obj=app.ribbonClasses[options.level]

		options.label=options.label || obj.label;
		options.icon=options.icon || null;

		//reset
		var currentLevel=$this.data('level'),
			currentClass=app.ribbonClasses[currentLevel];

		if(currentLevel&&currentClass){$this.removeClass(currentClass.className)}
		$this.attr("data-level","").find("a").html("");


		//set
		if(options.level>0&&obj){
			var html=(options.icon)?"<i class='glyphicon "+options.icon+"'></i>"+options.label:options.label;
			$this.addClass(obj.className).data('level', options.level).find("a").html(html)

			$this.show();
		}


	},


	//bookmark
	bookmark:function(type, data, callback){
		switch(type){
			case "save":
				var filter=run.getQueryParams(),
					$table=$("#bookmark-parameters"),
					html="<table class='table table-params'>",
					params={
						filter:filter,
						map:{
							zoom:app.map.getZoom(),
							centerLatLng:[app.map.getCenter().lat, app.map.getCenter().lng],
							basemapName:app.map.currentBasemap.options.title,
							activeLayerNames:$(".leaflet-control-mapGallery .active").toArray().map(function(dom){var $this=$(dom), v=$this.attr("layer"); return v}),
						},
						region:app.topic.name
					}


				//clear layer on the bookmark map
				if(app.bookmarkMap.options.queryBoundary){
					app.bookmarkMap.removeLayer(app.bookmarkMap.options.queryBoundary)
					app.bookmarkMap.options.queryBoundary=null;
				}

				//change bookmark map basemap
				run.switchBaseLayer(params.map.basemapName, app.bookmarkMap);


				$.each(params, function(key,obj){
					if(key!='region'){
						$.each(obj, function(k, v){
							if(k=='geojsonPolygon'&&v){
								app.bookmarkMap.options.queryBoundary=v;
								v.addTo(app.bookmarkMap)
							}else{
								html+="<tr><td>"+k+"</td><td>"+((v&&v instanceof Array)?v.join(", "):v)+"</td></tr>";
							}
						})
					}else{
						html+="<tr><td>"+key+"</td><td>"+obj+"</td></tr>";
					}
				})
				$table.html(html+"</table>");


				//refresh map
				$("#popup_bookmark").off("shown.bs.modal").on("shown.bs.modal", function(){
					setTimeout(function(){
						app.bookmarkMap.invalidateSize(false);
						if(filter.geojsonPolygon){
							app.bookmarkMap.fitBounds(filter.geojsonPolygon.getBounds())
						}else{
							app.bookmarkMap.setView(app.map.getCenter(), app.map.getZoom())
						}
					},100);
				}).data("params", params);
				show.popup("bookmark");


			break;
			case "submit":
				var params=$("#popup_bookmark").data("params"),
					filter=params.filter,
					now=moment().toDate(),
					name=$("#bookmark-name").val(),
					description=$("#bookmark-description").val(),
					$loading=$("#popup_bookmark .loading");

				//check name
				if(!name || name==""){alert("Please input the bookmark name!"); return; }

				//to geojson format
				if(filter.geojsonPolygon&&filter.geojsonPolygon.toGeoJSON){filter.geojsonPolygon=filter.geojsonPolygon.toGeoJSON();}

				params.name=name;
				params.description=description;
				params.date=now;

				//show loading
				$loading.show();

				//update params
				app.socket.emit("update", {username:app.user.username, token:app.user.token, type:"bookmark", value:params}, function(result){
					if(result.status=='error'){console.log("[ERROR] run.bookmark: "+ result.msg); $loading.hide(); return; }
					if(result.status=='exist'){$("#bookmark-name").val(result.msg).addClass('error'); $loading.hide(); return; }

					//if succeed
					var bookmarks=result.value;

					//update bookmarks
					show.bookmark([params])


					//hide loading
					$loading.hide();

					//show result
					show.alert("<b>Bookmark update successfully!</b>", function(){
						//hide bookmark popup
						$("#popup_bookmark").modal("hide");
					})

				})

			break;
			case "click":
				if(data){
					console.log(data)
					if(app.layer.queryBoundary){app.map.removeLayer(app.layer.queryBoundary)}

					var triggered=false,
						fx={
							keywords:function(v){$("#search-keyword").val(v)},
							geojsonPolygon:function(v){
								if(v){
									app.layer.queryBoundary=L.geoJson(v, {style:app.layerStyle}).addTo(app.map);
								}
							},
							startDate:function(v){$("#search-startDate").val(v)},
							endDate:function(v){$("#search-endDate").val(v)},
							viewMode:function(v){
								if(!v){v='both'}

								//if the viewmode of the bookmark equals to the current viewmode, it will no trigger click event. We need to manully trigger it.
								var $target=$(".search-viewMode input[type='radio']");
								if($target.filter(":checked").attr('value')!=v){
									$target.filter("[value='"+v+"']").prop('checked',true).parent().trigger("click")
									triggered=true;
								}
							}
						}

					//filter
					$.each(data.filter, function(k, v){
						if(fx[k]){fx[k](v);}
					})

					//map
					var map=data.map;
					app.map.setView(new L.LatLng(map.centerLatLng[0], map.centerLatLng[1]), map.zoom);
					run.switchBaseLayer(map.basemapName, app.map);

					//active layers
					$(".leaflet-control-mapGallery ul li").each(function(){
						var $this=$(this),
							name=$this.attr('layer')
							isActive=$this.hasClass('active');

						if(map.activeLayerNames.indexOf(name)!=-1){
							//if it is not active, trigger click to show the layer
							if(!isActive){
								$this.trigger('click')
							}
						}else{
							//if it is active but not in the acitveLayerNames list, trigger click to hide the layer
							if(isActive){
								$this.trigger('click')
							}
						}
					})



					//spatial query
					if(!triggered){run.spatialQuery();}
			}
			break;
			case "remove":
				if(data){
					//clear boundary
					//if(app.layer.queryBoundary){app.map.removeLayer(app.layer.queryBoundary)}

					app.socket.emit("update", {username:app.user.username, token:app.user.token, type:"bookmark-remove", value:data}, function(result){
						if(result&&result.status=='error'){alert(result.msg); return; }

						//if callback
						if(callback){callback(result)}
					})

				}

			break;
		}


	},


	//confirm
	confirm:function(type, $this){
		if(type&&type!=""){
			var $target=$("#popup_confirm"),
				$loading=$target.find("img.loading");


			switch(type){
				case "bookmark-remove":
					if($this&&$this.data("bookmark")){
						var data=$this.data('bookmark'),
							html="<h4>Are you sure to remove the saved search?</h4><p class='name'>"+data.name+"<p class='date'>"+data.date+"</p><p class='description'>"+((data.description)?data.description:"")+"</p>";

						$target.find(".modal-body").html(html);


						$target.find("button[value='submit']").off("click").click(function(){
							//show loading
							$loading.show();

							run.bookmark('remove', data, function(result){
								$this.remove();

								//show loading
								$loading.hide();

								//hide modal
								$target.modal("hide");
							});
						})



						//show modal
						$target.modal("show")




					}





				break;



			}



		}
	},

	//toggle video
	toggleVideo: function(state, $target) {
		// if state == 'hide', hide. Else: show video
		var div = $target[0];
		var iframe = div.getElementsByTagName("iframe")[0].contentWindow;
		div.style.display = state == 'hide' ? 'none' : '';
		func = state == 'hide' ? 'pauseVideo' : 'playVideo';
		iframe.postMessage('{"event":"command","func":"' + func + '","args":""}', '*');
	},

	//get layers
	getLayers:function(){
		var url="/"+((SERVERNAME=='vision')?"hdma":"ec2")+"/query/getLayer/"+encodeURIComponent(app.topic.name.replace("+","--"));
		$.getJSON(url, function(json){
			if(json&&json.length>=0){
				$("#basemapWidget .layer li.added").remove();
				run.showLayers(json)
			}
		});
	},


	//linkify
	linkify: function(text){
		return text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function(m) {
			return "<a href='"+m+"' target='_blank'>"+m+"</a>";
		}).replace(/[@#]\w+/g,function(b,c){
			c="twitter.com/";
			//return b.link("//"+(b[a="search"]("#")?c:a+"."+c+a+"?q=")+b)
			//return "<a href='"+("http://"+(b[a="search"]("#")?c:a+"."+c+a+"?q=")+b)+"' target='_blank'>"+b+"</a>"; //twitter rest api 1.1
			if(b.substring(0,1)=="#"){
				c=c+"hashtag/"+b.substring(1);
			}else{
				c=c+b.substring(1)
			}
			return "<a href='https://"+c+"' target='_blank'>"+b+"</a>";
		});
	},

	//test webgl
	webgl_detect: function(return_context){
		if (!!window.WebGLRenderingContext) {
			var canvas = document.createElement("canvas"),
				 names = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"],
			   context = false;

			for(var i=0;i<4;i++) {
				try {
					context = canvas.getContext(names[i]);
					if (context && typeof context.getParameter == "function") {
						// WebGL is enabled
						if (return_context) {
							// return WebGL object if the function's argument is present
							return {name:names[i], gl:context};
						}
						// else, return just true
						return true;
					}
				} catch(e) {}
			}

			// WebGL is supported, but disabled
			return false;
		}

		// WebGL not supported
		return false;
	},

	//switch base map
	switchBaseLayer: function(type, map){
		var layer = app.basemaps[type]() || null,
			slider=app.map.basemapSlider;

		if(layer&&slider){
			var opacity=slider.slider("getValue");

			if(map.currentBasemap){
				map.removeLayer(map.currentBasemap);
			}

			if(layer instanceof L.LayerGroup){
				layer.eachLayer(function(l){l.setOpacity(opacity)})
			}else{
				layer.setOpacity(opacity)
			}


			map.addLayer(layer);
			map.currentBasemap=layer;

			//fire baselayerchange event
			map.fire('baselayerchange', {layer: layer});
		}
	},

	//show layer
	showLayers: function(layers){
		var $target=$("#basemapWidget ul.layer"),
			$li;

		//inclue app.layer.box first
		layers=layers || [];
		layers.unshift({
			type:"LEAFLET-BBOX",
			url:null,
			label:"Twitter Search Boundary",
			link:null,
			description:"The boundary box is the area what we used to search tweets"
		});

		//test----------------------------------------
		layers.push(
			{
				type:"GEOJSON",
				url:"http://vision.sdsu.edu/"+((SERVERNAME=='vision')?"hdma":"ec2")+"/layer/nws/cap?x=0&y=0&f=geojson", //"http://vision.sdsu.edu/hdma/layer/nws/cap?x=TXC223&y=0&f=geojson" //test: taxas,
				label:"[NWS] NOAA NWS Weather Alert CAP",
				link:"https://alerts.weather.gov/",
				description:"National weather service from Natiaonl Oceanic and atmospheric administration (NOAA). Please refer to https://alerts.weather.gov/ for more detail.",
				defaultLayer:true
			}
		)
		//--------------------------------------------

		//re-orders
		layers.sort(function(a,b){if(a.label > b.label){return 1} if(a.label < b.label){return -1} if(a.label = b.label){return 0} })

		$.each(layers,function(i,layer){
			$li=$("<li class='added'><a class='layerInfo'><i class='fa fa-info-circle' /></a><div class='checkbox' title='"+layer.description+"'><label><input type='checkbox' name='layer_group'/>"+layer.label+"</label></div></li>");
			$li.find("input[type='checkbox']").data("layer", layer).click(function(){
				var $this=$(this),
					checked=this.checked,
					featureLayer=$this.data('featureLayer');
					data=$this.data("layer");

				if(checked){
					if(!featureLayer&&data){
						run.createLayer(data, function(l){
							featureLayer=l.addTo(app.map);
							$this.data("featureLayer", featureLayer)
						})
					}else{
						featureLayer.addTo(app.map);

						//if cartodb layer >> show legend and logo
						if(data.type=='CARTODB'){$(".cartodb-legend-stack, .cartodb-logo").show();}

					}
				}else{
					app.map.removeLayer(featureLayer)

					//if cartodb layer >> hide legend and logo
					if(data.type=='CARTODB'){$(".cartodb-legend-stack, .cartodb-logo").hide();}
				}
			})

			//if default layer trigger click
			if(layer.defaultLayer){$li.find("input[type='checkbox']").trigger("click")}


			//layerInfo click event
			$li.find('a.layerInfo').click(function(){
				$this=$(this),
				$chkbox=$this.siblings('.checkbox').find('input[type="checkbox"]'),
				data=$chkbox.data('layer');

				run.showLayerInfo(data);
			})

			$target.append($li)


			//default layer
			//if(layer.defaultLayer){
				//console.log("default layer and trigger click")
				//$li.find("input[type='checkbox']").prop('checked', true).trigger("click");

			//}
		});
	},


	//show layerInfo
	showLayerInfo: function(data){
		if(data){
			var $target=$('#popup_layerInfo'),
				$header=$target.find('.header'),
				$body=$target.find(".modal-body"),
				html=data.description + "<table>",
				fields=["type", "url"],
				types={"AGMS":"ArcGIS Map Service", "AGTS":"ArcGIS Tiled Map Service", "AGFS":"ArcGIS Feature Service", "LEAFLET":"Leaflet Feature Layer", "CARTODB":"Cartodb Layer", "WMS":"Web Mapping Service", "GEOJSON":"GeoJSON"}

			$header.html(data.label);

			//types && url
			html+="<tr><td>TYPE</td><td>"+types[data.type.split("-")[0]]+"</td></tr>"+
				  ((data.url)?("<tr><td>URL</td><td><a href='"+data.url+"' target='_blank'>"+data.url+"</a></td></tr>"):"");
			$body.html(html+"</table>");

			//if cap >> add download button
			if(data.url.indexOf("http://vision.sdsu.edu/hdma/layer/nws/cap")!=-1){
				$target.find(".modal-footer").html("<div class='download'><button class='btn btn-primary' onclick=window.open('http://vision.sdsu.edu/hdma/layer/nws/cap?x=0&y=0&f=shp')>Download SHP in U.S.</button><button class='btn btn-primary' onclick=window.open('http://vision.sdsu.edu/hdma/layer/nws/cap?x=CAC073&y=0&f=shp')>Download SHP in San Diego</button><div class='description'>* Please note that the SHP will be empty if there is no NWS alerts in the certain area.</div></div>").show();
			}



			//show popup_layerInfo
			$target.modal('show');
		}
	},


	//create layer
	createLayer: function(obj, callback){
		var featureLayer,
			types=obj.type.split('-');

		switch(types[0]){
			case "AGFS":
				if(!obj.options){obj.options={}}
				featureLayer=new L.esri.FeatureLayer(data.url, obj.options)
				if(callback){callback(featureLayer)}
			break;
			case "AGMS":
				if(!obj.options){obj.options={}}
				obj.options.opacity=obj.options.opacity || 0.5;
				featureLayer=new L.esri.DynamicMapLayer(data.url, obj.options)
				if(callback){callback(featureLayer)}
			break;
			case "AGTS": //ARCGIS TILE SERVICE
				featureLayer=L.esri.tiledMapLayer(data.url, {opacity:0.9})
				if(callback){callback(featureLayer)}
			break;
			case "CARTODB":
				cartodb.createLayer(app.map, obj.url, {}, function(layer){
					layer.setOpacity(0.7);
					layer.setInteraction(true);
					layer.on('featureOver', function(e, pos, latlng, data) {
						//console.log(e, pos, latlng, data);
					}).on('error', function(e){
						console.log(e)
					});

					if(callback){callback(layer)}
				});
			break;
			case "LEAFLET":
				if(types[1]&&app.layer[types[1].toLowerCase()]){
					if(callback){callback(app.layer[types[1].toLowerCase()])}
				}
			break;
			case "WMS":
				var url=obj.url,
					params={};

				//get params
				url.split('?')[1].split('&').map(function(p){
					var ps=p.split('=');
					params[ps[0]]=ps[1]
				});

				featureLayer=new L.tileLayer.wms(url.split('?')[0], {
					layers:params['layers'],
					styles:params['styles'],
					format:decodeURIComponent(params['format']) || "image/png",
					transparent:true,
					version:params['version'] || 1.1
				})
				if(callback){callback(featureLayer)}
			break;
			case "GEOJSON":
				$.getJSON(obj.url, function(json){
					featureLayer=new L.GeoJSON(json, app.layerStyle).on("click", function(e){
						var layer=e.layer,
							html="<ul class='popup-geojson'>";

						layer.hdma=layer.hdma || {}

						//read properties
						if(!layer.hdma.popupHtml){
							$.each(layer.feature.properties, function(k,v){
								html+="<li><b>"+k+": </b>"+(function(){
									var out=v;
									if(v instanceof Object){
										out="{";
										$.each(v, function(k1,v1){
											out+="<span>"+k1+": "+v1+"</span>";
										})
										out+="}"
									}
									return out
								})()+"</li>"
							})
							html+="</ul>";
							layer.hdma.popupHtml=html;
						}
						//show popup on the clicked latlng
						app.popup=new L.popup({maxHeight:400, maxWidth:400}).setLatLng(e.latlng).setContent(layer.hdma.popupHtml).openOn(app.map);
					})
					if(callback){callback(featureLayer)}
				})
			break;
		}



	},

	//geocoding
	geocoding:function(place, callback){
		if(place&&place!=""){
			app.geocoder.geocode({
				address:place
				//region:
			}, function(results, status){
				var output=null;

				if(status=='OK'&&results&&results.length>0){
					output=results;
				}

				if(callback){
					callback(results,status)
				}
			});

		}
	},



	//resize map and carousel
	resizeMap: function(css_map, css_carousel, callback){
		$("#map").animate(css_map, 500, function() {
			app.map.invalidateSize(false);

			$("#tweet-carousel").css(css_carousel);
			if(callback){callback();}
		});

	},

	//create hdma object
	createHDMA: function(t, sourceType){
		sourceType=sourceType || "streaming";

		if(t.coordinates&&t.coordinates.coordinates.length==2){
			var coordinates=t.coordinates,
				lat=coordinates.coordinates[1],
				lng=coordinates.coordinates[0],
				widget=run.createTweetWidgetHtml(t)

			//if user login and there is a note, the note will be saved in the tweet
			t=widget.tweet;

			obj={
				popupHtml:widget.html, //run.createMarkerPopupHtml(t),
				latlng:new L.latLng(lat, lng),
				marker:null,
				latlng_geomasking:new L.latLng(run.geomask(lat, app.geomask.meter), run.geomask(lng, app.geomask.meter)),
				hasMedia:(t.entities&&t.entities.media&&t.entities.media.length>0)?true:false,
				mLocal:moment(t.created_at),
				note:t.note
			};

			//delete note if in t
			delete t.note

			obj.marker=new L.marker((app.geomask.enable)?obj.latlng_geomasking:obj.latlng).bindPopup(obj.popupHtml).setIcon(app.icon.getActive());
			obj.marker.hdma={
				tweetText:t.text,
				latlng_geomasking:obj.latlng_geomasking
			}

			t.hdma=obj
			//t.hdma.carouselHtml=run.createTweetCarouselHtml(t);

			return t
		}
	},

	//get query params
	getQueryParams:function(){
		return {
			keywords:$("#search-keyword").val(),
			geojsonPolygon: ((app.layer.queryBoundary)?L.geoJson(app.layer.queryBoundary.toGeoJSON(), {style:app.layerStyle}):null),
			startDate:$("#search-startDate").val() || null,
			endDate:$("#search-endDate").val() || null,
			viewMode:$(".search-viewMode input[type='radio']:checked").attr('value') || null
		}
	},



	//spatial search
	spatialQuery: function(){
		//clear all layers on the map
		run.clearLayers();

		//remove search result
		var $masonry=app.streaming.$masonry;
		$masonry.find(">li.tweetBox.searchResult").remove();

		//get params
		var params=run.getQueryParams();
		console.log(params)
		//show loading
		//$(".search-msg").html("<img src='images/loading.gif' style='width:25px; height:25px;' />")
		var $loading=$(".searchMenu #btn-search .loading").show();


		//filter existing tweets
		app.filter=$.extend({}, params)
		run.filterExistingTweets(params, function(){
			//callback function: wait until all existring tweets has been filtered.

			//update heatmap
			if(app.heatmap.webGL){app.layer.heatmap.update();}

			//hide loading
			$loading.hide();

			//if endDate && startDate !="" >> do historical search
			if(params.startDate&&params.startDate!=""&&params.endDate&&params.endDate!=""){
				//show loading
				//$(".search-msg").html("<img src='images/loading.gif' style='width:25px; height:25px;' />")
				$loading.show();


				//keyword
				if(params.keywords&&params.keywords!=""){params.keywords=run.parseSearchTerm(params.keywords);}

				if(params.geojsonPolygon){
					var layer;
					for (var k in params.geojsonPolygon._layers){
						if(params.geojsonPolygon._layers[k]){layer=params.geojsonPolygon._layers[k]}
						break;
					}
					params.geojsonPolygon=layer.toGeoJSON();
				}


				//topic
				if(app.topic){
					params.searchArea=app.topic.name;
				}


				console.time('run.spatialQuery')
				//send query params by socket
				app.socket.emit("spatialQuery", params, function(results){
					//console.log("[spatial query] search and callback time= "+(console.timeEnd('startQuery')/1000)+"s")
					console.timeEnd('run.spatialQuery');
					console.log("[spatial query] search results = "+results.length)

					//app.search.results=results;
					show.spatialQueryResult(results);

					//update heatmap
					if(app.heatmap.webGL){app.layer.heatmap.update();}

					//hide loading
					$loading.hide()
				});
			}

		});



	},


	//filter existing tweets
	filterExistingTweets:function(params, callback){
		var $masonry=app.streaming.$masonry;

		//remove no match class
		$masonry.find(">li.tweetBox.tweet-notMatch").removeClass("tweet-notMatch")

		console.time('filter')
		//console.log("[filterExistingTweet] start filtering...")

		var	$obj=$masonry.find("li.tweetBox").hide(),
			$select=$obj.filter(function(){
				var $this=$(this),
					validType,
					tweet=$this.data('tweet');

				//show tweet
				//only show markers on the map
				if(tweet){
					validType=run.filterTweet(tweet, params);

					show.tweet(tweet, {masonry:{isShow:false}, validType:validType});
					if(validType=='notMatch'){$this.addClass("tweet-notMatch")}
				}

				return (validType=="true")?true:false;

			}).imagesLoaded(function(){
				setTimeout(function(){
					$select.show();
					$masonry.masonry("layout");

					//hide loading
					$(".search-msg").html("");

					//callback
					if(callback){callback()}
				}, 10)
			});


		//console.log("[filterExistingTweet]= "+ (console.timeEnd('startFiltering')/1000)+"s")
		console.timeEnd('filter')

		//update counts
		app.streaming.$streamingBadge.html($select.length);



	},


	//get bbox of layer
	layerToBBOX:function(layer){
		var bbox=[], result=null;

		if(layer&&layer._latlngs){
			layer._latlngs.map(function(obj,i){
				if(i==0 || i==2){  //get min and max latlng
					bbox.push(obj.lat)
					bbox.push(obj.lng)
				}
			})
			result=bbox;
		}


		return result;
	},


	//parse keyword to match the requirement of text search in mongodb
	//use ',' for multiple keywords and "-" for negative keyword
	parseSearchTerm:function(term){
		if(term&&String(term).trim()!=""){
			return term.split(',').map(function(k){
				k=String(k).trim();

				//check if k is a phrase, contains blank " ". e.g. san diego
				//if(k.split(" ").length>1){k="\""+k+"\""}

				//check if a hyphen is there to do negative search
				if(k.substring(0,1)=='-'){
					var rest=k.substring(1,k.length);
					if(rest.split(' ').length>1){rest="\""+rest+"\""}
					k="-"+rest
				}else{
					//make k as phrase to do text search in mongodb
					//e.g. san diego, fire >> "san diego" "fire" to do AND logical search
					k="\""+k+"\"";
				}

				return k
			}).join(" ");
		}
	},


	//filter tweet
	filterTweet: function(t, options){
		var filter=app.filter,
			isValid=true;

		//options
		if(!options){options={}}
		options.keywords=options.keywords || filter.keywords;
		options.viewMode=options.viewMode || filter.viewMode;
		options.startDate=options.startDate || filter.startDate;
		options.endDate=options.endDate || filter.endDate;
		options.geojsonPolygon=options.geojsonPolygon || filter.geojsonPolygon;

		//console.log(options.geojsonPolygon)

		if(t&&t.hdma){
			var obj=t.hdma;

			//filter by viewmode
			// null >> show both image and text
			// image >> only show image
			// text >> only show text
			if(options.viewMode&&isValid){
				switch (options.viewMode){
					case "image":
						if(!obj.hasMedia){isValid=false;}
					break;
					case "text":
						if(obj.hasMedia){isValid=false;}
					break;
				}
			}


			//filter by startDate and endDate
			if(((options.startDate&&options.startDate!="") || (options.endDate&&options.endDate!=""))&&isValid){
				if(options.startDate && moment(options.startDate) > obj.mLocal){
					isValid=false;
				}
				if(options.endDate && moment(options.endDate).add(1, "days") < obj.mLocal){
					isValid=false;
				}
			}

			//filter by location
			if(options.geojsonPolygon){
				var latlng=(app.geomask.enable)?obj.latlng_geomasking:obj.latlng,
					inside=leafletPip.pointInLayer(latlng, options.geojsonPolygon);

				if(inside.length==0){isValid=false}
			}



			//filter by keyword
			if(options.keywords&&options.keywords!=""&&isValid){
				var isExclude=false,
					keywords=options.keywords.split(",").map(function(k){
						k=String(k).trim();

						if(k.substring(0,1)=='-'){
							//exclude keyword
							isExclude=true;

							//var excludes=k.substring(k.lastIndexOf("(")+1, k.lastIndexOf(")")).split("|").map(function(v){return "(^|\\s+)"+String(v).trim()+"($|\\s+)"}).join("|");
							var excludes="(^|\\s+)"+k.substring(1,k.length)+"($|\\s+)";
							return "((?!("+excludes+")).)"
						}else{
							return "(?=.*?((^|\\s+)"+k.toLowerCase()+"($|\\s+)))";
						}
					}).join("");
				//keywords=new RegExp(keywords, "gi")
				keywords=new RegExp("^"+keywords+((!isExclude)?".":"")+"*$", "gi")


				//filter text
				isValid=keywords.test(t.text.toLowerCase())
			}

		}

		isValid=(isValid)?"true":"notMatch";

		return isValid;
	},


	//clear all layers on the map
	clearLayers: function(){
		var targetLayer=app.layer;

		if(targetLayer){
			//remove layer
			//cluster layer
			if(targetLayer.markerCluster){targetLayer.markerCluster.clearLayers();}

			//heatmap
			if(targetLayer.heatmap){

				if(app.heatmap.webGL){
					targetLayer.heatmap.clearData();
					targetLayer.heatmap.update();

					//need to call the core function to update the canvas of heatmap
					var heatmap=targetLayer.heatmap.WebGLHeatMap;
					heatmap.update();
					heatmap.display();
				}else{
					targetLayer.heatmap.setData({data:[]})
					if(targetLayer.heatmap._heatmap){
						targetLayer.heatmap._heatmap.repaint();
					}
				}

				//app.map.removeLayer(targetLayer.heatmap)

				/**

				//reset heatmap
				//app.layer.heatmap._reset()
				var visible=targetLayer.heatmap.options.visible
				targetLayer.heatmap=run.createHeatmapLayer({visible:visible})
				if(visible){targetLayer.heatmap.addTo(app.map)}
				*/
			}

			if(targetLayer.markerGroup){
				targetLayer.markerGroup.clearLayers();
			}
		}
	},

	//switch layers: app.layer.streaming or app.layer.search
	switchLayer: function(type){
		if(type&&type!=""){
			//hide all layers first
			$.each(app.layer, function(k,v){
				if(k=='streaming' || k=='search'){
					$.each(v, function(o,layer){
						if(layer&&layer._map){
							app.map.removeLayer(layer)
						}
					});
				}
			})

			//show target layers
			if(app.layer[type]){
				$.each(app.layer[type], function(k,v){
					if(v){
						//highlight icon in the leaflet-control-mapGallery
						//and show on the map depending on its options.visible
						var layerType=v.options.layerType;
						if(layerType&&layerType!=""){
							var $icon=$(".leaflet-control-mapGallery li[layer='"+layerType+"']");
							if($icon.length>0){
								$icon.css({"background-color":""})
								if(v.options.visible){$icon.css({"background-color":"#3071A9"}); app.map.addLayer(v);}
							}
						}
					}
				})
			}

		}

	},

	//create heatmap layer
	createHeatmapLayer: function(options){
		//options
		if(!options){options={}}
		options.visible=options.visible || false;
		options.radius=options.radius || parseInt(app.heatmap.radius)/111000;
		options.scaleRadius=true
		options.useLocalExtrema=false;
		options.maxOpacity=.8;


		//create heatmap layer
		var layer=new HeatmapOverlay(options)
		layer._max=app.heatmap.max;

		//var layer=L.heatLayer([], {radius: options.radius, max:1});

		if(layer.options){
			layer.options.visible=options.visible
			layer.options.layerType='heatmap'
		}else{
			layer.options={
				visible:options.visible,
				layerType:'heatmap'
			}
		}

		return layer
	},

	//create heatmap layer
	createWebGLHeatmapLayer: function(options){
		//options
		if(!options){options={}}
		options.visible=options.visible || false;
		options.size=options.size || app.heatmap.radius;
		options.autoresize=options.autoresize || true;
		options.opacity=options.opacity || 0.65;


		//create heatmap layer
		var layer=new L.TileLayer.WebGLHeatMap(options)

		if(layer.options){
			layer.options.visible=options.visible
			layer.options.layerType='heatmap'
		}else{
			layer.options={
				visible:options.visible,
				layerType:'heatmap'
			}
		}

		return layer
	},


	//create markerCluster layer
	createMarkerClusterLayer: function(options){
		//options
		if(!options){options={}}
		options.visible=options.visible || false;
		options.showKeywordCount=options.showKeywordCount || false;

		var layer=hdma.layer.markerCluster(null,{
			iconCreateFunction: function (cluster) {

				//get top keywords
				var topKeywords=run.getTopKeywords(cluster.getAllChildMarkers()),
					topKeyword=topKeywords.map(function(o,i){return "<li><a href='#' onclick='show.highlightKeyword(\""+o.keyword+"\")'>"+o.keyword+((options.showKeywordCount)?(" ("+o.count+")"):"")+"</a></li>"}).join("")


				var childCount = cluster.getChildCount();

				var c = ' marker-cluster-';
				if (childCount < 10) {
					c += 'small';
				} else if (childCount < 100) {
					c += 'medium';
				} else {
					c += 'large';
				}

				return new L.DivIcon({ html: '<div><span>' + childCount + '<ul>'+topKeyword+'</ul></span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
			},
			zoomToBoundsOnClick:true,
			removeOutsideVisibleBounds:true,
			showCoverageOnHover:true
		},{
				//clusterclick event
				clusterclick : run.clusterClickHandler
		});
		layer.options.visible=options.visible;
		layer.options.layerType='markerCluster'

		return layer;
	},


	getTopKeywords: function(markers, options){
		//options
		if(!options){options={}}
		options.top=options.top || 3;

		var text="";
		$.each(markers, function(i,m){
			if(m.hdma.tweetText){
				text+=m.hdma.tweetText+" ";
			}
		})

		//using underscore.js to get word frequency
		//remove a tag and other characters
		text=text.replace(/\<a.*?\<\/a\>/g, "").replace(/[^a-zA-Z\@\#]/g,' ').split(" ");
		var results=_.chain(text)
		  .without('',' ','and','then','it','RT','It','In', 'And', 'not', 'am', 'do', 'go', 'pm', 'me', 'so', 'at','amp','gt', 'my', 'blank', 'target', 'but', 'href', "it's", "co", "isn't", "don't", "very", "here", "there", "also", "us", "are", "they", "from", "your", "new", "about", "will","by", "now","more", "than", "dr", "you're", "we", "what", "get","or", 'the','to','a','an','of','for','as','i','with','it','is','on','that','this','can','in','be','has','if', 'have', 'has', 'http', 'https', 'rt', 'you')
		  .reject(function(w){ return w.length==1})  //remove only containing one character
		  .groupBy(function(word){return word;})
		  .sortBy(function(word){ return -word.length; })
		  .value();

		return results.splice(0,options.top).map(function(w){return {keyword:w[0], count:w.length}})
	},


	//cluster click event
	clusterClickHandler:function(e){
		if(e.layer && e.layer._childCount){
			//console.log(e.layer)

		}
	},


	//create marker popup html
	createMarkerPopupHtml: function(tweet){
		var html="<div class='marker-popup'>",
			text=tweet.text,
			time=tweet.created_at_local,
			m_time=moment(time).add(8, "hours"),  //<<<need to fix!!! need to consider daylight saving time!
			screen_name=tweet.user.screen_name.replace(/\_/g, " "),
			account=tweet.user.name,
			img=tweet.user.profile_image_url;

		html+="<div class='author'><img class='profile-image' src='"+img+"' /><span class='fullname'>"+screen_name+"</span><span class='account-name'>"+account+"</span></div>"+
			  "<div class='content'><p class='text'>"+text+"</p><span class='time'>"+m_time.format("hh:mm A - DD MMM YYYY")+"</span></div>"+
			  "</div>";

		return html;
	},





	//show tweet content in the list
	createTweetWidgetHtml: function(t, options){
		//options
		if(!options){options={}}
		options.twitterWidget=options.twitterWidget || false;


		var html="",
			tweetID=t.id_str,
			entities=t.entities,
			user=t.user,
			created_at=t.created_at,
			m_createdAt=moment(created_at),
			dateFormat="hh:mm A - DD MMM YYYY",
			datetime=m_createdAt.format(dateFormat) ,//|| m_createdAt.twitterLong(),
			//latlng_geomasking=t.hdma.latlng_geomasking || {lat:0, lng:0},
			//coordinates=(app.geomask.enable)?[latlng_geomasking.lng, latlng_geomasking.lat]:t.coordinates.coordinates,
			media='',
			size,
			linkify=function(text){
				return text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+/, function(m) {
					return "<a href='"+m+"' target='_blank'>"+m+"</a>";
				}).replace(/[@#]\w+/g,function(b,c){
					c="twitter.com/";
					//return b.link("//"+(b[a="search"]("#")?c:a+"."+c+a+"?q=")+b)
					//return "<a href='"+("http://"+(b[a="search"]("#")?c:a+"."+c+a+"?q=")+b)+"' target='_blank'>"+b+"</a>"; //twitter rest api 1.1
					if(b.substring(0,1)=="#"){
						c=c+"hashtag/"+b.substring(1);
					}else{
						c=c+b.substring(1)
					}
					return "<a href='https://"+c+"' target='_blank'>"+b+"</a>";
				});
			},
			hasLogin=(app.user&&app.user.username)?true:false;
			notes=(hasLogin&&app.user.note_geoviewer)?app.user.note_geoviewer[tweetID]:null
			currentTopic=(app.user)?app.user.currentTopic:null,
			note=(notes&&currentTopic)?notes[currentTopic]:null;


		//update t
		if(note){t.note=note;}

		//linkify
		t.text=linkify(t.text);

		if(entities&&entities.media&&entities.media.length>0){
			$.each(entities.media, function(i,m){
				switch(m.type){
					case "photo":
						size=m.sizes.small;
						media+="<a class='photo-link box-"+i+"' target='_blank' href='"+m.expanded_url+"' data=scribe='element:photo'><img class='autosized-media' data-srcset='"+""+"' data-crop-x='0' data-crop-y='21' data-image-index='0' alt='View image on Twitter' title='View image on Twitter' data-width='"+size.w+"' data-height='"+size.h+"' src='"+m.media_url+"' width='"+size.w+"' height='"+size.h+"'></a>";
					break;
				}
			})
		}

		//create widget manually
		html='<div class="twitter-tweet twitter-tweet-rendered" title="Embedded Tweet" >'+
		        '<div class="github-fork-ribbon-wrapper right">'+
					'<div class="tweet-sentiment" title="sentiment" style=""><i class="fa '+((note&&note.sentiment&&app.sentimentClasses[note.sentiment])?app.sentimentClasses[note.sentiment]:"")+'"></i></div>'+
					'<div class="github-fork-ribbon '+((note&&note.level)?app.ribbonClasses[note.level]['className']:"")+'" data-level="'+((note&&note.level)?note.level:"")+'"><a href="#">'+((note&&note.level)?app.ribbonClasses[note.level]['label']:"")+'</a></div>'+
				'</div>'+
				'<div class="root standalone-tweet ltr twitter-tweet not-touch">'+
					'<blockquote class="tweet subject expanded h-entry" data-tweet-id="'+tweetID+'" cite="https://twitter.com/'+user.screen_name+'/status/'+tweetID+'" >'+
						(function(){
							if(media!=""){
								//'<a class="photo-link  box-0" target="_blank" href="http://twitter.com/Salvie_Lozano/status/545092123335225344/photo/1" data-scribe="element:photo"><img class="autosized-media" data-srcset="https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FB5COZQRCQAApsvl.jpg%3Alarge 1024w,https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FB5COZQRCQAApsvl.jpg 600w,https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FB5COZQRCQAApsvl.jpg%3Asmall 340w" data-crop-x="0" data-crop-y="21" data-image-index="0" alt="View image on Twitter" title="View image on Twitter" data-width="600" data-height="600" src="https://pbs.twimg.com/media/B5COZQRCQAApsvl.jpg:small" width="251" height="251"></a>'+
								return '<div class="cards-base cards-multimedia customisable-border" data-scribe="component:card" >'+
											'<div class="media">'+media+'</div>'+
									   '</div>'
							}else{
								return ""
							}
						})()+
						'<div class="header">'+
							'<div class="h-card p-author" data-scribe="component:author">'+
								'<a class="u-url profile" target="_blank" href="https://twitter.com/'+user.screen_name+'/status/'+tweetID+'" aria-label="'+user.name+' (screen name: '+user.screen_name+')" data-scribe="element:user_link">'+
									'<img class="u-photo avatar" alt="" src="'+user.profile_image_url_https+'" data-scribe="element:avatar">'+
									'<span class="full-name"><span class="p-name customisable-highlight" data-scribe="element:name">'+user.name+'</span></span>'+
									'<span class="p-nickname" dir="ltr" data-scribe="element:screen_name">@<b>'+user.screen_name+'</b></span>'+
								'</a>'+
							'</div>'+
							'<a class="follow-button profile" target="_blank" href="https://twitter.com/'+user.screen_name+'" role="button" data-scribe="component:followbutton" title="Follow '+user.name+' on Twitter"><i class="ic-button-bird"></i>Follow</a>'+
						'</div>'+
						'<div class="content e-entry-content" data-scribe="component:tweet">'+
							'<p class="e-entry-title">'+t.text+'</p>'+
							'<div class="dateline collapsible-container">'+
								'<a class="u-url customisable-highlight long-permalink" target="_blank" href="https://twitter.com/'+user.screen_name+'/status/'+tweetID+'" data-datetime="'+created_at+'" data-scribe="element:full_timestamp"><time class="dt-updated" datetime="'+created_at+'" title="Time posted: '+created_at+'">'+datetime+'</time></a>'+
								((t.place&&t.place.full_name)?('<span class="p-geo" data-collapsed-class="collapse-geo" title="Posted from '+t.place.full_name+'"><i class="ic-geo ic-mask"></i><b>'+t.place.full_name+'</b></span>'):"")+
							'</div>'+
						'</div>'+
						'<div class="footer customisable-border row" data-scribe="component:footer">'+
							'<div class="tweet-actions col-lg-6 col-sm-8" role="menu" aria-label="Tweet actions" data-scribe="component:actions">'+
								'<a class="tweet-note" style="display:'+((hasLogin)?"inline":"none")+'"><i class="fa fa-tags"></i><span>Note</span></a>'+
								'<a class="tweet-mapit"><i class="fa fa-map-marker"></i><span>Map</span></a>'+
							'</div>'+
							'<div class="tweet-tags col-lg-6 col-sm-4">'+((note&&note.tags)?(note.tags.split(",").map(function(item){return "<span>"+item.trim()+"</span>"}).join("")):"")+'</div>'+
						'</div>'+
					'</blockquote>'+
				'</div>'+
			 '</div>';



		//use twttr.widgets.load or create
		if(options.twitterWidget){
			html="<blockquote class='twitter-tweet'>&mdash; "+user.name+" (@"+user.screen_name+") "+
				   "<a href='https://twitter.com/"+user.screen_name+"/status/"+tweetID+"'>"+moment(parseInt(t.timestamp_ms)).format("MMMM D, YYYY") +"</a>"+
				 "</blockquote>";
		}


		return {tweet:t, html:html};
	},


	//geomasking
	geomask: function(value, meter){
		var random=Math.random()*2-1
		return parseFloat(value)+((random*meter)/100000)
	},


	//change marker icon
	changeMarkerIcon: function(img_src, width, height) {
		var icon = new L.icon({
			iconUrl : img_src,
			iconSize : [width, height], //[12.5, 21],
			iconAnchor : [width / 2, height / 2]// [6.25, 10.5]
		});

		var iconHover = new L.icon({
			iconUrl : (function() {
				if(img_src.split("bullet").length>1){
					return "images/marker/1374590745_bullet-red.png"
				}else{
					return "images/marker/Magenta/5.png"
					//return "images/1374595382_marker_rounded_red.png"
				}

			})(),
			iconSize : [width, height], //[12.5, 21],
			iconAnchor : [width / 2, height / 2]// [6.25, 10.5]
		});



		$.each(app.layer.markerGroup.getLayers(), function(k, marker) {
			marker.setIcon(icon);
			marker.options.iconDefault = icon;
			marker.options.iconHover = iconHover;
		})


	},


	//after login
	afterLogin: function(result){
		$("#loginLoading").hide();

		if(result&&result.status=='ok'){
			//cache user
			app.user=result;

			//show username
			$(".navbar-nav #username").html(result.username);
			$(".navbar-nav #btn-login").off('click').click(function(){run.logout();}).find("span").html("Log out")

			//download button
			$("#btn-download").off('click').click(function(){
				if(result.downloadURL){
					var url=result.downloadURL,
						texts=$("#search-keyword").val() || "",
						startDate=$("#search-startDate").val() || "",
						endDate=$("#search-endDate").val() || "",
						searchRegion=app.topic.name.replace(/\+/gi,"%2b"),
						bbox="";

					//parse bbox
					if(app.filter.geojsonPolygon){
						var geojson=app.filter.geojsonPolygon.toGeoJSON();
						if(geojson.features&&geojson.features.length>0){
							var f=geojson.features[0];
							if(f&&f.geometry&&f.geometry.type=='Polygon'&&f.geometry.coordinates.length>0){
								bbox=f.geometry.coordinates[0].map(function(xys){return [xys[0].toFixed(6), xys[1].toFixed(6)].join(",")}).join(",");
							}
						}
					}

					//parse searchRegion
					searchRegion=(searchRegion=='sanDiego')?"San Diego region":searchRegion;

					url=url.replace("{texts}",texts).replace("{startDate}", startDate).replace("{endDate}", endDate).replace("{bbox}", bbox).replace("{searchRegion}", searchRegion);
					window.open(url);
				}
			}).show();


			//bookmark click function
			$("#btn-bookmark").off('click').click(function(){
				run.bookmark("save")
			});


			//show bookmarks
			if(result.bookmarks){
				show.bookmark(result.bookmarks)
			}


			//show topics
			var topics=app.user.topics,
				getHtml=function(t){return (app.tagTopics[t])?("<i class='glyphicon "+app.tagTopics[t].icon+"'></i>"+app.tagTopics[t].name):hdma.util.capitalizeFirstLetter(t)};

			app.user.currentTopic=topics[0];

			$("#tbx-note label").html(getHtml(topics[0]));
			//give topics in the tbx-note dropdown-menu and trigger click event on the first topic to show tag in tweets
			$("#tbx-note .dropdown-menu").html(topics.map(function(t,i){return "<li><a href='#' value='"+t+"' type='note'>"+getHtml(t)+"</a></li>"}).join("")).find("> li:first-child > a").trigger("click");


			//popup_tag
			var $target=$("#popup_tag"),
				fxButton=function(labels, icon){
					$target.find("#tag-color button").each(function(i,item){$(item).attr({"title": labels[i], "icon":icon}); if(i==1){$(item).trigger('click')}})
				},
				html="";
			topics.map(function(t,i){
				if(app.tagTopics[t]){
					labels=app.tagTopics[topics[0]].labels
					var obj=app.tagTopics[t];
					html+="<option data-icon='"+obj.icon+"' data-label='"+obj.labels.join(",")+"' value='"+t+"'>"+obj.name+"</option>";
				}
			})
			$target.find("#tag-topic select#tag-theme").html(html).selectpicker("refresh")
			fxButton(labels, app.tagTopics[topics[0]].icon)

			//show tag in tweets
			run.setTagInfo();

			//show
			$(".github-fork-ribbon-wrapper, .btn-afterLogin, #nav-bookmark, .twitter-tweet .tweet-note, #tbx-note").show();

			//hide login modal
			$("#popup_login").modal("hide");
		}else{
			$("#loginMsg").html(result.msg).show();
		}

	},


	//log out
	logout: function(){
		//clear all note
		run.setTagInfo({logout:true})


		app.user=null;

		$("#btn-download").off("click").hide();
		$(".navbar-nav #username, #nav-bookmark ul.dropdown-menu").html("");
		$(".navbar-nav #btn-login").off("click").click(function(){
			$("#popup_login").modal({})
		}).find("span").html("Log in")

		//hide
		$(".github-fork-ribbon-wrapper, .btn-afterLogin, #nav-bookmark, .twitter-tweet .tweet-note").hide();
	},


	//toolbar functions
	toolbar: function(value, type, $this){
		var streamingOptions=app.streaming.options,
			$masonry=app.streaming.$masonry,
			$label=$this.parents("ul.dropdown-menu").siblings("a.dropdown-toggle").find("label"),
			//$label=$this.parents(".btn-group").find(".btn:first-child"),
			timeInterval=app.streaming.timeInterval;

		if(value&&value!=""&&type&&type!=""){
			//swtich type
			switch(type){
				case "play-pause":
					//change label
					$label.html($this.html())

					//refresh app.streaming.$tweetsOnHold;
					app.streaming.$tweetsOnHold=$(".searchToolbar span.onHoldTweets");

					streamingOptions.playPause=value;
					if(value=='play'){
						var tweetsOnHold=app.streaming.tweetsOnHold;

						//show all onhold tweets
						run.showAllTweets(tweetsOnHold);


						//clear tweetsOnHold
						app.streaming.tweetsOnHold=[];
						app.streaming.$tweetsOnHold.html(0).hide();

						//clear time interval or onMaxHoldTweets
						if($this.attr('value')=='play'){
							if(timeInterval){
								console.log('clear interval');
								clearInterval(app.streaming.timeInterval)
							}

							if(app.streaming.onMaxHoldTweets){
								app.streaming.onMaxHoldTweets=null
							}
						}

					}
				break;
				case "refresh":
					//clear time interval
					if(timeInterval){clearInterval(app.streaming.timeInterval)}

					//change label
					$label.html($this.html())

					var values=value.split("-");

					switch(values[1]){
						case "min":
							run.toolbar("pause", "play-pause",$this)
							app.streaming.timeInterval=setInterval(function(){
								run.toolbar("play", "play-pause", $this);
								console.log("wait "+value);
								run.toolbar("pause", "play-pause", $this);
							}, parseInt(values[0])*60*1000)
						break;
						case "tweet":
							//pause
							run.toolbar("pause", "play-pause",$this)

							//wait until the number of tweets
							app.streaming.onMaxHoldTweets=function(){
								var tweetsOnHold=app.streaming.tweetsOnHold,
									max=parseInt(values[0]);

								if(tweetsOnHold.length>max){
									console.log("tweetsOnHold.length("+tweetsOnHold.length+") > "+ max )
									run.toolbar("play", "play-pause", $this);
									console.log("wait "+value);
									run.toolbar("pause", "play-pause", $this);
								}
							}

						break;
					}

				break;
				case "sort":
					//change label
					$label.html($this.html())

					var sort=app.streaming.options.sort;
					if(value!=sort){
						//clear all layers on the map
						//run.clearLayers();

						//clear existing but if remove(), the tweet data will not be accessiable later
						var $target=$masonry.find('.tweetBox');
						if(sort=='prepend'){$target.reverse()}


						//give sort value to global variable
						app.streaming.options.sort=value;

						run.showAllTweets($target, {updateStreamingNum:false, updateLayer:false});
					}
				break;
				case "analysis":

				break;
				case "note":
					//change label
					$label.html($this.html())

					//update current Topic
					app.user.currentTopic=value;

					//update each li
					$masonry.find('.tweetBox').each(function(){
						show.tagInfo($(this))
					})
					$masonry.masonry("layout")
				break;
			}
		}

	},


	//showPopup
	showPopup: function(id){


	},


	//show tweets at one time
	//the tweets need to be ordered by old to new
	showAllTweets: function(tweets, options){
		//options
		if(!options){options={}}
		options.addClass=options.addClass || null;
		options.updateStreamingNum=(options.updateStreamingNum==null)?true:options.updateStreamingNum;
		options.callback=options.callback || null;
		options.updateLayer=(options.updateLayer==null)?true:options.updateLayer;

		var num=0 ,validType, elems=[], $li, $masonry=app.streaming.$masonry,
			$streamingBadge=app.streaming.$streamingBadge,
			blacklists=app.blacklists,
			insertPosition=app.streaming.options.sort,
			masonryOptions={},
			isJquery=(tweets.selector)?true:false,
			$t;

			//masonryOptions
			if(options.addClass&&options.addClass!=""){
				masonryOptions['addClass']=options.addClass

				if(options.addClass=='searchResult'){
					//need to insert in different way
					insertPosition=masonryOptions['insertPosition']=(insertPosition=='append')?'prepend':"append"
					tweets.reverse()
				}
			}


			//time
			console.time("run.showAllTweets[parseTweet]")

			//read tweets
			var t, searchResultClass=false, bboxes=app.topic.bbox.split(",").map(function(k){return parseFloat(k)});
			for(var i=0, length=tweets.length; i<length; i++){
				t=tweets[i];



				//time
				//console.time("run.showAllTweets[parseEachTweet]")

			//$.each(tweets, function(i,t){

				searchResultClass=false;
				if(isJquery){
					$t=tweets.eq(i);
					t=tweets.eq(i).data('tweet');
					searchResultClass=$t.hasClass('searchResult')
					$t.remove();
				}

				if(t&&t.coordinates){
					if(blacklists.indexOf(t.user.screen_name)==-1){
						var coords=t.coordinates.coordinates;

						//check if coords is within bboxes
						if(coords[0]>=bboxes[0]&&coords[0]<=bboxes[2]&&coords[1]>=bboxes[1]&&coords[1]<=bboxes[3]){

							if(!t.hdma){
								t=run.createHDMA(t, "streaming");
							}

							//if the tweets are from the search result, do not need to check the valid again because the database has been checked!
							validType=(options.addClass=="searchResult" || searchResultClass)?true:run.filterTweet(t);
							validType=validType.toString();


							//[bugs]here took the most time >> need to increase performance (current 100 tweets took around 3s)
							//console.time("run.showAllTweets[parseEachTweet.showTweet]")
							$li=show.tweet(t, {validType:validType, id:i, masonry:masonryOptions, updateLayer:options.updateLayer})
							//console.timeEnd("run.showAllTweets[parseEachTweet.showTweet]")

							if(searchResultClass){$li.addClass('searchResult')}

							elems.push($li[0]);

							if(validType=="true"){num++}

							if(options.updateStreamingNum){app.streaming.num++}

						}
					}
				}

				//time
				//console.timeEnd("run.showAllTweets[parseEachTweet]")
				//console.log("*********************************************************")
			}//)

			//time
			console.timeEnd("run.showAllTweets[parseTweet]")

			//update badge num
			if(options.updateStreamingNum){
				$streamingBadge.html(parseInt($streamingBadge.html())+num);
			}


			//time
			console.time("run.showAllTweets[masonry]")

			//refresh masonry layout when all images are loaded
			$(elems).imagesLoaded(function(){
				$masonry.masonry(insertPosition+"ed", (insertPosition=='append')?elems:elems.reverse()).masonry('layout')

				console.timeEnd("run.showAllTweets[masonry]")
				//console.log('[run.showAllTweets]: took ' + ((new Date()-now)/1000 +"s"))

				if(options.callback){options.callback()}
			})
	}


}
















//get url parameter
function getURLParameter(name){
	var url=location.href + "&",
		splits=url.split("?"),
		host=splits[0],
		parameters=(splits[1])?splits[1].split("&"):[],
		result=null;

	$.each(parameters, function(i,p){
		var key=p.split("=")[0],
			value=p.split("=")[1];

		if(key==name){
			result=value
			return false;
		}
	})


	return result;
}
