/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.filter;

import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.system.Closer;

import java.io.PrintWriter;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
import java.io.ByteArrayOutputStream;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletResponse;

/**
 * FileFilter で使用する、File圧縮するServletOutputStreamクラスです。
 *
 * @og.group フィルター処理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FileResponseStream extends ServletOutputStream {
	/** 内部出力ストリーム */
	protected ByteArrayOutputStream filestream	;
	/** クローズ判定 */
	protected boolean isClosed	;
	/** サーブレット出力ストリーム */
	protected ServletOutputStream output	;

	private final String filename ;

	/**
	 * コンストラクター
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
	 *
	 * @param	response	レスポンス
	 * @param	filename	ファイル名
	 * @throws IOException 入出力エラーが発生したとき
	 */
	public FileResponseStream( final ServletResponse response,final String filename ) throws IOException {
		super();
		// 4.3.4.4 (2009/01/01)
		this.filename = filename;
		isClosed = false;
		this.output = response.getOutputStream();
		filestream = new ByteArrayOutputStream();
	}

	/**
	 * このストリームを閉じ、このストリームに関連するすべてのシステムリソースを解放します。
	 *
	 * close の汎用規約では、close は出力ストリームを閉じます。閉じられたストリームは
	 * 出力処理を実行できません。また、それを開き直すことはできません。
	 *
	 * @og.rev 5.6.4.2 (2013/05/17) fileDownload のエンコードは、UTF-8 ではなく、UnicodeLittle でセーブする。
	 *
	 * @throws IOException 入出力エラーが発生したとき
	 */
	@Override
	public void close() throws IOException {
		if( isClosed ) {
			return ;
		}
		try {
			filestream.flush();

			// 5.6.4.2 (2013/05/17) ファイル名が fileDownload で始まる場合は、Streamのままセーブする。
			if( filename.indexOf( "fileDownload:" ) >= 0 ) {
				final BufferedOutputStream toStream = new BufferedOutputStream( new FileOutputStream( filename.replace( "fileDownload:","" ) ) );
				filestream.writeTo( toStream );
				Closer.ioClose( toStream );
			}
			else {
				String msg = filestream.toString( "UTF-8" );
				final FileResponseTransform trans = new FileResponseTransform();
				msg = trans.replace( filename,msg );

				final PrintWriter writer = FileUtil.getPrintWriter( new File( filename ), "UTF-8" );
				writer.print( msg );
				Closer.ioClose( writer );
			}
			System.out.println( filename + " Saved" );

			output.flush();
		}
		catch( final Throwable th ) {
			System.out.println( filename + " Error! " + th );
		}
		finally {
			isClosed = true;
			Closer.ioClose( filestream );
			Closer.ioClose( output );
		}
	}

	/**
	 * この出力ストリームをフラッシュし、バッファに入っている出力バイトをすべて強制的書き込みますに。
	 *
	 * flush の汎用規約では、それまでに書き込まれたバイトが出力ストリームの
	 * 実装によってバッファに入れられている場合に flush を呼び出すと、それらのバイトは
	 * ただちにその目的の転送先に書き込まれます。
	 *
	 * @og.rev 5.1.7.0 (2010/06/01) isClosed == true の場合に Exception でなく、return にする。
	 * @throws IOException 入出力エラーが発生したとき
	 */
	@Override
	public void flush() throws IOException {
		if( isClosed ) {
			return ;
		}
		filestream.flush();
		output.flush();
	}

	/**
	 * この出力ストリームに指定されたバイトを書き込みます。
	 *
	 * write の汎用規約では、1 バイトが
	 * 出力ストリームに書き込まれます。書き込まれるバイトは、引数 b の下位 8 ビットです。
	 * b の上位 24 ビットは無視されます。
	 *
	 * @og.rev 5.1.7.0 (2010/06/01) isClosed == true の場合に Exception でなく、return にする。
	 *
	 * @param	bt	byteデータ
	 * @throws IOException 入出力エラーが発生したとき
	 */
	@Override
	public void write( final int bt ) throws IOException {
		if( isClosed ) {
			return ;
		}
		filestream.write((byte)bt);
		output.write((byte)bt);
	}

	/**
	 * 指定されたバイト配列からこの出力ストリームに b.length バイトを書き込みます。
	 *
	 * write(b) の汎用規約では、write(b) の効果は write(b, 0, b.length) を呼び出す
	 * 場合とまったく同じです。
	 *
	 * @param	bt	バイト配列
	 * @throws IOException 入出力エラーが発生したとき
	 */
	@Override
	public void write(final byte[] bt) throws IOException {
		write(bt, 0, bt.length);
	}

	/**
	 * オフセット off から始まる指定のバイト配列からこの出力ストリームに len バイトを書き込みます。
	 *
	 * write(b, off, len) の汎用規約では、配列 b 内の一定のバイトが出力ストリームに順番に
	 * 書き込まれます。この処理で最初に書き込まれるバイトは要素 b[off]、最後に書き込まれる
	 * バイトは要素 b[off+len-1] です。
	 *
	 * @og.rev 5.1.7.0 (2010/06/01) isClosed == true の場合に Exception でなく、return にする。
	 *
	 * @param	bt	バイト配列
	 * @param	off	オフセット数
	 * @param	len	書き込みバイト数
	 * @throws IOException 入出力エラーが発生したとき
	 */
	@Override
	public void write( final byte bt[], final int off, final int len ) throws IOException {
		if( isClosed ) {
			return ;
		}
		output.write(bt, off, len);
		filestream.write(bt, off, len);
	}

	/**
	 * すでにストリームが閉じられているかどうかを返します。
	 *
	 * @return	すでにストリームが閉じられているかどうか
	 */
	public boolean closed() {
		return isClosed;
	}

	/**
	 * Tomcat8 / Servlet 3.1 で追加された abstract メソッド。
	 *
	 * Checks if a non-blocking write will succeed. If this returns
	 * <code>false</code>, it will cause a callback to
	 * WriteListener#onWritePossible() when the buffer has emptied. If
	 * this method returns <code>false</code> no further data must be written
	 * until the contain calls WriteListener#onWritePossible().
	 *
	 * @og.rev 5.6.8.2 (2013/09/20) 新規追加(Tomcat8 / Servlet 3.1 で追加された abstract メソッド)
	 *
	 * @return true:書き込み可能/false:不可 (true if data can be written, else false)
	 *
	 * @since Servlet 3.1
	 */
	@Override
	public boolean isReady() { return false; }

	/**
	 * Tomcat8 / Servlet 3.1 で追加された abstract メソッド。
	 *
	 * Sets the WriteListener for this ServletOutputStream and
	 * thereby switches to non-blocking IO. It is only valid to switch to
	 * non-blocking IO within async processing or HTTP upgrade processing.
	 *
	 * @og.rev 5.6.8.2 (2013/09/20) 新規追加(Tomcat8 / Servlet 3.1 で追加された abstract メソッド)
	 *
	 * @param listener	The non-blocking IO write listener
	 *
	 * @throws IllegalStateException	If this method is called if neither
	 *									async nor HTTP upgrade is in progress or
	 *									if the WriteListener has already
	 *									been set
	 * @throws NullPointerException 	If listener is null
	 *
	 * @since Servlet 3.1
	 */
	@Override
	public void setWriteListener( final jakarta.servlet.WriteListener listener ) {
		// 何も実装しない。
	}
}
