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

import	java.awt.Dimension;

import	java.awt.event.KeyEvent;
import	java.awt.event.KeyListener;

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

import	javax.swing.JComponent;

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


/**
 * 選択肢を表示するキャンバスです。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.1 $
 */
public class SelectCanvas	extends JComponent	implements KeyListener	{

	/**
	 * ボタンの位置：左寄せ
	 */
	public static final int	ALIGN_LEFT = 0;

	/**
	 * ボタンの位置：中央
	 */
	public static final int	ALIGN_CENTER = 1;

	/**
	 * ボタンの位置：右寄せ
	 */
	public static final int	ALIGN_RIGHT = 2;

	/**
	 * ボタンの位置：上
	 */
	public static final int	VALIGN_TOP = 0;

	/**
	 * ボタンの位置：中央
	 */
	public static final int	VALIGN_MIDDLE = 1;

	/**
	 * ボタンの位置：下
	 */
	public static final int	VALIGN_BOTTOM = 2;


	/**
	 * 有効フラグ
	 */
	private boolean	_active = false;

	/**
	 * 選択肢リスト
	 */
	private List	_buttons = null;

	/**
	 * 選択された ID
	 */
	private String	_id = null;

	/**
	 * 現在選択中のボタン
	 */
	private SelectItemButton	_selected = null;

	/**
	 * 現在選択中のボタンのインデックス
	 */
	private int	_selectedIndex = -1;


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


	/**
	 * 選択肢ボタンの配置位置
	 */
	private int	_align = SelectCanvas.ALIGN_CENTER;

	/**
	 */
	private int	_verticalAlign = SelectCanvas.VALIGN_TOP;

	/**
	 * 選択肢の列数
	 */
	private int	_columns = 1;

	/**
	 * 列間
	 */
	private int	_columnSpan = 20;

	/**
	 * 行間
	 */
	private int	_rowSpan = 20;

	/**
	 * 境界線とルートキャンバスの枠との間
	 */
	private int	_boundSpan = 20;

	/**
	 * ボタンスタイルマップ
	 */
	private Map	_buttonStyle = null;

	/**
	 * Dimension cache
	 */
	private Dimension	_dimensionCache = null;


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


	/**
	 * 選択肢ボタンの配置位置を設定します。設定した内容は、次回選択指表示時から反映されます。
	 * @param	align	配置位置
	 * @throws	IllegalArgumentException	align が {@link SelectCanvas#ALIGN_CENTER} または {@link SelectCanvas#ALIGN_LEFT} または {@link SelectCanvas#ALIGN_RIGHT} 以外の場合。
	 */
	public void setAlignment( int align )
	{
		if( (align == SelectCanvas.ALIGN_CENTER) || (align == SelectCanvas.ALIGN_LEFT) || (align == SelectCanvas.ALIGN_RIGHT) )	{
			_align = align;
		}
		else	{
			throw new IllegalArgumentException( "invalid alignment type is specified." );
		}
	}

	/**
	 * 選択肢ボタンの配置位置を取得します。
	 * @return	選択肢ボタンの配置位置
	 */
	public int getAlignment()
	{
		return _align;
	}

	/**
	 * 選択肢ボタンの垂直方向の配置位置を設定します。
	 * @param	valign	配置位置
	 * @throws	IllegalArgumentException	align が {@link SelectCanvas#VALIGN_TOP} または {@link SelectCanvas#VALIGN_MIDDLE} または {@link SelectCanvas#VALIGN_BOTTOM} 以外の場合。
	 */
	public void setVerticalAlignment( int valign )
	{
		if( (valign == SelectCanvas.ALIGN_CENTER) || (valign == SelectCanvas.ALIGN_LEFT) || (valign == SelectCanvas.ALIGN_RIGHT) )	{
			_verticalAlign = valign;
		}
		else	{
			throw new IllegalArgumentException( "invalid vertical alignment type is specified." );
		}
	}

	/**
	 * 選択肢ボタンの垂直方向の配置位置を取得します。
	 * @return	選択肢ボタンの配置位置
	 */
	public int getVericalAlignment()
	{
		return _verticalAlign;
	}

	/**
	 * 選択肢ボタンの列数を設定します。設定した内容は、次回選択指表示時から反映されます。
	 * @param	columns 列数
	 * @throws	IllegalArgumentException	<code>columns <= 0</code> の場合
	 */
	public void setColumns( int columns )
	{
		_columns = columns;
	}

	/**
	 * 選択肢ボタンの列数を取得します。
	 * @return	選択肢ボタンの列数
	 */
	public int getColumns()
	{
		return _columns;
	}

	/**
	 * 列間を設定します。設定した内容は、次回選択指表示時から反映されます。
	 * @param	span	列間の長さ
	 */
	public void setColumnSpan( int span )
	{
		_columnSpan = span;
	}

	/**
	 * 列間を取得します。
	 * @return	列間の長さ
	 */
	public int getColumnSpan()
	{
		return _columnSpan;
	}

	/**
	 * 行間を設定します。設定した内容は、次回選択指表示時から反映されます。
	 * @param	span	行間の長さ
	 */
	public void setRowSpan( int span )
	{
		_rowSpan = span;
	}

	/**
	 * 行間を取得します。
	 * @return	行間の長さ
	 */
	public int getRowSpan()
	{
		return _rowSpan;
	}

	/**
	 * 選択肢領域の境界線とルートキャンバスの枠との間を設定します。設定した内容は、次回選択指表示時から反映されます。
	 * @param	span	行間の長さ
	 */
	public void setBoundSpan( int span )
	{
		_boundSpan = span;
	}

	/**
	 * 選択肢領域の境界線とルートキャンバスの枠との間を取得します。
	 * @return	行間の長さ
	 */
	public int getBoundSpan()
	{
		return _boundSpan;
	}

	/**
	 * 選択肢ボタンのスタイルを設定します。設定した内容は、次回選択指表示時から反映されます。
	 * @param	styles	選択肢ボタンのスタイルマップ
	 */
	public void setButtonStyle( Map styles )
	{
		_buttonStyle = styles;
	}

	/**
	 * 選択肢ボタンのスタイルを取得します。
	 * @return	選択肢ボタンのスタイル
	 */
	public Map getButtonStyle()
	{
		return _buttonStyle;
	}

	/**
	 * 選択肢を表示し、ユーザーが選択した選択肢の ID を取得します。
	 * @param	selectItems	選択肢
	 * @param	canvas	選択肢を配置するキャンバス
	 * @return	選択された選択肢の ID
	 */
	public String select( List selectItems )
	{
		return select( selectItems, -1 );
	}

	/**
	 * 選択肢を表示し、ユーザーが選択した選択肢の ID を取得します。
	 * @param	selectItems	選択肢
	 * @param	timeout	タイムアウトするまでの時間[ms]
	 * @return	選択された選択肢の ID
	 */
	public String select( List selectItems, long timeout )
	{
		if( _buttonStyle == null )	{
			throw new IllegalStateException( "button styles is not specified." );
		}

		if( _active )	{
			Logger.debug( "[canvas.select] already show selection." );
			return null;
		}

		synchronized( this )	{
			_id = null;

			Dimension	canvasSize = getSize( _dimensionCache );

			int	size = selectItems.size();
			int	buttonWidth = 320;
			Integer	intValue = (Integer)_buttonStyle.get( SelectItemButton.STYLE_WIDTH );
			if( intValue != null )	{
				buttonWidth = intValue.intValue();
			}

			setVisible( false );

			SelectItem	item = null;
			SelectItemButton	button = null;
			List	buttons = new java.util.ArrayList( size );
			int	x = 0;
			int	y = 0;
			int	rowHeight = 0;
			Dimension	buttonSize = new Dimension( 0, 0 );
			for( int i = 0; i < size; ++i )	{
				if( (i % _columns) == 0 )	{
					y += rowHeight;
					if( item != null )	{
						y += _rowSpan;
					}
					x = 0;
					rowHeight = 0;
				}
				else	{
					x += (buttonSize.width + _columnSpan);
				}

				item = (SelectItem)selectItems.get( i );
				button = new SelectItemButton( this );
				button.setSelectItem( item );
				button.prepare( _buttonStyle );
				buttons.add( button );

				add( button );
				button.setLocation( x, y );
				buttonSize = button.getSize( buttonSize );
				if( buttonSize.height > rowHeight )	{
					rowHeight = buttonSize.height;
				}
			}

			int	areaWidth = (buttonWidth * _columns) + (_columnSpan * (_columns - 1));
			int	leftPosition = 0;
			if( areaWidth > canvasSize.width )	{
				leftPosition = 0;
			}
			else if( (areaWidth + _boundSpan) > canvasSize.width )	{
				leftPosition = (canvasSize.width - areaWidth) / 2;
			}
			else if( _align == SelectCanvas.ALIGN_CENTER )	{
				leftPosition = (canvasSize.width - areaWidth) / 2;
			}
			else if( _align == SelectCanvas.ALIGN_LEFT )	{
				leftPosition = _boundSpan;
			}
			else if( _align == SelectCanvas.ALIGN_RIGHT )	{
				leftPosition = canvasSize.width - areaWidth - _boundSpan;
			}

			int	areaHeight = y + rowHeight;
			int	topPosition = 0;
			if( areaHeight > canvasSize.height )	{
				topPosition = 0;
			}
			else if( (areaHeight + _boundSpan) > canvasSize.height )	{
				topPosition = (canvasSize.height - areaHeight) / 2;
			}
			else if( _verticalAlign == SelectCanvas.VALIGN_TOP )	{
				topPosition = _boundSpan;
			}
			else if( _verticalAlign == SelectCanvas.VALIGN_MIDDLE )	{
				topPosition = (canvasSize.height - areaHeight) / 2;
			}
			else if( _verticalAlign == SelectCanvas.VALIGN_BOTTOM )	{
				topPosition = canvasSize.height - areaHeight - _boundSpan;
			}
			setLocation( leftPosition, topPosition );

			_buttons = buttons;
			_active = true;

			setVisible( true );
		}

		Logger.debug( "[canvas.select] wait for selecting." );
		ActionControler	controler = PekoSystem.getInstance().getActionControler();
		if( timeout > 0 )	{
			controler.stop( timeout );
		}
		else	{
			controler.stop();
		}

		synchronized( this )	{
			int	size = _buttons.size();
			for( int i = 0; i < size; ++i )	{
				remove( (SelectItemButton)_buttons.get(i) );
			}
			_buttons.clear();
			_buttons = null;
		}

		Logger.debug( "[canvas.select] id: " + _id );
		return _id;
	}

	/**
	 * 選択肢の選択待ちを解除します。
	 */
	public void cancel()
	{
		_active = false;
		synchronized( this )	{
			if( _buttons != null )	{
				_id = null;
				ActionControler	controler = PekoSystem.getInstance().getActionControler();
				controler.start();
				Logger.debug( "[cenvas.select] canceled." );
			}
		}
	}


//
//	選択イベント処理
//
	/**
	 * <code>button</code> が選択状態になりました。
	 * @param	button	選択状態になった SelectItemButton インスタンス
	 */
	public void itemSelecting( SelectItemButton button )
	{
		SelectItemButton	old = _selected;
		_selected = button;
		if( (old != null) && (old != button) )	{
			old.setSelected( false );
		}
		_selectedIndex = _buttons.indexOf( button );
	}

	/**
	 * <code>button</code> が非選択状態になりました
	 * @param	button	非選択状態になった SelectItemButton インスタンス
	 */
	public void itemDeselected( SelectItemButton button )
	{
		if( _selected == button )	{
			_selected = null;
			_selectedIndex = -1;
		}
	}

	/**
	 * <code>button</code> が選択されました。
	 * @param	button	選択された SelectItemButton インスタンス
	 */
	public void itemSelected( SelectItemButton button )
	{
		if( _active )	{
			synchronized( this )	{
				if( _active )	{
					_selected = null;
					_selectedIndex = -1;
					_id = button.getSelectItem().getID();
					_active = false;
					Logger.debug( "[canvas.select] selected: " + _id );
					ActionControler	controler = PekoSystem.getInstance().getActionControler();
					controler.start();
				}
			}
		}
	}


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

			int	index = _selectedIndex;
			int	size = _buttons.size();

			if( code == KeyEvent.VK_DOWN )	{
				index = (index + 1) % size;
			}
			else if( (code == KeyEvent.VK_RIGHT) && (_columns > 1) )	{
				index = (index + 1) % size;
			}
			else if( code == KeyEvent.VK_UP )	 {
				index--;
				if( index < 0 )	{
					index = _buttons.size() -1 ;
				}
			}
			else if( (code == KeyEvent.VK_LEFT) && (_columns > 1) )	{
				index--;
				if( index < 0 )	{
					index = _buttons.size() -1 ;
				}
			}
			else	{
				return;
			}
			SelectItemButton	button = (SelectItemButton)_buttons.get( index );
			button.setSelected( true );
		}
	}

	public void keyReleased( KeyEvent ev )
	{
		if( !_active || (_buttons == null) )	{
			return;
		}

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

		if( (code == KeyEvent.VK_ENTER) && (_selected != null) )	{
			itemSelected( _selected );
		}
		_lastKeyCode = -1;
	}

	public void keyTyped( KeyEvent ev )
	{
	}
}
