package core.file;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 改行テキスト出力(Zip)
 *
 * @author Tadashi Nakayama
 */
public class ZippedLineOutputStream extends ZipOutputStream {

	/** ページ内最大数 */
	private int line = -1;
	/** エントリ名 */
	private String name = null;
	/** ページ内カウント */
	private int count = -1;
	/** エントリ数 */
	private int entries = 0;
	/** 直前出力文字 */
	private int prev = 0;
	/** ページタイトル */
	private byte[] title;
	/** タイトル出力フラグ */
	private boolean titleFlg;

	/**
	 * コンストラクタ
	 *
	 * @param os OutputStream
	 */
	public ZippedLineOutputStream(final OutputStream os) {
		super(os);
	}

	/**
	 * コンストラクタ
	 *
	 * @param os OutputStream
	 * @param val Charset
	 */
	public ZippedLineOutputStream(final OutputStream os, final Charset val) {
		super(os, val);
	}

	/**
	 * @see java.io.FilterOutputStream#write(byte[])
	 */
	@Override
	public void write(final byte[] b) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.zip.ZipOutputStream#write(byte[], int, int)
	 */
	@Override
	public synchronized void write(final byte[] b, final int off, final int len) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.io.FilterOutputStream#write(int)
	 */
	@Override
	public void write(final int b) throws IOException {
		if (this.line == this.count) {
			putNextEntry(new ZipEntry(getNextEntryName(true)));
		} else if (!hasEntry()) {
			putNextEntry(new ZipEntry(getNextEntryName(false)));
		}

		super.out.write(b);

		if (this.prev == '\r' && b == '\n') {
			if (1 < this.line) {
				this.count++;
			}
		}
		this.prev = b;
	}

	/**
	 * ページ内最大数設定
	 *
	 * @param val ページ内最大数
	 */
	public void setLine(final int val) {
		this.line = val;
	}

	/**
	 * ページタイトル設定
	 *
	 * @param val ページタイトル（改行付）
	 */
	public void setTitle(final byte[] val) {
		this.title = null;
		if (val != null && 0 < val.length) {
			this.title = val.clone();
		}
	}

	/**
	 * エントリ名設定
	 *
	 * @param val エントリ名
	 */
	public void setEntryName(final String val) {
		this.name = val.replaceAll("[\\\\/:*?\"<>|：￥／？＊［］\\[\\]]", "").trim();
		this.entries = 0;
	}

	/**
	 * エントリ終了
	 *
	 * @throws IOException IO例外
	 */
	@Override
	public void finish() throws IOException {
		if (hasEntry()) {
			closeEntry();
			super.finish();
		}
	}

	/**
	 * エントリ設定
	 *
	 * @param ze ZipEntry
	 * @throws IOException IO例外
	 */
	@Override
	public void putNextEntry(final ZipEntry ze) throws IOException {
		if (hasEntry()) {
			closeEntry();
		}

		super.putNextEntry(ze);
		if (this.title != null && this.titleFlg) {
			super.out.write(this.title);
		}

		this.count = 0;
		this.entries++;
	}

	/**
	 * エントリ存在確認
	 *
	 * @return エントリが存在した場合 true を返す。
	 */
	private boolean hasEntry() {
		return 0 <= this.count;
	}

	/**
	 * 次Entry名取得
	 *
	 * @param flg タイトル出力フラグ
	 * @return Entry名
	 */
	public String getNextEntryName(final boolean flg) {
		this.titleFlg = flg;

		final Function<Integer, String> increment = e -> (0 < e) ? "_" + (e + 1) : "";

		var loc = this.name.lastIndexOf('.');
		if (loc < 0) {
			loc = this.name.length();
		}

		return this.name.substring(0, loc).trim()
				+ increment.apply(this.entries) + this.name.substring(loc);
	}
}
