/*
 * "Peko" Visual Novel System
 *
 * All Rights Reserved.
 * Copyright (c) 1999-2003 Tsukuba Bunko.
 *
 * $Id: ActionControler.java,v 1.1.2.3 2003/12/27 17:21:15 ppoi Exp $
 */
package tsukuba_bunko.peko;

import	java.awt.event.ActionListener;
import	java.awt.event.MouseEvent;
import	java.awt.event.MouseListener;
import	java.awt.event.KeyEvent;
import	java.awt.event.KeyListener;
import	java.awt.event.WindowListener;

import	java.text.MessageFormat;

import	java.util.Timer;
import	java.util.TimerTask;

import	javax.swing.ButtonGroup;
import	javax.swing.ImageIcon;
import	javax.swing.JButton;
import	javax.swing.JCheckBoxMenuItem;
import	javax.swing.JDialog;
import	javax.swing.JFrame;
import	javax.swing.JMenu;
import	javax.swing.JMenuBar;
import	javax.swing.JMenuItem;
import	javax.swing.JOptionPane;
import	javax.swing.JRadioButtonMenuItem;
import	javax.swing.KeyStroke;

import	tsukuba_bunko.util.GenericListener;

import	tsukuba_bunko.peko.canvas.CanvasManager;

import	tsukuba_bunko.peko.resource.ResourceManager;


/**
 * プレイヤーの操作を受け付けるコントローラです。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.3 $
 */
public class ActionControler	implements KeyListener, MouseListener	{

	/**
	 * プレイヤーが操作するまで停止するストップモード(デフォルト)
	 */
	public static final long	SM_DEFAULT = -1;

	/**
	 * ノンストップモード
	 */
	public static final long	SM_NONSTOP = 0;


	/**
	 * 早送りプレイ(次の選択肢までスキップ)モード
	 */
	public static final int	PM_SKIP = 1;

	/**
	 * 通常プレイモード
	 */
	public static final int PM_NORMAL = 0;

	/**
	 * 自動送りプレイモード
	 */
	public static final int	PM_AUTOMATIC = 2;


	/**
	 */
	private Object	_lock = this;

	/**
	 * 有効かどうか
	 */
	private boolean	_activity = false;

	/**
	 * プレイモード
	 */
	private int	_playMode = -1;

	/**
	 * プレイモード変更ボタン(メニュー)リスト
	 */
	private JMenuItem[]	_buttons = new JMenuItem[3];

	/**
	 * エフェクトを使用しないメニュー
	 */
	private JMenuItem	_usingEffectMenu = null;

	/**
	 * セーブメニュー
	 */
	private JMenuItem	_saveMenu = null;

	/**
	 * ロードメニュー
	 */
	private JMenuItem	_loadMenu = null;

	/**
	 * タイトルに戻るメニュー
	 */
	private JMenuItem	_returnTileMenu = null;

	/**
	 * 過去の文章メニュー
	 */
	private JMenuItem	_readAgainMenu = null;

	/**
	 * タイマー
	 */
	private Timer	_timer = new Timer( true );

	/**
	 * 最新のタスク
	 */
	private TimerTask	_lastTask = null;


	/**
	 * 読み返しコントローラ
	 */
	private TextHistoryControler	_readAgainControler = null;

	/**
	 * 最後に押されたキーのキーコード
	 */
	private int	_lastKeyCode = -1;

	/**
	 * active 待ちかどうか
	 */
	private boolean	_waitingActivate = false;

	/**
	 * テキストを隠しているかどうか
	 */
	private boolean	_hiddenText = false;


	/**
	 * <code>ActionControler</code> のインスタンスを作成します。
	 */
	public ActionControler()
	{
		super();
		prepareMenuBar();
		setPlayModeToNormal();
	}

	/**
	 * ActionContorler を有効にするかどうかを設定します。
	 * @param	activity	有効にする場合 <code>false</code>、それ以外 <code>false</code>。
	 */
	public void setActive( boolean activity )
	{
		_activity = activity;
		if( !activity )	{
			_lastKeyCode = -1;
		}
		else if( _playMode != ActionControler.PM_NORMAL )	{
			if( _waitingActivate )	{
				synchronized( _lock )	{
					if( _waitingActivate )	{
						_lock.notify();
					}
				}
			}
		}
	}

	/**
	 * ActionControler が有効かどうかを判定します。
	 * @return	有効な場合 <code>true</code>、それ以外 <code>false</code>。
	 */
	public boolean isActive()
	{
		return _activity;
	}

	/**
	 * セーブ機能の有効／無効を設定します。
	 * @param	enabled	有効にする場合 <code>true</code>、それ以外の場合 <code>false</code>
	 */
	public synchronized void setSaveEnabled( boolean enabled )
	{
		_saveMenu.setEnabled( enabled );
	}

	/**
	 * 現在のプレイモードを取得します。
	 */
	public int getPlayMode()
	{
		return _playMode;
	}

	/**
	 * プレイモードを設定します。
	 * @param	playMode	プレイモード。<code>PM_FORWARD</code>、<code>PM_NORMAL</code>、<code>PM_AUTOMATIC</code> から選択。
	 */
	protected void setPlayMode( int playMode )
	{
		_playMode = playMode;
		_buttons[playMode].setSelected( true );
		if( playMode == ActionControler.PM_SKIP )	{
			_readAgainMenu.setEnabled( false );
		}
		else if( !_readAgainMenu.isEnabled() )	{
			_readAgainMenu.setEnabled( true );
		}
		PekoSystem.getInstance().getCanvasManager().getStageCanvas().setUsingEffect( !_usingEffectMenu.isSelected() && (playMode != ActionControler.PM_SKIP) );
		if( (_playMode != ActionControler.PM_NORMAL) && !PekoSystem.getInstance().getCanvasManager().isShowingSelect() )	{
			start();
		}
	}

	/**
	 * プレイモードを「早送り」に設定します。
	 */
	public void setPlayModeToSKip()
	{
		setPlayMode( ActionControler.PM_SKIP );
	}

	/**
	 * プレイモードを「通常」に設定します。
	 */
	public void setPlayModeToNormal()
	{
		setPlayMode( ActionControler.PM_NORMAL );
	}

	/**
	 * プレイモードを「自動」に設定します。
	 */
	public void setPlayModeToAutomatic()
	{
		setPlayMode( ActionControler.PM_AUTOMATIC );
	}

	/**
	 * 画像切り替えエフェクトを使用するかどうかを切り替えます。
	 */
	public void changeUsingEffect()
	{
		PekoSystem.getInstance().getCanvasManager().getStageCanvas().setUsingEffect( !_usingEffectMenu.isSelected() && (_playMode != ActionControler.PM_SKIP) );
	}

	/**
	 * 読み返しコントローラを表示します。
	 */
	public void showReadAgain()
	{
		setActive( false );
		_saveMenu.setEnabled( false );
		_loadMenu.setEnabled( false );
		_returnTileMenu.setEnabled( false );
		_readAgainMenu.setEnabled( false );
		if( _readAgainControler == null )	{
			_readAgainControler = new TextHistoryControler();
			_readAgainControler.setLocationRelativeTo( PekoSystem.getInstance().getMainWindow() );
		}
		_readAgainControler.show();
		setActive( true );
		_saveMenu.setEnabled( true );
		_loadMenu.setEnabled( true );
		_returnTileMenu.setEnabled( true );
		_readAgainMenu.setEnabled( true );
	}

	/**
	 * ロードダイアログを表示します。
	 */
	public void showLoadDialog()
	{
		setActive( false );
		_loadMenu.setEnabled( false );
		PekoSystem	system = PekoSystem.getInstance();
		if( !system.load() )	{
			setActive( true );
		}
		_loadMenu.setEnabled( true );
	}

	/**
	 * セーブダイアログを表示します。
	 */
	public synchronized void showSaveDialog()
	{
		PekoSystem	system = PekoSystem.getInstance();
		setActive( false );
		_saveMenu.setEnabled( false );
		system.save();
		_saveMenu.setEnabled( true );
		setActive( true );
	}

	/**
	 * タイトル画面に戻ります。
	 */
	public void returnTitle()
	{
		returnTitle( false );
	}

	/**
	 * タイトル画面に戻ります。
	 */
	public void returnTitle( boolean silence )
	{
		setActive( false );
		setPlayMode( ActionControler.PM_NORMAL );
		_saveMenu.setEnabled( false );
		_loadMenu.setEnabled( true );
		_returnTileMenu.setEnabled( false );
		_readAgainMenu.setEnabled( false );
		if( silence )	{
			PekoSystem.getInstance().showTitle();
		}
		else	{
			ResourceManager	resources = ResourceManager.getInstance();
			String	message = (String)resources.getResource( "peko.dialog.return-title.message" );
			if( message == null )	{
				message = "Are you return title ?";
			}

			String	title = (String)resources.getResource( "peko.dialog.return-title.title" );
			if( title == null )	{
				title = "Confirm";
			}

			if( JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog(PekoSystem.getInstance().getMainWindow(), message, title, JOptionPane.OK_CANCEL_OPTION) )	{
				PekoSystem.getInstance().showTitle();
			}
			else	{
				setActive( true );
			}
		}
		_saveMenu.setEnabled( true );
		_returnTileMenu.setEnabled( true );
		_readAgainMenu.setEnabled( true );
	}


	/**
	 * {@link #stop()} により停止されたスレッドを再開します。
	 */
	public void start()
	{
		synchronized( _lock )	{
			if( _lastTask != null )	{
				_lastTask.cancel();
			}
			_lastTask = null;
			_lock.notify();
			Logger.debug( "[system.controler] notify to finish wait." );
		}
	}

	/**
	 * プレイヤーがアクションを起こすまで現在のスレッドを停止します。
	 */
	public void stop()
	{
		stop( ActionControler.SM_DEFAULT );
	}

	/**
	 * 現在のスレッドを停止します。
	 * @param	wait	待ち時間。プレイヤーが操作するまでストップする場合は <code>SM_DEFAULT</code> を、無停止の場合は <code>SM_NONSTOP</code> を指定する。
	 */
	public void stop( long wait )
	{
		Logger.debug( "[system.controler] stop :" + wait );
		if( (wait == 0) || (_playMode == ActionControler.PM_SKIP) )	{
			//NOOP
		}
		else if( wait < 0 )	{
			synchronized( _lock )	{
				try	{
					if( _playMode == ActionControler.PM_AUTOMATIC )	{
						TimerTask	task = new TimerTask()	{
							public void run()	{
								synchronized( _lock )	{
									_lock.notify();
								}
							}
						};
						_timer.schedule( task, 1000L );
						_lastTask = task;
					}
					_lock.wait();
				}
				catch( InterruptedException ie )	{
					Logger.debug( "[system.action] interrupted." );
				}
			}
		}
		else	{
			synchronized( _lock )	{
				try	{
					TimerTask	task = new TimerTask()	{
						public void run()	{
							synchronized( _lock )	{
								_lock.notify();
							}
						}
					};
					_timer.schedule( task, wait );
					_lastTask = task;
					_lock.wait();
				}
				catch( InterruptedException ie )	{
					Logger.debug( "[system.action] interrupted." );
				}
			}
		}

		if( !_activity && (_lastTask != null) )	{
			synchronized( _lock )	{
				if( !_activity && (_lastTask != null) )	{
					_waitingActivate = true;
					try	{
						_lock.wait();
						_lock.wait( 500 );
					}
					catch( InterruptedException ie )	{
					}
					_waitingActivate = false;
				}
			}
		}

		_lastTask = null;
		Logger.debug( "[system.controler] finish wait." );
	}


	/**
	 * メニューバーを準備します。
	 */
	private void prepareMenuBar()
	{
		PekoSystem	system = PekoSystem.getInstance();
		ResourceManager	resources = ResourceManager.getInstance();

		JMenuBar	menubar = new JMenuBar();
		JMenu	menu = null;
		JMenuItem	item = null;

		menu = new JMenu( (String)resources.getResource("peko.menu.system.menu-caption") );
		menu.setMnemonic( 'S' );
		menubar.add( menu );

		item = new JMenuItem( (String)resources.getResource("peko.menu.system.save") );
		item.setEnabled( false );
		item.setMnemonic( 'S' );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK) );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "showSaveDialog", true );
		menu.add( item );
		_saveMenu = item;

		item = new JMenuItem( (String)resources.getResource("peko.menu.system.load") );
		item.setEnabled( false );
		item.setMnemonic( 'O' );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK) );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "showLoadDialog", true );
		menu.add( item );
		_loadMenu = item;

		menu.addSeparator();

		item = new JMenuItem( (String)resources.getResource("peko.menu.system.return-title") );
		item.setEnabled( false );
		item.setMnemonic( 'T' );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.CTRL_MASK) );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "returnTitle", true );
		menu.add( item );
		_returnTileMenu = item;

		item = new JMenuItem( (String)resources.getResource("peko.menu.system.read-again") );
		item.setEnabled( false );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_R, 0) );
		item.setMnemonic( 'R' );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "showReadAgain" );
		menu.add( item );
		_readAgainMenu = item;

		menu.addSeparator();

		item = new JMenuItem( (String)resources.getResource("peko.menu.system.exit") );
		item.setMnemonic( 'X' );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK) );
		GenericListener.connect( item, system, ActionListener.class, "actionPerformed", "exit" );
		menu.add( item );


		menu = new JMenu( (String)resources.getResource("peko.menu.config.menu-caption") );
		menu.setMnemonic( 'C' );
		menubar.add( menu );

		JMenu	submenu = new JMenu( (String)resources.getResource("peko.menu.config.playmode.menu-caption") );
		submenu.setMnemonic( 'P' );
		menu.add( submenu );

		ButtonGroup	group = new ButtonGroup();
		item = new JRadioButtonMenuItem( (String)resources.getResource("peko.menu.config.playmode.normal") );
		item.setMnemonic( 'N' );
//		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK) );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "setPlayModeToNormal" );
		group.add( item );
		submenu.add( item );
		_buttons[ ActionControler.PM_NORMAL ] = item;

		item = new JRadioButtonMenuItem( (String)resources.getResource("peko.menu.config.playmode.automatic") );
		item.setMnemonic( 'A' );
//		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK) );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "setPlayModeToAutomatic" );
		group.add( item );
		submenu.add( item );
		_buttons[ ActionControler.PM_AUTOMATIC ] = item;

		item = new JRadioButtonMenuItem( (String)resources.getResource("peko.menu.config.playmode.skip") );
		item.setMnemonic( 'S' );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_S, 0) );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "setPlayModeToSKip" );
		group.add( item );
		submenu.add( item );
		_buttons[ ActionControler.PM_SKIP ] = item;

		menu.addSeparator();

		item = new JCheckBoxMenuItem( (String)resources.getResource("peko.menu.config.use-effect") );
		item.setSelected( false );
		item.setMnemonic( 'E' );
		GenericListener.connect( item, this, ActionListener.class, "actionPerformed", "changeUsingEffect" );
		menu.add( item );
		_usingEffectMenu = item;


		menu = new JMenu( (String)resources.getResource("peko.menu.help.menu-caption") );
		menu.setMnemonic( 'H' );
		menubar.add( menu );

		item = new JMenuItem( (String)resources.getResource("peko.menu.help.help-contents") );
		item.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0) );
		item.setMnemonic( 'H' );
		item.setEnabled( false );
//		GenericListener.connect( item, system, ActionListener.class, "actionPerformed", "showGameVersionInfo" );
		menu.add( item );

		item = new JMenuItem( (String)resources.getResource("peko.menu.help.version") );
		item.setMnemonic( 'A' );
		GenericListener.connect( item, system, ActionListener.class, "actionPerformed", "showGameVersionInfo" );
		menu.add( item );

		menu.addSeparator();

		item = new JMenuItem( (String)resources.getResource("peko.menu.help.about-pvns") );
		item.setMnemonic( 'P' );
		GenericListener.connect( item, system, ActionListener.class, "actionPerformed", "showSystemVersionInfo" );
		menu.add( item );


		JFrame	mainWindow = system.getMainWindow();
		mainWindow.setJMenuBar( menubar );
		mainWindow.pack();
		mainWindow.setResizable( false );
	}


//
//	MouseListener の実装
//
	public void mousePressed( MouseEvent ev )
	{
	}

	public void mouseReleased( MouseEvent ev )
	{
	}

	public void mouseEntered( MouseEvent ev )
	{
	}

	public void mouseExited( MouseEvent ev )
	{
	}

	public void mouseClicked( MouseEvent ev )
	{
		int	code = ev.getModifiers();
		if( _activity && (code == MouseEvent.BUTTON1_MASK) )	{
			start();
		}
		else if( (_playMode == ActionControler.PM_SKIP) && (code == MouseEvent.BUTTON3_MASK) )	{
			setPlayModeToNormal();
			return;
		}
		else if( _hiddenText )	{
			CanvasManager	canvasManager = PekoSystem.getInstance().getCanvasManager();
			canvasManager.hideTextCanvas();
			setActive( true );
		}
	}


//
//	KeyListener の実装
//
	public void keyPressed( KeyEvent ev )
	{
		if( _activity )	{
			int	code = ev.getKeyCode();
			if( _lastKeyCode == -1 )	{
				_lastKeyCode = code;
			}
			else if( _lastKeyCode != code )	{
				_lastKeyCode = -1;
			}
		}
	}

	public void keyReleased( KeyEvent ev )
	{
		int	code = ev.getKeyCode();
		if( code != _lastKeyCode )	{
			_lastKeyCode = -1;
			return;
		}

		if( _hiddenText )	{
			CanvasManager	canvasManager = PekoSystem.getInstance().getCanvasManager();
			canvasManager.showTextCanvas();
			_saveMenu.setEnabled( true );
			_loadMenu.setEnabled( true );
			_returnTileMenu.setEnabled( true );
			_hiddenText = false;
		}
		else if( _activity && (code == KeyEvent.VK_ENTER) )	{
			start();
		}
		else if( (_playMode != ActionControler.PM_NORMAL) && (code == KeyEvent.VK_ESCAPE) )	{
			setPlayModeToNormal();
		}
		else if( !_hiddenText && (code == KeyEvent.VK_SPACE) )	{
			CanvasManager	canvasManager = PekoSystem.getInstance().getCanvasManager();
			_saveMenu.setEnabled( false );
			_loadMenu.setEnabled( false );
			_returnTileMenu.setEnabled( false );
			canvasManager.hideTextCanvas();
			_hiddenText = true;
		}
		_lastKeyCode = -1;
	}

	public void keyTyped( KeyEvent ev )
	{
	}
}




/**
 * テキストの履歴をブラウジングするコントローラです。
 */
class TextHistoryControler	extends JDialog	{

	/**
	 * 現在のインデックス
	 */
	private int	_index = 0;

	/**
	 * キャンバスマネージャ
	 */
	private CanvasManager	_canvasManager = null;

	/**
	 * 「前」ボタン
	 */
	private JButton	_previous = null;

	/**
	 * 「次」ボタン
	 */
	private JButton	_next = null;


	/**
	 * <code>TextHistoryControler</code> のインスタンスを作成します。
	 */
	public TextHistoryControler()
	{
		super( PekoSystem.getInstance().getMainWindow(), true );
		initialize();
	}


	/**
	 * 初期化処理を実行します。
	 */
	private void initialize()
	{
		ResourceManager	resources = ResourceManager.getInstance();
		_canvasManager = PekoSystem.getInstance().getCanvasManager();
		ClassLoader	cl = getClass().getClassLoader();

		getContentPane().setLayout( new java.awt.FlowLayout() );
		JButton	button = new JButton();
		button.setIcon( new ImageIcon(cl.getResource("left-arrow.png")) );
		getContentPane().add( button );
		GenericListener.connect( button, this, ActionListener.class, "actionPerformed", "previous" );
		_previous = button;
		button.setEnabled( (_canvasManager.getPageHistoryCount() >= 1) );

		button = new JButton();
		button.setIcon( new ImageIcon(cl.getResource("right-arrow.png")) );
		button.setEnabled( false );
		getContentPane().add( button );
		_next = button;
		GenericListener.connect( button, this, ActionListener.class, "actionPerformed", "next" );

		pack();
		setResizable( false );

		GenericListener.connect( this, this, WindowListener.class, "windowClosing", "hide" );
	}

	/**
	 * 前のページを表示します。
	 */
	private void previous()
	{
		ResourceManager	resources = ResourceManager.getInstance();
		_index++;
		setTitle( ((MessageFormat)resources.getResource("peko.dialog.read-again.title-format")).format(new Object[]{String.valueOf(_index)}) );
		_next.setEnabled( (_index > 1 ) );
		if( _index == _canvasManager.getPageHistoryCount() )	{
			_previous.setEnabled( false );
		}
		_previous.setEnabled( _canvasManager.readAgain(_index) );
	}

	/**
	 * 次のページを表示します。
	 */
	private void next()
	{
		ResourceManager	resources = ResourceManager.getInstance();
		_index--;
		setTitle( ((MessageFormat)resources.getResource("peko.dialog.read-again.title-format")).format(new Object[]{String.valueOf(_index)}) );
		_previous.setEnabled( true );
		if( _index == 1 )	{
			_next.setEnabled( false );
		}
		_canvasManager.readAgain( _index );
	}


//
//	JDialog の実装
//
	public void show()
	{
		_index = 0;
		if( _canvasManager.getPageHistoryCount() > 0 )	{
			previous();
			super.show();
		}
	}

	public void hide()
	{
		_canvasManager.returnCurrent();
		super.hide();
	}
}