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

import	java.awt.Color;
import	java.awt.Dimension;
import	java.awt.Font;
import	java.awt.Insets;
import	java.awt.Point;

import	java.awt.font.FontRenderContext;

import	java.io.Serializable;

import	java.util.List;

import	tsukuba_bunko.peko.Logger;


/**
 * 一度にキャンバスに表示する行のリスト(= ページ)です。
 * @author	$Author: ppoi $
 * @version	$Revision: 1.1.2.1 $
 */
public class Page	implements Cloneable, Serializable	{

	/**
	 * このページを描画するテキストキャンバス
	 */
	transient private TextCanvas	_canvas = null;


	/**
	 * committed lines
	 */
	private List	_committedLines = null;

	/**
	 * コミットされた分のページの高さ
	 */
	private float	_commitedPageHeight = 0f;


	/**
	 * lines
	 */
	transient private List	_lines = null;

	/**
	 * 現在のページの高さ
	 */
	transient private float	_pageHeight = 0f;

	/**
	 * dirty flag
	 */
	transient private boolean	_dirty = false;


	/**
	 * デフォルトフォント
	 */
	transient private Font	_defaultFont = null;

	/**
	 * サイズ
	 */
	private Dimension	_size = new Dimension();

	/**
	 * 位置
	 */
	private Point	_location = new Point( 0, 0 );

	/**
	 * パディング
	 */
	private Insets	_padding = new Insets( 10, 10, 10, 10 );

	/**
	 * 前景色
	 */
	private Color	_foreground = Color.white;

	/**
	 * 影色
	 */
	private Color	_shadow = Color.black;

	/**
	 * 背景色
	 */
	private Color	_background = Color.black;

	/**
	 * 背景の透明度
	 */
	private float	_transparency = 0.5f;


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


	/**
	 * このページを描画するテキストキャンバスを設定します。
	 * @param	canvas	テキストキャンバス
	 */
	void setTextCanvas( TextCanvas canvas )
	{
		_canvas = canvas;
	}

	/**
	 * このページを描画するテキストキャンバスを取得します。
	 * @return	canvas
	 */
	public TextCanvas getTextCanvas()
	{
		return _canvas;
	}


	/**
	 * デフォルトフォントを設定します。
	 * @param	font	デフォルトフォント
	 */
	public void setDefaultFont( Font font )
	{
		_defaultFont = font;
	}

	/**
	 * デフォルトフォントを取得します。
	 * @return	デフォルトフォント
	 */
	public Font getDefaultFont()
	{
		return _defaultFont;
	}

	/**
	 * このページでテキストを描画する際に使用する FontRenderContext を取得します。
	 * @return	描画に使用する FontRenderContext インスタンス
	 */
	public FontRenderContext getFontRenderContext()
	{
		if( _canvas == null )	{
			return null;
		}
		else	{
			return _canvas.getFontRenderContext();
		}
	}

	/**
	 * 表示領域のサイズを設定します。
	 * @param	page	サイズ
	 */
	public void setSize( Dimension size )
	{
		_size.setSize( size );
	}

	/**
	 * 表示領域のサイズを取得します。
	 * @return	表示領域のサイズを格納した Dimension インスタンス
	 */
	public Dimension getSize()
	{
		return getSize( new Dimension() );
	}

	/**
	 * 表示領域のサイズを取得し、<code>d</code> に格納します。
	 * @param	d	表示領域のサイズを格納する Dimension インスタンス
	 * @return	<code>d</code>。<code>d == null</code> の場合は新規に生成された Dimension インスタンス
	 */
	public Dimension getSize( Dimension d )
	{
		if( d == null )	{
			d = new Dimension();
		}
		d.setSize( _size );
		return d;
	}

	/**
	 * 表示領域の位置を設定します。
	 * @param	x	表示領域の左上隅 x 座標
	 * @param	y	表示領域の左上隅 y 座標
	 */
	public void setLocation( int x, int y )
	{
		_location.x = x;
		_location.y = y;
	}

	/**
	 * マージン領域のサイズを取得します。
	 * @return	マージン領域のサイズを格納した Insets インスタンス
	 */
	public Point getMargin()
	{
		return getLocation( null );
	}

	/**
	 * マージン領域のサイズを取得し、<code>margin</code> に格納して返します。
	 * @param	margin	マージン領域のサイズを格納する Insets インスタンス
	 * @return	<code>margin</code>。<code>margin == null</code> の場合は新規に生成された Insets インスタンス
	 */
	public Point getLocation( Point location )
	{
		if( location == null )	{
			location = (Point)_location.clone();
		}
		else	{
			location.x = _location.x;
			location.y = _location.y;
		}
		return location;
	}

	/**
	 * パディング領域のサイズを設定します。
	 * @param	top	padding-top
	 * @param	left	padding-left
	 * @param	bottom	padding-bottom
	 * @param	right	padding-right
	 */
	public void setPadding( int top, int left, int bottom, int right )
	{
		_padding.top = top;
		_padding.left = left;
		_padding.bottom = bottom;
		_padding.right = right;
	}

	/**
	 * パディング領域のサイズを取得します。
	 * @return	パディング領域のサイズを格納した Insets インスタンス
	 */
	public Insets getPadding()
	{
		return getPadding( null );
	}

	/**
	 * パディング領域のサイズを取得し、<code>padding</code> に格納して返します。
	 * @param	padding	パディング領域のサイズを格納する Insets インスタンス
	 * @return	<code>padding</code>。<code>padding == null</code> の場合は、新規に生成された Insets インスタンス
	 */
	public Insets getPadding( Insets padding )
	{
		if( padding == null )	{
			padding = (Insets)_padding.clone();
		}
		else	{
			padding.top = _padding.top;
			padding.left = _padding.left;
			padding.bottom = _padding.bottom;
			padding.right = _padding.right;
		}
		return padding;
	}

	/**
	 * 前景色を設定します。
	 * @param	foreground	前景色
	 */
	public void setForeground( Color foreground )
	{
		_foreground = foreground;
	}

	/**
	 * 前景色を取得します。
	 * @return	前景色
	 */
	public Color getForeground()
	{
		return _foreground;
	}

	/**
	 * 影色を設定します。
	 * @param	shadow	影色
	 */
	public void setShadow( Color shadow )
	{
		_shadow = shadow;
	}

	/**
	 * 影色を取得します。
	 * @return	影色
	 */
	public Color getShadow()
	{
		return _shadow;
	}

	/**
	 * 背景色を設定します。
	 * @param	background	背景色
	 */
	public void setBackground( Color background )
	{
		_background = background;
	}

	/**
	 * 背景色を取得します。
	 * @return	背景色
	 */
	public Color getBackground()
	{
		return _background;
	}

	/**
	 * 背景色の透明度を設定します.
	 * @param	trans	透明度
	 */
	public void setTransparency( float trans )
	{
		_transparency = trans;
	}

	/**
	 * 背景色の透明度を取得します。
	 * @return	透明度
	 */
	public float getTransparency()
	{
		return _transparency;
	}


	/**
	 * このページで表示可能な行幅の最大値を取得します。
	 * @return	最大行幅
	 */
	public float getMaxLineWidth()
	{
		int	width = _size.width;
		width -= _padding.left;
		width -= _padding.right;

		return (float)width;
	}

	/**
	 * 行を末尾に追加します。
	 * @param	line	追加する行
	 */
	public synchronized void addLine( Line line )
	{
		if( _lines == null )	{
			_lines = new java.util.ArrayList();
		}
		_lines.add( line );
		_pageHeight += line.getAscent();
		_pageHeight += line.getDescent();
		_dirty = true;
	}

	/**
	 * 現在ページに含まれる行数を取得します。
	 * @return	行数
	 */
	public int getLineCount()
	{
		if( _lines != null )	{
			return _lines.size();
		}
		else	{
			return 0;
		}
	}

	/**
	 * <code>index</code> で指定された行を <code>line</code> で置き換えます。
	 * @param	index	置換する行のインデックス
	 * @param	line	新しい行
	 */
	public synchronized void setLine( int index, Line line )
	{
		try	{
			if( (_lines == null) || (index < 0) || (index >= _lines.size()) )	{
				Logger.debug( "[canvas.text] invalid length! index:" + index + " size:" + ((_lines != null)?_lines.size():0) );
			}

			synchronized( this )	{
				Line	old = (Line)_lines.get( index );
				_pageHeight -= (old.getAscent() + old.getDescent());
				_lines.set( index, line );
				_pageHeight += (line.getAscent() + line.getDescent());
				_dirty = true;
			}
		}
		catch( IndexOutOfBoundsException ioobe )	{
			Logger.error( "[canvas] invalid line number.", ioobe );
			throw ioobe;
		}
	}

	/**
	 * <code>index</code> で指定された行をページから削除します。<code>index</code>
	 * より後ろの行は一行ずつ上に詰められます。
	 * @param	index	削除する行のインデックス
	 * @throws	IndexOutOfBoundsException	<code>index</code> が範囲外の場合
	 */
	public void removeLine( int index )
	{
		try	{
			if( _lines == null )	{
				throw new IndexOutOfBoundsException( "there is still no line." );
			}

			synchronized( this )	{
				Line	line = (Line)_lines.remove( index );
				_pageHeight -= (line.getAscent() + line.getDescent());
				_dirty = true;
			}
		}
		catch( IndexOutOfBoundsException ioobe )	{
			Logger.error( "[canvas] invalid line number.", ioobe );
			throw ioobe;
		}
	}

	/**
	 * 行リストを取得します。
	 */
	public List getLines()
	{
		if( _lines != null )	{
			return (List)((java.util.ArrayList)_lines).clone();
		}
		else	{
			return null;
		}
	}

	/**
	 * すべての行をクリアします。
	 */
	public void clearLines()
	{
		synchronized( this )	{
			if( _lines != null )	{
				_lines.clear();
				_dirty = true;
				_pageHeight = 0f;
			}
		}
	}

	/**
	 * <code>line</code> をページに追加可能かどうかを判定します。
	 * @param	line	判定する行
	 * @return	追加可能な場合 <code>true</code>、追加できな場合は <code>false</code>
	 */
	public boolean isAdaptive( Line line )
	{
		if( line == null )	{
			throw new NullPointerException( "てめぇ、なめてんじゃねぇぞぐらぁ！" );
		}
		float	pageHeight = _pageHeight + _padding.top + _padding.bottom;
		return ((pageHeight + line.getAscent() + line.getDescent()) < (float)_size.height);
	}


	/**
	 * このページへの変更をキャンバスに反映させます。
	 */
	public void updateCanvas()
	{
		if( _canvas != null )	{
			_canvas.updateCanvas();
		}
	}

	/**
	 * このページの内容（テキスト）の変更をキャンバスに反映させます。
	 */
	public void updateContents()
	{
		if( _canvas != null )	{
			_canvas.updateText();
		}
	}

	/**
	 * 変更をコミットします。
	 */
	public void commit()
	{
		Logger.debug( "[canvas.text] commit." );
		if( _lines != null )	{
			if( _committedLines != null )	{
				_committedLines.clear();
				_committedLines.addAll( _lines );
			}
			else	{
				_committedLines = (List)((java.util.ArrayList)_lines).clone();
			}
			Logger.debug( "[canvas.text] committed lines :" + _committedLines.size() );
		}
		_commitedPageHeight = _pageHeight;
	}

	/**
	 * コミットされていない変更を破棄します。
	 */
	public void rollback()
	{
		Logger.debug( "[canvas.text] rollback." );
		if( _lines != null )	{
			_lines.clear();
		}
		_lines = _committedLines;
		_committedLines = null;
		_pageHeight = _commitedPageHeight;
	}


//
//	Clonable の実装
//
	public Object clone()
	{
		return clone( new Page() );
	}

	/**
	 * <code>page</code> をこのページのクローンとします。
	 * @param	copy	このページのクローンにする Page インスタンス
	 * @return	<code>copy</code>
	 */
	public Object clone( Page copy )
	{
		synchronized( this )	{
			if( _lines != null )	{
				copy._lines = (List)((java.util.ArrayList)_lines).clone();
			}
		}
		copy._dirty = false;
		copy._defaultFont = _defaultFont;
		copy._size = (Dimension)_size.clone();
		copy._location = (Point)_location.clone();
		copy._padding = (Insets)_padding.clone();
		copy._foreground = _foreground;
		copy._background = _background;
		copy._transparency = _transparency;

		return copy;
	}
}
