package org.opengion.hayabusa.servlet;

import org.opengion.fukurou.util.LogWriter;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;

/**
 * CometProcessor インターフェースを実装した、コメットサーブレットのサンプルクラスです。
 *
 * これは、Tomcat の実装で、HTTP の接続を保持したまま、クライアント側を動的に変更する技術です。
 * ここでは、どのようなことができるか、テスト的にサーブレットを作成しました。
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public class TestCometServlet extends HttpServlet  implements CometProcessor {
	private static final long serialVersionUID = 400020070911L;

	private static final int    TIMEOUT		 = 60 * 1000 * 30 ;	// (ms)
	private static final String CONTENT_TYPE = "text/html;charset=UTF-8" ;
	private static final String HTML_HEADER  =
					"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
				+	"<head><title>TestCometServlet Message</title></head>\n"
				+	"<body>" ;
	private static final String HTML_FOOTER  = "</body></html>" ;

	private transient List<HttpServletResponse> responses = new ArrayList<HttpServletResponse>();

	/**
	 * CometEvent を処理します。
	 * 
	 * @param event CometEvent
	 * @throws IOException, ServletException
	 */
	public void event( final CometEvent event ) throws IOException, ServletException {
		final HttpServletRequest request = event.getHttpServletRequest();

		System.out.println( request.getMethod() + " : " + event.getEventType() );

		if( "POST".equals(request.getMethod() )) { 	// 送信されたメッセージ
			callPOST( request );
			event.close();
		}
		else {
			final HttpServletResponse response = event.getHttpServletResponse();
			response.setContentType( CONTENT_TYPE );
			final PrintWriter writer = response.getWriter();
			switch( event.getEventType() ) {
				case BEGIN :	// コネクション確立
					// タイムアウトの設定
					event.setTimeout( TIMEOUT );
					callBEGIN( writer );
					addResponce(response);
					callPOST( request );
					break;
				case READ :		// データ入力
					removeResponce(response);
					callREAD( writer );
					event.close();
					break;
				case END :		// リクエスト終了
					removeResponce(response);
					callEND( writer );
					event.close();
					break;
				case ERROR :	// エラー
					removeResponce(response);
					callERROR( writer );
					event.close();
					break;
				default:		// ありえないはず。
					removeResponce(response);
					event.close();
					break;
			}
		}
	}

	/**
	 * POST を処理します。
	 * 
	 * @param request HttpServletRequest
	 */
	private void callPOST( final HttpServletRequest request ) {
		final String user	 = request.getParameter("user");
		final String message = request.getParameter("message");
		System.out.println( user + "> " + message );
		// 全てのクライアントへ送信
		if( user != null ) {
			sendMessage( user + "> " + message + "<br/>" );
		}
	}

	/**
	 * BEGIN を処理します。
	 * 
	 * @param writer PrintWriter
	 */
	private void callBEGIN( final PrintWriter writer ) {
		System.out.println( "コネクション確立" );
		writer.println( HTML_HEADER );
		// バッファの問題か、最初にある程度データを入力しないと、表示されません。
		writer.println( "ようこそ、お越しくださいました。comet の実験コーナーです。<br/>" );
		writer.println( "このメッセージは、コネクション成立ユーザーにのみ、返信しています。<br/>" );
		writer.flush();
	}

	/**
	 * READ を処理します。
	 * 
	 * @param writer PrintWriter
	 */
	private void callREAD( final PrintWriter writer ) {
		System.out.println( "データ入力" );
		writer.println( HTML_FOOTER );
	}

	/**
	 * END を処理します。
	 * 
	 * @param writer PrintWriter
	 */
	private void callEND( final PrintWriter writer ) {
		System.out.println( "リクエスト終了" );
		writer.println( HTML_FOOTER );
	}

	/**
	 * ERROR を処理します。
	 * 
	 * @param writer PrintWriter
	 */
	private void callERROR( final PrintWriter writer ) {
		System.out.println( "エラー" );
	}

	/**
	 * HttpServletResponse を追加します。
	 * 
	 * ここで追加されたレスポンスが、同時にサーバーからクライアントに変更を
	 * かけることができます。
	 * 
	 * @param response HttpServletResponse
	 */
	private void addResponce( final HttpServletResponse response ) {
		synchronized( responses ) {
			responses.add( response );
		}
	}

	/**
	 * HttpServletResponse を削除します。
	 * 
	 * クライアントに変更をかける必要が無くなった レスポンスを削除します。
	 * 
	 * @param response HttpServletResponse
	 */
	private void removeResponce( final HttpServletResponse response ) {
		synchronized( responses ) {
			responses.remove( response );
		}
	}

	/**
	 * HttpServletResponse にメッセージを転送します。
	 * 
	 * 各、クライアントにメッセージを転送します。
	 * 
	 * @param message String
	 */
	public void sendMessage( final String message ) {
		final HttpServletResponse[] resp ;

		synchronized( responses ) {
			resp = responses.toArray( new HttpServletResponse[responses.size()] );
		}

		PrintWriter writer ;
		if( resp != null ) {
//			for( int i=0; ( resp != null && i<resp.length ); i++ ) {
			for( int i=0; i<resp.length; i++ ) {
				try {
					writer = resp[i].getWriter();
					writer.println( message );
					writer.flush();
				} catch( IOException ex ) {
					LogWriter.log( ex );
				}
			}
		}
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectOutputStream
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectInputStream
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
		strm.defaultReadObject();
		responses = new ArrayList<HttpServletResponse>();
	}
}
