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


import java.util.*;
import java.io.*;
import paraselene.*;
import paraselene.tag.*;
import paraselene.tag.form.*;

class PathData {
	boolean	called = false;
	Page	from = null;
	Page	to = null;
	Page	redirect = null;
	Integer	from_histry = null;
	Integer	to_histry = null;
}

class Null extends NullPage {
	public Forward input( RequestParameter req, Forward fw ) throws PageException{
		return null;
	}
}

/**
 * トランザクションシーケンサ。
 * URIの生成と解析を司ります。
 */
public class TransactionSequencer implements Serializable {
	private static final long serialVersionUID = 2L;
	private static final String[] SALANE = new String[] {
		"lu", "mo", "on", "na", "art", "mis"
	};
	/**
	 * Paraselene拡張子。
	 */
	public static final String EXTENSION = "." + SALANE[3];
	private volatile int seq = 0;
	private HashMap<Integer,Boolean>	no_map = new HashMap<Integer,Boolean>();
	private static final String[] HEAD = { "seleno", "lilith" };
	private static final int HEAD_LENGTH = 6;

	TransactionSequencer() {
	}

	private int newNo( PageID id ) {
		long	dt = (new Date().getTime() / 1000) & 0x7fffff;
		int		ret = (int)(dt << 8);
		synchronized( this ) {
			ret |= (seq & 0xff);
			seq += (int)(Math.random() * 5);
			no_map.put( ret, false );
		}
		return ret;
	}

	/**
	 * URIの生成。
	 * @param seq シーケンサ。nullなら通番発行しない。
	 * @param from 遷移元ページ。null可。
	 * @param to 遷移先ページ。null不可。
	 * @param from_key 遷移元キー。
	 * @param to_key 遷移先キー。
	 * @param flag true:通常、false:検索エンジン。
	 * @return URI。
	 */
	public static String makeURI( TransactionSequencer seq, PageID from, PageID to, int from_key, int to_key, boolean flag ) {
		StringBuilder	buf = new StringBuilder();
		if ( flag && seq != null ) {
			buf = buf.append( SALANE[0] );
			buf = buf.append( "." );
			buf = buf.append( HEAD[0] );
			buf = buf.append( Integer.toString( seq.newNo( to ), Character.MAX_RADIX ) );
			buf = buf.append( "." );
		}
		if ( from != null ) {
			buf = buf.append( SALANE[1] );
			buf = buf.append( "." );
			buf = buf.append( Integer.toString( from.getID(), Character.MAX_RADIX ) );
			buf = buf.append( "." );
			if ( flag && from_key != HistorySet.ROOT ) {
				buf = buf.append( SALANE[4] );
				buf = buf.append( "." );
				buf = buf.append( Integer.toString( from_key, Character.MAX_RADIX ) );
				buf = buf.append( "." );
			}
		}
		if ( flag && to_key != HistorySet.ROOT ) {
			buf = buf.append( SALANE[5] );
			buf = buf.append( "." );
			buf = buf.append( Integer.toString( to_key, Character.MAX_RADIX ) );
			buf = buf.append( "." );
		}
		buf = buf.append( SALANE[2] );
		buf = buf.append( "." );
		buf = buf.append( Integer.toString( to.getID(), Character.MAX_RADIX ) );
		buf = buf.append( "." );
		buf = buf.append( SALANE[3] );
		return buf.toString();
	}

	static String makeRedirectURI( TransactionSequencer seq, PageID to ) {
		StringBuilder	buf = new StringBuilder();
		buf = buf.append( SALANE[0] );
		buf = buf.append( "." );
		buf = buf.append( HEAD[1] );
		buf = buf.append( Integer.toString( seq.newNo( null ), Character.MAX_RADIX ) );
		buf = buf.append( "." );
		buf = buf.append( SALANE[2] );
		buf = buf.append( "." );
		buf = buf.append( Integer.toString( to.getID(), Character.MAX_RADIX ) );
		buf = buf.append( "." );
		buf = buf.append( SALANE[3] );
		return buf.toString();
	}

	private static Page getPage( String no, PageFactory fact ) {
		Page	ret = null;
		try {
			ret = fact.getPage( Integer.parseInt( no, Character.MAX_RADIX ) );
		}
		catch( Exception e ) {}
		if ( ret == null )	return new Null();
		return ret;
	}

	static PathData parsePath( TransactionSequencer seq, String uri, PageFactory fact, HistorySet hset ) {
		String[]	path = uri.split( "\\?" );
		if ( path != null )	uri = path[0];
		path = uri.split( "/" );
		for ( int i = path.length - 1; i >= 0; i-- ) {
			String[]	body = path[i].split( "\\." );
			if ( body.length == 0 )	continue;
			if ( !SALANE[3].equals( body[body.length - 1] ) )	continue;
			PathData	ret = new PathData();
			int	cnt = body.length - 1;
			boolean	redirect_f = false;
			boolean	request_id = false;
			for ( int j = 0; j < cnt; j += 2 ) {
				if ( seq != null && SALANE[0].equals( body[j] ) ) {
					if ( body[j + 1].length() <= HEAD_LENGTH )	return null;
					String	head = body[j + 1].substring( 0, HEAD_LENGTH );
					String	number = body[j + 1].substring( HEAD_LENGTH );
					if ( HEAD[1].equals( head ) )	redirect_f = true;
					else if ( !HEAD[0].equals( head ) ) {
						return null;
					}
					int	no = Integer.parseInt( number, Character.MAX_RADIX );
					synchronized( seq ) {
						Boolean	flag = seq.no_map.get( no );
						if ( flag == null )	return null;
						request_id = true;
						ret.called = flag;
						seq.no_map.put( no, true );
					}
				}
				else if ( SALANE[1].equals( body[j] ) ) {
					ret.from = getPage( body[j + 1], fact );
				}
				else if ( SALANE[2].equals( body[j] ) ) {
					if ( redirect_f ) {
						ret.redirect = getPage( body[j + 1], fact );
					}
					else {
						ret.to = getPage( body[j + 1], fact );
					}
				}
				else if ( SALANE[4].equals( body[j] ) ) {
					ret.from_histry = Integer.parseInt( body[j + 1], Character.MAX_RADIX );
				}
				else if ( SALANE[5].equals( body[j] ) ) {
					ret.to_histry = Integer.parseInt( body[j + 1], Character.MAX_RADIX );
					if ( hset != null && ret.to_histry == HistorySet.RAND ) {
						ret.to_histry = hset.toInt();
					}
				}
			}
			if ( ret.from != null ) {
				Page	out = ret.to;
				if ( out == null )	out = ret.redirect;
				if ( out != null ) {
					if ( seq != null && out.isCheckRepeatSameRequest() && !request_id ) {
						return null;
					}
				}
			}
			return ret;
		}
		return null;
	}

	static boolean isMustSession( String uri ) {
		String[]	path = uri.split( "/" );
		for ( int i = path.length -1; i >= 0; i-- ) {
			String[]	body = path[i].split( "\\." );
			if ( body.length == 0 )	continue;
			if ( !SALANE[3].equals( body[body.length - 1] ) )	continue;
			int	cnt = body.length - 1;
			for ( int j = 0; j < cnt; j += 2 ) {
				if ( SALANE[0].equals( body[j] ) )	return true;
				if ( SALANE[1].equals( body[j] ) )	return true;
			}
			return false;
		}
		return false;
	}
}

