/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/
//
//	ScormServerInfoControl
//	変更履歴
//		2004.02.01	新規作成
//

package jp.co.ntt.lms.lo.scorm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.co.ntt.lms.lo.LOException;
import jp.co.ntt.lms.xmo.util.DebugLog;

/**
 * SCORM LOサーバ情報管理クラス。<BR>
 * 起動するサーバの選択、サーバの使用人数の増減を管理します。<br>
 * 管理クラスのため、シングルトン・パターンで生成します。<BR>
 * @author T.Nishiki
 */
public final class ScormServerInfoControl {
	/**
	 * シングルトン・パターン用オブジェクト。
	 */
	private static final ScormServerInfoControl CONTROL;

	/**
	 * 起動開始するサーバを選択するために比較オブジェクト。
	 * @see #getServerSelected(String, String)
	 */
	private static final Comparator CHOICE_START_SERVER_COMPARATOR;

	/**
	 * スタティック・イニシャライザ。<br>
	 */
	static {
		CONTROL = new ScormServerInfoControl();
		CHOICE_START_SERVER_COMPARATOR = new StartServerComparator();
	}

	/**
	 * SCORM LO サーバ情報を格納するマップ。<br>
	 * キーとしてサーバ番号、値としてScormServerInfoを格納します。
	 */
	private Map serverInfoMap;

	/**
	 * SCORM LO サーバの使用状況を格納するマップ。<br>
	 * キーとしてユーザID／LOIDの組、値としてScormServerInfoを格納します。
	 */
	private Map serverUsedMap;

	/**
	 * コンストラクタ。
	 * シングルトン・パターンのため、privateとして宣言します。
	 */
	private ScormServerInfoControl() {
		serverInfoMap = new HashMap();
		serverUsedMap = new HashMap();
	}

	/**
	 * ScormServerInfoControlを取得します。
	 * @return プロセス内で唯一のScormServerInfoControlオブジェクト。
	 */
	public static ScormServerInfoControl getInstance() {
		return CONTROL;
	}

	/**
	 * SCORM LOサーバ情報を初期化します。<br>
	 * 起動時に１度だけ呼び出してください。
	 * @throws LOException 設定ファイルの起動に失敗したとき。
	 */
	public void initialize() throws LOException {
		// メソッド開始ログ
		DebugLog.write(this.getClass(), "HEAD initialize()", DebugLog.ROW);
		// 設定ファイル
		ScormLoEnv env = new ScormLoEnv();

		// 設定ファイルの登録されているSCORM LOサーバ情報を読み込みます。
		ScormServerInfo[] serverList = env.getScormLoServerList();
		for (int i = 0; i < serverList.length; i++) {
			serverInfoMap.put(serverList[i].getServerNo(), serverList[i]);
		}
		// メソッド終了ログ
		DebugLog.write(this.getClass(), "TAIL initialize()", DebugLog.ROW);
	}

	/**
	 * 学習起動するSCORM LOサーバを決定し、そのサーバ情報を戻します。<br>
	 * すでに２重起動などにより、指定されたユーザ、教材で起動している
	 * サーバがあるか確認します。
	 * もし、サーバを確認できればそれを戻し、確認できなければ、新たに選択します。<br>
	 * 選択する場合、使用人数のもっとも少ないSCORM LOサーバを戻します。
	 * 使用人数が同じサーバが複数ある場合、サーバ番号が若いサーバーの情報を戻します。
	 * また、新たに選択したサーバ情報は、使用人数を１つ増やしてから戻します。<br>
	 * @param userID ユーザID。
	 * @param loID  LOID。
	 * @return 選択されたサーバ情報。
	 */
	public synchronized ScormServerInfo getServerSelected(
				String userID, String loID) {
		// メソッド開始ログ
		DebugLog.write(
			this.getClass(),
			"HEAD getServerSelected(" + userID + "," + loID + ")",
			DebugLog.ROW);

		// このメソッドの戻り値
		ScormServerInfo selected = null;

		// すでに使用しているか判断する。
		selected = (ScormServerInfo)serverUsedMap.get(
						makeKeyOfServerUsed(userID, loID));
		// 使用しているサーバがなければ、選択する。
		if ( selected == null ) {
			//サーバ番号順の情報を取得
			List serverInfoList = null;
			serverInfoList = getServerInfoList();
			// 使用人数順、サーバ番号順に並び替える。
			Collections.sort(serverInfoList, CHOICE_START_SERVER_COMPARATOR);
			// 先頭要素を選択し、使用人数を増やします。
			selected = (ScormServerInfo) serverInfoList.get(0);
			selected.incrementNumberOfUsers();
			// ユーザID + LOID で接続サーバを格納します。
			serverUsedMap.put(makeKeyOfServerUsed(userID, loID), selected);
		}

		// メソッド終了ログ
		DebugLog.write(
			this.getClass(),
			"TAIL getServerSelected(" + userID + "," + loID + ")"
				+ " retrun " + selected,
			DebugLog.ROW);

		return selected;
	}

	/**
	 * 指定されたユーザ、教材の組で使用中扱いのサーバのの使用人数減らす。<br>
	 *
	 * @param userID ユーザID。
	 * @param loID LOID。
	 */
	public synchronized void decrementNumberOfUsers(
			String userID, String loID) {
		// メソッド開始ログ
		DebugLog.write( getClass(),
			"HEAD decrementNumberOfUsers(" + userID + "," + loID + ")",
			DebugLog.ROW);

		// 使用中となっているサーバを取得
		ScormServerInfo serverInfo = null;
		String mapKey = makeKeyOfServerUsed(userID, loID);
		serverInfo = (ScormServerInfo)serverUsedMap.get(mapKey);
		DebugLog.write( getClass(),
			"serverUsedMap[" + userID + "][" + loID + "]"
				+ "->serverInfo!=null:" + (serverInfo != null), DebugLog.MID);
		// 取得できた場合は、使用数を減らす。
		if ( serverInfo != null ) {
			serverInfo.decrementNumberOfUsers();
			serverUsedMap.remove(mapKey);
		}
		// メソッド終了ログ
		DebugLog.write( getClass(),
			"TAIL decrementNumberOfUsers(" + userID + "," + loID + ")",
			DebugLog.ROW);
	}

	/**
	 * SCORM LOサーバ情報を取得します。<br>
	 * サーバ番号順に並んだリストを戻します。
	 * @return 要素がScormServerInfoオブジェクトであるリスト。
	 */
	public List getServerInfoList() {
		// メソッド開始ログ
		DebugLog.write( this.getClass(),
			"HEAD getServerInfoList()", DebugLog.ROW);

		// サーバ情報マップから値の集合を取得し、
		// サーバ番号順に並び替えてからもどす。
		Collection collection = serverInfoMap.values();
		List serverInfoList = new ArrayList(collection);
		Collections.sort(serverInfoList);

		// メソッド開始ログ
		DebugLog.write( this.getClass(),
			"HEAD getServerInfoList() return " + serverInfoList, DebugLog.ROW);

		return serverInfoList;
	}

	/**
	 * 使用中サーバマップのキーを生成します。<br>
	 * @param userID ユーザID。
	 * @param loID 教材のLOID。
	 * @return 使用中サーバマップのキー。
	 */
	private static String makeKeyOfServerUsed(String userID, String loID) {
		return userID + loID;
	}

	/**
	 * SCORM LOサーバ情報について比較クラス。<br>
	 * {@link ScormServerInfoControl#getServerSelected(String, String)}
	 * メソッドのサーバ選択で利用します。
	 * 使用人数の大小でまず比較し、おなじ場合はサーバ番号順とします。
	 */
	private static class StartServerComparator implements Comparator {
		/**
		 * ２つの「SCORM LO サーバ情報」オブジェクトと順序を比較します。<br>
		 * まず、使用人数の大小で比較し、より使用人数の少ない
		 * SCORM LO サーバ情報」を小さい扱いとします。
		 * もし、使用人数が同じ場合、サーバ番号のプレフィックス部分を
		 * 除いた後の数値の大小で比較し、
		 * 数値がより小さい「SCORM LO サーバ情報」を小さい扱いとします。
		 * @param obj1 比較対象の「SCORM LO サーバ情報」オブジェクトその１。
		 * @param obj2 比較対象の「SCORM LO サーバ情報」オブジェクトその２。
		 * @return obj1が小さい場合は-1を、
		 *          obj2が小さい場合は1を、同じ場合は0を戻します。
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 * @see #compare(ScormServerInfo, ScormServerInfo)
		 */
		public int compare(Object obj1, Object obj2) {
			return compare((ScormServerInfo) obj1, (ScormServerInfo) obj2);
		}

		/**
		 * ２つの「SCORM LO サーバ情報」オブジェクトと順序を比較します。<br>
		 * まず、使用人数の大小で比較し、より使用人数の少ない
		 * SCORM LO サーバ情報」を小さい扱いとします。
		 * もし、使用人数が同じ場合、サーバ番号のプレフィックス部分を
		 * 除いた後の数値の大小で比較し、
		 * 数値がより小さい「SCORM LO サーバ情報」を小さい扱いとします。
		 * @param server1 比較対象の「SCORM LO サーバ情報」オブジェクトその１。
		 * @param server2 比較対象の「SCORM LO サーバ情報」オブジェクトその２。
		 * @return server1が小さい場合は-1を、
		 *          server2が小さい場合は1を、同じ場合は0を戻します。
		 * @see ScormServerInfo#compareTo(ScormServerInfo)
		 */
		public int compare(ScormServerInfo server1, ScormServerInfo server2) {
			if (server1.getNumberOfUsers() < server2.getNumberOfUsers()) {
				return -1;
			}
			if (server1.getNumberOfUsers() > server2.getNumberOfUsers()) {
				return 1;
			}
			return server1.compareTo(server2);
		}
	}
}