/**
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2004 satoshi akabane(akabane@logical-paradox.org)
 *
 */
package org.logical_paradox.console;

import java.io.IOException;
import java.io.OutputStream;

import javax.swing.JTextArea;

/**
 * ドキュメントを管理するクラス
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.8 $
 */
public class Document {
	/** プロンプト */
	private String prompt;
	/** プロンプトのサイズ */
	private int promptLength;

	/** テキストコンポーネント */
	private JTextArea textarea;
	/** ロケーションカウンタ */
	private int ptr;
	/** カーソルのX位置(論理行) */
	private int xpos;
	private boolean inputmode = false;

	/** ENTERが押下された際の行 */
	private String commitedLine;

	/** 出力ストリーム */
	private final OutputStream ostream;

	/**
	 * コンストラクタ
	 * @param text テキストエリア
	 */
	public Document(JTextArea text) {
		textarea = text;
		xpos = ptr = 0;
		ostream = new DocumentOutputStream(this);
		prompt = "";
		promptLength = 0;
	}
	/**
	 * コンストラクタ
	 * @param text テキストエリア
	 * @param ppt プロンプト文字列
	 */
	public Document(JTextArea text, String ppt) {
		this(text);
		if(ppt != null) {
			prompt = ppt;
			promptLength = ppt.length();
		}
	}
	/**
	 * このドキュメントに対する出力ストリームを返す
	 * @return このドキュメントに連結している出力ストリーム
	 */
	public OutputStream getOutputStream() {
		return ostream;
	}
	/**
	 * 現在のテキストを返す
	 * @return テキスト
	 */
	public String getText() {
		return textarea.getText();
	}
	/**
	 * カーソルを任意の位置へ移動する
	 * @param pos テキスト中でのカーソルの位置
	 */
	public void moveCursor(int pos) {
		textarea.setCaretPosition(pos);
		ptr = pos;
	}
	/**
	 * カーソルの現在の位置を再調整する
	 */
	public void relocate() {
		moveCursor(ptr);
	}
	public boolean isJapaneseMode() {
		return inputmode;
	}
	public void turnInputMode() {
	}
	/**
	 * 現在のカーソル位置へテキストを挿入する
	 * @param msg 挿入するテキスト
	 */
	public void insert(String msg) {
		// カーソル表示OFF
		textarea.getCaret().setVisible(false);
		textarea.insert(msg, ptr);
		// カーソルを移動
		ptr += msg.length();
		xpos += msg.length();
		// カーソル表示ON
		textarea.getCaret().setVisible(true);
	}
	/**
	 * 現在のカーソル位置へ文字を挿入する
	 * @param c
	 */
	public void insert(char c) {
		insert("" + c);
	}
	/**
	 * 行頭から行末までを削除する
	 */
	public void deleteLine() {
		moveToLineBegin();
		textarea.setText(textarea.getText().substring(0, ptr));
	}
	/**
	 * カーソルを行頭へ移動する
	 */
	public void moveToLineBegin() {
		// 一度最後の行の最後の文字へ移動する
		moveToEnd();
		int p = getLineBeginPos();
		xpos = promptLength;
		moveCursor(p);
		relocate();
	}
	/**
	 * カーソルを行末へ移動する
	 */
	public void moveToEnd() {
		textarea.setCaretPosition(textarea.getText().length());
		ptr = textarea.getText().length();
		xpos = getCurrentXPos();
	}
	/**
	 * カーソルを右へ1文字移動する
	 */
	public void moveToRight() {
		// 行末でない場合は移動できる
		if(
			ptr < textarea.getText().length() &&
			textarea.getText().charAt(ptr) != '\n'
		) {
			ptr++;
			xpos++;
			relocate();
		}
	}
	/**
	 * カーソルを左へ1文字移動する
	 */
	public void moveToLeft() {
		// 行頭でなければ移動できる
		if(xpos > promptLength) {
			ptr--;
			xpos--;
			relocate();
		}
	}
	/**
	 * 改行する
	 */
	public void CRLF() {
		moveToEnd();
		insert('\n');
		xpos = 0;
	}
	/**
	 * 行を確定する
	 */
	public void commit() {
		moveToEnd();
		setCommitedLine(getCurrentLine());
		CRLF();
	}
	/**
	 * プロンプトを出力する
	 */
	public void prompt() {
		insert(prompt);
	}
	/**
	 * 現在のカーソル位置を返す
	 * @return カーソル位置
	 */
	public int getCurrentPosition() {
		return ptr;
	}
	/**
	 * 現在のカーソル位置の文字を削除する
	 * DELキー押下と同じ状態になる
	 */
	public void delete() {
		if(ptr < textarea.getText().length()) {
			String text = textarea.getText();
			textarea.setText(text.substring(0, ptr) + text.substring(ptr+1));
		}

		relocate();
	}
	/**
	 * 現在のカーソル位置の1つ前の文字を削除し，カーソルを移動する
	 * BSキー押下と同じ状態になる
	 */
	public void backSpace() {
		// カーソルを移動する
		// ただし，行頭の場合は移動しない
		if(xpos > promptLength) {
			String text = textarea.getText();
			textarea.setText(
				text.substring(0, ptr-1) +
				(ptr >= text.length() ? "" : text.substring(ptr))
			);
			xpos--;
			ptr--;

			relocate();
		}
	}
	/**
	 * 行頭を探す
	 * @return 行頭の位置
	 */
	public int getLineBeginPos() {
		int bXpos = xpos;
		int bPtr = ptr;
		while(bXpos > promptLength) {
			bXpos--;
			bPtr--;
		}

		return bPtr;
	}
	/**
	 * 現在のカーソルのX座標を計算する
	 * @return カーソルのX座標
	 */
	protected int getCurrentXPos() {
		int bPtr = ptr;
		int xpos = 0;
		while(bPtr > promptLength && textarea.getText().charAt(bPtr-1) != '\n') {
			bPtr--;
			xpos++;
		}

		return xpos;
	}
	/**
	 * 最後にENTERが押下された際の行内容を設定する
	 * @return 行内容
	 */
	protected void setCommitedLine(String line) {
		commitedLine = line;
	}
	/**
	 * 最後にENTERが押下された際の行内容を返す
	 * @return 行内容
	 */
	public String getCommitedLine() {
		return commitedLine == null ? "" : commitedLine;
	}
	/**
	 * 現在カーソルのある行の内容を返す
	 * @return 行内容
	 */
	protected String getCurrentLine() {
		return textarea.getText().substring(getLineBeginPos());
	}
	/**
	 * ドキュメントクラスに連結する出力ストリーム
	 * @author satoshi akabane@logical-paradox.org
	 * @version $Revision: 1.8 $
	 */
	class DocumentOutputStream extends OutputStream {
		/** ドキュメント */
		private final Document doc;
		/**
		 * コンストラクタ
		 * @param d ドキュメント
		 */
		protected DocumentOutputStream(Document d) {
			doc = d;
		}
		/**
		 * バッファに対して1バイト出力する
		 * @param b 出力対象
		 * @throws IOException 出力に失敗した場合の例外
		 */
		public void write(int b) throws IOException {
			if(b == '\n') {
				doc.CRLF();
			} else {
				doc.insert((char)b);
			}
		}
	}
}
