/*
 * 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.servlet;

import javax.websocket.OnOpen; 
import javax.websocket.OnClose; 
import javax.websocket.OnMessage;
import javax.websocket.OnError;
import javax.websocket.Session; 
import javax.websocket.EndpointConfig; 
import javax.websocket.CloseReason; 
import javax.websocket.server.ServerEndpoint;
import java.io.IOException; 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer; 
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.util.Set;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;

/**
 * http://enterprisegeeks.hatenablog.com/entry/2015/12/17/104815 
 *
 * WebSocketBasicEndpoint.java のソースを参照しています。
 *
 * 設定が、いくつか必要です。
 *   ① /wsdemo をサーバーエンドポイントのURLにしているため
 *      WEB-INF/web.xml の security-constraint の web-resource-collection の
 *      url-pattern に、/wsdemo を追加する必要がある。
 *        <url-pattern>/wsdemo/*</url-pattern>
 *
 *   ② コンパイル時(build.xml)のクラスパスの設定に、
 *      <pathelement path="${env.CATALINA_HOME}/lib/websocket-api.jar" />
 *      を追加する必要がある。
 */
// 1. サーバーエンドポイントのURLの設定を行う。
@ServerEndpoint(value="/wsdemo")
public class WebSocketDemo {

	/** クライアントからの全接続を保持するセット */
	private static final Set<Session> sessSET = Collections.newSetFromMap( new ConcurrentHashMap<Session, Boolean>() );

	/**
	 * デフォルトコンストラクター
	 *
	 */
	public WebSocketDemo() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * 2.クライアントからの接続時にコールされる。
	 * 
	 * 引数は以下が設定可能だが、メソッド内で使用しないなら省略できる。
	 * @param client クライアントの接続情報
	 * @param config 設定情報
	 */
	@OnOpen
	public void onOpen( final Session client, final EndpointConfig config ) {
		System.out.println( client.getId() + " was connected." );
		printSession( client );

		sessSET.add( client );
	}

	/** 
	 * 3.クライアントの切断時にコールされる
	 * 
	 * 引数は使用しなければ省略可能。
	 * @param client 接続
	 * @param reason 切断理由
	 */
	@OnClose
	public void onClose( final Session client, final CloseReason reason ) {
		System.out.println( client.getId() + " was closed by " 
								+		reason.getCloseCode()
								+ "[" + reason.getCloseCode().getCode()+"]" );
		printSession( client );

		sessSET.remove( client );
	}

	/**
	 * 4.エラー時にコールされる。
	 * 
	 * 引数は使用しなければ省略可能。
	 * @param client クライアント接続
	 * @param error エラー
	 */
	@OnError
	public void onError( final Session client, final Throwable error ) {
		System.out.println( client.getId() + " was error." );
		error.printStackTrace();
	}

	/**
	 * 5.テキストメッセージ受信時の処理
	 * 
	 * 全クライアントにメッセージを送信する。（サンプル）
	 * 
	 * 引数は使用しなければ省略可能。
	 * @param text クライアントから送信されたテキスト
	 * @param client 接続情報
	 */
	@OnMessage
	public void onMessage( final String text, final Session client ) throws IOException {
		System.out.println( client.getId() + " was message." + text );

	//	// sessionが持つendPointに紐付く session を取り出す。
	//	for( final Session other : client.getOpenSessions() ) {
	//		other.getBasicRemote().sendText( text );
	//	}
		broadCast( text );		// Setで管理しているopenされたsession (元とかぶる)
	}

	/**
	 * すべてのクライアントにメッセージを送信する。
	 * 
	 * @param text クライアントに送信するテキスト
	 */
	private static void broadCast( final String text ) {
		for( final Session client : sessSET) {
			client.getOpenSessions().forEach( sess -> sess.getAsyncRemote().sendText( text ) );
		}
	}

	/**
	 * 6.バイナリ受信時の処理
	 * 
	 * 送信元に画像を変換して送り返す。
	 * 
	 * 引数は使用しなければ省略可能。
	 * @param buf クライアントから送信されたバイナリ
	 * @param client 接続情報
	 */
	@OnMessage
	public void onMessage( final ByteBuffer buf, final Session client ) throws IOException {
		client.getBasicRemote().sendBinary( grayScall( buf ) );
	}

	/**
	 * 7.画像をグレースケールに変換する。本筋とは関係ない。
	 * 
	 * @param input 引数のバイトバッファー
	 * @return グレースケールに変換されたバイトバッファー
	 */
	private ByteBuffer grayScall( final ByteBuffer input ) throws IOException {
		final BufferedImage img = ImageIO.read( new ByteArrayInputStream( input.array() ) );
		final BufferedImage glay = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY );
		glay.getGraphics().drawImage( img, 0, 0, null );
		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ImageIO.write( glay, "png", bos );

		return ByteBuffer.wrap( bos.toByteArray() );
	}

	/**
	 * Session のセッションを標準出力に表示します。
	 * 
	 * デバッグ用の仮メソッドです。
	 * 
	 * @param session セッションオブジェクト
	 */
	private void printSession( final Session session ) {
		System.out.println( " ID    = " + session.getId() );
	//	System.out.println( " Query = " + session.getQueryString() );		// 引数部分のみ
		System.out.println( " URI   = " + session.getRequestURI() );		// URI すべて

		System.out.println( " RequestParameter : " );						// 引数部分を、展開して、Map化
		session.getRequestParameterMap().forEach( (k,v) -> {
				System.out.println( "  Key=" + k );
				System.out.println( "  Val=" + java.util.Arrays.toString( v.toArray( new String[v.size()] ) ) );
			}
		);
	}
}
