
class GPS {
	parse(data) {
		this.data = data;
		this.index = 0;
		
		this.maxTimeDiff = 3600 * 6;
		this.maxDist = 3000;
		
		this.tracks = [];
		let t;
		while(t = gps.next()) {
			if (t.length > 1)
				this.tracks.push(new Track(t));
		}
		this.regions = this.getRegions();
	}
	next() {
		if(this.index >= this.data.length)
			return;
		let points = [];
		while(this.index < this.data.length - 1) {
			const cur = this.data[this.index];
			const nxt = this.data[this.index+1];
			
			cur.timeDiff = nxt.timestamp - cur.timestamp;
			cur.distance = distance(cur, nxt);
			
			if(isNaN(cur.distance))
				cur.distance = 0;
			
			if(cur.speed > 0) {
				cur.speed = cur.speed * 1.852 / 100;
			} else {
				cur.speed = cur.distance / cur.timeDiff * 3.6;
			}
			
			if(cur.timeDiff > this.maxTimeDiff || cur.distance > this.maxDist) {
				cur.distance = null;
				cur.timeDiff = null;
				cur.speed = null;
				break;
			}
			
			points.push(cur);
			this.index++;
		}
		if(this.index == this.data.length - 1) {
			this.data[this.index].distance = 0;
			this.data[this.index].timeDiff = 0;
			this.data[this.index].speed = 0;
		}
		points.push(this.data[this.index]);
		this.index++;
		
		return points;
	}
	getRegions() {
		var trips = [];
		var s = 0;
		for(let i=0; i<=this.tracks.length; i++) {
			while(i < this.tracks.length - 2) {
				const cur = this.tracks[i].info;
				const nxt = this.tracks[i+1].info;
				if((nxt.startTime - cur.endTime) > 3600 * 24 * 7 || distance(cur.center, nxt.center) > 100000) {
					break;
				}
				i++;
			}
			
			let trip = this.tracks.slice(s, i);
			let dist = trip.reduce((sum, p) => p.info.distance + sum, 0);
			if (dist > 30000) {
				trips.push({
					lat: median(trip.map(v => v.info.center.lat)),
					lng: median(trip.map(v => v.info.center.lng)),
					timestamp: median(trip.map(v => v.info.startTime)),
					distance: dist
				});
			}
			s = i;
		}
		return trips;
	}
	getInfo() {
		let i = {
			points: this.data.length,
			distance: this.tracks.reduce((sum, p) => p.info.distance + sum, 0),
			topSpeed: this.tracks.reduce((max, p) => p.info.topSpeed > max ? p.info.topSpeed : max, 0),
			ascend: this.tracks.reduce((sum, p) => p.info.ascend + sum, 0),
			descend: this.tracks.reduce((sum, p) => p.info.descend + sum, 0),
			movTime: this.tracks.reduce((sum, p) => p.info.movTime + sum, 0),
			totalTime: this.tracks.reduce((sum, p) => p.info.totalTime + sum, 0)
		};
		i.avgSpeed = i.distance / i.movTime * 3.6;
		return i;
	}
}
let trackId = 0;

class Track {
	constructor(data) {
		this.data = data;
		this.id = trackId++;
		
		this.deleted = this.clean();
		this.info = this.getInfo();
	}
	
	clean() {
		let last = this.data[this.data.length-2];
		let speed = last.speed, alt = last.alt;
		let res = [];
		for(let i=this.data.length-2; i>=0; i--) {
			let cur = this.data[i];
			let lst = this.data[i+1];
			if(Math.abs(cur.speed - lst.speed) / cur.timeDiff > 10 || Math.abs(cur.speed - speed) > 50) {
				res.push(this.data.splice(i, 1)[0]);
			} else
			speed = speed * 0.9 + cur.speed * 0.1;
			alt = alt * 0.9 + cur.alt * 0.1;
		}
		return res;
	}
	getInfo() {
		let i = {
			id: this.id,
			points: this.data.length,
      color: getRandomColor(this.data[0].timestamp),
			distance: this.data.reduce((sum, p) => p.distance + sum, 0),
			topSpeed: this.data.reduce((max, p) => p.speed > max ? p.speed : max, this.data[0].speed),
			ascend: 0,
			descend: 0,
			startTime: this.data[0].timestamp,
			endTime: this.data[this.data.length-1].timestamp,
			movTime: this.data.reduce((sum, p) => p.speed > 2 ? p.timeDiff + sum : sum, 0),
			minLat: this.data.reduce((min, p) => p.lat < min ? p.lat : min, this.data[0].lat),
			maxLat: this.data.reduce((max, p) => p.lat > max ? p.lat : max, this.data[0].lat),
			minLng: this.data.reduce((min, p) => p.lng < min ? p.lng : min, this.data[0].lng),
			maxLng: this.data.reduce((max, p) => p.lng > max ? p.lng : max, this.data[0].lng),
			
		};
		for(let n=1; n<this.data.length; n++) {
			const cur = this.data[n].alt;
			const lst = this.data[n-1].alt;
			if(cur > lst)
				i.ascend  += cur - lst;
			else
				i.descend += lst - cur;
		}
		i.totalAscend = i.ascend - i.descend;
		i.avgSpeed = i.distance / i.movTime * 3.6;
		i.center = {
			lat: (i.maxLat+i.minLat)/2, 
			lng: (i.maxLng+i.minLng)/2
		},
		i.totalTime = i.endTime - i.startTime;
		
		return i;
	}
	
}

function getRandomColor(i) {
  return "hsl(" + (i * 2 % 360) + ", " + (i * 3 % 60 + 40) + "%, " + (i * 5 % 40 + 30) + "%)";
}
function distance (a, b) {
 
	// Convert degrees to radians
	var lat1 = a.lat * Math.PI / 180.0;
	var lon1 = a.lng * Math.PI / 180.0;
 
	var lat2 = b.lat * Math.PI / 180.0;
	var lon2 = b.lng * Math.PI / 180.0;
 
	// radius of earth in metres
	var r = 6378100;
 
	// P
	var rho1 = r * Math.cos(lat1);
	var z1 = r * Math.sin(lat1);
	var x1 = rho1 * Math.cos(lon1);
	var y1 = rho1 * Math.sin(lon1);
 
	// Q
	var rho2 = r * Math.cos(lat2);
	var z2 = r * Math.sin(lat2);
	var x2 = rho2 * Math.cos(lon2);
	var y2 = rho2 * Math.sin(lon2);
 
	// Dot product
	var dot = (x1 * x2 + y1 * y2 + z1 * z2);
	var cos_theta = dot / (r * r);
 
	var theta = Math.acos(cos_theta);
 
	// Distance in Metres
	return r * theta;
}

function timeFormat (sec, x = "time") {
	let d = new Date(sec*1000);
	let t = new Date(null,null,null,null,null,sec).toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0];
	
	switch(x) {
		case "time":
			if(sec < 3600*24)
				return t;
			else
				return Math.floor(sec/3600/24) + "d " + t;
		case "datetime":
			return d.toLocaleDateString('de-DE', {
				year: "numeric",
				month: "2-digit",
				day: "2-digit",
				hour: "2-digit",
				minute: "2-digit",
				second: "2-digit",
			});
		case "date":
			return d.toLocaleDateString('de-DE', {
				year: "numeric",
				month: "2-digit",
				day: "2-digit"
			});	
		case "short":
			return d.toLocaleDateString('de-DE', {
				year: "numeric",
				month: "2-digit"
			});	
	}
}
function median(values) {

	values.sort( function(a,b) {return a - b;} );

	var half = Math.floor(values.length/2);

  return values[half];
}
function avg (v) {
  return v.reduce((a,b) => a+b, 0)/v.length;
}

function smoothOut (vector, variance) {
  var t_avg = avg(vector)*variance;
  var ret = Array(vector.length);
  for (var i = 0; i < vector.length; i++) {
    (function () {
      var prev = i>0 ? ret[i-1] : vector[i];
      var next = i<vector.length ? vector[i] : vector[i-1];
      ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
    })();
  }
  return ret;
}