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

import java.util.*;
import java.io.*;

/**
 * 画面遷移履歴セット。スレッドセーフです。
 */
public class HistorySet implements Serializable {
	private static final long serialVersionUID = 2L;
	/**
	 * ルート履歴キー。
	 */
	public static final int	ROOT = 0;
	static final int	RAND = 0x7fffffff;
	private HashMap<String,Integer>		set_no = new HashMap<String,Integer>();
	private HashMap<Integer,History>	hist = new HashMap<Integer,History>();
	private String session_id;

	synchronized void replaceSession( String sid ) {
		session_id = sid;
		for ( History	h : hist.values() ) {
			h.session_id = sid;
		}
	}

	HistorySet( String sid) throws ParaseleneException {
		session_id = sid;
		newNo( ROOT );
	}

	synchronized int getKey( String name ) {
		Integer	key = set_no.get( name );
		if ( key == null )	return -1;
		return key;
	}

	/**
	 * 履歴の取得。
	 * @param key 履歴キー。
	 * @return 履歴。
	 */
	public synchronized History get( int key ) {
		return hist.get( key );
	}

	/**
	 * 履歴キーの列挙。
	 * @return 存在する履歴キー。
	 */
	public synchronized int[] getAllKey() {
		int[]	ret = new int[hist.size()];
		int	i = 0;
		for ( int k : hist.keySet() ) {
			ret[i] = k;
			i++;
		}
		return ret;
	}

	/**
	 * 履歴の破棄。ただし、ルート履歴は破棄できません。
	 * @param key 履歴キー。ROOT であれば無視される。
	 */
	public synchronized void dropHistory( int key ) {
		if ( key == ROOT )	return;
		for ( String k : set_no.keySet() ) {
			if ( set_no.get( k ) == key ) {
				set_no.remove( k );
				break;
			}
		}
		remove( key );
	}

	/**
	 * 履歴の破棄。ルート履歴以外を全て破棄します。
	 */
	public synchronized void dropHistory() {
		int[]	key = getAllKey();
		for ( int i = 0; i < key.length; i++ ) {
			dropHistory( key[i] );
		}
	}

	private synchronized void remove( int key ) {
		if ( set_no.containsValue( new Integer( key ) ) )	return;
		History	h = hist.remove( key );
		if ( h != null ) {
			h.drop_f = true;
			h.removePage();
		}
	}

	synchronized void drop() {
		set_no.clear();
		boolean flag = true;
		while( flag ){
			flag = false;
			for ( int key: hist.keySet() ) {
				remove( key );
				flag = true;
				break;
			}
		}
		Option.trace( "history all drop" );
	}

	synchronized private int newNo( int no ) throws ParaseleneException {
		if ( no < 0 )	no *= -1;
		if ( no == RAND )	no = 1;
		int	org = no;
		while ( true ) {
			if ( hist.get( no ) != null ) {
				no++;
				if ( no < 0 || no == RAND )	no = 1;
				if ( no == org ) {
					throw new ParaseleneException( "HistorySet overflow." );
				}
				continue;
			}
			hist.put( no, new History( no, session_id ) );
			Option.trace( "issue history key %d", no );
			return no;
		}
	}

	int toInt() throws ParaseleneException {
		return newNo( (int)new Date().getTime() & RAND );
	}

	synchronized void forceNewNo( int no ) throws ParaseleneException {
		if ( hist.get( no ) != null )	return;
		newNo( no );
	}

	synchronized int toInt( String name, int org ) throws ParaseleneException {
		if ( name == null )	return org;
		if ( name.isEmpty() || "_self".equals( name ) )	return org;
		if ( "_top".equals( name ) )	return ROOT;
		if ( "_blank".equals( name ) || "_parent".equals( name ) ) {
			return RAND;
		}
		if ( name.indexOf( "paraselene" ) >= 0 )	return org;
		Integer	ret = set_no.get( name );
		if ( ret != null )	return ret;
		int	n = name.hashCode();
		if ( n == ROOT || n == RAND )	n = 1;
		n = newNo( n );
		set_no.put( name, n );
		return n;
	}
}

