/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: Node.java,v 1.9 2005/07/20 16:40:18 rampil Exp $
 */
package org.logical_paradox.rss.dsr.routing;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.rss.dsr.RSSDistributedServiceRegistry;
import org.logical_paradox.rss.dsr.routing.metrics.MetricCountException;
import org.logical_paradox.rss.dsr.routing.metrics.MetricCountingAlgorithmFactory;

/**
 * オーバーレイネットワークに配置されているノード
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.9 $
 */
public class Node {
	/** ロガー */
	private static final Log log = LogFactory.getLog(Node.class);

	/** ノードID */
	private String nodeId;
	/** このノードに関連づけられているDSR */
	private final RSSDistributedServiceRegistry dsr;
	/** 関連づいているノードのリスト */
	private ArrayList relativeNodes = new ArrayList();
	/** このノードが選択されているかどうか(true:選択済み / false:未選択) */
	private boolean selected = false;
	/** このノードがマークされているかどうか(true:マーク済み / false:マークされていない) */
	private boolean marked = false;

	/** ルーティングアルゴリズム */
	private final RoutingAlgorithm algorithm;

	/**
	 * コンストラクタ
	 * @param dsr_ DSR
	 * @param nn ノードID
	 */
	public Node(RSSDistributedServiceRegistry dsr_, String nn) {
		nodeId = nn;
		dsr = dsr_;
		algorithm = RoutingAlgorithmFactory.getDefaultAlgorithm(this);
	}
	/**
	 * 分散サービスレジストリを返す
	 * @return 分散サービスレジストリ
	 */
	public RSSDistributedServiceRegistry getRegistry() {
		return dsr;
	}
	/**
	 * ノードIDを返す
	 * @return ノードID
	 */
	public String getNodeId() {
		return nodeId;
	}

	/**
	 * ノード同士を比較する
	 * @param o 比較対象
	 * @return true:同じ / false:違う
	 */
	public boolean equals(Object o) {
		if(o == null || o instanceof Node == false) {
			return false;
		}
		Node node = (Node)o;
		return node.nodeId.equals(nodeId);
	}
	/**
	 * 分散ネットワークから離脱する
	 */
	public void disconnect() {
		algorithm.notifyDisconnect();
	}
	/**
	 * 指定ノードがノードリストに存在するかどうかを返す
	 * @param node ノード
	 * @return true:ある / false:ない
	 */
	public boolean contains(Node node) {
		return relativeNodes.contains(node);
	}
	/**
	 * ノードリストに存在する全ての接続相手が，自分のノードの参照を保持しているか検証する
	 * @return true:保持されている / false:保持されていない
	 */
	public boolean isHoldByBuddies() {
		for(Iterator it = iteratorRelativeNodes(); it.hasNext();) {
			Node node = (Node)it.next();
			if(node.contains(this) == false) {
				return false;
			}
		}
		return true;
	}
	/**
	 * HUGする
	 * @param node HUG相手
	 * @return true:成功 / false:失敗
	 */
	public boolean hug(Node node) {
		return algorithm.hug(node);
	}
	/**
	 * ノードリストに追加する
	 * @param node 追加するノード
	 */
	public void add(Node node) {
		try {
			if(contains(node) == true) {
				log.warn("ノード[" + node.getNodeId() + "]は既に登録されているため、重複して登録されません");
				return;
			}
			getRegistry().bind(RSSDistributedServiceRegistry.RSS_SERVID_SERVICE_LOCATER, node.getNodeId(), node.getRegistry());
			relativeNodes.add(node);
			log.info("ノード[" + node.getNodeId() + "]と結合しました");
		} catch (RemoteException e) {
			log.warn("ノード[" + node.getNodeId() + "]との結合に失敗", e);
		}
	}
	/**
	 * ノードリストに一度に追加する
	 * @param nodes 追加するノードのリスト
	 */
	public void addAll(Collection nodes) {
		relativeNodes.addAll(nodes);
	}
	/**
	 * ノードリストから削除する
	 * @param node 削除するノード
	 */
	public void remove(Node node) {
		try {
			getRegistry().unbind(RSSDistributedServiceRegistry.RSS_SERVID_SERVICE_LOCATER, node.getNodeId());
			relativeNodes.remove(node);
			log.info("ノード[" + node.getNodeId() + "]との結合を解除しました");
		} catch (RemoteException e) {
			log.warn("ノード[" + node.getNodeId() + "]との結合解除に失敗しました", e);
		}
	}
	/**
	 * ノードリストを初期化する
	 */
	public void clear() {
		relativeNodes.clear();
	}
	/**
	 * 2点間の距離を計測する
	 * @param node 比較するノード
	 * @return 長さ(直線距離)
	 */
	public double distanceFrom(Node node) {
		// 相手ノードがどのくらいリンクを保持しているかを加味する
		int links = 5 - node.getRelativeNodes().size();
		if(links < 0) {
			links = 0;
		}
		try {
			// 2ノード間の距離を計測する
			double distance = MetricCountingAlgorithmFactory.getAlgorithm().count(node);
			log.trace("ノード[" + node.getNodeId() + "]との論理的距離 = " + distance);
			return (double)distance + links * 100;
		} catch(MetricCountException e) {
			// 通信エラーが発生したので、距離を0としておく
			log.warn("ノード[" + node.getNodeId() + "]とのメトリック計測に失敗", e);
			return 0;
		}
	}

	/**
	 * このノードが保有しているリンク情報を全て返す
	 * @return 関連づけられているノードのリスト
	 */
	public Iterator iteratorRelativeNodes() {
		return relativeNodes.iterator();
	}
	/**
	 * このノードが保有しているリンク情報を全て返す
	 * このメソッドは，DSRに直接問い合わせをしない
	 * @return リスト
	 */
	public List getRelativeNodes() {
		return getRelativeNodes(false);
	}
	/**
	 * このノードが保有しているリンク情報を全て返す
	 * @param refresh true:DSRに問い合わせる / false:DSRに問い合わせない
	 * @return リスト
	 */
	public List getRelativeNodes(boolean refresh) {
		if(refresh == true) {
			// DSRに問い合わせる
			try {
				String[] services = getRegistry().services(RSSDistributedServiceRegistry.RSS_SERVID_SERVICE_LOCATER);
				synchronized(relativeNodes) {
					ArrayList nodes = new ArrayList();
					for(int i = 0; i < services.length; i++) {
						try {
							// ノードを追加する
							RSSDistributedServiceRegistry dsr = (RSSDistributedServiceRegistry)getRegistry().getService(services[i], 1);
							if(dsr != null) {
								nodes.add(new Node(dsr, dsr.getName()));
							} else {
								log.warn("DSR[" + services[i] + "]は発見されませんでした.");
							}
						} catch(RemoteException e) {
							log.warn("DSR[" + services[i] + "]との通信に失敗しました．このDSRを無視します:", e);
						}
					}
					relativeNodes.clear();
					relativeNodes.addAll(nodes);
				}
			} catch(RemoteException e) {
				// DSRとのやりとりで何らかの例外が発生した
				log.error("DSR[" + getNodeId() + "]との通信中に例外が発生しました．ノード関連情報は更新されません:", e);
			}
		}
		return relativeNodes;
	}
	/**
	 * 最適ノード選択を実行する
	 * このメソッドを実行すると，選択されているルーティングアルゴリズムに従って
	 * 最適ノードを選択しなおす。その結果として，自分のノードおよび他の関連するノードとの結合
	 * 状態が更新される。場合によってはいくつも連鎖的に変動する。
	 */
	public void configure() {
		algorithm.configure();
	}
}
