/**
 * @author mtanaka
 */

var GeoInfoManager = Class.create();

GeoInfoManager.prototype = {
	
	nRemainingRouteRequest: 0, // 未終了の経路リクエストの数
	nDestination: 0, // 地点数
	routeMatrix: new Array(), // 経路情報のマトリクス GoogleDirectionsServiceRequest[][]
	arrayDestinations: new Array(), // 地点の配列 GeoInfo[]
	hashGeoInfo: new Hash(), // 地理情報のハッシュ．　id, GLatLng, name, address, desc．キーはid
	mapHandler: null, // Google mapsのハンドラ
	idStart: null, // 開始地点のID string
	idEnd: null, //執着地点のID string	
	
	/**
	 * コンストラクタ
	 * @param {Object} params
	 *  params.gmap Google mapsのハンドラ GMap2
	 */
	initialize: function(params) {
		this.mapHandler = params.handler;
	},
	
	/**
	 * Google mapsのハンドラを返す
	 */
	getMapHandler: function() {
		return this.mapHandler;
	},
	
	/**
	 * 検索された地点の情報を保存
	 * @param {GeoInfo} dest
	 */
	addGeoInfo: function(dest) {
		this.hashGeoInfo.set(dest.getId(), dest);
	},
	
	/**
	 * 地点情報を追加し，他の地点との経路をリクエスト
	 * @param {string} id 追加される地点のid
	 */
	addDestinationById: function(id) {
		var dest = this.hashGeoInfo.get(id);
		this.addDestination(dest);
	},
	
	/**
	 * 地点情報を追加し，他の地点との経路をリクエスト
	 * @param {GeoInfo} dest 追加される地点
	 */
	addDestination: function(dest) {
		if(this.hashGeoInfo.get(dest.getId()) == undefined) {
			this.hashGeoInfo.set(dest.getId(), dest);
		}

		// Geo coding
		if(dest.getAddress() == "") {
			dest.address = YahooReverseGeocoding(dest.getPoint()) + '付近';
		}
		if (dest.getName() == "") {
			dest.name = dest.getAddress();
		}

		this.arrayDestinations.push(dest);
		
		// 他の地点との経路をリクエスト
		var indexNewPoint = this.arrayDestinations.length - 1;
		this.routeMatrix[indexNewPoint] = new Array();
		var point = dest.getPoint();
		
		// 出発地・到着地が設定されていない場合，追加された地点に設定
		if(!this.idStart) this.idStart = dest.getId();
		if(this.arrayDestinations.length > 1 && !this.idEnd) this.idEnd = dest.getId();
		
		// 順路を更新
		printRouteTable(this.arrayDestinations, this.idStart, this.idEnd);
		
		// マーカを追加
		this.mapHandler.addDestinationMarker(dest, indexNewPoint);
		

		for(var i=0; i<indexNewPoint; i++) {
			di = this.arrayDestinations[i];
			pi = di.getPoint();
			start = pi.lat() + ',' + pi.lng();
			end = point.lat() + ',' + point.lng();
					
			setTimeout(this.createDirectionRequest.bind(this, i, indexNewPoint, start, end), 500*i);
		}
		
	},
	
	createDirectionRequest: function(indexStart, indexEnd, start, end) {
		var route = new GoogleDirectionsServiceRequest({
				indexStart: indexStart,
				indexEnd: indexEnd,
				start: start,
				end: end,
				map: this.mapHandler,
				manager: this
			});
		this.routeMatrix[indexStart][indexEnd] = route;

		var spanRouteLoad = $('spanRouteLoad');
		if($('spanRouteLoad').innerHTML.length == 0)
			$('spanRouteLoad').innerHTML = '<img src="img/loader.gif"/>';
	},
	
	/**
	 * 目的地を削除
	 * @param {string} id
	 */
	removeDestination: function(id) {
		var dest = this.hashGeoInfo.get(id);
		
		this.mapHandler.removeAllDestinationMarkers(); // インデックスが変わるので，マーカは一度全て削除
		this.mapHandler.hideRoute(); // 地図上のルート表示は削除

		// ルート情報を再構築
		var oldArrayDist = this.arrayDestinations;
		this.arrayDestinations = new Array();
		
		for(var i=0; i<oldArrayDist.length; i++) {
			if(oldArrayDist[i].getId() != id) 
				this.arrayDestinations.push(oldArrayDist[i]);
		}
		
		var oldMatRoute = this.routeMatrix;
		this.routeMatrix = new Array();
		indexA = 0;
		for(var i=0; i<oldMatRoute.length; i++) {
			if (oldArrayDist[i].getId() != id) {
				var row = new Array();
				indexB = indexA+1;
				for (var j = i + 1; j < oldMatRoute.length; j++) {
					if (oldArrayDist[j].getId() != id) {
						row[indexB++] = oldMatRoute[i][j];
					}
				}
				this.routeMatrix[indexA++] = row;
			}
		}

		// 変更されたインデックスに基づいて目的地を追加
		for (var i = 0; i < this.arrayDestinations.length; i++) {
			this.mapHandler.addDestinationMarker(this.arrayDestinations[i], i);
		}

		// 削除された地点が出発地・到着地だった場合，適当に他の点を選択
		if (this.arrayDestinations.length > 1) {
			if (dest.getId() == this.idStart) {
				for (var i = 0; i < this.arrayDestinations.length; i++) {
					if (this.arrayDestinations[i].getId() != this.idEnd) {
						this.idStart = this.arrayDestinations[i].getId();
						break;
					}
				}
			}
			if (dest.getId() == this.idEnd) {
				for (var i = this.arrayDestinations.length - 1; i >= 0; i++) {
					if (this.arrayDestinations[i].getId() != this.idStart) {
						this.idEnd = this.arrayDestinations[i].getId();
						break;
					}
				}
			}
		}
		
		// 順路を更新
		printRouteTable(this.arrayDestinations, this.idStart, this.idEnd);
	},
	
	
	/**
	 * 経路を検索．検索結果は地図に表示．
	 */
	searchRoute: function() {
		if (this.nRemainingRouteRequest != 0) {
			alert('検索中です。');
			return;
		}
		
		if (this.arrayDestinations.length < 2) {
			alert('地図をクリックして、2ヶ所以上の目的地を設定してください．');
			return;
		}

		// 開始と終了のインデックスを計算
		var indexStart, indexEnd;
		for(var i=0; i<this.arrayDestinations.length; i++) {
			if(this.arrayDestinations[i].getId() == this.idStart) indexStart = i;
		}
		for(var i=0; i<this.arrayDestinations.length; i++) {
			if(this.arrayDestinations[i].getId() == this.idEnd) indexEnd = i;
		}
		
		// 距離の行列を計算
		var distanceMatrix = new Array();
		for(var i=0; i<this.arrayDestinations.length; i++) {
			distanceMatrix[i] = new Array();
			for (var j = i+1; j < this.arrayDestinations.length; j++) {
				distanceMatrix[i][j] = this.routeMatrix[i][j].getDistance();
			}
		}

		// 探索	
		var route = localSearch(distanceMatrix, indexStart, indexEnd);
		
		// 探索結果に合わせて行き先リストと経路行列を並べ替え
		var adist = this.arrayDestinations;
		this.arrayDestinations = new Array();
		for(var i=0; i<adist.length; i++) {
			this.arrayDestinations[i] = adist[route[i]];
		}

		var mtdist = this.routeMatrix;
		this.routeMatrix = new Array();
		for (var i = 0; i < adist.length; i++) {
			this.routeMatrix[i] = new Array();
		}
		for (var i = 0; i < adist.length; i++) {
			for (var j = i+1; j < adist.length; j++) {
				ptx = route[i]; pty =  route[j];
				var tmp;
				if(ptx >= pty) {
					tmp = ptx;
					ptx = pty;
					pty = tmp;
				}
				this.routeMatrix[i][j] = mtdist[ptx][pty];
			}
		}

		// 順路を更新
		printRouteTable(this.arrayDestinations, this.idStart, this.idEnd);
	
		// マーカとルートを削除
		this.mapHandler.hideRoute();
		this.mapHandler.removeAllDestinationMarkers();
		
		setTimeout(this.setNewRoutes.bind(this), 500);

	},

		
	setNewRoutes: function() {
		// 地図上でルートを表示
		var routelines = new Array();
		for (var i = 0; i < this.arrayDestinations.length - 1; i++) {
			routelines.push(this.routeMatrix[i][i+1].gerRouteline());
		}
		handler.showRoute(routelines);
		
		// マーカを表示
		for (var i = 0; i < this.arrayDestinations.length; i++) {
			this.mapHandler.addDestinationMarker(this.arrayDestinations[i], i);
		}

		var distance = 0;
		
		for (var i = 0; i < this.arrayDestinations.length - 1; i++) {
			distance += this.routeMatrix[i][i+1].getDistance();
		}
		$('spanRouteLoad').innerHTML = '総距離: 約' + (distance / 1000.0) + 'km';
	},
	
	/**
	 * ルート検索が新たに発行された際にこのメソッドを介して通知される
	 * （ルート検索のリクエスト数をインクリメント）
	 */
	notifyRouteRequestIssued: function() {
		this.nRemainingRouteRequest++;
	},
	
	/**
	 * ルート検索が終了した際にこのメソッドを介して通知される
	 * （ルート検索のリクエスト数をデクリメント.
	 * 全てのリクエストが完了していれば，onAllRequestCompletedを呼び出す．）
	 */
	notifyRouteRequestCompleted: function() {
		this.nRemainingRouteRequest--;
		
		if(this.nRemainingRouteRequest == 0)
			this.notifyAllRouteRequestCompleted();
	},
	
	/**
	 * 全てのリクエストが完了した際に呼ばれるコールバック関数
	 */
	notifyAllRouteRequestCompleted: function() {

		
		$('spanRouteLoad').innerHTML = '';
	},
	
	/**
	 * 目的地に設定されているかどうか調べる
	 */
	containsDestination: function(id) {
		for (var i = 0; i < this.arrayDestinations.length; i++) {
			var info = this.arrayDestinations[i];
			if(info.getId() == id) return true;
		}
		return false;
	},
	
	/**
	 * 地点数を返す
	 */
	getDestinationCount: function() {
		return this.arrayDestinations.length;
	},
	
	/**
	 * 開始点を設定
	 * @param {string} id 開始点のId
	 */
	setStart: function(id) {
		if(this.idEnd == id) {
			this.idEnd = this.idStart;
		}
		this.idStart = id;

		// 順路を更新
		printRouteTable(this.arrayDestinations, this.idStart, this.idEnd);
	},
	
	/**
	 * 終着点を設定
	 * @param {string} id 開始点のId
	 */
	setEnd: function(id) {
		if(this.idStart == id) {
			this.idStart = this.idEnd;
		}
		this.idEnd = id;

		// 順路を更新
		printRouteTable(this.arrayDestinations, this.idStart, this.idEnd);
	},

	showInfoWindow: function(id) {
		var info = this.hashGeoInfo.get(id);
		
		var name = info.getName();
		var url = info.getUrl();
		if(url != null) name = '<a target="_blank" href="' + url + '">' + name + '</a>';
		
		var strHtml = '<div class="destination_infowindow"><div class="infowindow_name">' + name + '</div>'
				+ '<div class="infowindow_operation" onClick="manager.setStart(\'' + info.getId() + '\')"><img src="img/icon_start.gif"/>出発地点に設定</div>'
				+ '<div class="infowindow_operation" onClick="manager.setEnd(\'' + info.getId() + '\')"><img src="img/icon_end.gif"/>到着地点に設定</div>'
				+ '<div class="infowindow_operation" onClick="manager.removeDestination(\'' + info.getId() + '\')"><img src="img/icon_delete.png"/>目的地から削除</div>'
				+ '<div class="infowindow_operation" onClick="hotelManager.addHotelSearchResult(\'' + info.getId() + '\')"><img src="img/icon_hotel.png"/>付近のホテル</div>'
				+ '<div class="infowindow_operation" onClick="restaurantManager.addRestaurantSearchResult(\'' + info.getId() + '\')"><img src="img/icon_restaurant.png"/>付近のレストラン</div>'
				+ '<div class="infowindow_operation" onClick="articleManager.addArticleSearchResult(\'' + info.getId() + '\')"><img src="img/icon_article.png"/>付近に関する記事</div>'
				+ '</div>';
		var map = this.mapHandler.getMap();
		map.openInfoWindowHtml(info.getPoint(), strHtml);
	},

	/**
	 * 収集された地点間距離を<table>で出力
	 * @return <table>タグにいれた地点間距離の情報
	 */
	printDistanceMatrix: function() {
//		if(this.nRemainingRouteRequest != 0) return 'Requests not completed.';
		
		var table = '<table>';
	
		for(var i=0; i<this.arrayDestinations.length; i++) {
			table += '<tr>'
			for (var j = 0; j < this.arrayDestinations.length; j++) {
				if (j <= i) {
					table += '<td> - </td>';
				}
				else {
					table += '<td>' + this.routeMatrix[i][j].getDistance() + '</td>';
				}
			}
			table += '</tr>';
		}
		table += '</table>';


		alert(this.arrayDestinations.toSource());

		return table;

	},
	
	runDemo: function(demo) {
		var jsonDemoData;
		var lat;
		var lng;
		var zoom;
		
		if(demo == 'kyoto10') {
			jsonDemoData = demoData.kyotoDestinations10;
			lat = demoCenter.kyotoDestinations10.lat;
			lng = demoCenter.kyotoDestinations10.lng;
			zoom = demoCenter.kyotoDestinations10.zoom;
		}
		if(demo == 'tokyo10') {
			jsonDemoData = demoData.tokyoDestinations10;
			lat = demoCenter.tokyoDestinations10.lat;
			lng = demoCenter.tokyoDestinations10.lng;
			zoom = demoCenter.tokyoDestinations10.zoom;
		}
		
		gmap.setCenter(new GLatLng(lat, lng), zoom);
		
		dataArrayDestinations = eval('(' + jsonDemoData + ')');
		this.demoGeoInfo = new Array();
		
		this.idStart = null;
		this.idEnd = null;
	
		// マーカとルートを削除
		this.mapHandler.hideRoute();
		this.mapHandler.removeAllDestinationMarkers();

		this.arrayDestinations = new Array();
		for (var i = 0; i < dataArrayDestinations.length; i++) {
			var info = dataArrayDestinations[i];
			var geoinfo = new GeoInfo(info.id, new GLatLng(info.point.y, info.point.x),
						info.name, info.address, info.desc, info.url);

			this.demoGeoInfo.push(geoinfo);
			setTimeout(this.addDestinationForDemo.bind(this), 1000*i);
		}

	},
	
	addDestinationForDemo: function() {
		var geoinfo = this.demoGeoInfo.shift();
		this.addDestination(geoinfo);
	},
	
	clear: function() {
		this.idStart = null;
		this.idEnd = null;
	
		// マーカとルートを削除
		this.mapHandler.hideRoute();
		this.mapHandler.removeAllDestinationMarkers();

		this.arrayDestinations = new Array();

		
		$('routeTable').innerHTML = '';
	}
	
	
}