package jp.co.ntt.lms.imode.extension;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

import jp.co.ntt.lms.imode.common.StringSeparator;
import jp.co.ntt.lms.xmo.util.DebugLog;

/**
 * CMIデータモデルのパラメータを格納するHashtableです。
 * このクラスはHashtableと同様、get/putの両メソッドでデータの入出力を行うことができます。
 * また、CMIデータからAICCデータの形式に変換するメソッドを実装しています。
 * 対応するAICCデータは次のとおりです。<br>
 * <pre>
 *  - PutParam, GetParam
 *  - GetLmsComments
 *  - PutComments, GetComments
 *  - PutInteractions
 * </pre>
 * 
 *
 */
public class CmiParameters extends Hashtable {

	private static final long serialVersionUID = 1L;

	private final String COMMA = "%2C"; //URLEncodeされた","

	/**
	 * CMIデータを格納する空のHashtableを作成します。
	 */
	public CmiParameters(){
		super();
	}
	
	/**
	 * AICCデータのテキストをCMIデータに変換し、このHashtableに格納します。
	 * (GetParamで使用)
	 * @param aiccData AICCデータの文字列
	 */
	public void setCmiDataParam(String aiccData){
		try{
			aiccData = URLDecoder.decode(aiccData, "UTF-8");
			// ＬＯＧ出力
			DebugLog.write(getClass(), "[setCmiDataParam]"+ aiccData, DebugLog.ROW);
		}catch(Exception e){
		}

		StringSeparator st = new StringSeparator(aiccData, "\r\n");
		while(st.hasNext()){
			String line = st.next();
			String[] paramValue = getParamAndValue(line, "=");
			//"="がない場合
			if(paramValue == null){
				if(line.indexOf("[Core_Lesson]") >= 0){
					String value = "";
					if(st.hasNext()) value = st.next();
					this.put("cmi.suspend_data", value);
				}
				else if(line.indexOf("[Core_Vendor]") >= 0){
					String value = "";
					if(st.hasNext()) value = st.next();
					this.put("cmi.launch_data", value);
				}
				continue;
			}
			//キー名に"."が含まれる場合は、キー名と"."以降の数字を分離する
			String[] paramNumber = null;
			if(paramValue[0].indexOf(".") >= 0){
				paramNumber = getParamAndValue(paramValue[0], ".");
				paramValue[0] = paramNumber[0];
				if(paramNumber[1].equals("")) paramNumber[1] = "0";
			}
			//aiccデータをcmi形式に変換
			if(paramValue[0].equals("Student_ID")){
				this.put("cmi.learner_id", paramValue[1]);
			}else if(paramValue[0].equals("Student_Name")){
				this.put("cmi.learner_name", paramValue[1]);
			}else if(paramValue[0].equals("Lesson_Location")){
				this.put("cmi.location", paramValue[1]);
			}else if(paramValue[0].equals("Credit")){
				this.put("cmi.credit", paramValue[1]);
			}else if(paramValue[0].equals("Lesson_Status")){
				String[] values = getCsvStringArray(paramValue[1], 3);
				this.put("cmi.success_status", values[0]);
				this.put("cmi.completion_status", values[1]);
				this.put("cmi.entry", values[2]);
			}else if(paramValue[0].equals("Score")){
				String[] values = getCsvStringArray(paramValue[1], 4);
				this.put("cmi.score.scaled", values[0]);
				this.put("cmi.score.raw", values[1]);
				this.put("cmi.score.max", values[2]);
				this.put("cmi.score.min", values[3]);
			}else if(paramValue[0].equals("Time")){
				this.put("cmi.total_time", paramValue[1]);
			}else if(paramValue[0].equals("Lesson_Mode")){
				this.put("cmi.mode", paramValue[1]);
			}else if(paramValue[0].equals("Comments_File")){
				this.put("cmi.evalution.comments", paramValue[1]);
			}else if(paramValue[0].equals("Interactions_File")){
				this.put("cmi.evalution.interactions", paramValue[1]);
			}else if(paramValue[0].equals("Objectives_Status_File")){
				this.put("cmi.evalution.objectives_status", paramValue[1]);
			}else if(paramValue[0].equals("J_ID")){
				this.put("cmi.objectives." + paramNumber[1] + ".id", paramValue[1]);
			}else if(paramValue[0].equals("J_Score")){
				String[] values = getCsvStringArray(paramValue[1], 4);
				this.put("cmi.objectives." + paramNumber[1] + ".score.scaled", values[0]);
				this.put("cmi.objectives." + paramNumber[1] + ".score.raw", values[1]);
				this.put("cmi.objectives." + paramNumber[1] + ".score.max", values[2]);
				this.put("cmi.objectives." + paramNumber[1] + ".score.min", values[3]);
			}else if(paramValue[0].equals("J_Status")){
				String[] values = getCsvStringArray(paramValue[1], 2);
				this.put("cmi.objectives." + paramNumber[1] + ".success_status", values[0]);
				this.put("cmi.objectives." + paramNumber[1] + ".completion_status", values[1]);
			}else if(paramValue[0].equals("Mastery_Score")){
				this.put("cmi.scaled_passing_score", paramValue[1]);
			}else if(paramValue[0].equals("Max_Time_Allowed")){
				this.put("cmi.max_time_allowed", paramValue[1]);
			}else if(paramValue[0].equals("Time_Limit_Action")){
				this.put("cmi.time_limit_action", paramValue[1]);
			}else if(paramValue[0].equals("Audio")){
				this.put("cmi.learner_preference.audio", paramValue[1]);
			}else if(paramValue[0].equals("Language")){
				this.put("cmi.learner_preference.language", paramValue[1]);
			}else if(paramValue[0].equals("Speed")){
				this.put("cmi.learner_preference.speed", paramValue[1]);
			}else if(paramValue[0].equals("Text")){
				this.put("cmi.learner_preference.text", paramValue[1]);
			}
		}
	}
	
	/**
	 * AICCデータのテキストをCMIデータに変換し、このHashtableに格納します。
	 * (GetLmsCommentsで使用)
	 * @param aiccData AICCデータの文字列
	 */
	public void setCmiDataLmsComments(String aiccData){
		// ＬＯＧ出力
		DebugLog.write(getClass(), "[setCmiDataLmsComments]"+ aiccData, DebugLog.ROW);

		StringTokenizer st = new StringTokenizer(aiccData, "\r\n");
		String[] values = new String[3];
		//1行目を取得
		if(st.hasMoreTokens()){
			st.nextToken();
		}
		//次の行からデータ
		int lineCount = 0;
		while(st.hasMoreTokens()){
			String line = st.nextToken();
			if(line.equals("")) continue;

			values = getCsvStringArrayWithoutDoubleQuot(line, 3);
			this.put("cmi.comments_from_lms." + lineCount + ".date_time", values[0]);
			this.put("cmi.comments_from_lms." + lineCount + ".location", values[1]);
			this.put("cmi.comments_from_lms." + lineCount + ".comment", values[2]);
			lineCount++;
		}
	}

	/**
	 * AICCデータのテキストをCMIデータに変換し、このHashtableに格納します。
	 * (GetCommentsで使用)
	 * @param aiccData AICCデータの文字列
	 */
	public void setCmiDataComments(String aiccData){
		StringTokenizer st = new StringTokenizer(aiccData, "\n");
		String[] values = new String[3];
		//1行目を取得
		if(st.hasMoreTokens()){
			st.nextToken();
		}
		//次の行からデータ
		int lineCount = 0;
		while(st.hasMoreTokens()){
			String line = st.nextToken();
			if(line.equals("")) continue;

			values = getCsvStringArrayWithoutDoubleQuot(line, 3);
			this.put("cmi.comments_from_learner." + lineCount + ".date_time", values[0]);
			this.put("cmi.comments_from_learner." + lineCount + ".location", values[1]);
			this.put("cmi.comments_from_learner." + lineCount + ".comment", values[2]);
			lineCount++;
		}
	}

	/**
	 * AICCデータのテキストをCMIデータに変換し、このHashtableに格納します。
	 * (GetInteractionsで使用)
	 * @param aiccData AICCデータの文字列
	 */
	public void setCmiDataInteractions(String aiccData){
		StringTokenizer st = new StringTokenizer(aiccData, "\n");
		String[] values = new String[3];
		//1行目を取得
		if(st.hasMoreTokens()){
			st.nextToken();
		}
		//次の行からデータ
		int lineCount = 0;
		while(st.hasMoreTokens()){
			String line = st.nextToken();
			if(line.equals("")) continue;

			values = getCsvStringArrayWithoutDoubleQuot(line, 10);
			this.put("cmi.interactions." + lineCount + ".timestamp", values[0]);
			this.put("cmi.interactions." + lineCount + ".id", values[1]);

			// objectivesをカンマ分割
			StringSeparator objectives = new StringSeparator(values[2], ",");

			for(int i= 0; objectives.hasNext(); i++) {
				this.put("cmi.interactions." + lineCount + ".objectives." + i + ".id", objectives.next());
			}

			this.put("cmi.interactions." + lineCount + ".type", values[3]);

			// correct_responsesセミコロン分割
			StringSeparator correct = new StringSeparator(values[4], ";");

			for(int i= 0; correct.hasNext(); i++) {
				this.put("cmi.interactions." + lineCount + ".correct_responses." + i + ".pattern", correct.next());
			}

			this.put("cmi.interactions." + lineCount + ".learner_response", values[5]);
			this.put("cmi.interactions." + lineCount + ".result", values[6]);
			this.put("cmi.interactions." + lineCount + ".weighting", values[7]);
			this.put("cmi.interactions." + lineCount + ".latency", values[8]);
			this.put("cmi.interactions." + lineCount + ".description", values[9]);
			
			lineCount++;
		}
	}
	
	/**
	 * このクラスが保持しているCMIデータをAICCデータの文字列に変換し、返します。
	 * (PutParamで使用)
	 * @return AICCデータの文字列
	 */
	public String getAiccDataParam(){
		StringBuffer sbOutput = new StringBuffer();
		
		sbOutput.append("[Core]\n");
		sbOutput.append("Lesson_Location=" + getStringValue("cmi.location") + "\n");
		sbOutput.append("Lesson_Status=");
		sbOutput.append(getStringValue("cmi.success_status") + COMMA);
		sbOutput.append(getStringValue("cmi.completion_status") + COMMA);
		sbOutput.append(getStringValue("cmi.exit") + "\n");
		sbOutput.append("Score=");
		sbOutput.append(getStringValue("cmi.score.scaled") + COMMA);
		sbOutput.append(getStringValue("cmi.score.raw") + COMMA);
		sbOutput.append(getStringValue("cmi.score.max") + COMMA);
		sbOutput.append(getStringValue("cmi.score.min") + "\n");

		if (!getStringValue("cmi.session_time").equals("")) {
			sbOutput.append("Time=" + getStringValue("cmi.session_time") + "\n");
		}

		sbOutput.append("[Core_Lesson]\n");
		sbOutput.append(getStringValue("cmi.suspend_data") + "\n");
		sbOutput.append("[Objectives_Status]\n");
		//インデックス付きパラメータの処理
		for(int i=0; i<this.size(); i++){
			String value = (String)this.get("cmi.objectives." + (i) + ".id");
			if(value != null){
				sbOutput.append("J_ID." + (i) + "=" + value + "\n");
			}
		}
		for(int i=0; i<this.size(); i++){
			String scaled = getStringValue("cmi.objectives." + (i) + ".score.scaled");
			String raw = getStringValue("cmi.objectives." + (i) + ".score.raw");
			String max = getStringValue("cmi.objectives." + (i) + ".score.max");
			String min = getStringValue("cmi.objectives." + (i) + ".score.min");
			if (!scaled.equals("") ||
				!raw.equals("")    ||
				!max.equals("")    ||
				!min.equals("")) {
				sbOutput.append("J_Score." + (i) + "=" + scaled + COMMA);
				sbOutput.append(raw + COMMA);
				sbOutput.append(max + COMMA);
				sbOutput.append(min + "\n");
			}
		}
		//インデックス付きパラメータの処理
		for(int i=0; i<this.size(); i++){
			String value1 = (String)this.get("cmi.objectives." + (i) + ".success_status");
			String value2 = (String)this.get("cmi.objectives." + (i) + ".completion_status");
			if((value1 != null) || (value2 != null)){
				sbOutput.append("J_Status." + i + "=");
				sbOutput.append(getStringValue("cmi.objectives." + (i) + ".success_status") + COMMA);
				sbOutput.append(getStringValue("cmi.objectives." + (i) + ".completion_status") + "\n");
			}
		}
		sbOutput.append("[Student_Preferences]\n");
		sbOutput.append("Audio=" + getStringValue("cmi.learner_preference.audio") + "\n");
		sbOutput.append("Language=" + getStringValue("cmi.learner_preference.language") + "\n");
		sbOutput.append("Speed=" + getStringValue("cmi.learner_preference.speed") + "\n");
		sbOutput.append("Text=" + getStringValue("cmi.learner_preference.text") + "\n");
		
		return sbOutput.toString();
	}
	
	/**
	 * このクラスが保持しているCMIデータをAICCデータの文字列に変換し、返します。
	 * (GetLmsCommentsで使用)
	 * @return AICCデータの文字列
	 */
	public String getAiccDataLmsComments(){
		StringBuffer sbOutput = new StringBuffer();
		
		sbOutput.append("\"Date\",\"Location\",\"Comment\"\n");
		for(int i=0; ; i++){
			String str = (String)this.get("cmi.comments_from_lms." + i + ".date_time");
			if(str == null) break;
			sbOutput.append("\"" + str);
			sbOutput.append("\",\"" + getStringValue("cmi.comments_from_lms." + i + ".location"));
			sbOutput.append("\",\"" + getStringValue("cmi.comments_from_lms." + i + ".comment"));
			sbOutput.append("\"\n");
		}
		return sbOutput.toString();
	}

	/**
	 * このクラスが保持しているCMIデータをAICCデータの文字列に変換し、返します。
	 * (GetCommentsで使用)
	 * @return AICCデータの文字列
	 */
	public String getAiccDataComments(){
		String strCommentsData = null;
		String strCommentsHead = "\"Date\",\"Location\",\"Comment\"\n";
		StringBuffer sbOutput = new StringBuffer();
		
//		sbOutput.append("\"Date\",\"Location\",\"Comment\"\n");

		for(int i=0; ; i++){
			String str = (String)this.get("cmi.comments_from_learner." + i + ".date_time");
			if(str == null) {
				break;
			}
			sbOutput.append("\"" + str);
			sbOutput.append("\",\"" + getStringValue("cmi.comments_from_learner." + i + ".location"));
			sbOutput.append("\",\"" + getStringValue("cmi.comments_from_learner." + i + ".comment"));
			sbOutput.append("\"\n");
		}

		if (sbOutput.length() > 0) {
			strCommentsData = strCommentsHead + sbOutput.toString();
		}
		return strCommentsData;
	}

	/**
	 * このクラスが保持しているCMIデータをAICCデータの文字列に変換し、返します。
	 * (GetInteractionsで使用)
	 * @return AICCデータの文字列
	 */
	public String getAiccDataInteractions(){
		String strInteractionsData = null;
		String strInteractionsHead = "\"Date\",\"Interaction_ID\",\"Objective_ID\",\"Type_Interaction\",\"Correct_Response\",\"Student_Response\",\"Result\",\"Weighting\",\"Latency\",\"Description\"\n";
		StringBuffer sbOutput = new StringBuffer();
		
		for(int i=0; ; i++){
			// 訪問日時取得
			String timestamp = getStringValue("cmi.interactions." + i + ".timestamp");
			// 訪問ID取得
			String id = getStringValue("cmi.interactions." + i + ".id");
			// 訪問別学習目標取得
			StringBuffer  objective = new StringBuffer();
			for(int j = 0; ; j++) {
				String value = getStringValue("cmi.interactions." + i + ".objectives." + j + ".id");
				if (value.equals("")) {
					break;
				}
				if (j > 0) {
					objective.append(";");
				}
				objective.append(value);
			}
			// 訪問タイプ取得
			String type = getStringValue("cmi.interactions." + i + ".type");
			// 正答取得
			StringBuffer correct = new StringBuffer();
			for(int j = 0; ; j++) {
				String value = getStringValue("cmi.interactions." + i + ".correct_responses." + j + ".pattern");
				if (value.equals("")) {
					break;
				}

				if (j > 0) {
					correct.append(";");
				}
				correct.append(value);
			}
			// 学習者回答取得
			String learner_response = getStringValue("cmi.interactions." + i + ".learner_response");
			// 学習結果取得
			String result = getStringValue("cmi.interactions." + i + ".result");
			// 重み取得
			String weighting = getStringValue("cmi.interactions." + i + ".weighting");
			// 学習経過時間取得
			String latency = getStringValue("cmi.interactions." + i + ".latency");
			// 訪問説明取得
			String description = getStringValue("cmi.interactions." + i + ".description");

			// 訪問日時取得〜訪問説明まで全ての値が取得できない場合、処理終了
			if (timestamp.equals("") &&
				id.equals("") &&
				objective.toString().equals("") &&
				type.equals("") &&
				correct.toString().equals("") &&
				learner_response.equals("") &&
				result.equals("") &&
				weighting.equals("") &&
				latency.equals("") &&
				description.equals("")) {
				break;
			}

			// 訪問日時設定
			sbOutput.append("\"" + timestamp);
			// 訪問ID設定
			sbOutput.append("\",\"" + id);
			// 訪問別学習目標設定
			sbOutput.append("\",\"" + objective.toString());
			// 訪問タイプ設定
			sbOutput.append("\",\"" + type);
			// 正答設定
			sbOutput.append("\",\"" + correct.toString());
			// 学習者回答設定
			sbOutput.append("\",\"" + learner_response);
			// 学習結果設定
			sbOutput.append("\",\"" + result);
			// 重み設定
			sbOutput.append("\",\"" + weighting);
			// 学習経過時間設定
			sbOutput.append("\",\"" + latency);
			// 訪問説明設定
			sbOutput.append("\",\"" + description);
			sbOutput.append("\"\n");
		}

		if (sbOutput.length() > 0) {
			strInteractionsData = strInteractionsHead + sbOutput.toString();
		}
		return strInteractionsData;
	}

	protected String[] getCsvStringArray(String csvValue, int arrayLength){
		StringSeparator st = new StringSeparator(csvValue, ",");
		String[] strArray = new String[arrayLength];
		
		for(int i = 0; i<strArray.length; i++){
			strArray[i] = "";
			if(st.hasNext()){
				strArray[i] = st.next();
			}
		}
		return strArray;
	}

	protected String[] getCsvStringArrayWithoutDoubleQuot(String csvValue, int arrayLength){
		String strNew = csvValue.substring(1, csvValue.length()-1);
		StringSeparator st = new StringSeparator(strNew, "\",\"");
		String[] strArray = new String[arrayLength];
		
		for(int i=0; i<strArray.length; i++){
			strArray[i] = "";
			if(st.hasNext()){
				strArray[i] = st.next();
			}
		}
		return strArray;
	}

	protected String[] getParamAndValue(String line, String separator){
		String[] paramAndValue = new String[2];
		int equalPos = line.indexOf(separator);
		try{
			paramAndValue[0] = line.substring(0, equalPos);
			paramAndValue[1] = line.substring(equalPos + separator.length());
			return paramAndValue;
		}catch(Exception e){
			return null;
		}
	}
	protected String getStringValue(String key){
		String value = (String)this.get(key);
		value = (value==null) ? "" : value;
		try{
			value = URLEncoder.encode(value, "UTF-8");
		}catch(Exception e){
		}
		return value;
	}
}
