/*
このファイルの中身は、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) ______________________________________.
*/
				
// 
//	機能名称	
//		CMI データモデル　メインインターフェース クラス（CBT クラス)
//
//		変更履歴	
//			2003.12.01 gxl 新規作成
//

package jp.co.ntt.lms.lo.scorm.cbtcom;

import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Date;


/**
 * CMI データモデル　メインインターフェース クラス（CBT クラス）<br>
 * 
 * @author gxl
 */
public class CMICBT extends CMIObjBase {

	/**
	* CMIError オブジェクト
	*/
	private CMIError m_lastError = null;

	/**
	* CMILesson オブジェクト
	*/
	private CMILesson m_lesson = null;

	/**
	* CMILaunch オブジェクト
	*/
	private CMILaunch m_launch = null;

	/**
	* CMIEvaluationData オブジェクト
	*/
	private CMIEvaluationData m_evaluationData = null;
	   
	/**
	* CBT バージョン
	*/
	private String m_version;

	/**
	* CMI バージョン
	*/
	private String m_cmiversion;

	/**
	* CMIElement オブジェクト
	*/
	private HashMap m_categoryMap = new HashMap();

	/**
	* CMIElement オブジェクト
	*/
	private HashMap m_elementpathMap  = new HashMap();

	/**
    * setValue のターゲットがLessonValue であるフラグ(通常：false)
    */
	private boolean m_targetLessonValue;

	/**
	* 更新フラグ
	*/
	private boolean m_bUpdate;

	private final String DEF_MODULE_VERSION = "1.0";
	private final String DEF_MODULE_MODEL = "releaseMinDepedency";

	private final String DEF_ENCODE = "UTF-8";

	/**
	 * CMICBTクラスの初期処理<br>
	 */
	public CMICBT() {

		// CMI AICC バージョン
		m_cmiversion = CMIElement.DEF_CMI_AICC_VERSION;
		m_lastError = new CMIError();
		m_isOpen = false;
		setCBT(this);
		m_proto	= CMIElementProto.g_cbtProto;
		makeChildren(CMIElementProto.g_cbtProto);

		m_lesson = new CMILesson(this);
		m_launch = new CMILaunch(this);
		m_evaluationData = new CMIEvaluationData(this);
		
		// デフォルト値をセットする
		setDefaultValue();
		m_isOpen	= false;
		m_bUpdate	= false;

		// テストデバッグ用
		// setVale のターゲットを切り替える
		m_targetLessonValue = false;	
		CMIUtil.putLog("CMICBT created");

		CMIUtil.setEncodeString(DEF_ENCODE);
	}

	/**
	 * CMICBTクラスの初期処理。エンコードする文字列を指定する<br>
	 */
	public CMICBT( String strEncWord ) {
		// CMI AICC バージョン
		m_cmiversion = CMIElement.DEF_CMI_AICC_VERSION;
		m_lastError = new CMIError();
		m_isOpen = false;
		setCBT(this);
		m_proto	= CMIElementProto.g_cbtProto;
		makeChildren(CMIElementProto.g_cbtProto);

		m_lesson = new CMILesson(this);
		m_launch = new CMILaunch(this);
		m_evaluationData = new CMIEvaluationData(this);
		
		// デフォルト値をセットする
		setDefaultValue();
		m_isOpen	= false;
		m_bUpdate	= false;

		// テストデバッグ用
		// setVale のターゲットを切り替える
		m_targetLessonValue = false;	
		CMIUtil.putLog("CMICBT created");

		if ((strEncWord == null) || ("".equals(strEncWord))) {
			CMIUtil.setEncodeString(DEF_ENCODE);
		} else {
			CMIUtil.setEncodeString(strEncWord);
		}
	}
	
	/**			
	 * CMICBTクラスの終了処理<br>		
	 */		
	public void finalize() {
		m_lesson = null;
		m_launch = null;
		m_evaluationData = null;
		m_categoryMap=null;
		m_elementpathMap=null;
		CMIUtil.putLog("CMICBT deleted");
	}
	
	/**
	 * セッションを開始する。<br>
	 * @return boolean オープンフラグ
	 */
	public boolean isOpen() {
		clearError();
		return m_isOpen;
	}
	
	/**
	 * セッションを開始する。<br>
	 * @return String CBT起動URL
	 */
	public String open() {

		String strCbtUrl = "";
		clearError();
		if (!m_isOpen) {

			if (!checkMandatory()) {
				// 入力必須のエラー
				setError(CMIError.ERR_NOT_INITALIZED);
				setVendorError(getVendorData("CMICBT.open", this));
			} else {
				// セッション初期化前処理
				before_initialize();
				// セッション初期化処理
				initialize();	
				// CBT URL を生成する
				strCbtUrl = m_launch.makeCbtURL();	
				// オープン状態にする
				m_isOpen = true;	
				// Launch のオープン処理
				m_launch.myOpen();	
				// Lesson のオープン処理
				m_lesson.myOpen();
				// evaluationData のオープン処理		
				m_evaluationData.myOpen();	

				// CMIデータに更新情報があった場合、強制的にPutParamログを出力する）
				writePutParamLog();
			}

		} else {
			setError(CMIError.ERR_IGNORED_OPERATION);
			setVendorError(getVendorData("CMICBT.open", this));
		}
		CMIUtil.putLog("CMICBT.open() retStr[" + strCbtUrl + "]");
		// CBT URL を生成する
		return strCbtUrl;		
	}
	
	/**
	 * セッションを終了する。<br>
	 */
	public void close() {

		clearError();
		if (m_isOpen) {
		//（CMIデータに更新情報があった場合、強制的にPutParamログを出力する）
			writePutParamLog();
			m_lesson.myClose();
			m_launch.myClose();
			m_evaluationData.myClose();
			m_isOpen = false;
		} else {
			setError(CMIError.ERR_IGNORED_OPERATION);
			setVendorError(getVendorData("CMICBT.close", this));
		}
		CMIUtil.putLog("CMICBT.close()");
	}
	
	/**
	 * 受信データを解析し、コマンドに対応する処理を実行する。<br>
	 * @param request String 請求データ
	 * @return String コマンド名+カンマセパレータ+応答データ
	 */
	public String hacp(String request) {		
		clearError();
		String strRes = "";
		StringBuffer response = new StringBuffer();
		
		// デバッグログ出力
		CMIUtil.putLog("CMICBT.hacp request[" + request + "]");
		if (CMIUtil.isDetailLogging()) {
			String data = getDiagnostic(CMIError.VERR_DEBUG_CMIDATA);
			CMIUtil.putLog(data);
		}

		CMIHacpComm commObj = 
								new CMIHacpComm(this, m_lesson, m_evaluationData);
		try {
			if (m_isOpen) {				
				strRes = commObj.doAnalysis(request, response);
				//CMIデータに更新情報があった場合、強制的にPutParamログを出力する）
				writePutParamLog();
			} else {
				setError(CMIError.ERR_NOT_INITALIZED);
				setVendorError(getVendorData("CMICBT.hacp", this));
				response.setLength(0);
				response.append( 
							commObj.makeResponse("", HACPError.HACP_ERR_CMI ));
			}

			// デバッグログ出力
			CMIUtil.putLog("CMICBT.hacp response[" + response.toString() + "]");
			if (CMIUtil.isDetailLogging()) {
				String data = getDiagnostic(CMIError.VERR_DEBUG_CMIDATA);
				CMIUtil.putLog(data);
			}
			// -----------------------
		}
		catch(Exception e) {
			response.setLength(0);
			response.append( commObj.makeResponse("", HACPError.HACP_ERR_CMI));
		}
		return strRes + "," + response.toString();
	}
	
	/**
	 * (LocalValueに)値を設定する。<br>
	 * @param name String データモデル名称
	 * @param value String 設定データ値
	 */
	public void setValue(String name, String value) {

		clearError();
		if ( !m_isOpen ) {
			CMIGlobalCBT pGCBT = CMIGlobalCBT.getGlobalCBT();
			// グローバルCBT の アクティブCBT を更新する
			pGCBT.setCBT(this);

			// SCORM1.3 用のCMIモデル名をAICC用（AICC2.0〜SCORM1.2）の
			// CMIモデル名に変換する
			String strName = CMIS13ModelUtil.changeAiccModelName(name);

			if ( !m_targetLessonValue ) {
				// デバッグログ出力
				CMIUtil.putLog("CMICBT.setValue name[" + name + "], " +
										"value[" + value + "]");
				if ( CMIUtil.isDetailLogging() ) {
					String strData = getDiagnostic(CMIError.VERR_DEBUG_CMIDATA);
					CMIUtil.putLog(strData);
				}

				// 指定されたデータ値をローカル値として設定する
				localSetValue(strName, value);
				CMIUtil.putLog("CMICBT.setValue");
				if ( CMIUtil.isDetailLogging() ) {
					String strData = getDiagnostic(CMIError.VERR_DEBUG_CMIDATA);
					CMIUtil.putLog(strData);
				}
			} else {
				CMIParameterPath param = new CMIParameterPath(strName);
				String theName = param.popFirstElement();
				if (theName.equals( getName() ) ){
					// 自エレメント名が指定されている場合
					// LessonValue に値をセットする。
					setLessonValue(param.getPath(), value);
				} else {
					// 自エレメント名が指定されていない場合
					// エラー(CMI) 引数エラー(理由：無効パスを指定した)
					setError(CMIError.ERR_INVALID_AURGUMENT_ERROR);
					setVendorError(getVendorData("CMICBT.setValue", this));
				}
			}
			// グローバルCBT の アクティブCBT を未設定にする
			pGCBT.setCBT(null);
		} else {
			// セッション開始中（openメソッド実行〜closeメソッド実行までの間）
			// は、値の設定はできない。
			setError(CMIError.ERR_IGNORED_OPERATION);
			setVendorError(getVendorData("CMICBT.setValue", this));
		}
	}
	
	/**
	 * データ値を取得する。<br>
	 * @param name String キー名
	 * @return String データ値
	 */
	public String getValue(String name) {

		CMIGlobalCBT pGCBT = CMIGlobalCBT.getGlobalCBT();
		clearError();
		String retStr = null;

		// グローバルCBT の アクティブCBT を更新する
		pGCBT.setCBT(this);

		// デバッグログ出力
		CMIUtil.putLog("CMICBT.getValue name[" + name + "]");
		if (CMIUtil.isDetailLogging()) {
			String data = getDiagnostic(CMIError.VERR_DEBUG_CMIDATA);
			CMIUtil.putLog(data);
		}

		String strName = CMIS13ModelUtil.changeAiccModelName(name);

		retStr = localGetValue(strName);
		// デバッグログ出力
		if ( retStr != null ) {
			CMIUtil.putLog("CMICBT.getValue  retStr[" + retStr + "]");
		} else {
			CMIUtil.putLog("CMICBT.getValue  retStr[null]");
			retStr = "";
		}
		if ( CMIUtil.isDetailLogging() ) {
			String strData = getDiagnostic(CMIError.VERR_DEBUG_CMIDATA);
			CMIUtil.putLog(strData);
		}
		// グローバルCBT の アクティブCBT を未設定にする
		pGCBT.setCBT(null);
		return retStr;
	}

	/**
	 *保存データを取得する。<br>
	 * @return String データ文字列
	 */
	public String getAllValue() {

		String retStr="";
		clearError();
		StringBuffer outstr = new StringBuffer();
		if ( !m_isOpen ) {
			serialize( outstr );
			retStr = outstr.toString();
		} else {
			setError(CMIError.ERR_IGNORED_OPERATION);
			setVendorError(getVendorData("CMICBT.getAllValue", this));
			retStr="";
		}
		return retStr;
	}

	/**
	 *保存データを設定する。<br>		
	 * @param valueStr String データ集合値
	 * @return なし		
	 */
	public void setAllValue(String valueStr) {
		clearError();
		if ( !m_isOpen ) {
			StringBuffer childrenValues = new StringBuffer();

			int intHead = valueStr.indexOf("=");
			intHead = getNestedValue(valueStr, intHead+1, childrenValues);
			deserialize(childrenValues.toString());
		} else {
			setError(CMIError.ERR_IGNORED_OPERATION);
			setVendorError(getVendorData("CMICBT.setAllValue", this));
		}
	}

	/**
	 *エラーコードを取得する。<br>
	 * @return int エラーコード
	 */
	public int getLastError() {
		return m_lastError.getErrorNumber();
	}
	
	/**
	 *エラーメッセージを取得する。<br>
	 * @param errCode int エラーコード
	 * @return String エラーメッセージ
	 */
	public String getErrorString(int errCode) {
		return m_lastError.getErrorString(errCode);
	}
	
	/**
	 *ベンダー情報を取得。<br>
	 * @param errCode int エラーコード
	 * @return String ベンダー情報
	 */
	public String getDiagnostic(int errCode) {
		
		int	intLastErrCode;
		StringBuffer sbuff = new StringBuffer();

		SimpleDateFormat sDataFormat = 
							new SimpleDateFormat("MMM dd yyyy HH:mm:ss");
		String DATE_TIME = sDataFormat.format(new Date());

		sbuff.append("Ver.");
		sbuff.append(DEF_MODULE_VERSION);
		sbuff.append(",");
		sbuff.append(DEF_MODULE_MODEL);
		sbuff.append(",").append(DATE_TIME);
		if ( CMIElementProto.IS_GLOBAL_MEM ) {
			sbuff.append("(GLOBAL)");
		} else {
			sbuff.append("(INSTANCE)");
		} 
		sbuff.append("\n");

		// 最新のエラー情報を生成する。
		if ( errCode < 0 ) {
			intLastErrCode = m_lastError.getErrorNumber();
		} else {
			intLastErrCode = errCode;
		}

		if ( CMIError.VERR_DEBUG_PROTO == errCode ) {
			// デバッグ情報を生成する。
			sbuff.append("デバッグ(プロトタイプ)\n");
			String strDebugProto = getDebugProto();
			if (strDebugProto != null) {
				sbuff.append(strDebugProto);
			}
		} else if (CMIError.VERR_DEBUG_CMIDATA == errCode) {
			// デバッグ情報を生成する。
			sbuff.append("デバッグ(CMIデータ)\n");
			String strDeBugCMI = getDebugCMIData();
			if (strDeBugCMI != null) {
				sbuff.append(strDeBugCMI);
			}
		} else if (CMIError.VERR_DEBUG_CHANGE_TARGETVALUE == errCode) {
			// setValue のターゲットVALUEを切り替える(トグル)
			sbuff.append("ターゲットVALUE切替 = Now Target Value is ");
			m_targetLessonValue = m_targetLessonValue ? false : true;	// トグル切替
			sbuff.append(m_targetLessonValue ? "Lesson" : "Local");
		} else {
			// errCode のエラー情報を生成する。
			sbuff.append(m_lastError.getErrorString(intLastErrCode));
			if (errCode < 0 && CMIError.ERR_NO_ERROR != intLastErrCode) {
				sbuff.append(". ").append(m_lastError.getVendorMessage());
			}
		}
		return sbuff.toString();
	}
	
	/**
 	 * CMI Launchクラスを取得する。<br>
	 * @return CMILaunch　インスタンスされたオブジェクト
	 */
	public CMILaunch getCmiLaunch() {
		return m_launch;
	}

	/**
	 * CMI Lessonクラスを取得する。<br>
	 * @return CMILesson　インスタンスされたオブジェクト
	 */
	public CMILesson getCmiLesson() {
		return m_lesson;
	}

	/**
	 * CMI EvaluationDataクラスを取得する。<br>
	 * @return CMIEvaluationData　インスタンスされたオブジェクト
	 */					
	public CMIEvaluationData getCmiEvaluationData() {
		return m_evaluationData;
	}

	/**
	 *発生したエラーコードをセットする。<br>
	 * @param error int エラーコード
	 */
	public void setError(int error) {
		m_lastError.setErrorNumber(error);
	}

	/**
	 *発生したエラーコードを取得する。<br>
	 * @return int エラーコード
	 */
	public int getError() {
		return m_lastError.getErrorNumber();
	}

	/**
	 *発生したベンダー情報をセットする。<br>
	 * @param msg String メッセージ情報
	 */
	public void setVendorError(String msg) {
		m_lastError.setVendorMessage(msg);
	}

	/**
	 * CBT バージョンをセットする。<br>
	 * @param version String バージョン情報
	 */
	public void setVersion(String version) {
		m_version = version;
	}

	/**
	 * CBT バージョンを取得する。<br>
	 * @return String バージョン情報
	 */
	public String getVersionStr() {
		return m_version;
	}

	/**
	 * CBTバージョンが 3 以上であることの判定。<br>
	 * @return booleanフラグ
	 */
	public boolean isV3Above() {

		boolean blnRet = false;
		String strVer = getVersionStr();
		
		if ( strVer != null ) {
			int intVer = Integer.parseInt(strVer.substring(0, 1));
			//v3.0 or above
			if ( intVer >= 3 ) {
				blnRet = true;
			}
		}
		return blnRet;
	}

	/**
	 * CMI バージョンを取得する。<br>
	 * @return String バージョン情報
	 */
	public String getCmiVersion() {
		return m_cmiversion;
	}

	/**
	 * CMIバージョンが 3 以上であることの判定。<br>
	 * @return booleanフラグ
	 */
	public boolean isCmiV3Above() {
		boolean blnRet = false;
		String strCMIVer = getCmiVersion();
		
		if ( strCMIVer != null ) {
			int intVer = Integer.parseInt( strCMIVer.substring( 0,  1 ) );
			//v3.0 or above
			if ( intVer >= 3 ) {
				blnRet = true;
			}
		}
		return blnRet;
	}

	/**
	 * キーパスからエレメントを取得する。<br>
	 * @param key String データモデルのキーパス名
	 * @return CMIElement データモデルエレメント
	 */				
	public CMIElement getPath2Element(String key) {		
		CMIElement element = null;
		Iterator iterator = m_elementpathMap.keySet().iterator();
		Object value = null;
		while (iterator.hasNext()) {
			value = iterator.next();
			if ( key.equals(value) ) {
				element = (CMIElement)m_elementpathMap.get(value);
				break;
			}
		}
		return element;
	}

	/**
	 *パスに相当するエレメントを登録する。<br>
	 * @param key String データモデルのキーパス名
	 * @param element CMIElement 登録するエレメントクラス
	 * @return CMIElement データモデルエレメント
	 */
	public CMIElement addPathElement(String key, CMIElement element) {
		m_elementpathMap.put(key, element);
		return element;
	}

	/**
	 * データモデルに対して更新があった場合true、更新がない場合falseを
	 * 設定する。<br>
	 * @param bUpdFlag boolean 更新フラグ
	 */
	public void updateFlag(boolean bUpdFlag) {
		m_bUpdate = bUpdFlag;
	}

	/**
	 * CMIデータモデルが更新されているか確認する。<br>
	 * @return boolean 更新フラグ
	 */
	public boolean isUpdate() {
		return m_bUpdate;
	}

	/**
	 * エラーメッセージを削除する。<br>
	 */
	public void clearError() {
		m_lastError.setErrorNumber(CMIError.ERR_NO_ERROR);
	}

	/**
	 * 内部データに更新があった場合、PutParam形式のログを出力する。<br>
	 */
	private void writePutParamLog() {
		if ( isUpdate() ) {		
			// 更新されていた場合、再度パラメータログファイルを出力する
			// 取得データ格納用
			StringBuffer strAiccData = new StringBuffer();	
			// PutParamの結果用
			StringBuffer resData = new StringBuffer();			
			m_lesson.doGetPutParam(strAiccData);
			m_evaluationData.doPutParam(strAiccData.toString(), resData);
			// ログ出力後、更新フラグをfalseに設定する
			updateFlag(false);
		}
	}

	/**
	 * CBTCOM内部データをXML形式で出力する。<br>
	 * @param outData OutputStream 出力データ
	 */
	public void getLearninLog(OutputStream outData) {

		StringBuffer sbuff = new StringBuffer();

		sbuff.append("<?xml version=\"1.0\" encoding=\"Windows-31J\"?>" );
		sbuff.append("\r\n");
		serializeXMLLog( sbuff );

		byte[] bData = sbuff.toString().getBytes();
		try {
			outData.write(bData,0,bData.length);
		}
		catch (Exception ex) {
			;
		}
		
	}
}
