/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: Topology.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.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * オーバーレイネットワークのトポロジを管理するオブジェクト
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.3 $
 */
public class Topology {
	/** ネットワークに配置されているノードのリスト */
	private ArrayList nodes = new ArrayList();
	/** 次に作成するノード番号 */
	private int nextNodeId = 0;
	/** 分散ネットワークに配備されている全てのノードが完全につながっているかどうか */
	private boolean connectivity = false;
	/** 分散ネットワークの複雑性(1ノードあたりのリンク保持数) */
	private double complex = 0.0;
	/** 分散ネットワークに配置されているノードで最小のリンク */
	private int min;
	/** 分散ネットワークに配置されているノードで最大のリンク */
	private int max;

	/**
	 * ネットワーク上に新しいノードを追加する
	 */
	public void deployNewNode(int x, int y) {
		Node node = new Node(nextNodeId++, x, y);
		// 選択されているノードがある場合、全てのノードとhugする
		// 選択ノードを追加した場合、選択状態を解除する
		for(Iterator it = nodes.iterator(); it.hasNext();) {
			Node n = (Node)it.next();
			if(n.isSelected()) {
				node.add(n);
				n.setSelected(false);
				n.hug(node);
			}
		}
		// 最適ノード選択処理
		node.configure();
		nodes.add(node);

		// ネットワークに変化が発生したため，トポロジ調査を実行する
		researchConnectivity();
	}
	/**
	 * 指定された位置に存在するノードを1つ返す
	 * @param index リスト上のインデックス
	 * @return ノード
	 */
	public Node getNode(int index) {
		return (Node)nodes.get(index);
	}
	/**
	 * 指定された位置に存在するノードを1つ返す
	 * 存在しない場合はnullを返す
	 * @param point
	 */
	public Node getNode(Point point) {
		for(Iterator it = iteratorNodes(); it.hasNext();) {
			Node node = (Node)it.next();
			if(node.getSelectableRectangle().contains(point)) {
				return node;
			}
		}
		return null;
	}
	/**
	 * 現在選択状態の全てのノードを除去する
	 */
	public void removeSelectedNodes() {
		for(Iterator it = nodes.iterator(); it.hasNext();) {
			Node node = (Node)it.next();
			if(node.isSelected()) {
				// 分散ネットワークから離脱する
				node.disconnect();
				it.remove();
			}
		}
		// ネットワークに変化が発生したため，トポロジ調査を実行する
		researchConnectivity();
	}
	/**
	 * 指定ノードを分散ネットワークから除去する
	 * @param node 除去するノード
	 */
	public void removeNode(Node node) {
		node.disconnect();
		nodes.remove(node);
		researchConnectivity();
	}
	/**
	 * トポロジーが完全に維持されているかどうかを返す
	 * @return true:完全に維持されている / false:ネットワークがどこかで分断している
	 */
	public boolean isCompleteConnectivity() {
		return connectivity;
	}
	/**
	 * 分散ネットワークに配置されているノードで最小のリンク数を返す
	 * @return リンク数
	 */
	public int getMinNodeCount() {
		return min;
	}
	/**
	 * 分散ネットワークに配置されているノードで最大のリンク数を返す
	 * @return リンク数
	 */
	public int getMaxNodeCount() {
		return max;
	}
	/**
	 * ネットワークの複雑性を返す
	 * @return 複雑性
	 */
	public double getComplex() {
		return complex;
	}
	/**
	 * 全ノードに対して、最適ノード選択処理の実行を通知する
	 */
	public void configureAll() {
		for(Iterator it = nodes.iterator(); it.hasNext();) {
			Node node = (Node)it.next();
			node.configure();
		}
	}
	/**
	 * このトポロジーの文字列表現を返す
	 * 文字列表現には、トポロジに関する情報が含まれている
	 */
	public String toString() {
		return nodes.size() + "nodes, " + getComplex() + " cplx, " + getMinNodeCount() + " (min), " + getMaxNodeCount() + " (max)";
	}
	/**
	 * 現在のネットワークの接続性について調査する
	 * @return true:完全に維持されている / false:ネットワークがどこかで分断している
	 */
	public boolean researchConnectivity() {
		if(nodes.size() == 0) {
			connectivity = false;
		} else {
			// ライントレースを実行する
			LineTracer tracer = new LineTracer((Node)nodes.get(0));
			tracer.run();
			// 孤立しているノードが存在するようであれば、どこかで分断している
			ArrayList all = new ArrayList();
			all.addAll(nodes);
			all.removeAll(tracer.getTracedNodes());

			// 現在ネットワーク上に存在するノードのうち，トレースできたものを
			connectivity = all.size() == 0;
			complex = tracer.getComplex();
			min = tracer.getMinNodeCount();
			max = tracer.getMaxNodeCount();
		}

		return isCompleteConnectivity();
	}
	/**
	 * ノードリスト上の指定インデックスのノードを返す
	 * @param index インデックス
	 * @return ノード
	 */
	public Node get(int index) {
		return (Node)nodes.get(index);
	}
	/**
	 * リードリストのサイズを返す
	 * @return ノードリストのサイズ
	 */
	public int getNodeListSize() {
		return nodes.size();
	}
	/**
	 * ノードリストを返す
	 * @return ノードリスト
	 */
	public Iterator iteratorNodes() {
		return nodes.iterator();
	}
	/**
	 * 1つ以上のノードが選択されているかどうかを返す
	 * @return true:選択状態 / false:非選択状態
	 */
	public boolean isSelected() {
		for(Iterator it = nodes.iterator(); it.hasNext();) {
			Node node = (Node)it.next();
			if(node.isSelected()) {
				return true;
			}
		}
		return false;
	}
	/**
	 * 全てのノードの選択状態を解除する
	 */
	public void deSelectAll() {
		for(Iterator it = iteratorNodes(); it.hasNext();) {
			Node node = (Node)it.next();
			node.setSelected(false);
		}
	}
	/**
	 * ノードをトレースするロボット
	 * 指定ノードを起点として限界までノードをトレースする
	 * @author satoshi akabane@logical-paradox.org
	 * @version $Revision: 1.3 $
	 */
	class LineTracer {
		/** トレースの開始点 */
		private final Node startPoint_;
		/** ノード間のリンク数 */
		private int connections_;
		/** 最小ノード数 */
		private int min = 100000000;
		/** 最大ノード数 */
		private int max = 0;
		/** 実際にトレースしたノード */
		private final List tracedNodes_ = new ArrayList();

		/**
		 * コンストラクタ
		 * @param node トレースの開始点
		 */
		private LineTracer(Node node) {
			startPoint_ = node;
		}
		/**
		 * トレースを開始する
		 */
		private void run() {
			if(startPoint_ == null) {
				// 開始点が既に空なので終了
				return;
			}
			trace(startPoint_);
		}
		/**
		 * トレースする
		 * @param node 現在扱っているノード
		 */
		private void trace(Node node) {
			// このノードを踏破済みとして先に登録しておく
			if(tracedNodes_.contains(node)) {
				return;
			}
			tracedNodes_.add(node);
			// このノードが保持しているリンクについて，全て探査する
			int nodeCount = node.getRelativeNodes().size();
			connections_ += nodeCount;
			if(min > nodeCount) {
				min = nodeCount;
			}
			if(max < nodeCount) {
				max = nodeCount;
			}
			Collection link = node.getRelativeNodes();
			for(Iterator it = link.iterator(); it.hasNext();) {
				Node n = (Node)it.next();
				// さらに探査を進める
				trace(n);
			}
		}
		/**
		 * トレースされたノードのリストを返す
		 * @return トレースされたノードのリスト
		 */
		private List getTracedNodes() {
			return tracedNodes_;
		}
		/**
		 * 最大ノード数を返す
		 * @return 最大ノード数
		 */
		private int getMaxNodeCount() {
			return max;
		}
		/**
		 * 最小ノード数を返す
		 * @return 最小ノード数
		 */
		private int getMinNodeCount() {
			return min;
		}
		/**
		 * ネットワークの複雑性を返す
		 * @return ネットワークの複雑性
		 */
		private double getComplex() {
			return getTracedNodes().size() == 0 ? 0 : connections_ / getTracedNodes().size();
		}
	}
}
