package org.phosphoresce.commons.io;

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

import org.phosphoresce.commons.util.ByteFIFOStack;
import org.phosphoresce.commons.util.StringUtil;

/**
 * ストリーム出力時に特定の文字列を置き換えて出力を行うOutputStreamラッパークラス<br>
 * <br>
 * 当クラスは内部で出力バイトデータをバッファリングし、クラス初期化時に指定された
 * 置換対象文字列をバッファされたデータとマッチング処理して、一致した場合に、
 * 置換処理を行って出力を行います。<br>
 * 出力処理自体は元のOutputStreamに委譲する形となりますが、マッチング処理の為、
 * 内部的にバイトデータをバッファリングします。その為、ユーザーは適宜、
 * ストリームのflush処理を行う必要があることに留意してください。<br>
 *
 * @author Kitagawa<br>
 *
 *<!--
 * 更新日		更新者			更新内容
 * 2008/11/06	Kitagawa		新規作成
 *-->
 */
public class ReplacableOutputStream extends OutputStream {

	/** ソースストリームオブジェクト */
	private OutputStream stream;

	/** バイトデータスタック */
	private ByteFIFOStack stack;

	/** 正規表現比較 */
	private boolean regexp;

	/** 置換対象文字列 */
	private String match;

	/** 置換文字列 */
	private String replace;

	/**
	 * コンストラクタ<br>
	 */
	private ReplacableOutputStream() {
		super();
	}

	/**
	 * コンストラクタ<br>
	 * @param stream ソースストリームオブジェクト
	 * @param match 置換対象文字列
	 * @param replace 置換文字列
	 * @param regexp 正規表現比較を行う場合はtrueを指定
	 * @param regexpSize 正規表現比較を行う場合、比較用バッファサイズを指定します
	 */
	public ReplacableOutputStream(OutputStream stream, String match, String replace, boolean regexp, int regexpSize) {
		super();
		this.stream = new BufferedOutputStream(stream);
		this.stack = new ByteFIFOStack(match == null ? 0 : regexp ? regexpSize < 0 ? 0 : regexpSize : match.getBytes().length);
		this.match = match == null ? "" : match;
		this.replace = replace == null ? "" : replace;
		this.regexp = regexp;
	}

	/**
	 * コンストラクタ<br>
	 * @param stream ソースストリームオブジェクト
	 * @param match 置換対象文字列
	 * @param replace 置換文字列
	 */
	public ReplacableOutputStream(OutputStream stream, String match, String replace) {
		this(stream, match, replace, false, -1);
	}

	/**
	 * 指定されたバイトデータを出力します。<br>
	 * 但し、flushが行われるまではソースストリームに対して
	 * 全てのバイトデータが書き込まれるわけではありません。<br>
	 * @param data バイトデータ
	 * @throws IOException ソースストリームに対する操作で入出力例外が発生した場合にスローされます
	 * @see java.io.OutputStream#write(int)
	 */
	public void write(int data) throws IOException {
		if (StringUtil.isEmpty(match)) {
			stream.write(data);
		} else {
			byte[] bytes = stack.push(data);
			if (bytes.length > 0) {
				stream.write(bytes);
			}
			String stackedValue = new String(stack.getBytes());
			if (regexp) {
				if (stackedValue.matches(match)) {
					stackedValue = stackedValue.replaceAll(match, replace);
					stream.write(stackedValue.getBytes());
					stack.clear();
				}
			} else {
				if (match.equals(stackedValue)) {
					stackedValue = StringUtil.replace(stackedValue, match, replace);
					stream.write(stackedValue.getBytes());
					stack.clear();
				}
			}
		}
	}

	/**
	 * ストリームのクローズ処理を行います。<br>
	 * @throws IOException ソースストリームに対する操作で入出力例外が発生した場合にスローされます
	 * @see java.io.OutputStream#close()
	 */
	public void close() throws IOException {
		flush();
		stream.close();
	}

	/**
	 * バッファされているデータを全て書き込みます。<br>
	 * @throws IOException ソースストリームに対する操作で入出力例外が発生した場合にスローされます
	 * @see java.io.OutputStream#flush()
	 */
	public void flush() throws IOException {
		stream.write(stack.popAll());
		stream.flush();
	}
}
