//
// CopyRight (C) 2002, IPA, eLC, NTT Resonant Inc. All rights reserved.
//
//	Status
//
//	変更履歴
//		2002.12.25  新規作成
//

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

import java.util.Calendar;
import java.util.Vector;

import jp.co.ntt.lms.lo.scorm.kernel.exception.SystemException;
import jp.co.ntt.lms.lo.scorm.util.CachedNode;
import jp.co.ntt.lms.lo.scorm.util.DateUtility;
import jp.co.ntt.lms.lo.scorm.util.XMLUtil;
import jp.co.ntt.lms.lo.scorm.util.XPathUtil;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Status class<BR>
 * Activityごとのステータス情報を表すクラス<BR>
 */
public class Status {
	/** activity is active のフラグ */
	private boolean activityisActive;

	/** activity is suspended */
	private boolean activityisSuspended;

	/** available children */
	private Activity[] availableChildren;

	/** attempt suspended time - Calendarが入る */
	private Vector attemptSuspendedTime;

	/** attempt start time - Calendarが入る */
	private Vector attemptStartTime;

	/** activity start time */
	private Calendar activityStartTime;

	/** selection status */
	private boolean selectionStatus;

	/** randomize status */
	private boolean randomizeStatus;

	/** this Activity */
	private Activity activity;

	/**
	 * デフォルトコンストラクタ
	 */
	public Status() {
	}

	/**
	 * コンストラクタ<BR>
	 *
	 * @param activity statusを持つアクティビティ<BR>
	 * @param manifestNode 教材構造ノード<BR>
	 */
	public Status(Activity activity, CachedNode manifestNode) {
		//アクティビティを設定
		this.activity = activity;
	}

	/**
	 * 中断情報取得<BR>
	 *
	 * @return statusの中断情報の文字列<BR>
	 */
	public void save(Document doc, Element parent, boolean traceLog) throws SystemException {
		try {
			Element statusNode = XMLUtil.append(doc, parent, "status");

			XMLUtil.append(doc, statusNode, XMLUtil.createElement(doc, "activityisactive", Boolean.toString(activityisActive)));

			XMLUtil.append(doc, statusNode, XMLUtil.createElement(doc, "activityissuspended", Boolean.toString(activityisSuspended)));

			Element availableChildrenNode = XMLUtil.append(doc, statusNode, "availablechildren");

			if (availableChildren != null) {
				for (int i = 0; i < availableChildren.length; i++) {
					XMLUtil.append(doc, availableChildrenNode, "id", availableChildren[i].getIdentifier());
				}
			}

			Element activityStartTimeNode = XMLUtil.append(doc, statusNode, "activitystarttime");
			XMLUtil.append(doc, activityStartTimeNode, "date", DateUtility.toString(activityStartTime));

			Element attemptSuspendedTimeNode = XMLUtil.append(doc, statusNode, "attemptsuspendedtime");

			for (int i = 0; i < attemptSuspendedTime.size(); i++) {
				XMLUtil.append(doc, attemptSuspendedTimeNode, "date", DateUtility.toString((Calendar)attemptSuspendedTime.get(i)));
			}

			Element attemptStartTimeNode = XMLUtil.append(doc, statusNode, "attemptstarttime");

			for (int i = 0; i < attemptStartTime.size(); i++) {
				XMLUtil.append(doc, attemptStartTimeNode, "date", DateUtility.toString((Calendar)attemptStartTime.get(i)));
			}

			XMLUtil.append(doc, statusNode, XMLUtil.createElement(doc, "selectionstatus", Boolean.toString(selectionStatus)));

			XMLUtil.append(doc, statusNode, XMLUtil.createElement(doc, "randomizestatus", Boolean.toString(randomizeStatus)));
		}
		catch (SystemException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new SystemException("save failed [ActivityID:" + activity.getIdentifier() + "]", ex);
		}
	}

	/**
	 *  status の初期化<BR>
	 * 
	 */
	public void initializeStatusInformation(CachedNode parent) throws SystemException {
		activityisActive = false;
		activityisSuspended = false;
		availableChildren = activity.getChildren();
		attemptSuspendedTime = new Vector();
		attemptStartTime = new Vector();
		selectionStatus = false;
		randomizeStatus = false;

		if (parent == null) {
			return;
		}

		try {
			Node statusNode = XPathUtil.selectSingleChildNode(parent.getNode(), "status");

			int max;
			Node[] list;

			// activity is active
			activityisActive = Boolean.valueOf(XPathUtil.selectSimpleSingleNode(statusNode, "activityisactive/text()").getNodeValue()).booleanValue();

			// activity is suspended
			activityisSuspended = Boolean.valueOf(XPathUtil.selectSimpleSingleNode(statusNode, "activityissuspended/text()").getNodeValue()).booleanValue();

			// available children
			list = XPathUtil.selectSimpleNodeList(statusNode, "availablechildren/id/text()");

			if (list != null) {
				max = list.length;

				if (max > 0) {
					availableChildren = new Activity[max];
					for (int i = 0; i < max; i++) {
						availableChildren[i] = activity.getActivity(list[i].getNodeValue());
					}
				}
			}

			// activity start time
			Node node = XPathUtil.selectSimpleSingleNode(statusNode, "activitystarttime/date/text()");

			if (node != null) {
				activityStartTime = DateUtility.toCalendar(node.getNodeValue());
			}

			// attempt suspended time
			list = XPathUtil.selectSimpleNodeList(statusNode, "attemptsuspendedtime/date/text()");

			if (list != null) {
				max = list.length;

				for (int i = 0; i < max; i++) {
					attemptSuspendedTime.add(DateUtility.toCalendar(list[i].getNodeValue()));
				}
			}

			// attempt start time
			list = XPathUtil.selectSimpleNodeList(statusNode, "attemptstarttime/date/text()");

			if (list != null) {
				max = list.length;

				for (int i = 0; i < max; i++) {
					attemptStartTime.add(DateUtility.toCalendar(list[i].getNodeValue()));
				}
			}

			// selection status
			selectionStatus = Boolean.valueOf(XPathUtil.selectSimpleSingleNode(statusNode, "selectionstatus/text()").getNodeValue()).booleanValue();

			// randomize status
			randomizeStatus = Boolean.valueOf(XPathUtil.selectSimpleSingleNode(statusNode, "randomizestatus/text()").getNodeValue()).booleanValue();
		}
		catch (SystemException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new SystemException("initializeStatusInformation failed [ActivityID:" + activity.getIdentifier() + "]", ex);
		}
	}

	/**
	 * 試行情報の初期化<BR>
	 */
	public void initializeAttemptInformation() throws SystemException {
		try {
			// 学習開始時間を初期化する
			attemptStartTime.clear();

			// 学習中断時間を初期化する
			attemptSuspendedTime.clear();
		}
		catch (Exception ex) {
			throw new SystemException("initializeAttemptInformation failed [ActivityID:" + activity.getIdentifier() + "]", ex);
		}
	}

	/**
	 * 終期化<BR>
	 */
	public void terminate() {
		if (availableChildren != null) {
			for (int i = 0; i < availableChildren.length; i++) {
				availableChildren[i] = null;
			}
		}

		activity = null;
	}

	/**
	 * activity is active のプロパティへのセット<BR>
	 *
	 * @param	status	true:activity is active を true にする<BR>
	 *						( そのアクティビティは現在学習中、もしくは学習中のアクティビティの祖先である。)<BR>
	 *					false:activity is active を false にする<BR>
	 *						( そのアクティビティは現在、未学習、もしくは学習済みである。)<BR>
	 */
	public void setActivityIsActive(boolean status) {
		activityisActive = status;
	}

	/**
	 *	activity is active のプロパティの取得<BR>
	 *
	 *	@return	activity is active を返却する
	 */
	public boolean getActivityIsActive() {
		return activityisActive;
	}

	/**
	 *	activity is suspended のプロパティへのセット<BR>
	 *
	 *	@param	 status	 true:activity is suspended を true にする<BR>
	 *							( そのアクティビティは現在、学習を中断している、
	 *								もしくは中断したアクティビティの祖先である。)<BR>
	 *					  false:activity is suspended を false にする<BR>
	 *							( そのアクティビティは現在、学習を中断していない。)<BR>
	 */
	public void setActivityIsSuspended(boolean status) {
		activityisSuspended = status;
	}

	/**
	 *	activity is suspended のプロパティの取得<BR>
	 *
	 *	@return	activity is suspended を返却する
	 */
	public boolean getActivityIsSuspended() {
		return activityisSuspended;
	}

	/**
	 * 子アクティビティのうち、selection process と randomize process により、<BR>
	 * 学習可能と判断されたアクティビティを available children として、セットする。<BR>
	 *
	 * @param	availableChildren 学習可能と判断された子アクティビティの配列<BR>
	 */
	public void setAvailableChildren(Activity[] availableChildren) {
		this.availableChildren = availableChildren;
	}

	/**
	 * available children を取得する<BR>
	 *
	 * @return  学習可能と判断された子アクティビティの配列<BR>
	 */
	public Activity[] getAvailableChildren() {
		return availableChildren;
	}

	/**
	 * 中断時間を登録する<BR>
	 *
	 * @param  suspend 中断時の日時<BR>
	 */
	public void setAttemptSuspendedTime(Calendar suspend) {
		attemptSuspendedTime.add(suspend);
	}

	/**
	 * 学習開始時間をセットする<BR>
	 *
	 * @param	start 開始日時<BR>
	 */
	public void setAttemptStartTime(Calendar start) {
		if (activityStartTime == null) {
			activityStartTime = start;
		}
		attemptStartTime.add(start);
	}

	/**
	 * 総学習時間の取得<BR>
	 *
	 * @param	current 現在の時間( 学習終了時の日時 )<BR>
	 * @return  総学習時間<BR> 
	 */
	public Duration getActivityAbsoluteDuration(Calendar current) throws SystemException {

		if (activityStartTime == null) {
			return new Duration(0);
		}
		//アクティビティ経過時間を取得する
		return new Duration(current.getTimeInMillis() - activityStartTime.getTimeInMillis());
	}

	/**
	 * Attempt学習時間の取得<BR>
	 *
	 * @param	current 現在の時間( 学習終了時の日時 )<BR>
	 * @return  総学習時間<BR> 
	 */
	public Duration getAtemptAbsoluteDuration(Calendar current) throws SystemException {

		if (attemptStartTime.size() <= 0) {
			return new Duration(0);
		}
		//現在の試行の経過時間を取得する
		return new Duration(current.getTimeInMillis() - ((Calendar)attemptStartTime.get(0)).getTimeInMillis());
	}

	/**
	 * 現試行の実学習時間の取得<BR>
	 *
	 * @param	current 現在の日時( 学習終了時の日時 ) <BR>
	 * @return	実学習時間<BR>
	 */
	public Duration getAttemptExperiencedDuration(Calendar current) throws SystemException {
		try {
			// スタートタイムが存在しない場合は0を返却
			if (attemptStartTime.size() <= 0) {
				return new Duration(0);
			}

			long duration = 0;
			long attemptStartTimePoint = 0;

			// スタートタイムでループ
			for (int i = 0; i < attemptStartTime.size(); i++) {
				attemptStartTimePoint = ((Calendar)attemptStartTime.get(i)).getTimeInMillis();
				// 中断時間が存在する場合
				if (i < attemptSuspendedTime.size()) {
					for (int j = 0; j < attemptSuspendedTime.size(); j++) {
						// スタートタイムより大きい
						if (((Calendar)attemptSuspendedTime.get(j)).getTimeInMillis() > attemptStartTimePoint) {
							// 最後のスタートタイムまたは次のスタートタイムより小さい
							if (i == attemptStartTime.size() - 1
								|| (i < attemptStartTime.size() - 1 && ((Calendar)attemptSuspendedTime.get(j)).getTimeInMillis() <= ((Calendar)attemptStartTime.get(i + 1)).getTimeInMillis())) {
								duration += (((Calendar)attemptSuspendedTime.get(j)).getTimeInMillis() - attemptStartTimePoint);
								break;
							}
						}
					}
				}
			}

			return new Duration(duration);
		}
		catch (Exception ex) {
			throw new SystemException("getAttemptExperiencedDuration failed [ActivityID:" + activity.getIdentifier() + "]", ex);
		}
	}

	/**
	 * 最新学習時間の取得<BR>
	 *
	 * @param	current 現在の日時( 学習終了時の日時 ) <BR>
	 * @return	実学習時間<BR>
	 */
	public Duration getLastExperiencedDuration(Calendar current) throws SystemException {
		try {
			// スタートタイムが存在しない場合は0を返却
			if (attemptStartTime.size() <= 0) {
				return new Duration(0);
			}

			long duration = 0;
			long attemptStartTimePoint = 0;

			// 最後のスタートタイムスタートタイム
			attemptStartTimePoint = ((Calendar)attemptStartTime.get(attemptStartTime.size() - 1)).getTimeInMillis();

			// 中断時間が存在する場合
			if (0 < attemptSuspendedTime.size()) {
				for (int j = 0; j < attemptSuspendedTime.size(); j++) {
					// スタートタイムより大きいか判定
					if (((Calendar)attemptSuspendedTime.get(j)).getTimeInMillis() > attemptStartTimePoint) {
						// 中断時間が最後の中断時間であるか判定
						if (j == attemptSuspendedTime.size() - 1) {
							duration = (((Calendar)attemptSuspendedTime.get(j)).getTimeInMillis() - attemptStartTimePoint);
						}
						break;
					}
				}
			}

			return new Duration(duration);
		}
		catch (Exception ex) {
			throw new SystemException("getLastExperiencedDuration failed [ActivityID:" + activity.getIdentifier() + "]", ex);
		}
	}

	/**
	 * selection状態を設定する
	 *
	 * @param status 設定する状態を指定する
	 */
	public void setSelectionStatus(boolean status) {
		selectionStatus = status;
	}

	/**
	 * selection状態を取得する
	 *
	 * @return selection状態
	 */
	public boolean getSelectionStatus() {
		return selectionStatus;
	}

	/**
	 * randomize状態を設定する
	 *
	 * @param status 設定する状態を指定する
	 */
	public void setRandomizeStatus(boolean status) {
		randomizeStatus = status;
	}

	/**
	 * randomize状態を取得する
	 *
	 * @return randomize状態
	 */
	public boolean getRandomizeStatus() {
		return randomizeStatus;
	}
}
