/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: Node.java,v 1.3 2005/03/18 13:55:26 rampil Exp $
 */
package org.logical_paradox.testsite.oln.object;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.logical_paradox.testsite.oln.OverlayNetworkConstant;
import org.logical_paradox.testsite.oln.model.RoutingAlgorithm;
import org.logical_paradox.testsite.oln.model.RoutingAlgorithmFactory;

/**
 * オーバーレイネットワークに配置されているノード
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.3 $
 */
public class Node extends Point {
	/** ノードID */
	private int nodeNo;
	/** 関連づいているノードのリスト */
	private ArrayList relativeNodes = new ArrayList();
	/** このノードが選択されているかどうか(true:選択済み / false:未選択) */
	private boolean selected = false;
	/** このノードがマークされているかどうか(true:マーク済み / false:マークされていない) */
	private boolean marked = false;

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

	/**
	 * コンストラクタ
	 * @param nodeId ノードID
	 * @param x ノードのX座標
	 * @param y ノードのY座標
	 */
	public Node(int nn, int x, int y) {
		super(x, y);
		nodeNo = nn;
		algorithm = RoutingAlgorithmFactory.getDefaultAlgorithm(this);
	}

	/**
	 * ノードIDを返す
	 * @return ノードID
	 */
	public int getNodeNo() {
		return nodeNo;
	}

	/**
	 * ノード同士を比較する
	 * @param o 比較対象
	 * @return true:同じ / false:違う
	 */
	public boolean equals(Object o) {
		Node node = (Node)o;

		return node.x == x && node.y == y && node.nodeNo == nodeNo;
	}
	/**
	 * 分散ネットワークから離脱する
	 */
	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) {
		relativeNodes.add(node);
	}
	/**
	 * ノードリストに一度に追加する
	 * @param nodes 追加するノードのリスト
	 */
	public void addAll(Collection nodes) {
		relativeNodes.addAll(nodes);
	}
	/**
	 * ノードリストから削除する
	 * @param node 削除するノード
	 */
	public void remove(Node node) {
		relativeNodes.remove(node);
	}
	/**
	 * 指定位置へノードを移動する
	 * @param point 移動先座標
	 */
	public void move(Point point) {
		setLocation(point.getX(), point.getY());
		configure();
	}
	/**
	 * ノードリストを初期化する
	 */
	public void clear() {
		relativeNodes.clear();
	}
	/**
	 * このノードがマークされているかどうかを返す
	 * @return true: マーク済み / false:マークされていない)
	 */
	public boolean isMarked() {
		return marked;
	}
	/**
	 * このノードのマークを解除する
	 */
	public void unmark() {
		marked = false;
	}
	/**
	 * このノードをマーク済みに設定する
	 */
	public void mark() {
		marked = true;
	}
	/**
	 * このノードが選択済みかどうかを返す
	 * @return true:選択済み / false:未選択
	 */
	public boolean isSelected() {
		return selected;
	}

	/**
	 * このノードの選択状態を設定する
	 * @param b true:選択済み / false:未選択
	 */
	public void setSelected(boolean b) {
		selected = b;
	}
	/**
	 * 2点間の距離を計測する
	 * @param node 比較するノード
	 * @return 長さ(直線距離)
	 */
	public double distanceFrom(Node node) {
		double holizon = Math.abs(getX() - node.getX());
		double verical = Math.abs(getY() - node.getY());

		// 相手ノードがどのくらいリンクを保持しているかを加味する
		int links = 5 - node.getRelativeNodes().size();
		if(links < 0) {
			links = 0;
		}
		// 直角三角形の斜辺の長さ = 2点間の距離
		return (double)Math.sqrt(Math.pow(holizon, 2) + Math.pow(verical, 2)) + links * 100;
	}

	/**
	 * フォーカス領域を返す
	 * @return フォーカス料金
	 */
	public Rectangle getSelectableRectangle() {
		Rectangle rect = getRectangle();
		rect.x -= OverlayNetworkConstant.NODE_CLICK_FORCUS_PIXEL /2;
		rect.y -= OverlayNetworkConstant.NODE_CLICK_FORCUS_PIXEL /2;
		rect.width += OverlayNetworkConstant.NODE_CLICK_FORCUS_PIXEL;
		rect.height += OverlayNetworkConstant.NODE_CLICK_FORCUS_PIXEL;
		
		return rect;
	}
	/**
	 * 画面描画用のレクタングルを返す
	 * @return 画面描画用のレクタングル
	 */
	public Rectangle getRectangle() {
		Rectangle rect = new Rectangle();
		rect.setBounds(
			(int)getX() - OverlayNetworkConstant.NODE_DRAW_WIDTH /2,
			(int)getY() - OverlayNetworkConstant.NODE_DRAW_HEIGHT /2,
			OverlayNetworkConstant.NODE_DRAW_WIDTH,
			OverlayNetworkConstant.NODE_DRAW_HEIGHT
		);
		return rect;
	}

	/**
	 * このノードが保有しているリンク情報を全て返す
	 * @return 関連づけられているノードのリスト
	 */
	public Iterator iteratorRelativeNodes() {
		return relativeNodes.iterator();
	}
	/**
	 * このノードが保有しているリンク情報を全て返す
	 * @return リスト
	 */
	public List getRelativeNodes() {
		return relativeNodes;
	}
	/**
	 * 最適ノード選択を実行する
	 * このメソッドを実行すると，選択されているルーティングアルゴリズムに従って
	 * 最適ノードを選択しなおす。その結果として，自分のノードおよび他の関連するノードとの結合
	 * 状態が更新される。場合によってはいくつも連鎖的に変動する。
	 */
	public void configure() {
		algorithm.configure();
	}
}
