package jongs;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.awt.RenderingHints;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.util.HashMap;
import java.util.Vector ;

// 
// JONGS - Java Open Novels Game Script
// 
//    JongsDisplay.java 
//
//  Copyright 2007  DENZI TOMIZKA
//
//  ver 1.00 Create by DENZI TOMIZKA 2007.5.25
//  ver 1.01           DENZI TOMIZKA 2007.5.28
//  ver 1.02           DENZI TOMIZKA 2007.5.31
//  ver 1.03           DENZI TOMIZKA 2007.6.02
//
/*
 *-----------------------------------------------------------------------
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *----------------------------------------------------------------------
 */




public class jongsDisplay extends Canvas implements Runnable{
	//	トランシジョンの定数
	public final static int EFE_NOEFFECT   =2000 ;
	public final static int EFE_ZOOMIN     =2001 ;
	public final static int EFE_MOSAIC     =2002 ;
	public final static int EFE_FADEIN     =2003 ;
	public final static int EFE_WIPE		  =2004 ;
	public final static int EFE_HASH       =2005 ;
	public final static int EFE_BOOK_LEFT  =2006 ;
	public final static int EFE_BOOK_RIGHT =2007 ;
	

	static final long serialVersionUID = 0x00100001 ;

	//画面バッファ
	BufferedImage disp_buf00 = null ; //１次画面バッファ
	BufferedImage disp_buf01 = null ; //２次画面バッファ
	BufferedImage disp_buf02 = null ; //Shift押下時画面バッファ
	Graphics   gdb01 ;

	//現在の画面の情報
	int width , height ;
	
	//フォント情報
	Font textFont ;
	int  fontCol1 , fontCol2 ;
	Color fontColor1 , fontColor2 ;
	
	//文章
	String text ;
	//文章表示位置
	Rectangle txtArea ;
	boolean letterWriteMode;
	int letter_spacing ;
	int line_height;
	int textWaittime;
	int textPosition ;
	
	//マウス・キーイベント系
	char presskey ;
	int pressx , pressy ;
	int releasex , releasey ;
	int mouseX , mouseY ;
	
	//選択ボタン系
	Rectangle selectArea ;
	int[] selectColor ;
	Rectangle selectbtn[];
	Font selectFont ;
	Vector <String> selectTexts;
	String selectText ;
	int buttonNum , buttonT1st ;
	int selectButton , cellvalue , pressed , nowSelect ;

	//トランシジョン系
	HashMap <String , int[]> transitionMap ;
	int transCode ;
	int commandCode ;
	int transition_wtime ;
	boolean busy ;
	
	public jongsDisplay() {
		initJongsDisplay(800,600);
	}

	public jongsDisplay(int w , int h) {
		initJongsDisplay(w,h);
	}

	public void initJongsDisplay(int dispw , int disph) {
		
		//イベントハンドラをセットする
		//キー入力系
		this.addKeyListener( this.getKeyListener() );
		//マウス入力系
		this.addMouseListener( this.getMouseListener() );
		//マウス移動系
		this.addMouseMotionListener( this.getMouseMotionListener() );
			
		
		//ディフォルト画面サイズ決定
		this.setBounds( dispw , disph );
		//ディフォルトフォント・色決定
		this.setTextFont( new Font("Selif", Font.PLAIN , 14) );
		this.setFontColor(0xffffff,0xaaaaaa);
		this.setTextArea( dispw / 10 , disph / 10  , dispw * 8 / 10 , disph * 8 / 10);
		text = "";
		this.letter_spacing = 3;
		this.line_height = 0 ;

		
		//選択ボタンを表示する位置とサイズを決定する
		this.setSelectArea(50, disph / 2 , dispw - 100 , 50);
		//ディフォルト選択ボタンフォント・色決定
		this.setSelectFont(new Font("Selif", Font.BOLD , 8) );
		this.setSelectColor(0xffffff,0xbbbbff,0xffaaaa,0x000000);

		this.width = this.getWidth();
		this.height = this.getHeight();
		
		this.initTransitionCode();
		this.commandCode = 0;
		this.busy = false ;
		this.mouseX = this.mouseY = -1 ;
	}
	
	
	/**
	 * マウスイベント処理系
	 * @return
	 */
	private MouseListener getMouseListener() {
		MouseListener ml =
		new MouseListener(){
			public void mouseClicked(MouseEvent e){
			}
			public void mouseReleased(MouseEvent e){
				//現在Displayが処理中であれば戻る
				//テキスト表示のクリック待ち				
				if ( commandCode == JongScripter.CMD_text ) {
					//Scripterへのキー入力終了通知
			    	if ( letterWriteMode ) {
			    		//文字流し中ならやめる
			    		letterWriteMode  = false ;  			    		
			    	} else {
				    	presskey = ' ';
			    		commandCode = 0;
			    	}
				}
				if (busy) return ;
				mouseX = calcPositionX(e.getX());
				mouseY = calcPositionY(e.getY());					
				//ボタン選択イベント取得時の場合
				if ( commandCode == JongScripter.CMD_select ) {
					selectButton = checkSelectButton(mouseX, mouseY);
					if ( selectButton != -1 ) {
						//押下時の値と同じか？
						if ( selectButton == pressed ) {
							drawSelectButton(gdb01,selectButton,2);
							selectText = selectTexts.get(buttonT1st+selectButton);
							commandCode = 0;
						} else {
							drawSelectButton(gdb01,pressed,0);
							selectButton = -1 ;
							pressed = -1;
						}
					} else {
						nowSelect = pressed ;
						drawSelectButton(gdb01,pressed,1);
						pressed = -1;
					}
					//drawSelectButtons(gdb01);
					drawDisplay();
				}
				//セルクリックイベント取得時の場合
				else if ( commandCode == JongScripter.CMD_cellClick ) {
					releasex = mouseX ;
					releasey = mouseY ;
					commandCode = 0;
				}
			}
			
			public void mousePressed(MouseEvent e){
				//現在Displayが処理中であれば戻る
				if (busy) return ;
				mouseX = calcPositionX(e.getX());
				mouseY = calcPositionY(e.getY());					
				//ボタン選択イベント取得時の場合
				if ( commandCode == JongScripter.CMD_select ) {
					pressed = checkSelectButton(mouseX, mouseY);
					if ( pressed != -1 ) {
						if ( nowSelect != -1 ) drawSelectButton(gdb01,nowSelect,0);
						drawSelectButton(gdb01,pressed,2);
						nowSelect = pressed ;
					}
					//drawSelectButtons(gdb01);
					drawDisplay();
				}
				//セルクリックイベント取得時の場合
				else if ( commandCode == JongScripter.CMD_cellClick ) {
					pressx = mouseX;
					pressy = mouseY;
				}
			}
			public void mouseExited(MouseEvent e){
			}
			public void mouseEntered(MouseEvent e){
			}
		};
		return ml ;
	}
	
	
	/**
	 * キーイベント処理系
	 * @return
	 */
	private KeyListener getKeyListener () {
		KeyListener kl =
		new KeyListener(){
			public void keyReleased(KeyEvent ke){
				if ( 	commandCode == JongScripter.CMD_text 
				||		commandCode == JongScripter.CMD_select		) {
		    		if ( ke.getKeyCode() == KeyEvent.VK_SHIFT ) {
		    			busy = false ;
		    			Graphics disp_g = getGraphics();
		    			disp_g.drawImage(disp_buf01, 0, 0,getWidth(),getHeight(),null);
						drawDisplay();
		    			return ;
		    		}				
				}
				//現在Displayが処理中であれば戻る
				if (busy) return ;
				//ボタン選択イベント取得時の場合
				if ( commandCode == JongScripter.CMD_select ) {
					if ( ke.getKeyCode() == KeyEvent.VK_RIGHT || ke.getKeyCode() == KeyEvent.VK_DOWN) {
						if ( nowSelect < 0 ) nowSelect = 0 ;
						else {
							//現在の選択ボタンを元に戻す
							drawSelectButton(gdb01,nowSelect,0);
							nowSelect = nowSelect + 1;
							if (nowSelect >= buttonNum ) nowSelect = 0;
						}
						//新たな選択ボタンを描きなおす
						drawSelectButton(gdb01,nowSelect,1);
						selectButton = pressed = -1;
						drawDisplay();
					} else if ( ke.getKeyCode() == KeyEvent.VK_LEFT ||  ke.getKeyCode() == KeyEvent.VK_UP ) {
						if ( nowSelect < 0 ) nowSelect = 0 ;
						else {
							//現在の選択ボタンを元に戻す
							drawSelectButton(gdb01,nowSelect,0);
							nowSelect = nowSelect - 1;
							if (nowSelect < 0 ) nowSelect = buttonNum - 1;								
						}
						//新たな選択ボタンを描きなおす
						drawSelectButton(gdb01,nowSelect,1);
						selectButton = pressed = -1;
						drawDisplay();							
					} else if ( ke.getKeyCode() == KeyEvent.VK_ENTER ) {
						if ( nowSelect < 0 ) {
							nowSelect = 0 ;
							drawSelectButton(gdb01,nowSelect,1);
							drawDisplay();								
						} else {
							selectButton = nowSelect ;
							selectText = selectTexts.get(buttonT1st+selectButton);
							//Scripterへのボタン選択終了通知
							commandCode = 0;
							pressed = -1;
							drawSelectButton(gdb01,selectButton,2);
							drawDisplay();
						}
					}						
				}
			}
			public void keyTyped(KeyEvent e){}
			public void keyPressed(KeyEvent ke){
				//テキスト表示のキー入力待ち
				if ( 	commandCode == JongScripter.CMD_text ) {
			    	if ( letterWriteMode ) {
			    		//文字流し中ならやめる
			    		letterWriteMode  = false ;  			    		
			    	} else {
			    		if ( ke.getKeyCode() == KeyEvent.VK_SHIFT ) {
			    			if (!busy) {
				    			busy = true ;
				    			Graphics disp_g = getGraphics();
				    			disp_g.drawImage(disp_buf02, 0, 0,getWidth(),getHeight(),null);
			    			}
			    		} else {
				    		presskey = ke.getKeyChar();
							//Scripterへのキー入力終了通知
					    	commandCode = 0;
			    		}
			    	}
				}
				if (	commandCode == JongScripter.CMD_select		) {
		    		if ( ke.getKeyCode() == KeyEvent.VK_SHIFT ) {
		    			if (!busy) {
			    			busy = true ;
			    			Graphics disp_g = getGraphics();
			    			disp_g.drawImage(disp_buf02, 0, 0,getWidth(),getHeight(),null);
		    			}
		    		}
				}
			}
		};
		return kl;
	}
	
	
	/**
	 * マウス移動イベント処理系
	 * （この処理は極力処理を軽くしないとかなーり重くなります。）
	 * @return
	 */
	private MouseMotionListener getMouseMotionListener() {
		MouseMotionListener mml =
		new MouseMotionListener(){
			public void mouseMoved(MouseEvent e){
				//現在Displayが処理中であれば戻る
				if (busy) return ;
				//ボタン選択イベント取得時の場合
				if ( commandCode == JongScripter.CMD_select ) {
					mouseX = calcPositionX(e.getX());
					mouseY = calcPositionY(e.getY());					
					if ( selectArea.contains(mouseX, mouseY) ) {
						int ns = checkSelectButton(mouseX, mouseY);
						if ( ns != -1 ) {
							if ( nowSelect != -1 ) drawSelectButton(gdb01,nowSelect,0);
							drawSelectButton(gdb01,ns,1);
							nowSelect = ns ;
							drawDisplay();		
						}
					}
				}
			}
			public void mouseDragged(MouseEvent e){
			}
		};
		
		return mml ;
	}
	
	private int calcPositionX(int zx) {
		return zx *  this.width  / this.getWidth() ;
	}
	
	private int calcPositionY(int zy) {
		return zy * this.height / this.getHeight() ;
	}
	

	/**
	 * トランシジョン効果予約語の登録
	 *
	 */
	private void initTransitionCode() {
		//トランシジョン効果予約語の登録
		transitionMap = new HashMap <String , int[] >();
		transitionMap.put("_noeffect", this.getIntArray(EFE_NOEFFECT  ) ); 
		transitionMap.put("_zoomin", this.getIntArray(EFE_ZOOMIN    ) ); 
		transitionMap.put("_mosaic", this.getIntArray(EFE_MOSAIC    ) ); 
		transitionMap.put("_fadein", this.getIntArray(EFE_FADEIN    ) ); 
		transitionMap.put("_wipe", this.getIntArray(EFE_WIPE		 ) ); 
		transitionMap.put("_hash", this.getIntArray(EFE_HASH      ) ); 
		transitionMap.put("_bookleft", this.getIntArray(EFE_BOOK_LEFT  ) ); 
		transitionMap.put("_bookright", this.getIntArray(EFE_BOOK_RIGHT ) ); 
	}
	
	/**
	 * トランシジョン効果予約語をトランシジョンコードに変換する
	 * @param tstr
	 * @return
	 */
	public int getTransitionCode(String tr) {
		String tstr = tr.trim().toLowerCase();
		if ( transitionMap.containsKey(tstr) ) 
			return transitionMap.get(tstr)[0];
		else
			return 0 ;
	}
	
		
	/**
	 * 	intをint[1]の形式にし、オブジェクトとして扱えるようにする
	 * @param cd
	 * @return
	 */
	private int[] getIntArray (int cd) {
		int [] code = new int[1];
		code[0] = cd ;
		return code ;	
	}

	
	/**
	 * 画面サイズを決定する
	 * @param w
	 * @param h
	 */
	public void setBounds (int w , int h ) {
        this.setVisible(false);
        this.setBounds( 0, 0, w, h);        
        //画面バッファを作成
        disp_buf01 = new BufferedImage(w, h, BufferedImage.TYPE_INT_BGR);
        gdb01 = disp_buf01.getGraphics();
        this.setVisible(true);
        this.setTextArea(10, 30, w-20, h-60);
	}
		
	/**
	 * テキストの表示エリアを設定する。
	 * @param x
	 * @param y
	 * @param w
	 * @param h
	 */
	public void setTextArea(int x ,int y , int w , int h ) {
		txtArea = new Rectangle(x,y,w,h);
	}
	
	public void setTextInterval (int ls  , int lh ) {
		this.letter_spacing = ls;
		this.line_height = lh;
	}
	
	/**
	 * フォント色を決定する
	 * @param fc1
	 * @param fc2
	 */
	public void setFontColor(int fc1 , int fc2) {
		this.fontColor1 = new Color(fc1);
		this.fontColor2 = new Color(fc2);
		this.fontCol1 = fc1 ;
		this.fontCol2 = fc2 ;
	}
		
	/**
	 * 文章のフォントを決定する
	*/
	public void setTextFont(Font fnt) {
		textFont = fnt ;
	}
	
	
	/**
	 * セル選択のスタート指示
	 *
	 */
	public void cellClick() {
		this.cellvalue = this.pressed = this.nowSelect = -1 ;
		this.commandCode = JongScripter.CMD_cellClick;
	}	

	
	/**
	 * ボタン選択のスタート指示
	 *
	 */
	public void selectClick() {
		this.commandCode = JongScripter.CMD_select;
		this.selectButton = this.pressed = this.nowSelect = -1 ;
		this.buttonsWrite();		
	}
	
	/**
	 * 現在の画面上に指定色でボタン群を描く。
	 *
	 */
	public void buttonsWrite(){
		Graphics disp_g = this.getGraphics();
		this.gdb01.drawImage(this.disp_buf00,0,0,this);
		this.textWrite(this.gdb01);
		this.drawSelectButtons(this.gdb01);
		disp_g.drawImage(this.disp_buf01,0,0,this.getWidth(),this.getHeight(),this);
		disp_g.dispose();
	}

	/**
	 * 選択ボタンの表示エリアを設定する
	 * @param x
	 * @param y
	 * @param w
	 * @param h
	 */
	public void setSelectArea(int x ,int y , int w , int h ) {
		this.selectArea = new Rectangle(x,y,w,h);
	}

	public void setSelectArea(Rectangle area ) {
		this.selectArea = area;
	}

	public void setSelectButton( Rectangle [] btn ) {
		selectbtn = btn ;
	}


	public void setSelectText(  Vector <String> vct , int stt , int num ) { 
		selectTexts = vct ;
		buttonT1st = stt ;
		buttonNum = num ;
	}

	
	public void setSelectFont( Font fnt ) {
		selectFont = fnt ;
	}

	/**
	 * 選択ボタンの色を設定する
	 * @param c1 通常色 
	 * @param c2
	 * @param c3 押下時色
	 * @param c4 フォント色
	 */
	public void setSelectColor(int c1 , int c2 , int c3 , int c4 ) {
		this.selectColor = new int[4];
		this.selectColor[0] = c1;
		this.selectColor[1] = c2;
		this.selectColor[2] = c3;
		this.selectColor[3] = c4;
	}

	public void setSelectColor(int [] bc ) {
		this.selectColor = bc ;
	}
	
	
	/**
	 * 選択ボタン群を描く
	 * @param g2d
	 */
	public void drawSelectButtons(Graphics gp) {
		FontMetrics fm = gp.getFontMetrics(this.selectFont);
		if ( this.commandCode == JongScripter.CMD_select ) {
			if ( selectbtn == null ) return ;
			for (int n = 0 ; n < this.buttonNum ; n++  ) {
				if		( this.selectButton == n )  this.drawSelectButton(gp, n, 2 , fm);
				else if	( this.pressed == n )  this.drawSelectButton(gp, n, 2 , fm);
				else if	( this.nowSelect == n ) this.drawSelectButton(gp, n, 1 , fm);
				else this.drawSelectButton(gp, n, 0 , fm);
			}			
		}
	}


	public void drawSelectButton(Graphics g , int bnum , int stt ) {
		FontMetrics fm = g.getFontMetrics(this.selectFont);
		this.drawSelectButton(g, bnum , stt , fm);
	}

	
	/**
	 * 状態に応じた選択ボタンを1つ描く
	 * @param g
	 * @param bnum ボタン番号
	 * @param stt  0 通常 1 選択　2 押下
	 */
	public void drawSelectButton(Graphics g , int bnum , int stt , FontMetrics fm ) {
		int x , y , w , h ;
		int fw , fh , fy , fx ;
		int fc ;
		boolean flag;
		
		if ( bnum < 0 || bnum > this.buttonNum) return ;
		
		Rectangle ra = selectbtn[bnum];
		String t = (String) selectTexts.get(bnum+this.buttonT1st);
		
		//描く対象の座標値を得る
		x = ra.x ; y = ra.y; w = ra.width; h = ra.height;
		
		switch (stt) {
		case 0:
			fc = this.selectColor[0]; flag = true; break ;
		case 1:
			fc = this.selectColor[1]; flag = true; break ;
		case 2:
			fc = this.selectColor[2]; flag = false; break ;
		default :
			return;
		}
		
		g.setColor(new Color(fc));
		g.fill3DRect(x, y, w, h, flag);
		g.draw3DRect(x+1,y+1,w-2,h-2, flag );
		g.draw3DRect(x+2,y+2,w-4,h-4, flag );
		
		g.setColor(new Color(this.selectColor[3]));
		fh = fm.getHeight();
		fy = y + fh + ( h - fh )/ 2;
		fw = fm.stringWidth(t);
		fx = x + (w - fw) / 2 ;
		g.drawString(t, fx , fy);		
	}

	
	/**
	 * 座標を指定し、ボタンの画面上エリアならばボタン番号を返す
	 * @param zx
	 * @param zy
	 * @return 該当ボタンなし -1
	 */
	public int checkSelectButton(int zx , int zy ) {
		for (int n = 0 ; n < buttonNum ; n++  ) {
			if ( selectbtn[n] != null)
				if ( selectbtn[n].contains(zx, zy) ) return n ;
		}
		return -1 ;
	}


	/**
	 * 画面に描くテキストをセットし描画するコマンドをセットする
	 * @param txt
	 */
	public void drawText(String txt) {
		if ( txt.charAt(0) == '+') {
			this.textPosition = this.text.length();
			this.text = this.text + txt.substring(1);
		} else {
			this.textPosition = 0;
			this.text = txt ;
		}
		this.commandCode = JongScripter.CMD_text ;
		this.drawText();
	}

	/**
	 * 画面に描くテキストをセットし描画・時間待ちするコマンドをセットする
	 * @param txt
	 */
	public void drawWaitText(String txt, int wtime) {
		if ( txt.charAt(0) == '+') {
			this.textPosition = this.text.length();
			this.text = this.text + txt.substring(1);
		} else {
			this.textPosition = 0;
			this.text = txt ;
		}
		textWaittime = wtime ;
		this.commandCode = JongScripter.CMD_WaitText ;
		this.drawText();
		this.waitMiliSec(this.textWaittime);
		this.commandCode = 0 ;
	}

	public void clearText() {
		this.text = "";
		this.gdb01.drawImage(this.disp_buf00,0,0,this);
		this.commandCode = 0 ;
	}
	
	
	
	
	/**
	 * テキストを描画する。
	 *
	 */
	public void drawText() {
		this.busy = true ;
		VolatileImage buf ;
		Graphics2D bfg;
		
		buf = this.createVolatileImage(this.width,this.height);
		bfg = (Graphics2D) buf.getGraphics();

		Graphics disp_g = this.getGraphics();
		
		if ( this.textPosition == 0) {
			this.gdb01.drawImage(this.disp_buf00,0,0,this);
			bfg.drawImage(this.disp_buf00,0,0,this);			
		} else {
			bfg.drawImage(this.disp_buf01,0,0,this);			
		}

		//一文字ずつ描画する
		this.letterWriteMode = true ;
		this.textLetterWrite(disp_g ,bfg , buf);
		this.letterWriteMode = false;
		bfg.dispose();
		buf.flush();

		//一気に描画する
		this.textWrite(this.gdb01);
		disp_g.drawImage(this.disp_buf01, 0, 0,this.getWidth(),this.getHeight(),this);
		
		this.busy = false ;
	}
	
	/**
	 * テキストを描画する
	 */
	public void textWrite(Graphics gg)
	{
		Graphics2D sg = (Graphics2D) gg;
		int b,e,c,yz;
		String S = this.text + " ";
		sg.setFont(this.textFont);
		FontMetrics fm = sg.getFontMetrics(this.textFont);
		int fhei = fm.getHeight() + this.line_height ;
		int sx , sy ,  sw  ;		
		sx = this.txtArea.x ;
		sy = this.txtArea.y ;
		sw = this.txtArea.width;
	    
		//アンチエイリアスで出す
		sg.setRenderingHint(
			RenderingHints.KEY_TEXT_ANTIALIASING,
			RenderingHints.VALUE_TEXT_ANTIALIAS_ON
		);
				
		for ( c=1 , b = e = 0 ; e < S.length() ; e++ )
		{
			if ( calcStringWidth(S,b,e+1,fm) > (sw) 
			|| ( S.charAt(e) == '\\' &&  S.charAt(e+1) == 'n' ) )
			{
				yz = c * fhei ;
				if (e >= this.textPosition) 
					this.drawTextString(sg,S.substring(b,e),sx,sy+yz,fm);
				if ( ( S.charAt(e) == '\\' &&  S.charAt(e+1) == 'n' ) )
					e = b = e + 2 ;
				else
					b = e;
				c ++ ;
			}
		}
		yz = c * fhei ;
		this.drawTextString(sg,S.substring(b),sx,sy+yz,fm);
	}	
		
	
	/**
	 * 一文字づつテキストを描画する
	 */
	public void textLetterWrite(Graphics dg , Graphics2D bufg , VolatileImage buf) {
		int b,e,c,yz;
		String S = this.text + " ";
		bufg.setFont(this.textFont);
		FontMetrics fm = bufg.getFontMetrics(this.textFont);
		int fhei = fm.getHeight() + this.line_height ;
		int sx , sy ,  sw  ;
		
		sx = this.txtArea.x ;
		sy = this.txtArea.y ;
		sw = this.txtArea.width;
	    
		//アンチエイリアスで出す
		bufg.setRenderingHint(
			RenderingHints.KEY_TEXT_ANTIALIASING,
			RenderingHints.VALUE_TEXT_ANTIALIAS_ON
		);
				
		
		for ( c=1 , b = e = 0 ; e < S.length() ; e++ )
		{
			if ( calcStringWidth(S,b,e+1,fm) > (sw) 
			|| ( S.charAt(e) == '\\' &&  S.charAt(e+1) == 'n' ) )
			{
				yz = c * fhei ;
				if (e >= this.textPosition) 
					this.drawTextString(dg,bufg,buf,S.substring(b,e),sx,sy+yz,fm,10);
				if ( this.letterWriteMode == false ) return;
				if ( ( S.charAt(e) == '\\' &&  S.charAt(e+1) == 'n' ) )
					e = b = e + 2 ;
				else
					b = e;
				c ++ ;
			}
		}
		yz = c * fhei ;
		this.drawTextString(dg,bufg,buf,S.substring(b),sx,sy+yz,fm,20);	
	}
	
	
	/**
	 * 文字列の画像表現上の幅を求める
	 * @param str
	 * @param begin
	 * @param end
	 * @param fm
	 * @return
	 */
	private int calcStringWidth ( String str , int begin , int end , FontMetrics fm ) {
		return fm.stringWidth(str.substring(begin,end)) + this.letter_spacing * ( end - begin ) ;
	}
	

	/**
	 * 一行の文字列を一文字づつ時間待ちをしながら画面に描画する
	 * @param dg
	 * @param bfg
	 * @param buf
	 * @param str
	 * @param x
	 * @param y
	 * @param fm
	 * @param wtime
	 */
	private void drawTextString(Graphics dg , Graphics2D bfg , VolatileImage buf , String str , int x , int y , FontMetrics fm , int wtime) {
		int p , sx ;
		String letter ;
		for ( p = 0 ; p < str.length() ; p++ ) {
			sx = x + this.calcStringWidth(str, 0, p , fm);
			letter = str.substring(p,p+1);
			bfg.setColor(this.fontColor2);
			bfg.drawString(letter,sx-1,y-1);
			bfg.drawString(letter,sx+1,y+1);
			bfg.drawString(letter,sx-1,y+1);
			bfg.drawString(letter,sx+1,y-1);
			bfg.setColor(this.fontColor1);		
			bfg.drawString(letter, sx, y);
			dg.drawImage(buf,0,0,this.getWidth(),this.getHeight(),this);
			if ( this.letterWriteMode == false ) return;
			this.waitMiliSec(wtime);
		}
	}

	/**
	 * 一行の文字列を1文字づつ描画する
	 * @param g
	 * @param str
	 * @param x
	 * @param y
	 * @param fm
	 */
	private void drawTextString(Graphics2D g , String str , int x , int y , FontMetrics fm ) {
		int p , sx ;
		String letter ;
		for ( p = 0 ; p < str.length() ; p++ ) {
			sx = x + this.calcStringWidth(str, 0, p , fm);
			letter = str.substring(p,p+1);
			g.setColor(this.fontColor2);
			g.drawString(letter,sx-1,y-1);
			g.drawString(letter,sx+1,y+1);
			g.drawString(letter,sx-1,y+1);
			g.drawString(letter,sx+1,y-1);
			g.setColor(this.fontColor1);		
			g.drawString(letter, sx, y);
		}		
	}
	
	/**
	 * 画面バッファをディスプレイに描画するコマンドをセットする
	 *
	 */
	public void viewDisplay(BufferedImage buf , BufferedImage buf2) {
		this.commandCode = JongScripter.CMD_viewDisplay;
		this.disp_buf00 = buf ;
		this.disp_buf02 = buf2 ;
		this.viewDisplay();
		this.commandCode = 0 ;
	}

	
	/**
	 * 画面バッファを受け取りトランシジョンしつつディスプレイに描画する。
	 *
	 */	
	public void transitionDisplay(BufferedImage  buf , BufferedImage buf2 ,String trstr ) {
		this.commandCode = JongScripter.CMD_transitionDisplay;
		int tCode = this.getTransitionCode(trstr);
		//登録された予約語になければ通常表示
		if ( tCode == 0 ) this.viewDisplay(buf,buf2);
		else transitionDisplay(buf ,buf2, tCode );
	}	
	
	/**
	 * 画面バッファを受け取りトランシジョンしつつディスプレイに描画するコマンドをセットする
	 *
	 */
	public void transitionDisplay(BufferedImage  buf , BufferedImage buf2 , int tCode ) {
		this.commandCode = JongScripter.CMD_transitionDisplay;
		transCode = tCode ;//トランシジョンコードをセット
		this.disp_buf00 = buf ;
		this.disp_buf02 = buf2 ;
		this.transitionDisplay();
		this.commandCode = 0 ;
	}
	
	
	/**
	 * コマンドがCMD_viewDisplayの場合だけコールされるpaint()
	 *
	 */
	private void viewDisplay() {
		Graphics disp_g = this.getGraphics();
		this.gdb01.drawImage(this.disp_buf00,0,0,this);
		disp_g.drawImage(this.disp_buf00,0,0,this.getWidth(),this.getHeight(),this);
		disp_g.dispose();
	}

	/**
	 *
	 */
	private void drawDisplay() {
		Graphics disp_g = this.getGraphics();
		disp_g.drawImage(this.disp_buf01,0,0,this.getWidth(),this.getHeight(),this);
		disp_g.dispose();
	}

	
	/**
	 * コマンドがCMD_transitionDisplayの場合だけコールされるpaint()
	 * 
	 * @param g
	 */
	public void transitionDisplay() {		
		VolatileImage img1 , img2;
		Graphics ig , disp_g;
		
		disp_g = this.getGraphics();
		
		img1 = this.createVolatileImage(this.width,this.height);
		ig = img1.getGraphics();
		ig.drawImage(this.disp_buf01,0,0,this);
		img2 = this.createVolatileImage(this.width,this.height);
		ig = img2.getGraphics();
		ig.drawImage(this.disp_buf00,0,0,this);
		ig.dispose();
		
		this.busy = true ;
    	
    	switch ( transCode ) {
    	case EFE_FADEIN :
    	   	transition_wtime = 20;
    	    this.EffectFadein( disp_g , img1 , img2 );
    		break ;
    	
    	case EFE_HASH :
    	   	transition_wtime = 10;
    		this.EffectHash ( disp_g , img1 , img2 );
    		break ;
    		
    	case EFE_MOSAIC :
    	   	transition_wtime = 20;
    		this.EffectMosaic ( disp_g , img1 , img2 );
    		break ;
    		    		
    	case EFE_WIPE :
    	   	transition_wtime = 5;
    		this.EffectWipe ( disp_g , img1 , img2  , 50 );
    		break ;
    		    		
    	case EFE_ZOOMIN :
    	   	transition_wtime = 20;
    		this.EffectZoomin ( disp_g , img1 , img2 ,  100 );
    		break ;
    		    		
    	case EFE_BOOK_RIGHT :
    	   	transition_wtime = 5;
    		this.EffectBook ( disp_g , img1 , img2 , 0 , 50 );
    		break ;
    		    		
    	case EFE_BOOK_LEFT :
    	   	transition_wtime = 5;
    		this.EffectBook ( disp_g , img1 , img2 , 1 , 50 );
    		break ;
    		    		
    	default :
    		break ;
    		
    	}
     	
		this.gdb01.drawImage(img2,0,0,this);
		disp_g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);
		disp_g.dispose();

		img1.flush();
    	img2.flush();

		this.transCode = -1;
		this.busy = false ;
	}
	

	
	private void EffectFadein( Graphics g ,  Image img1 , Image img2 ){
		int i , x , y , bkw ;
		int iw = this.width;
		int ih = this.height;
		VolatileImage fbuf = this.createVolatileImage(iw,ih*2);
		Graphics fg = fbuf.getGraphics();

		VolatileImage bbuf = this.createVolatileImage(iw,ih);
		Graphics bg = bbuf.getGraphics();
		
		fg.drawImage(img1,0,0,iw,ih,this);
		fg.drawImage(img2,0,ih,iw,ih,this);
		
		bkw = 5;
				
		for ( i = 0 ; i < bkw ; i ++ ) {
			for ( x = i      ; x < iw ; x += bkw ) fg.copyArea(x,0+ih, 1,ih,0,-ih);
			bg.drawImage(fbuf,0,0,iw,ih*2,this);	
			g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
	    	this.waitMiliSec(transition_wtime);
			for ( y = bkw-1-i; y < ih ; y += bkw ) fg.copyArea(0,y+ih,iw,1, 0,-ih);
			bg.drawImage(fbuf,0,0,iw,ih*2,this);	
			g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
	    	this.waitMiliSec(transition_wtime);
			for ( x = bkw-1-i; x < iw ; x += bkw ) fg.copyArea(x,0+ih, 1,ih,0,-ih);
			bg.drawImage(fbuf,0,0,iw,ih*2,this);	
			g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
	    	this.waitMiliSec(transition_wtime);
			for ( y = i      ; y < ih ; y += bkw ) fg.copyArea(0,y+ih,iw,1, 0,-ih);
			bg.drawImage(fbuf,0,0,iw,ih*2,this);	
			g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
	    	this.waitMiliSec(transition_wtime);
		}
		g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);
		bbuf.flush();
	}
	
	
	private void EffectHash( Graphics g ,  Image img1 , Image img2 )
	{
		int x,x1,x2,x3;
		int iw = this.width;
		int ih = this.height;
		int w = 20;
		int mw = iw / w;
		int wmw = w+mw;
		VolatileImage wbuf= createVolatileImage(iw, ih*2); 
		Graphics wg= wbuf.getGraphics();

		VolatileImage bbuf = this.createVolatileImage(iw,ih);
		Graphics bg = bbuf.getGraphics();
		
		wg.drawImage(img1, 0, 0, iw, ih , this); 
		wg.drawImage(img2, 0, ih , iw, ih, this); 
		wg.copyArea(0, iw, 1, ih, 0, -ih); 
		for ( x1 = 0 ; x1 < wmw ; x1 ++ ) {
			for ( x2 = 0 , x3 = x1 ; x2 < x1 ; x2 ++ , x3 -- ){
				x = mw * ( x1 - x3 ) + x3 ;
				wg.copyArea(x, ih, 1, ih, 0, -ih); 
			}
			bg.drawImage(wbuf,0,0,this);
			g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
	    	this.waitMiliSec(transition_wtime);
		}
		
		wbuf.flush();
		wg.dispose();
		bbuf.flush();
		bg.dispose();
		g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);
		
	}
	
	
	private void EffectMosaic( Graphics g ,  Image img1 , Image img2 )
	{
		int f;
		int iw = this.width;
		int ih = this.height;
		int fw ,fh ;
		VolatileImage wimg ;
		Graphics wg;
		for ( f = 2 ; f <= 64 ; f= f*2 )
		{
			fw = iw/f;
			fh = iw/f;
			wimg=createVolatileImage(fw,fh);
			wg=wimg.getGraphics();
			wg.drawImage(img1,0,0,fw,fh,this);
			g.drawImage(wimg,0,0,this.getWidth(),this.getHeight(),this);
			wimg.flush();
			wg.dispose();
	    	this.waitMiliSec(transition_wtime);
		}
		for ( f = 64 ; f > 1 ; f= f/2 )
		{
			fw = iw/f;
			fh = ih/f;
			wimg=createVolatileImage(fw,fh);
			wg=wimg.getGraphics();
			wg.drawImage(img2,0,0,fw,fh,this);
			g.drawImage(wimg,0,0,this.getWidth(),this.getHeight(),this);
			wimg.flush();
			wg.dispose();
	    	this.waitMiliSec(transition_wtime);
		}
		g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);
	
	}
	

	private void EffectWipe( Graphics g ,  Image img1 , Image img2 , int STEP )
	{
		int i,p;
		int iw = this.width;
		int ih = this.height;

		VolatileImage bbuf = this.createVolatileImage(iw,ih);
		Graphics bg = bbuf.getGraphics();
		
		p = iw / STEP ;
		
		Image bbuf2 = this.createImage(iw,ih);
		Graphics bg2 = bbuf2.getGraphics();
		bg2.drawImage(img1,0,0,this);
		for ( i = 0 ; i < iw ; i += p) {
			bg.drawImage(img2,0,0,this);
			bg.drawImage(bbuf2,i,0,this);
			g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
			bg2.copyArea(p,0,iw-i, ih, -p,0); 
	    	this.waitMiliSec(transition_wtime);
		}

		bbuf.flush();
		bg.dispose();
		g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);			
	}
	

	private void EffectZoomin( Graphics g ,  Image img1 , Image img2 ,int STEP )
	{
		int i , x , y , w , h; 
		int iw = this.getWidth();
		int ih = this.getHeight();
						
		i = 0;
		while ( i < STEP ) {
			w = iw * i / STEP ;
			h = ih * i / STEP ;
			x = (iw - w) / 2;
			y = (ih - h) / 2;
			g.drawImage(img2,x,y,w,h,this);			
	    	this.waitMiliSec(transition_wtime);
	    	i += (i/10 + 1);
		}

		g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);
	}
	

	private void EffectBook( Graphics g ,  Image img1 , Image img2 , int mode , int STEP ) {
		int x ; 
		int iw = this.width;
		int ih = this.height;
		int hw = iw / 2 ;
		int dx = iw / STEP ;
		
		Graphics bg , bk ;

		VolatileImage bbuf = this.createVolatileImage(iw,ih);
		bg = bbuf.getGraphics();
		
		VolatileImage left1 = this.createVolatileImage(hw,ih);
		bk = left1.getGraphics();
		bk.drawImage(img1,0,0,this);

		VolatileImage right1 = this.createVolatileImage(hw,ih);
		bk = right1.getGraphics();
		bk.drawImage(img1,-hw,0,this);

		VolatileImage left2 = this.createVolatileImage(hw,ih);
		bk = left2.getGraphics();
		bk.drawImage(img2,0,0,this);
		
		VolatileImage right2 = this.createVolatileImage(hw,ih);
		bk = right2.getGraphics();
		bk.drawImage(img2,-hw,0,this);
		
		bk.dispose();
		
		if ( mode == 0 ) {
			for ( x = 0 ; x < hw ; x+=dx ) {
				bg.drawImage(left1,0,0,this);
				bg.drawImage(right2,hw,0,this);
				bg.drawImage(right1,hw,0,hw-x,ih,this);
				g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
		    	this.waitMiliSec(transition_wtime);			
			}

			for ( x = 0 ; x < hw  ; x+=dx ) {
				bg.drawImage(left1,0,0,this);
				bg.drawImage(right2,hw,0,this);
				bg.drawImage(left2,hw-x,0,x,ih,this);
				g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
		    	this.waitMiliSec(transition_wtime);					
			}
			
		} else {
			for ( x = hw ; x > 0  ; x-=dx ) {
				bg.drawImage(left2,0,0,this);
				bg.drawImage(right1,hw,0,this);
				bg.drawImage(left1,hw-x,0,x,ih,this);
				g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
		    	this.waitMiliSec(transition_wtime);					
			}

			for ( x = hw ; x > 0  ; x-=dx ) {
				bg.drawImage(left2,0,0,this);
				bg.drawImage(right1,hw,0,this);
				bg.drawImage(right2,hw,0,hw-x,ih,this);
				g.drawImage(bbuf,0,0,this.getWidth(),this.getHeight(),this);
		    	this.waitMiliSec(transition_wtime);			
			}
		}
		
		left1.flush();
		left2.flush();
		right1.flush();
		right2.flush();
		
		bbuf.flush();
		bg.dispose();
		g.drawImage(img2,0,0,this.getWidth(),this.getHeight(),this);
		
	}
	
	
	
	/**
	 * メインループ
	 */
	public void run() {
		/**
		 * 万が一定期的アニメーション（目パチなど）の実装が必要な場合は此処で組みます。
		 * 
		 */
		this.waitMiliSec(100);
	}
	
	
	public void waitMiliSec(int w) {
        try {
            Thread.sleep(w);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
		//if (w == 0 ) return ;
		//long start = System.currentTimeMillis();
		//long wtime = w;
		//long now ;
		//do {
		//	now = System.currentTimeMillis();
		//}while ( wtime > ( now - start) );
		//System.out.println("waitMiliSec:START"+start);
		//System.out.println("waitMiliSec:  END"+now);
		//System.out.println("waitMiliSec:WTIME"+wtime);
	}
	

	
	/**
	 * 画面の更新
	 */
	public void update(Graphics g) {
		if( busy ) return;
		paint(g);
	}
	
	/**
	 * 画面を描画する
	 */
	public void paint(Graphics g){
		g.drawImage(disp_buf01, 0, 0,this.getWidth(),this.getHeight(),this );
	}


}
