/*
 * Paraselene
 * Copyright (c) 2009  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene.ajax;

import java.io.*;
import java.util.*;
import javax.servlet.http.*;


class AjaxHolder {
	private long	time = new Date().getTime();
	Serializable	obj;
	AjaxHolder( Serializable o ) {
		obj = o;
	}
	boolean isLife() {
		long	tm = new Date().getTime() - 1000 * 60 * 5;
		return	time > tm;
	}
}

class AjaxMonitor extends Thread {
	private HashMap<String, HashMap<String, LinkedList<AjaxHolder>>> map;
	AjaxMonitor( HashMap<String, HashMap<String, LinkedList<AjaxHolder>>> m ) {
		map = m;
		setDaemon( true );
		setPriority( MIN_PRIORITY );
		start();
	}
	public void run() {
		while( true ) {
			try {
				Thread.sleep( 1000 * 60 );
				synchronized( map ) {
					for ( String k : map.keySet() ) {
						HashMap<String, LinkedList<AjaxHolder>>	map2 = map.get( k );
						for ( String k2 : map2.keySet() ) {
							LinkedList<AjaxHolder>	list = map2.get( k2 );
							if ( list == null )	continue;
							while( true ) {
								AjaxHolder	h = list.peek();
								if ( h == null )	break;
								if ( h.isLife() )	break;
								list.poll();
							}
						}
					}
				}
			}
			catch( Exception e ) {
				e.printStackTrace();
			}
		}
	}
}

/**
 * 非同期通信用データのキューイング。
 * 任意のpublicクラス(Serializableを実装したもの)をクライアントサイドのjavascriptに
 * JSONとして送る事ができます。詳しくは{@link JSON}を参照して下さい。<br>
 * クライアントから非同期通信が発生すると、蓄積されたキューの内容全てが
 * 送信されます。<br>
 * キューに登録後、5分間のうちに取得されなかったものは削除されます。
 */
public class Ajax implements Serializable, HttpSessionBindingListener {
	private static final long serialVersionUID = 1L;
	private static final String KEY = "paraselene$ajax";
	private static HashMap<String, HashMap<String, LinkedList<AjaxHolder>>>	map =
		new HashMap<String, HashMap<String,LinkedList<AjaxHolder>>>();
	private static AjaxMonitor	monitor = new AjaxMonitor( map );

	/**
	 * 非同期通信用。
	 */
	public Serializable[]	data;
	private Ajax() {}
	private Ajax( LinkedList<AjaxHolder> obj ) {
		ArrayList<Serializable>	list = new ArrayList<Serializable>();
		while ( true ) {
			AjaxHolder	h = obj.poll();
			if ( h == null )	break;
			list.add( h.obj );
		}
		data = list.toArray( new Serializable[0] );
	}
	public void valueBound( HttpSessionBindingEvent ev ) {}
	public void valueUnbound( HttpSessionBindingEvent ev ) {
		String	id = ev.getSession().getId();
		synchronized( map ) {
			map.remove( id );
		}
	}

	/**
	 * 非同期通信蓄積情報の取得。
	 * @return 非同期通信蓄積情報。
	 */
	public static AjaxInformation[] getInformation() {
		synchronized( map ) {
			return AjaxInformation.getInformation( map );
		}
	}

	private static LinkedList<AjaxHolder> getQueue( HttpSession session, String key ) {
		String	id = session.getId();
		HashMap<String, LinkedList<AjaxHolder>>	obj = map.get( id );
		LinkedList<AjaxHolder>	que = null;
		if ( obj == null ) {
			session.removeAttribute( KEY );
			obj = new HashMap<String, LinkedList<AjaxHolder>>();
			map.put( id, obj );
			session.setAttribute( KEY, new Ajax() );
		}
		else {
			que = obj.get( key );
		}
		if ( que == null ) {
			que = new LinkedList<AjaxHolder>();
			obj.put( key, que );
		}
		return que;
	}

	private static LinkedList<AjaxHolder> getQueue( String id, String key ) {
		HashMap<String, LinkedList<AjaxHolder>>	obj = map.get( id );
		if ( obj == null )	return null;
		LinkedList<AjaxHolder>	que = obj.get( key );
		if ( que == null ) {
			que = new LinkedList<AjaxHolder>();
			obj.put( key, que );
		}
		return que;
	}

	/**
	 * 非同期通信応答キュー追加。
	 * このセッションに指定キーのキューを追加します。
	 * @param session セッション。
	 * @param key 登録キー。
	 * @param o キューに追加するオブジェクト。nullならキューの準備だけ行います。
	 */
	public static void add( HttpSession session, String key, Serializable o ) {
		if ( session == null )	return;
		synchronized( map ) {
			LinkedList<AjaxHolder>	que = getQueue( session, key );
			if ( o != null ) {
				que.offer( new AjaxHolder( o ) );
			}
		}
	}
	/**
	 * 非同期通信応答キュー追加。
	 * このセッションに指定キーのキューを追加します。
	 * 事前に第一引数が HttpSession の add を呼び出しておく必要があります。
	 * @param session セッションID。
	 * @param key 登録キー。
	 * @param o キューに追加するオブジェクト。
	 */
	public static void add( String session, String key, Serializable o ) {
		if ( session == null )	return;
		if ( o == null )	return;
		synchronized( map ) {
			LinkedList<AjaxHolder>	que = getQueue( session, key );
			if ( que != null ) {
				que.offer( new AjaxHolder( o ) );
			}
		}
	}

	/**
	 * 非同期通信応答キュー取得。
	 * このセッションから指定キーのキューを取得します。
	 * 取得されるとキューは削除されます。
	 * @param session セッション。
	 * @param key 登録キー。
	 * @return 応答キュー。
	 */
	public static JSON get( HttpSession session, String key ) {
		if ( session == null )	return null;
		JSON	ret = null;
		synchronized( map ) {
			LinkedList<AjaxHolder>	que = getQueue( session, key );
			ret = new JSON( new Ajax( que ) );
		}
		return ret;
	}

	/**
	 * 非同期通信応答キューの削除。
	 * 指定キーのキューを削除します。
	 * @param session セッション。
	 * @param key 登録キー。
	 */
	public static void remove( HttpSession session, String key ) {
		if ( session == null )	return;
		synchronized( map ) {
			getQueue( session, key ).clear();
		}
	}
}

