/*
 * "Peko" Visual Novel System
 *
 * All Rights Reserved.
 * Copyright (c) 1999-2003 Tsukuba Bunko.
 *
 * $Id: SceneContext.java,v 1.1.2.1 2003/12/11 10:13:39 ppoi Exp $
 */
package tsukuba_bunko.peko.scenario;

import	java.io.Serializable;

import	java.util.List;
import	java.util.Map;
import	java.util.Set;

import	tsukuba_bunko.peko.Logger;

import	tsukuba_bunko.peko.session.Session;


/**
 * シーン処理のコンテクストです。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.1 $
 */
public class SceneContext	implements Serializable	{

	/**
	 * シーン名
	 */
	protected String	_sceneName = null;

	/**
	 * シーンタイトル
	 */
	protected String	_sceneTitle = null;

	/**
	 * シーンのプロパティ情報
	 */
	protected Map	_properties = null;

	/**
	 * シーン遷移先表
	 */
	protected NextSceneMapping	_nextSceneMapping = null;

	/**
	 * 現在位置
	 */
	transient protected Node	_current = null;

	/**
	 * 保存された最後の位置
	 */
	protected Node	_lastCommittedNode = null;

	/**
	 * シーンスコープ フラグ
	 */
	protected Set	_sceneScopeFlags = null;

	/**
	 * セッション
	 */
	transient protected Session	_session = null;

	/**
	 * このシーンを処理中の SceneProcessor
	 */
	transient protected SceneProcessor	_processor = null;


	/**
	 * <code>SceneContext</code> のインスタンスを生成します。
	 * @param	sceneName	シーン名
	 * @param	session	セッション
	 * @param	processor	このシーンを処理するシーンプロセッサ
	 */
	public SceneContext( String sceneName, Session session, SceneProcessor processor )
	{
		_sceneName = sceneName;
		_session = session;
		_processor = processor;

		SceneContext	previous = session.getSceneContext();
		if( previous != null )	{
			_lastCommittedNode = previous.getLastCommittedNode();
			_properties = previous._properties;
			_nextSceneMapping = previous.getNextSceneMapping();
			_sceneScopeFlags = previous._sceneScopeFlags;
		}
		else	{
			_nextSceneMapping = new NextSceneMapping();
			_sceneScopeFlags = new java.util.HashSet();
		}
	}


	/**
	 * シーン名を取得します。
	 * @return	シーン名
	 */
	public String getSceneName()
	{
		return _sceneName;
	}

	/**
	 * シーンタイトルを設定します。
	 * @param	title	シーンタイトル
	 */
	public void setSceneTitle( String title )
	{
		_sceneTitle = title;
	}

	/**
	 * シーンタイトルを取得します。
	 * @return	シーンタイトル
	 */
	public String getSceneTitle()
	{
		return _sceneTitle;
	}

	/**
	 * セッションを取得します。
	 * @return	セッション
	 */
	public Session getSession()
	{
		return _session;
	}

	/**
	 * シーンプロセッサを取得します。
	 * @return	シーンプロセッサ
	 */
	public SceneProcessor getSceneProcessor()
	{
		return _processor;
	}

	/**
	 * シーンプロパティを設定します。
	 * @param	name	プロパティ名
	 * @param	value	プロパティ値
	 */
	public void setProperty( String name, String value )
	{
		if( _properties == null )	{
			_properties = new java.util.HashMap( 17 );
		}
		_properties.put( name, value );
	}

	/**
	 * シーンプロパティを取得します。
	 * @param	name	プロパティ名
	 * @return	プロパティ値。<code>name</code> で識別されるプロパティが設定されていない場合は <code>null</code>。
	 */
	public String getProperty( String name )
	{
		if( _properties == null )	{
			return null;
		}
		else	{
			return (String)_properties.get( name );
		}
	}

	/**
	 * 遷移先表を設定します。
	 * @param	mapping	遷移先表
	 */
	public void setNextSceneMapping( NextSceneMapping mapping )
	{
		_nextSceneMapping = mapping;
	}

	/**
	 * 遷移先表を取得します。
	 * @return	遷移先表
	 */
	public NextSceneMapping getNextSceneMapping()
	{
		return _nextSceneMapping;
	}

	/**
	 * 次のシーンを取得します。
	 * @return	このシーンの処理後に遷移するシーンのシーン名
	 */
	public String getNextSceneName()
	{
		return _nextSceneMapping.getNextScene( this );
	}

	/**
	 * 指定されたノードの処理が開始されたことを通知します。
	 */
	public void pushNode( String nodeName )
	{
		if( _current == null )	{
			_current = new Node( null, nodeName.intern(), 1 );
		}
		else	{
			int	position = _current.countSibling(nodeName) + 1;
			Node	child = new Node( _current, nodeName.intern(), position );
			_current.addChildNode( child );
			_current = child;
		}
	}

	/**
	 * 現在のノードの処理が終了したことを通知します。
	 */
	public void popNode()
	{
		if( _current == null )	{
			Logger.error( "[scenario] BUG! invalid state of SceneLog." );
		}
		else	{
			_current.removeChildren();
			_current = _current.getParentNode();
		}
	}

	/**
	 * 現在の位置を保存します。
	 */
	public void saveCurrentNode()
	{
		_lastCommittedNode = _current;
	}

	/**
	 * 保存された最後の位置を取得します。
	 * @return	保存された最後の位置
	 */
	public Node getLastCommittedNode()
	{
		return _lastCommittedNode;
	}

	/**
	 * <code>node</code> が現在処理中のノードと同一であるかどうかを判定します。
	 * @param	node	判定対象のノード
	 * @return	同一での場合 <code>true</code>、それ以外の場合 <code>false</code>
	 */
	public boolean isCurrentNode( Node node )
	{
		if( node == _current )	{
			return true;
		}
		else if( _current != null )	{
			return _current.equals(node);
		}
		else {
			return false;
		}
	}

	/**
	 * 現在処理中のノードのコピーを取得します。コピーには、子ノードリストは含まれません。
	 * @return	現在処理中のノードのコピー
	 */
	public Node getCurrentNode()
	{
		Node	node = _current;
		if( node == null )	{
			return null;
		}
		else	{
			Node	copy = new Node( null, node._nodeName, node._position );
			Node	temp = copy;
			while( (node = node.getParentNode()) != null )	{
				temp._parent = new Node( null, node.getNodeName(), node.getPosition() );
				temp = temp.getParentNode();
			}
			return copy;
		}
	}

	/**
	 * 現在処理中のパスを XPath で取得します。
	 * @return	現在処理中のパス
	 */
	public String getCurrentPath()
	{
		if( _current != null )	{
			return _current.getPath();
		}
		else	{
			return "/";
		}
	}


	/**
	 * フラグを立てます。
	 * @param	flagID	フラグ ID
	 * @param	scope	フラグスコープ
	 * @throws	IllegalArgumentException	<code>flagID</code> が <code>null</code> の場合
	 */
	public void declareFlag( String flagID, FlagScope scope )
	{
		if( flagID == null )	{
			throw new IllegalArgumentException();
		}

		if( scope == FlagScope.SCENE )	{
			_sceneScopeFlags.add( flagID );
		}
		else if( scope == FlagScope.SESSION )	{
			_session.declareSessionFlag( flagID );
		}
		else if( scope == FlagScope.SYSTEM )	{
			_session.declareSystemFlag( flagID );
		}
	}

	/**
	 * フラグをおろします。
	 * @param	flagID	フラグ ID
	 * @param	scope	フラグスコープ
	 * @throws	IllegalArgumentException	<code>flagID</code> が <code>null</code> の場合
	 */
	public void undeclareFlag( String flagID, FlagScope scope )
	{
		if( flagID == null )	{
			throw new IllegalArgumentException();
		}

		if( scope == FlagScope.SCENE )	{
			_sceneScopeFlags.remove( flagID );
		}
		else if( scope == FlagScope.SESSION )	{
			_session.undeclareSessionFlag( flagID );
		}
		else if( scope == FlagScope.SYSTEM )	{
			_session.undeclareSystemFlag( flagID );
		}
	}

	/**
	 * スコープを問わず、フラグが立っているかどうかを判定します。
	 * @param	flagID	フラグ ID
	 */
	public boolean isDeclaredFlag( String flagID )
	{
		if( _sceneScopeFlags.contains(flagID) )	{
			return true;
		}
		else if( _session.isDeclaredSessionFlag(flagID) )	{
			return true;
		}
		else if( _session.isDeclaredSystemFlag(flagID) )	{
			return true;
		}
		else	{
			return false;
		}
	}

	/**
	 * 指定されたスコープでフラグが立っているかどうかを判定します。
	 * @param	flagID	フラグ ID
	 * @param	scope	フラグスコープ
	 * @return	指定されたフラグスコープでフラグが立っている場合 <code>true</code>、それ以外の場合 <code>false</code>
	 */
	public boolean isDeclaredFlag( String flagID, FlagScope scope )
	{
		if( scope == FlagScope.SCENE )	{
			return _sceneScopeFlags.contains( flagID );
		}
		else if( scope == FlagScope.SESSION )	{
			return _session.isDeclaredSessionFlag( flagID );
		}
		else if( scope == FlagScope.SYSTEM )	{
			return _session.isDeclaredSystemFlag( flagID );
		}
		else	{
			return false;
		}
	}

//
//	ノード
//
	/**
	 * PSML Scene 文書を構成するノードです。
	 */
	public static class Node	implements Serializable	{

		/**
		 * 親ノード
		 */
		protected Node	_parent = null;

		/**
		 * ノード名
		 */
		protected String	_nodeName = null;

		/**
		 * position
		 */
		protected int	_position = 0;

		/**
		 * 子ノードリスト
		 */
		protected List	_children = null;


		/**
		 * <code>SceneContext.Node</code> のインスタンスを生成します。
		 */
		public Node( Node parent, String nodeName, int position )
		{
			_parent = parent;
			_nodeName = nodeName.intern();
			_position = position;
		}

		/**
		 * 親ノードを取得します。
		 * @return	親ノード
		 */
		public Node getParentNode()
		{
			return _parent;
		}

		/**
		 * ノード名を取得します。
		 * @return	ノード名
		 */
		public String getNodeName()
		{
			return _nodeName;
		}

		/**
		 * ポジションを取得します。
		 * @return	ポジション
		 */
		public int getPosition()
		{
			return _position;
		}


		/**
		 * 子ノードを追加します。
		 * @param	child	子ノード
		 */
		public void addChildNode( Node child )
		{
			if( _children == null )	{
				_children = new java.util.ArrayList();
			}
			_children.add( child );
		}

		/**
		 * 子ノードのなかで、指定された名前のノードの数をカウントします。
		 * @param	nodeName	子ノード名
		 * @return	ノード数
		 */
		public int countSibling( String nodeName )
		{
			if( _children == null )	{
				return 0;
			}
			else	{
				String	symbol = nodeName.intern();
				List	children = _children;
				int	size = children.size();
				Node	node = null;
				int	count = 0;
				for( int i = 0; i < size; ++i )	{
					node = (Node)children.get( i );
					if( node._nodeName == symbol )	{
						count++;
					}
				}
				return count;
			}
		}

		/**
		 * このノードをルートとするサブツリーを破棄します。
		 */
		public void removeChildren()
		{
			if( _children != null )	{
				List	children = _children;
				int	size = children.size();
				for( int i = 0; i < size; ++i )	{
					((Node)children.get(i)).removeChildren();
				}
				children.clear();
				_children = null;
			}
		}

		/**
		 * このノードまでのパスを XPath 形式で取得します。
		 * @return	このノードまでのパス
		 */
		public String getPath()
		{
			StringBuffer	sink = new StringBuffer();
			Node	node = this;
			while( node != null )	{
				sink.insert( 0, node );
				node = node.getParentNode();
			}
			return new String(sink);
		}

		public boolean equals( Object obj )
		{
			if( obj instanceof Node )	{
				Node	target = (Node)obj;
				Node	node = this;
				while( (node != null) && (target != null) )	{
					if( !node._nodeName.equals(target._nodeName) || (node._position != target._position) )	{
						return false;
					}
					node = node._parent;
					target = target._parent;
				}
				return ((node == null) && (target == null));
			}
			else	{
				return false;
			}
		}

		public String toString()
		{
			return "/" + _nodeName + "[" + _position + "]";
		}
	}
}
