/*
 * uNav http://launchpad.net/unav
 * Copyright (C) 2015-2016 Marcos Alvarez Costales https://launchpad.net/~costales
 *
 * uNav is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * uNav is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

function Navigator(maths, lang) {
	this.maths = maths;
	this.ui_lang = lang;
	
	this.pos_now = new Object();
		this.pos_now['lat'] = null;
		this.pos_now['lng'] = null;
	
	this.pos_start = new Object();
		this.pos_start['lat'] = null;
		this.pos_start['lng'] = null;
	
	this.pos_end = new Object();
		this.pos_end['lat'] = null;
		this.pos_end['lng'] = null;
	
	this.accuracy = this.ACCU4DRIVE + 1;
	this.speed = null;

	this.route = new Object();
		this.route['start_check_out'] = false;
		this.route['status'] = 'no'; // no | errorAPI | waiting4signal | calc | drawing | 2review | yes | out | ended
		this.route['total'] = {
			distance: 0,
			time: 0
		}
		this.route['track'] = {
			ind: 0,
			total: 0,
			percent: 100
		};
		this.route['tracks'] = [];
		this.route['turf'] = null;
		this.route['radars'] = [];

	this.nearest = new Object();
		this.nearest['indication'] = '1';
		this.nearest['dist2turn'] = 0;
		this.nearest['msg'] = '';
		this.nearest['distance'] = 0;
		this.nearest['dist_track_done'] = 0;
		this.nearest['distance_total'] = 0;
		this.nearest['time'] = 0;
		this.nearest['voice'] = false;
		this.nearest['speaked'] = false;
		this.nearest['radar'] = false;
		this.nearest['radar_sound'] = false;
		this.nearest['radar_speed'] = '!';
}

Navigator.prototype.ACCU4DRIVE = 250;
Navigator.prototype.IS_IN_ROUTE = 95;
Navigator.prototype.MAX_START_CHECK_OUT = 250;
Navigator.prototype.DIST4INDICATION = 99; // Never > 99 for preserve voices
Navigator.prototype.SPEED_CITY = 62;
Navigator.prototype.SPEED_INTERCITY = 82;
Navigator.prototype.RADAR_IN_ROUTE = 27;
Navigator.prototype.RADARS_MAX = 60;


Navigator.prototype.set_gps_data = function(lat, lng, accu, speed) {
	if (lat === null || lng === null)
		return;
	
	this.pos_now['lat'] = lat;
	this.pos_now['lng'] = lng;
	this.accuracy = parseInt(accu);
	this.speed = this.maths.speed2human(speed);
}

Navigator.prototype.get_pos_data = function() {
	return {
		now_lat:	this.pos_now['lat'],
		now_lng:	this.pos_now['lng'],
		start_lat:	this.pos_start['lat'],
		start_lng:	this.pos_start['lng'],
		end_lat:	this.pos_end['lat'],
		end_lng:	this.pos_end['lng'],
		accu:		this.accuracy, 
		speed:		this.speed
	};
}

Navigator.prototype.radars_clear = function () {
	this.route['radars'] = [];
}

Navigator.prototype.set_radar = function(lat, lng, speed) {
	var dist2line = 0;
	var pt_radar = turf.point([lng, lat]);
	var pt_near = turf.pointOnLine(this.route['complete_line'], pt_radar);
	
	dist2line = geolib.getDistance(
		{latitude: lat, longitude: lng},
		{latitude: pt_near.geometry.coordinates[1], longitude: pt_near.geometry.coordinates[0]}
	);
	
	if (dist2line <= this.RADAR_IN_ROUTE && this.route['radars'].length <= this.RADARS_MAX) { // Add radar
		this.route['radars'].push({
			lat: lat,
			lng: lng,
			speed: speed,
			alerted: false
		});
	}
}

Navigator.prototype.get_radars = function () {
	return this.route['radars'];
}

Navigator.prototype.set_route = function(total_m, total_s, line_encoded, tracks) {
	// Set cycle route
	this.route['status'] = 'drawing';

	// Total
	this.nearest['distance_total'] = total_m;
	this.nearest['time'] = total_s;
	
	// For draw line
	this.route['start_check_out'] = false;
	var coords = this.maths.decode(line_encoded, true);
	
	// Set new start/end points for markers
	this.pos_start['lat'] = coords[0][0];
	this.pos_start['lng'] = coords[0][1];
	this.pos_end['lat'] = coords[(coords.length)-1][0];
	this.pos_end['lng'] = coords[(coords.length)-1][1];
	
	// All tracks
	this.route['tracks'] = [];
	this.route['turf'] = [];
	this.route['complete_line'] = {
		"type": "Feature",
		"properties": {},
		"geometry": {
			"type": "LineString",
			"coordinates": this.maths.decode(line_encoded, false)
		}
	};
	
	var turf_line = [];
	var adding_coord = false;
	var instruction = '';
	
	for (i=0; i < tracks.length; i++) {

		// Hack roundabouts
		if (tracks[i].type == 26) {
			switch (tracks[i].roundabout_exit_count) {
				case 1:
					tracks[i].type = 261;
					break;
				case 2:
					tracks[i].type = 262;
					break;
				case 3:
					tracks[i].type = 263;
					break;
				case 4:
					tracks[i].type = 264;
					break;
				case 5:
					tracks[i].type = 265;
					break;
				case 6:
					tracks[i].type = 266;
					break;
				case 7:
					tracks[i].type = 267;
					break;
				case 8:
					tracks[i].type = 268;
					break;
				case 9:
					tracks[i].type = 269;
					break;
			}
		}
		if (tracks[i].type == 27 && i > 0) {
			switch (tracks[i-1].type) {
				case 261:
					tracks[i].type = 271;
					break;
				case 262:
					tracks[i].type = 272;
					break;
				case 263:
					tracks[i].type = 273;
					break;
				case 264:
					tracks[i].type = 274;
					break;
				case 265:
					tracks[i].type = 275;
					break;
				case 266:
					tracks[i].type = 276;
					break;
				case 267:
					tracks[i].type = 277;
					break;
				case 268:
					tracks[i].type = 278;
					break;
				case 269:
					tracks[i].type = 279;
					break;
			}
		}
		
		if (this.ui_lang == 'en' || this.ui_lang == 'e1' || this.ui_lang == 'e2')
			instruction = tracks[i].instruction.slice(0, -1);
		else
			instruction = this.compose_instruction(
				tracks[i].type,
				tracks[i].instruction,
				tracks[i].street_names,
				tracks[i].sign
			);

		
		// Turn indications
		if (tracks[i].type < 4 || tracks[i].type > 6)
			this.route['tracks'].push({
				type: tracks[i].type,
				coordinates: [coords[tracks[i].begin_shape_index][1], coords[tracks[i].begin_shape_index][0]], // begin coord lng,lat
				instruction: instruction,
				distance: (tracks[i].length*1000),
				duration: tracks[i].time,
				way_name: tracks[i].hasOwnProperty('street_names') ? tracks[i].street_names[0] : '', // get 1st for split instruction
				speaked: false
			});
		else // end
			this.route['tracks'].push({
				type: tracks[i].type,
				coordinates: [coords[tracks[i].begin_shape_index][1], coords[tracks[i].begin_shape_index][0]], // begin coord lng,lat
				instruction: t("You are near to the destination"),
				distance: 0,
				duration: 0,
				way_name: '',
				speaked: false
			});
		
		// Check Inside + near point
		if (i_coord=tracks[i].begin_shape_index != tracks[i].end_shape_index) { // Not considerate end point
			turf_line = [];
			for (i_coord=tracks[i].begin_shape_index; i_coord <= tracks[i].end_shape_index; i_coord++) {
				turf_line.push([coords[i_coord][1], coords[i_coord][0]]); // lng,lat
			}
			this.route['turf'].push(turf.linestring(turf_line));
		}
	}
}

Navigator.prototype.compose_instruction = function(type, en_instruction, street_names, sign) {
	var instruction = '';
	
	switch (type) {
		case 0: // None
			instruction = '';
			break;
		case 1: // Start
			instruction = t("Go");
			break;
		case 2: // StartRight
			instruction = t("Go to your right");
			break;
		case 3: // StartLeft
			instruction = t("Go to your left");
			break;
		case 4: // Destination
			instruction = t("You have arrived at your destination");
			break;
		case 5: // DestinationRight
			instruction = t("Your destination is on the right");
			break;
		case 6: // DestinationLeft
			instruction = t("Your destination is on the left");
			break;
		case 7: // Becomes
			instruction = t("Current road becomes");
			break;
		case 8: // Continue
			instruction = t("Continue");
			break;
		case 9: // SlightRight
			instruction = t("Bear right");
			break;
		case 10: // Right
			instruction = t("Turn right");
			break;
		case 11: // SharpRight
			instruction = t("Make a sharp right");
			break;
		case 12: // UturnRight
			instruction = t("Make a right U-turn");
			break;
		case 13: // UturnLeft
			instruction = t("Make a left U-turn");
			break;
		case 14: // SharpLeft
			instruction = t("Make a sharp left");
			break;
		case 15: // Left
			instruction = t("Turn left");
			break;
		case 16: // SlightLeft
			instruction = t("Bear left");
			break;
		case 17: // RampStraight
			instruction = t("Stay straight on ramp");
			break;
		case 18: // RampRight
			instruction = t("Turn right on ramp");
			break;
		case 19: // RampLeft
			instruction = t("Turn left on ramp");
			break;
		case 20: // ExitRight
			instruction = t("Take the exit on the right");
			break;
		case 21: // ExitLeft
			instruction = t("Take the exit on the left");
			break;
		case 22: // StayStraight
			instruction = t("Keep straight at the fork");
			break;
		case 23: // StayRight
			instruction = t("Keep right at the fork");
			break;
		case 24: // StayLeft
			instruction = t("Keep left at the fork");
			break;
		case 25: // Merge
			instruction = t("Merge");
			break;
		case 26: // RoundaboutsEnter
			instruction = t("Enter the roundabout");
			return instruction;
		case 261:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 1);
			return instruction;
		case 262:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 2);
			return instruction;
		case 263:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 3);
			return instruction;
		case 264:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 4);
			return instruction;
		case 265:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 5);
			return instruction;
		case 266:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 6);
			return instruction;
		case 267:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 7);
			return instruction;
		case 268:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 8);
			return instruction;
		case 269:
			instruction = t("Enter the roundabout and take the exit %1").replace('%1', 9);
			return instruction;
		case 27: // RoundaboutExits
			instruction = t("Exit the roundabout");
			return instruction;
		case 271:
			instruction = t("Take the exit %1").replace('%1', 1);
			return instruction;
		case 272:
			instruction = t("Take the exit %1").replace('%1', 2);
			return instruction;
		case 273:
			instruction = t("Take the exit %1").replace('%1', 3);
			return instruction;
		case 274:
			instruction = t("Take the exit %1").replace('%1', 4);
			return instruction;
		case 275:
			instruction = t("Take the exit %1").replace('%1', 5);
			return instruction;
		case 276:
			instruction = t("Take the exit %1").replace('%1', 6);
			return instruction;
		case 277:
			instruction = t("Take the exit %1").replace('%1', 7);
			return instruction;
		case 278:
			instruction = t("Take the exit %1").replace('%1', 8);
			return instruction;
		case 279:
			instruction = t("Take the exit %1").replace('%1', 9);
			return instruction;
		case 28: // FerryEnter
			instruction = t("Take the Ferry");
			break;
		case 29: // FerryExit
			instruction = t("Leave the Ferry");
			break;
	}
	
	var number = '';
	var branch = '';
	var toward = '';
	var name = '';
	var max = 0;
	if (sign) {
		if (sign.hasOwnProperty('exit_number_elements')) {
			max = 0;
			sign.exit_number_elements.forEach( function( item ) {
				if (max++ < 2) {
					if (!number)
						number = item['text'];
					else
						number = number + ', ' + item['text'].trim();
				}
			});
		}


		if (sign.hasOwnProperty('exit_branch_elements')) {
			max = 0;
			sign.exit_branch_elements.forEach( function( item ) {
				if (max++ < 2) {
					if (!branch)
						branch = item['text'];
					else
						branch = branch + ', ' + item['text'].trim();
				}
			});
		}
		
		if (sign.hasOwnProperty('exit_toward_elements')) {
			max = 0;
			sign.exit_toward_elements.forEach( function( item ) {
				if (max++ < 2) {
					if (!toward)
						toward = item['text'];
					else
						toward = toward + ', ' + item['text'].trim();
				}
			});
		}
		
		if (sign.hasOwnProperty('exit_name_elements')) {
			max = 0;
			sign.exit_name_elements.forEach( function( item ) {
				if (max++ < 2) {
					if (!name)
						name = item['text'];
					else
						name = name + ', ' + item['text'].trim();
				}
			});
		}
	}
	
	if ((type != 20 && type != 21) &&
	   (street_names && !branch && !toward && !name)) {
		instruction = instruction + t(" onto %1").replace('%1', street_names);
	}
	if (number)
		instruction = instruction + t(". Exit %1").replace('%1', number);
	if (branch)
		instruction = instruction + t(" to take the %1").replace('%1', branch);
	if (toward)
		instruction = instruction + t(" toward %1").replace('%1', toward);
	if (name)
		instruction = instruction + t(", %1").replace('%1', name);

	return instruction;
}

Navigator.prototype.get_route_indication = function() {
	var voice_tmp = this.nearest['voice'];
	if (voice_tmp) // Need because ended will call here several times
		this.nearest['voice'] = false;
	
	return {
		indication:			this.nearest['indication'],
		dist2turn:			this.nearest['dist2turn'],
		msg:				this.nearest['msg'],
		time:				this.nearest['time'],
		distance:			this.nearest['distance'],
		dist_track_done:	this.nearest['dist_track_done'],
		distance_total:		this.nearest['distance_total'],
		speed:				this.speed,
		voice:				voice_tmp,
		speaked:			this.nearest['speaked'],
		radar:				this.nearest['radar'],
		radar_sound:		this.nearest['radar_sound'],
		radar_speed:		this.nearest['radar_speed']
	};
}

Navigator.prototype.get_route_tracks = function() {
	return nav.route['tracks'];
}

Navigator.prototype.get_route_line_map = function() {
	var line_coords = [];
	var line_added = false;
	
	// For all tracks
	for (i=0; i < this.route['turf'].length; i++) {
		// For all coordenates in each track
		for (i2=0; i2 < this.route['turf'][i].geometry.coordinates.length; i2++) {
			// Already added? Because the last of each will be the same of next start
			line_added = false;
			for (i3=0; i3 < line_coords.length; i3++)
				if (this.route['turf'][i].geometry.coordinates[i2][0] == line_coords[i3][0] && this.route['turf'][i].geometry.coordinates[i2][1] == line_coords[i3][1])
					line_added = true;
			// Add
			if (!line_added)
				line_coords.push(this.route['turf'][i].geometry.coordinates[i2]);
		}
	}

	return line_coords;
}

Navigator.prototype.update = function() {
	if (this.route['status'] != 'yes' && this.route['status'] != 'out' && this.route['status'] != 'calc_from_out')
		return
	
	var dist2line = 0;
	var pt_now = null;
	var pt_near = null;
	this.nearest['distance'] = 0;
	this.nearest['dist_track_done'] = 0;
	this.nearest['distance_total'] = 0;
	this.nearest['time'] = 0;
	this.nearest['voice'] = false;
	this.nearest['speaked'] = false;
	this.nearest['radar'] = false;
	this.nearest['radar_sound'] = false;
	
	
	// DISTANCES pos to route lines
	var all_distances = [];
	for (i=0; i < this.route['turf'].length; i++) {
		pt_now = turf.point([this.pos_now['lng'], this.pos_now['lat']]);
		pt_near = turf.pointOnLine(this.route['turf'][i], pt_now);
		dist2line = geolib.getDistance(
			{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
			{latitude: pt_near.geometry.coordinates[1], longitude: pt_near.geometry.coordinates[0]}
		);
		all_distances.push(dist2line);
	}
	var ind_nearest = all_distances.indexOf(Math.min.apply(Math, all_distances));


	// STORE the nearest. There are i+1 tracks, because the turf are lines and tracks are end points of track
	this.nearest['indication'] = this.route['tracks'][ind_nearest+1]['type'];
	this.nearest['msg'] = this.route['tracks'][ind_nearest+1]['instruction'];
	this.nearest['dist2turn'] = geolib.getDistance(
		{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
		{latitude: this.route['tracks'][ind_nearest+1]['coordinates'][1], longitude: this.route['tracks'][ind_nearest+1]['coordinates'][0]}
	);
	this.nearest['dist_track_done'] = geolib.getDistance(
		{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
		{latitude: this.route['tracks'][ind_nearest]['coordinates'][1], longitude: this.route['tracks'][ind_nearest]['coordinates'][0]}
	);
	
	// Speak now?
	if (!this.route['tracks'][ind_nearest+1]['speaked']) {
		var dist2speed = this.DIST4INDICATION;
		if (this.speed > this.SPEED_CITY)
			dist2speed = this.DIST4INDICATION * 3;
		if (this.speed > this.SPEED_INTERCITY)
			dist2speed = this.DIST4INDICATION * 10;
		
		if (this.nearest['dist2turn'] < dist2speed) {
			this.route['tracks'][ind_nearest+1]['speaked'] = true;
			this.nearest['voice'] = true;
			this.nearest['speaked'] = true;
		}
	}
	else {
		this.nearest['speaked'] = true;
	}
	
	// Total distance + time
	for (i=ind_nearest; i < this.route['tracks'].length; i++) {
		if (i == ind_nearest) { // Percent left
			var percent_completed = ((this.route['tracks'][i]['distance'] - this.nearest['dist2turn']) / this.route['tracks'][i]['distance']) * 100;
			this.nearest['time'] = this.route['tracks'][i]['duration'] - Math.abs(this.route['tracks'][i]['duration'] * percent_completed / 100);
			this.nearest['distance'] = this.route['tracks'][i]['distance'] - Math.abs(this.route['tracks'][i]['distance'] * percent_completed / 100);
			this.nearest['distance_total'] = this.nearest['distance'];
		}
		else {
			this.nearest['time'] += this.route['tracks'][i]['duration'];
			this.nearest['distance'] += this.route['tracks'][i]['distance'];
			this.nearest['distance_total'] += this.route['tracks'][i]['distance'];
		}
	}



	// RADARS
	var dist_radar_nearest = 9999;
	var ind_radar_nearest = -1;
	var dist2radar = this.DIST4INDICATION;
	if (this.speed > this.SPEED_CITY)
		dist2radar = this.DIST4INDICATION * 3;
	// Get nearest
	for (i=0; i<this.route['radars'].length; i++) {
		// Is it near?
		var dist_radar = geolib.getDistance(
			{latitude: this.pos_now['lat'], longitude: this.pos_now['lng']},
			{latitude: this.route['radars'][i]['lat'], longitude: this.route['radars'][i]['lng']}
		);
		if (dist_radar <= dist2radar && dist_radar < dist_radar_nearest) {
			ind_radar_nearest = i;
			dist_radar_nearest = dist_radar;
		}
	}
	// Alert nearest?
	if (ind_radar_nearest !== -1) {
		this.nearest['radar'] = true;
		this.nearest['radar_speed'] = this.route['radars'][ind_radar_nearest]['speed'];
		if (!this.route['radars'][ind_radar_nearest]['alerted']) {
			this.route['radars'][ind_radar_nearest]['alerted'] = true;
			this.nearest['radar_sound'] = true;
		}
	}



	// UPDATE STATUS
	// On route?
	if (all_distances[ind_nearest] <= this.IS_IN_ROUTE) {
		this.route['start_check_out'] = true;
		this.set_route_status('yes');
	}
	else if ((this.route['start_check_out'] || all_distances[ind_nearest] > this.MAX_START_CHECK_OUT) && nav.get_route_status() != 'calc_from_out'){
		this.set_route_status('out');
		return;
	}
		
	// Ended?
	if (this.nearest['indication'] >= 4 && this.nearest['indication'] <= 6 && this.nearest['dist2turn'] <= (this.DIST4INDICATION/2)) {
		this.set_route_status('ended');
		return;
	}
}

Navigator.prototype.set_route_status = function(status) {
	this.route['status'] = status;
}

Navigator.prototype.get_route_status = function() {
	return this.route['status'];
}

Navigator.prototype.calc2coord = function(lat, lng) {
	this.set_route_status('waiting4signal');
	
	this.pos_start['lat'] = this.pos_now['lat'];
	this.pos_start['lng'] = this.pos_now['lng'];
	this.pos_end['lat'] = lat;
	this.pos_end['lng'] = lng;

	this.nearest['dist2turn'] = 0;
}

Navigator.prototype.cancel_route = function() {
	this.set_route_status('no');
	
	this.pos_start['lat'] = null;
	this.pos_start['lng'] = null;
	this.pos_end['lat'] = null;
	this.pos_end['lng'] = null;
}
