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

import	java.awt.Point;

import	java.awt.event.WindowEvent;
import	java.awt.event.WindowAdapter;

import	java.io.BufferedReader;
import	java.io.IOException;
import	java.io.InputStream;
import	java.io.InputStreamReader;

import	java.net.URL;

import	java.util.List;

import	javax.swing.Icon;
import	javax.swing.ImageIcon;
import	javax.swing.JFrame;
import	javax.swing.JOptionPane;

import	tsukuba_bunko.peko.canvas.CanvasManager;

import	tsukuba_bunko.peko.resource.ResourceManager;

import	tsukuba_bunko.peko.scenario.ScenarioProcessor;

import tsukuba_bunko.peko.session.Session;
import	tsukuba_bunko.peko.session.SessionManager;


/**
 * "Peko" Visual Novel System のメインクラスです。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.1 $
 */
public class PekoSystem	{

	/**
	 * 唯一の <code>PekoSystem</code> のインスタンス
	 */
	private static PekoSystem	_instance = null;


	/**
	 * <code>PekoSysmtem</code> のバージョン情報
	 */
	private Object[]	_versionInfo = null;

	/**
	 * メインウィンドウ
	 */
	private JFrame	_mainWindow = null;

	/**
	 * CanvasManager
	 */
	private CanvasManager	_canvasManager = null;

	/**
	 * ScenrioProcessor
	 */
	private ScenarioProcessor	_scenarioProcessor = null;

	/**
	 * SessionManager
	 */
	private SessionManager	_sessionManager = null;

	/**
	 * ActionControler
	 */
	private ActionControler	_actionControler = null;

	/**
	 * スタートフラグ
	 */
	private boolean	_started = false;


	/**
	 * <code>PekoSystem</code> のインスタンスを作成します。
	 */
	protected PekoSystem()
	{
		super();
	}


	/**
	 * PVNS を開始します。
	 */
	public void start()
	{
		if( _started )	{
			return;
		}

		Point	location = (Point)_sessionManager.getSystemSaveData().getEntry( "windowLocation" );
		if( location != null )	{
			_mainWindow.setLocation( location );
		}
		else	{
			_mainWindow.setLocationRelativeTo( null );
		}

		_mainWindow.show();
		synchronized( _mainWindow )	{
			try	{
				if( !_mainWindow.isShowing() )	{
					_mainWindow.wait();
					Logger.debug( "[system] window opened." );
				}
			}
			catch( InterruptedException ie )	{
				Logger.debug( "[system] interrupted in waiting for opening window." );
			}
		}
		_actionControler.returnTitle( true );
	}

	/**
	 * PVNS を終了します。
	 */
	public void exit()
	{
		boolean	last = _actionControler.isActive();
		_actionControler.setActive( false );
		ResourceManager	resources = ResourceManager.getInstance();
		String	title = (String)resources.getResource( "peko.dialog.exit.title" );
		String	message = (String)resources.getResource( "peko.dialog.exit.message" );
		if( JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(_mainWindow, message, title, JOptionPane.OK_CANCEL_OPTION) )	{
			_actionControler.setActive( last );
			return;
		}
		_mainWindow.dispose();

		try	{
			_sessionManager.getSystemSaveData().addEntry( "windowLocation", _mainWindow.getLocation() );
			_sessionManager.saveSystemSaveData();
		}
		catch( Exception e )	{
			//TODO: システムセーブデータの保存に失敗した旨、および回復方法を出力
			PekoSystem.showErrorDialog( "fail to system save data.", e, false );
		}

		Logger.info( "Bye!" );
		System.exit( 0 );
	}

	/**
	 * タイトル画面画像を表示します。
	 */
	public void showTitle()
	{
		try	{
			if( _started )	{
				_scenarioProcessor.exit();
				_canvasManager.clearAll();
			}
			else	{
				_canvasManager.getStageCanvas().setUsingEffect( false );
				_canvasManager.clearAll();
				_canvasManager.getStageCanvas().setUsingEffect( true );
				_started = true;
			}

			_mainWindow.setTitle( (String)ResourceManager.getInstance().getResource("game-info.title") );
			boolean	first = true;
			while( true )	{
				String	id = _canvasManager.showTitle( first );
				if( id == null )	{
					Logger.debug( "[system] canceled." );
					break;
				}
				else if( "start".equals(id) )	{
					ResourceManager	resources = ResourceManager.getInstance();
					String	startPage = (String)resources.getResource( "peko.system.start-scene" );
					if( startPage == null )	{
						Logger.fatal( "[system] not specified scenario.start-scene." );
						throw new InitializationError( "[system] not specified scenario.start-scene." );
					}
					else	{
						_canvasManager.clearAll();
						_sessionManager.initializeSession();
						_scenarioProcessor.playScenario( startPage, _sessionManager.getSession() );
					}
					break;
				}
				else if( "resume".equals(id) )	{
					if( load() )	{
						break;
					}
				}
				else if( "exit".equals(id) )	{
					exit();
				}
				first = false;
			}
		}
		catch( Exception e )	{
			Logger.fatal( "[system] fatal error occured during saving states.", e );
			JOptionPane.showMessageDialog( _mainWindow, "ERROR!", "Error!", JOptionPane.ERROR_MESSAGE );
		}
	}

	/**
	 * セーブします。
	 */
	public void save()
	{
		try	{
			Session	session = _sessionManager.getSession();
			_canvasManager.saveState( session );
			_sessionManager.saveCurrentSession();
		}
		catch( Exception e )	{
			Logger.fatal( "[system] fatal error occured during saving states.", e );
			JOptionPane.showMessageDialog( _mainWindow, "[system] fatal error occured during saving states.", "Error!", JOptionPane.ERROR_MESSAGE );
			return;
		}

		try	{
			_sessionManager.saveSystemSaveData();
		}
		catch( Exception e )	{
			Logger.fatal( "[system] fatal error occured during saving states.", e );
			PekoSystem.showErrorDialog( "A fatal error occured during saving states", e, true );
		}
	}

	/**
	 * ロードします。
	 */
	public boolean load()
	{
		boolean	result = false;
		try	{
			_actionControler.setPlayModeToNormal();
			if( _sessionManager.load() )	{
				_scenarioProcessor.exit();
				_canvasManager.clearAll();

				Session	session = _sessionManager.getSession();
				_mainWindow.setTitle( session.getSceneContext().getSceneTitle() + " - " + ResourceManager.getInstance().getResource("game-info.title") );
				_canvasManager.resume( session );
				_scenarioProcessor.playScenario( session.getSceneContext().getSceneName(), session );
				result = true;
			}
			else	{
				result = false;
			}
		}
		catch( Exception e )	{
			Logger.fatal( "[system] fatal error occured during saving states.", e );
			JOptionPane.showMessageDialog( _mainWindow, "[system] fatal error occured during saving states.", "Error!", JOptionPane.ERROR_MESSAGE );
		}
		return result;
	}

	/**
	 * システムのバージョン情報ダイアログを表示します。
	 */
	public void showSystemVersionInfo()
	{
		ResourceManager	resources = ResourceManager.getInstance();

		ImageIcon	icon = null;
		URL	iconURL = getClass().getClassLoader().getResource( "pvns-logo.gif" );
		if( iconURL != null )	{
			icon = new ImageIcon( iconURL, "PVNS Logo" );
		}
		JOptionPane.showMessageDialog( _mainWindow, _versionInfo, (String)_versionInfo[0], JOptionPane.INFORMATION_MESSAGE, icon );
	}

	/**
	 * ゲームのバージョン情報ダイアログを表示します。
	 */
	public void showGameVersionInfo()
	{
		ResourceManager	resources = ResourceManager.getInstance();

		Icon	icon = (Icon)resources.getResource( "game-info.logo", true );
		List	texts = new java.util.ArrayList();
		String[]	props = { "game-info.title", "game-info.version", "game-info.publisher", "game-info.copyright" };
		for( int i = 0; i < props.length; ++i )	{
			String	var = (String)resources.getResource( props[i], true );
			if( var != null )	{
				texts.add( var );
			}
		}

		List	additionalInfo = (List)resources.getResource( "game-info.additional-info", true );
		if( (additionalInfo != null) && !additionalInfo.isEmpty() )	{
			if( !texts.isEmpty() )	{
				texts.add( " " );
			}
			texts.addAll( additionalInfo );
		}

		if( !texts.isEmpty() )	{
			JOptionPane.showMessageDialog( _mainWindow, texts.toArray(), (String)resources.getResource("game-info.title"), JOptionPane.INFORMATION_MESSAGE, icon );
		}
	}

	/**
	 * PVNS のバージョン情報を取得します.
	 * @return	バージョン情報
	 */
	public Object[] getPekoSystemVersion()
	{
		return _versionInfo;
	}

	/**
	 * メインウィンドウを取得します。
	 * @return	メインウィンドウ
	 */
	public JFrame getMainWindow()
	{
		return _mainWindow;
	}

	/**
	 * CanvasManager を取得します。
	 * @return	CanvasManager
	 */
	public CanvasManager getCanvasManager()
	{
		return _canvasManager;
	}

	/**
	 * ActionControler を取得します。
	 * @return	ActionControler
	 */
	public ActionControler getActionControler()
	{
		return _actionControler;
	}


	/**
	 * "Peko" のバージョン情報を準備します。
	 */
	private void prepareVersionInfo()
	{
		try	{
			InputStream	is = PekoSystem.class.getResourceAsStream( "version.txt" );
			if( is != null )	{
				BufferedReader	reader = new BufferedReader( new InputStreamReader(is, "Shift_JIS") );
				String	line = reader.readLine();
				List	lines = new java.util.ArrayList();
				while( line != null )	{
					lines.add( line );
					line = reader.readLine();
				}
				reader.close();
				is.close();
				_versionInfo = lines.toArray();
			}
			else	{
				Logger.error( "[system] missing version.txt." );
				Logger.debug( "[system] using embeded version info." );
				_versionInfo = new Object[]{ "\"Peko\" Visual Novel System", "version 1.0", "All Rights Reserved.", "(c)Copyright by Tsukuba Bunko." };
			}
		}
		catch( IOException ioe )	{
			Logger.error( "[system] fail to read version info.", ioe );
			Logger.debug( "[system] using embeded version info." );
			_versionInfo = new Object[]{ "\"Peko\" Visual Novel System", "version 1.0", "All Rights Reserved.", "(c)Copyright by Tsukuba Bunko." };
		}
	}

	/**
	 * メインウィンドウを準備します。
	 */
	private void prepareMainWindow()
	{
		JFrame	window = new JFrame( (String)_versionInfo[0] );
		_mainWindow = window;

		window.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
		window.addWindowListener( new WindowAdapter()	{
				public void windowOpened( WindowEvent ev )
				{
					synchronized( _mainWindow )	{
						_mainWindow.notify();
					}
				}
				public void windowClosing( WindowEvent ev )
				{
					exit();
				}
			});
	}

	/**
	 * ResourceManager を初期化します.
	 * @throws	InitializationError	初期化に失敗した場合
	 */
	private void prepareResources()
	{
		try	{
			ResourceManager	resources = ResourceManager.getInstance();
		}
		catch( Exception e )	{
			throw new InitializationError();
		}
	}

	/**
	 * CanvasManager を初期化します。
	 */
	private void prepareCanvasManager()
	{
		_canvasManager = new CanvasManager();
		_canvasManager.initialize();
	}

	/**
	 * ScenarioProcessor を準備します。
	 */
	private void prepareScenarioProcessor()
	{
		_scenarioProcessor = new ScenarioProcessor();
	}

	/**
	 * SessionManager を準備します。
	 */
	private void prepareSessionManager()
	{
		_sessionManager = new SessionManager();
	}

	/**
	 * ActionControler を準備します。
	 */
	private void prepareActionControler()
	{
		ActionControler	controler = new ActionControler();
		_canvasManager.getTextCanvas().addMouseListener( controler );
		_canvasManager.getStageCanvas().addMouseListener( controler );
		_mainWindow.addKeyListener( controler );
		_actionControler = controler;
	}

	/**
	 * 唯一の <code>PekoSystem</code> のインスタンスを取得します.
	 * @return	唯一の <code>PekoSystem</code> のインスタンス
	 */
	public static PekoSystem getInstance()
	{
		if( _instance == null )	{
			synchronized( PekoSystem.class )	{
				if( _instance == null )	{
					_instance = new PekoSystem();
					_instance.prepareVersionInfo();
					_instance.prepareResources();
					_instance.prepareSessionManager();
					_instance.prepareMainWindow();
					_instance.prepareCanvasManager();
					_instance.prepareActionControler();
					_instance.prepareScenarioProcessor();
				}
			}
		}
		return _instance;
	}

	/**
	 * エラーダイアログを表示します。
	 * @param	message	エラーメッセージ
	 * @param	e	例外オブジェクト
	 * @param	exit	強制終了する場合 <code>true</code>、しない場合 <code>false</code>
	 */
	public static void showErrorDialog( String message, Throwable e, boolean exit )
	{
		if( message == null )	{
			message = e.getMessage();
		}

		StackTraceElement[]	stackTrace = e.getStackTrace();
		Object[]	messages = new Object[ stackTrace.length + 2 ];
		for( int i = 0; i < stackTrace.length; ++i )	{
			messages[i + 1] = stackTrace[i];
		}
		messages[0] = "Fatal Error :" + message;
		messages[1] = e.getClass().getName() + " : " + e.getMessage();
		JOptionPane.showMessageDialog( null, messages, "FATAL ERROR ! -\"Peko\" Visual Novel System", JOptionPane.ERROR_MESSAGE );
		if( exit )	{
			System.exit( -1 );
		}
	}


	/**
	 * "Peko" Visual Novel System を起動します。
	 */
	public static void main( String[] args )
		throws Exception
	{
		Logger.prepare();
		try	{
			PekoSystem	system = PekoSystem.getInstance();
			Object[]	versionInfo = system.getPekoSystemVersion();
			Logger.info( (String)versionInfo[0] );
			Logger.info( (String)versionInfo[1] );

			system.start();
		}
		catch( Throwable e )	{
			showErrorDialog( null, e, true );
		}
	}
}
