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

import	java.io.InputStream;

import	java.net.URL;

import	javax.xml.parsers.ParserConfigurationException;
import	javax.xml.parsers.SAXParser;
import	javax.xml.parsers.SAXParserFactory;

import	org.xml.sax.Attributes;
import	org.xml.sax.InputSource;
import	org.xml.sax.SAXException;
import	org.xml.sax.SAXParseException;
import	org.xml.sax.helpers.DefaultHandler;

import	tsukuba_bunko.peko.ActionControler;
import	tsukuba_bunko.peko.Logger;
import	tsukuba_bunko.peko.PekoSystem;

import	tsukuba_bunko.peko.resource.ResourceManager;

import	tsukuba_bunko.peko.scenario.select.SelectCoordinator;

import	tsukuba_bunko.peko.scenario.stage.StageCoordinator;

import	tsukuba_bunko.peko.scenario.text.TextCoordinator;

import	tsukuba_bunko.peko.session.Session;


/**
 * シーンの処理を行うプロセッサモジュールです。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.2 $
 */
public class SceneProcessor	extends DefaultHandler	implements Runnable	{

	/**
	 * PSML 1.0 Scene DTD Public ID
	 */
	public static final String	PSML_PUBLICID = "-//Tsukuba Bunko//DTD PSML Scene 1.0//EN";

	/**
	 * PSML 1.0 Scene DTD System ID
	 */
	public static final String	PSML_SYSTEMID = "http://softlab.tsukuba-bunko.org/dtd/psml10-scene.dtd";


	/**
	 * PSML 解析を行う SAX Parser
	 */
	protected SAXParser	_parser = null;

	/**
	 * 処理状態フラグ
	 */
	protected boolean	_running = false;

	/**
	 * 中止フラグ
	 */
	protected boolean	_aborted = false;

	/**
	 * 処理中のシーン
	 */
	protected URL	_sceneURL = null;

	/**
	 * 処理中のシーンのコンテクスト
	 */
	protected SceneContext	_sceneContext = null;

	/**
	 * この SceneProcessor を制御する ScenarioProcessor
	 */
	protected ScenarioProcessor	_owner = null;

	/**
	 * 現在のツリー中のレベル
	 */
	protected int	_level = -1;

	/**
	 * ElementHandler が処理中のサブツリーの開始レベル
	 */
	protected int	_processStartedLevel = -1;

	/**
	 * 評価しないサブツリーの開始レベル
	 */
	protected int	_ignoreStartedLevel = -1;

	/**
	 * ハンドラレジストリ
	 */
	protected HandlerRegistry	_registry = null;

	/**
	 * 現在要素を処理中の ElementHandler
	 */
	protected ElementHandler	_handler = null;

	/**
	 * スタートのノード位置
	 */
	protected SceneContext.Node	_startNode = null;


	/**
	 * TextCanvas コーディネータ
	 */
	protected TextCoordinator	_textCoordinator = null;

	/**
	 * StageCanvas コーディネータ
	 */
	protected StageCoordinator	_stageCoordinator = null;

	/**
	 * SelectCanvas コーディネータ
	 */
	protected SelectCoordinator	_selectCoordinator = null;


	/**
	 * <code>SceneProcessor</code> のインスタンスを生成します。
	 * @param	owner	この SceneProcessor インスタンスを制御する ScenarioProcessor
	 */
	public SceneProcessor( ScenarioProcessor owner )
	{
		super();
		_owner = owner;
		_registry = HandlerRegistry.newInstance();
		getSAXParser();
		Logger.debug( "[scenario] create SceneProcessor." );
	}


	/**
	 * シーンコンテクストを取得します。
	 * @return	シーンコンテクスト
	 */
	public SceneContext getSceneContext()
	{
		return _sceneContext;
	}


	/**
	 * ハンドラレジストリを取得します。
	 * @return	ハンドラレジストリ
	 */
	public HandlerRegistry getHandlerRegistry()
	{
		return _registry;
	}

	/**
	 * TextCanvas コーディネータを設定します。
	 * @param	coordinator	TextCanvas コーディネータ
	 */
	public void setTextCoordinator( TextCoordinator coordinator )
	{
		_textCoordinator = coordinator;
	}	

	/**
	 * TextCanvas コーディネータを取得します。
	 * @return	TextCanvas コーディネータ
	 */
	public TextCoordinator getTextCoordinator()
	{
		return _textCoordinator;
	}

	/**
	 * StageCanvas コーディネータを設定します。
	 * @param	coordinator	StageCanvas コーディネータ
	 */
	public void setStageCoordinator( StageCoordinator coordinator )
	{
		_stageCoordinator = coordinator;
	}

	/**
	 * StageCanvas コーディネータを取得します。
	 * @return	StageCanvas コーディネータ
	 */
	public StageCoordinator getStageCoordinator()
	{
		return _stageCoordinator;
	}

	/**
	 * SelectCanvas コーディネータを設定します。
	 * @param	coodinator	SelectCanvas コーディネータ
	 */
	public void setSelectCoordinator( SelectCoordinator coordinator )
	{
		_selectCoordinator = coordinator;
	}

	/**
	 * SelectCanvas コーディネータを取得します。
	 * @return	SelectCanvas コーディネータ
	 */
	public SelectCoordinator getSelectCoordinator()
	{
		return _selectCoordinator;
	}


	/**
	 * シーンの処理を開始します。
	 * @param	sceneName	処理するシーンのシーン名
	 */
	public void process( String sceneName, Session session )
	{
		if( _running )	{
			IllegalStateException	ise = new IllegalStateException( "scene processor is still running." );
			Logger.fatal( MessageIDs.SCN0007F );
			PekoSystem.showErrorDialog( MessageIDs.SCN0007F.getMessage(), ise.fillInStackTrace(), true );
		}
		_sceneURL = getSceneURL( sceneName );
		_sceneContext = new SceneContext( sceneName, session, this );
		_startNode = _sceneContext.getLastCommittedNode();
		if( _startNode != null )	{
			Logger.debug( "[scenario.scene] start from: " + _startNode.getPath() );
		}

		Thread	thread = new Thread( this );
		session.setSceneContext( _sceneContext, thread );

		_textCoordinator.prepare( _sceneContext, thread );
		_stageCoordinator.prepare( _sceneContext, thread );
		_selectCoordinator.prepare( _sceneContext, thread );

		_aborted = false;
		thread.start();
	}

	/**
	 * シーンの処理を中止します。
	 */
	public void abort()
	{
		_aborted = true;
		_textCoordinator.dormantize();
		_stageCoordinator.dormantize();
		_selectCoordinator.dormantize();
		Logger.debug( "[scenario] release stopped thread." );
		ActionControler	controler = PekoSystem.getInstance().getActionControler();
		controler.start();
	}

	/**
	 * シーン処理が中止されているかどうかを判定します。
	 * @return	シーン処理が中止されている場合 <code>true</code>、それ以外の場合 <code>false</code>
	 */
	public boolean isAborted()
	{
		return _aborted;
	}

	/**
	 * PSML Scene データの構文解析を行う SAX パーザを取得します。
	 * @return	PSML Scene データの構文解析を行う SAX パーザ
	 */
	protected SAXParser getSAXParser()
	{
		if( _parser == null )	{
			try	{
				SAXParserFactory	factory = SAXParserFactory.newInstance();
				factory.setNamespaceAware( true );
				if( "on".equals(System.getProperty("debug", "off")) )	{
					Logger.debug( "[scenario.scene] set PSML validating on." );
					factory.setValidating( true );
				}
				else	{
					Logger.debug( "[scenario.scene] set PSML validating off." );
					factory.setValidating( false );
				}
				_parser = factory.newSAXParser();
			}
			catch( SAXException se )	{
				Logger.fatal( MessageIDs.SCN0000F );
				PekoSystem.showErrorDialog( MessageIDs.SCN0000F.getMessage(), se, true );
			}
			catch( ParserConfigurationException pce )	{
				Logger.fatal( MessageIDs.SCN0000F );
				PekoSystem.showErrorDialog( MessageIDs.SCN0000F.getMessage(), pce, true );
			}
		}
		return _parser;
	}

	/**
	 * シーンデータの URL を取得します。
	 * @param	scene	シーン名
	 * @return	シーンデータの URL
	 */
	protected URL getSceneURL( String scene )
	{
		ResourceManager	resources = ResourceManager.getInstance();
		URL	sceneDir = resources.getLocationResources().getScenesDirecotryURL();
		try	{
			return new URL( sceneDir, scene );
		}
		catch( Exception e )	{
			Logger.fatal( MessageIDs.SCN0006F, e );
			PekoSystem.showErrorDialog( MessageIDs.SCN0006F.getMessage(), e, true );
			return null;
		}
	}

//
//	Runnable の実装
//
	public void run()
	{
		_running = true;
		try	{
			Logger.debug( _sceneURL.toString() );
			SAXParser	parser = getSAXParser();
			parser.parse( _sceneURL.toString(), this );
		}
		catch( Exception e )	{
			Logger.fatal( MessageIDs.SCN0005F, new Object[]{_sceneContext.getCurrentPath()}, e );
			PekoSystem.showErrorDialog( MessageIDs.SCN0005F.getMessage(new Object[]{_sceneContext.getCurrentPath()}), e, true );
		}
		_running = false;
		_owner.pushSceneProcessor( this );
	}


//
//	ContentHandler の実装
//
	public void startDocument()
		throws SAXException
	{
		if( _aborted )	{
			return;
		}

		_level  = 0;
		_processStartedLevel = -1;
		_ignoreStartedLevel = -1;
		_handler = null;
	}

	public void endDocument()
		throws SAXException
	{
		if( _aborted )	{
			return;
		}
		else	{
			_owner.sceneEnded( this );
		}
	}

	public void startElement( String namespaceURI, String localName, String qName, Attributes attrs )
		throws SAXException
	{
		if( _aborted )	{
			return;
		}

		++_level;
		_sceneContext.pushNode( qName );

		if( _startNode != null )	{
			if( _sceneContext.isCurrentNode(_startNode) )	{
				_startNode = null;
			}
			else	{
				return;
			}
		}
		else if( _ignoreStartedLevel != -1 )	{
			return;
		}
		else if( !PSMLUtil.isEvaluatable(attrs, _sceneContext) )	{
			_ignoreStartedLevel = _level;
			return;
		}

		if( _handler == null )	{
			_handler = _registry.getElementHandler( namespaceURI, localName );
			if( _handler == null )	{
				return;
			}
			else	{
				_sceneContext.saveCurrentNode();
				_handler.setSceneContext( _sceneContext );
				_processStartedLevel = _level;
				_handler.startDocument();
			}
		}
		try	{
			_handler.startElement( namespaceURI, localName, qName, attrs );
		}
		catch( Exception e )	{
			Logger.fatal( MessageIDs.SCN0005F, new Object[]{_sceneContext.getCurrentPath()}, e );
			PekoSystem.showErrorDialog( MessageIDs.SCN0005F.getMessage(new Object[]{_sceneContext.getCurrentPath()}), e, true );
		}
	}

	public void endElement( String namespaceURI, String localName, String qName )
		throws SAXException
	{
		if( _aborted )	{
			return;
		}

		if( _startNode != null )	{
			//NOOP
		}
		else if( _level == _ignoreStartedLevel )	{
			_ignoreStartedLevel = -1;
		}
		else if( _ignoreStartedLevel != -1 )	{
			//NOOP
		}
		else if( _handler != null )	{
			try	{
				_handler.endElement( namespaceURI, localName, qName );
				if( _level == _processStartedLevel )	{
					_handler.endDocument();
					if( _handler.isEndOfScene() )	{
						_owner.sceneEnded( this );
						_aborted = true;
					}
					_handler = null;
					_processStartedLevel = -1;
				}
			}
			catch( Exception e )	{
				Logger.fatal( MessageIDs.SCN0005F, new Object[]{_sceneContext.getCurrentPath()}, e );
				PekoSystem.showErrorDialog( MessageIDs.SCN0005F.getMessage(new Object[]{_sceneContext.getCurrentPath()}), e, true );
			}
		}

		--_level;
		_sceneContext.popNode();
	}

	public void characters( char[] ch, int begin, int length )
		throws SAXException
	{
		if( _aborted )	{
			return;
		}

		if( (_ignoreStartedLevel != -1 ) || (_handler == null) )	{
			return;
		}
		else if( _handler != null )	{
			_handler.characters( ch, begin, length );
		}
	}

	public void processingInstruction( String target, String data )
	{
	}

//
//	ErrorHandler の実装
//
	public void warning( SAXParseException exception )
	{
		Logger.warn( MessageIDs.SCN0002W, new Object[]{exception.getMessage()}, exception );
	}

	public void error( SAXParseException exception )
	{
		Logger.error( MessageIDs.SCN0003E, new Object[]{exception.getMessage()}, exception );
	}

	public void fatalError( SAXParseException exception )
	{
		Logger.fatal( MessageIDs.SCN0004F, new Object[]{exception.getMessage()}, exception );
		PekoSystem.showErrorDialog( "fail to parsing scene data.", exception, true );
	}


//
//	EntityResolver の実装
//
	public InputSource resolveEntity( String publicId, String systemId )
	{
		boolean	isPSMLScene = false;
		if( publicId != null )	{
			if( SceneProcessor.PSML_PUBLICID.equals(publicId) )	{
				isPSMLScene = true;
			}
		}
		else if( systemId != null )	{
			if( SceneProcessor.PSML_SYSTEMID.equals(systemId) )	{
				isPSMLScene = true;
			}
		}

		if( isPSMLScene )	{
			InputStream	is = null;
			try	{
				is = getClass().getClassLoader().getResourceAsStream( "psml10-scene.dtd" );
			}
			catch( Exception e )	{
				Logger.warn( MessageIDs.SCN0001W, e );
				return null;
			}

			if( is != null )	{
				return new InputSource( is );
			}
			else	{
				Logger.warn( MessageIDs.SCN0001W );
				return null;
			}
		}
		else	{
			return null;
		}
	}
}
