package org.phosphoresce.commons.util;

import java.io.IOException;
import java.io.Serializable;

/**
 * バイトデータスタッククラス<br>
 * <br>
 * 当クラスは動的に読み込むバイト配列情報における前後の情報を参照する際にユーザー側で、
 * 前後のバイトデータを比較することを容易にするデータスタック機能を提供します。<br>
 * <br>
 * 但し、全てのバイト情報を当クラスで保持して行くことは不可能であるため、
 * クラス初期家事に指定されたスタックサイズまでのデータを管理します。<br>
 * スタックサイズを超えるバイトデータがプッシュされた場合、古いバイトデータから
 * 破棄されて行きます。<br>
 * <br>
 * 尚、当クラスはjava.util.Stackとはインタフェースは統一されていません。<br>
 *
 * @author Kitagawa<br>
 *
 *<!--
 * 更新日		更新者			更新内容
 * 2008/10/31	Kitagawa		新規作成
 * 2008/11/06	Kitagawa		0スタックサイズ時の不具合対応
 *-->
 */
public class ByteFIFOStack implements Serializable {

	/** 最大スタックサイズ */
	protected int maxSize;

	/** 現在のスタックサイズ */
	protected int nowSize;

	/** スタックバッファ */
	protected byte[] stack;

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

	/**
	 * コンストラクタ<br>
	 * @param maxSize スタックサイズ
	 */
	public ByteFIFOStack(int maxSize) {
		super();
		this.maxSize = maxSize < 0 ? 0 : maxSize;
		this.nowSize = 0;
		this.stack = new byte[maxSize < 0 ? 0 : maxSize];
	}

	/**
	 * 指定された1バイトをスタックに対してpushします。<br>
	 * @param b バイトデータ
	 * @return スタックに入りきれずに強制的にpopされたバイトデータ(スタック許容内の場合は0サイズ配列が返却されます)
	 * @throws IOException push、pop処理実行中に入出力例外が発生した場合にスローされます
	 */
	public byte[] push(int b) throws IOException {
		return push(new byte[] { (byte) b }, 0, 1);
	}

	/**
	 * 指定されたバイトをスタックに対してpushします。<br>
	 * @param bytes バイトデータ
	 * @param offset データの開始オフセット
	 * @param length 書き込むバイト数
	 * @return スタックに入りきれずに強制的にpopされたバイトデータ(スタック許容内の場合は0サイズ配列が返却されます)
	 * @throws IOException push、pop処理実行中に入出力例外が発生した場合にスローされます
	 */
	public byte[] push(byte[] bytes, int offset, int length) throws IOException {
		byte[] buffer = new byte[nowSize + length];
		System.arraycopy(stack, 0, buffer, 0, nowSize);
		System.arraycopy(bytes, offset, buffer, nowSize, length);
		if (buffer.length - maxSize >= 0) {
			byte[] result = new byte[buffer.length - maxSize];
			System.arraycopy(buffer, 0, result, 0, result.length);
			stack = new byte[maxSize];
			System.arraycopy(buffer, result.length, stack, 0, buffer.length - result.length);
			nowSize = nowSize + length > maxSize ? maxSize : nowSize + length;
			return result;
		} else {
			stack = new byte[maxSize];
			System.arraycopy(buffer, 0, stack, 0, buffer.length);
			nowSize = buffer.length;
			return new byte[0];
		}
	}

	/**
	 * 指定されたバイトをスタックに対してpushします。<br>
	 * @param bytes バイトデータ
	 * @return スタックに入りきれずに強制的にpopされたバイトデータ(スタック許容内の場合は0サイズ配列が返却されます)
	 * @throws IOException push、pop処理実行中に入出力例外が発生した場合にスローされます
	 */
	public byte[] push(byte[] bytes) throws IOException {
		return push(bytes, 0, bytes.length);
	}

	/**
	 * 指定されたサイズをpopしてバイトデータを取得します。<br>
	 * @param length popするサイズ
	 * @return popされたバイトデータ
	 * @throws IOException push、pop処理実行中に入出力例外が発生した場合にスローされます
	 */
	public byte[] pop(int length) throws IOException {
		length = length > nowSize ? nowSize : length;
		byte[] result = new byte[length];
		byte[] buffer = new byte[maxSize];

		System.arraycopy(stack, 0, result, 0, length);
		nowSize -= length;

		System.arraycopy(stack, length, buffer, 0, maxSize - length);
		stack = buffer;

		return result;
	}

	/**
	 * 指定されたサイズをpopして1バイトデータを取得します。<br>
	 * @return popされた1バイトデータ
	 * @throws IOException push、pop処理実行中に入出力例外が発生した場合にスローされます
	 */
	public byte[] pop() throws IOException {
		return pop(1);
	}

	/**
	 * 全てのバイトデータをpopします。<br>
	 * @return 全てのバイトデータ
	 * @throws IOException
	 */
	public byte[] popAll() throws IOException {
		return pop(nowSize);
	}

	/**
	 * スタックされているデータを全てクリアします。<br>
	 */
	public void clear() {
		nowSize = 0;
		stack = new byte[maxSize];
	}

	/**
	 * スタックされているバイト情報を取得します。<br>
	 * ここで提供されるバイトサイズはクラス初期化時に定義されたスタックサイズと一致するとは限りません。<br>
	 * 初期化時に定義されたスタックサイズを超えることはありませんが、スタック許容内のバッファ時には
	 * このサイズに満たない配列データとして返却されます。<br>
	 * @return スタックされているバイト情報
	 */
	public byte[] getBytes() {
		byte[] result = new byte[nowSize];
		System.arraycopy(stack, 0, result, 0, nowSize);
		return result;
	}
}

