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


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

/**
 * リクエスト。
 */
public class RequestParameter {
	/**
	 * 呼び出しリクエストメソッド。
	 */
	public enum Method {
		GET, POST;

		private static final long serialVersionUID = 2L;
	}

	/**
	 * 日本の携帯キャリア。
	 */
	public enum Mobile {
		/**
		 * DoCoMo。
		 */
		DOCOMO,
		/**
		 * au。
		 */
		AU,
		/**
		 * auまたはTU-KAでHDML機。<BR>ParaseleneはHDMLに対応していません。
		 */
		TU_KA,
		/**
		 * SoftBank、Vodafone、J-PHONE。
		 */
		J_PHONE,
		/**
		 * EMOBILE。
		 */
		EMOBILE,
		/**
		 * その他、PC等。
		 */
		NO_MOBILE;

		private static final long serialVersionUID = 1L;
	}

	/**
	 * ユーザーエージェントから、携帯キャリアを判定する。
	 * @param ua ユーザーエージェント。nullなら、NO_MOBILEを返します。
	 * @return 判定結果。
	 */
	public static Mobile judgeMobile( String ua ) {
		if ( ua == null )	return Mobile.NO_MOBILE;
		if ( ua.indexOf( "DoCoMo" ) != -1 )	return Mobile.DOCOMO;
		if ( ua.indexOf( "KDDI" ) != -1 )	return Mobile.AU;
		if ( ua.indexOf( "emobile" ) != -1 )	return Mobile.EMOBILE;
		if ( ua.indexOf( "SoftBank" ) != -1 ||
			ua.indexOf( "Vodafone" ) != -1 ||
			ua.indexOf( "J-PHONE" ) != -1
		) {
			return Mobile.J_PHONE;
		}
		if ( ua.indexOf( "UP.Browser" ) != -1 )	return Mobile.TU_KA;
		return Mobile.NO_MOBILE;
	}

	/**
	 * 検索エンジンクローラー。
	 */
	public enum SearchEngine {
		/**
		 * Google AdSense。
		 * Google AdSense の掲載広告選定用クローラー
		 * (ページの文言を解析して、適した掲載広告を決定します)です。
		 * Google の検索結果自体には影響しません。
		 */
		GOOGLE_ADSENSE( "mediapartners-google" ),
		/**
		 * Google。
		 * Google の検索結果自体に反映するためのクローラーです。
		 */
		GOOGLE( "googlebot" ),
		/**
		 * Yahoo!。
		 */
		YAHOO( "yahoo!", "slurp" ),
		/**
		 * bing。
		 */
		MSN( "msnbot" ),
		/**
		 * 百度。
		 */
		BAIDU( "baiduspider" ),
		/**
		 * 検索エンジンではない。
		 */
		NO_SEARCHENGINE();
		private String[]	str = null;
		private SearchEngine( String ... s ) { str = s; }
		boolean isHit( String ua ) {
			if ( str == null )	return true;
			for ( int i = 0; i < str.length; i++ ) {
				if ( ua.indexOf( str[i] ) == -1 )	return false;
			}
			return true;
		}
	}
	/**
	 * ユーザーエージェントから、検索エンジンクローラーを判定する。
	 * @param ua ユーザーエージェント。nullなら、NO_SEARCHENGINEを返します。
	 * @return 判定結果。
	 */
	public static SearchEngine judgeSearchEngine( String ua ) {
		if ( ua == null )	return SearchEngine.NO_SEARCHENGINE;
		SearchEngine[]	dim = SearchEngine.values();
		ua = ua.toLowerCase( Locale.ENGLISH );
		for ( int i = 0; i < dim.length; i++ ) {
			if ( dim[i].isHit( ua ) )	return dim[i];
		}
		return SearchEngine.NO_SEARCHENGINE;
	}

	private HashMap<String,RequestItem>	item_map = new HashMap<String,RequestItem>();
	private Method	method;
	private URI	uri = null;
	private HttpSession	session = null;
	private Cookie[]	in_cookie;
	private String	remote_addr;
	private HashMap<String,String[]>	header = new HashMap<String,String[]>();
	private Supervisor	supervisor = null;
	static final String UA_HEAD = "User-Agent";

	/**
	 * ユーザーエージェントから、携帯キャリアを判定する。
	 * @return 判定結果。
	 */
	public Mobile judgeMobile() {
		String[]	ua = getHeader( UA_HEAD );
		if ( ua == null )	return Mobile.NO_MOBILE;
		if ( ua.length < 1 )	return Mobile.NO_MOBILE;
		return judgeMobile( ua[0] );
	}

	/**
	 * ユーザーエージェントから、携帯キャリアを判定する。
	 * @return 判定結果。
	 */
	public SearchEngine judgeSearchEngine() {
		String[]	ua = getHeader( UA_HEAD );
		if ( ua == null )	return SearchEngine.NO_SEARCHENGINE;
		if ( ua.length < 1 )	return SearchEngine.NO_SEARCHENGINE;
		return judgeSearchEngine( ua[0] );
	}

	/**
	 * コンストラクタ。
	 */
	public RequestParameter() {
	}

	/**
	 * メソッドの設定。
	 * @param m メソッド。
	 */
	protected void setMethod( Method m ) {
		method = m;
	}

	/**
	 * セッションの設定。
	 * @param s セッション。
	 */
	protected void setSession( HttpSession s ) {
		session = s;
	}

	/**
	 * クッキーの設定。
	 * @param c クッキー。
	 */
	protected void setCookie( Cookie[] c ) {
		in_cookie = c;
	}

	/**
	 * リクエスト元IPアドレスの設定。
	 * @param ip リモートIP。
	 */
	protected void setRemoteAddr( String ip ) {
		remote_addr = ip;
	}

	/**
	 * リクエストURI設定。
	 * @param u URI。
	 */
	protected void setURI( URI u ) {
		uri = u;
	}

	/**
	 * サーブレット設定。
	 * @param sv サーブレット。
	 */
	protected void setSupervisor( Supervisor sv ) {
		supervisor = sv;
	}

	/**
	 * ヘッダ登録。
	 * @param name ヘッダ名。
	 * @param val 値。
	 */
	public void setHeader( String name, String[] val ) {
		header.put( name.toLowerCase( Locale.ENGLISH ), val );
	}

	void setParam( Method m, HttpServletRequest request, Supervisor sv ) {
		setSupervisor( sv );
		setMethod( m );
		setSession( request.getSession( false ) );
		setCookie( request.getCookies() );
		setRemoteAddr( request.getRemoteAddr() );
		try {
			StringBuffer	buf = request.getRequestURL();
			String	query = request.getQueryString();
			if ( query != null ) {
				buf = buf.append( "?" );
				buf = buf.append( query );
			}
			setURI( new URI( buf.toString() ) );
		}
		catch(Exception e){}
		for ( Enumeration<?>	em = request.getHeaderNames(); em.hasMoreElements(); ) {
			String	key = (String)em.nextElement();
			ArrayList<String>	data = new ArrayList<String>();
			for ( Enumeration<?>	e2 = request.getHeaders( key ); e2.hasMoreElements(); ) {
				data.add( (String)e2.nextElement() );
			}
			setHeader( key, data.toArray( new String[0] ) );
		}
	}

	/**
	 * サーブレットインスタンスの取得。
	 * Supervisorの派生クラスである、実行中のGateインスタンスが得られます。
	 * Supervisorは、HttpServletの派生クラスです。
	 * Gateにメソッドを準備する事により、サーブレットコンテナへアクセスできます。
	 * @return サーブレットインスタンス。
	 */
	public Supervisor getSupervisor() {
		return supervisor;
	}

	/**
	 * リクエストヘッダの取得。複数行存在すれば複数個の配列となります。
	 * 戻り値の配列はカンマで区切りません。行による配列です。
	 * カンマ区切りで扱いたい場合は、getHeaderWithQualityを使用して下さい。
	 * @param name ヘッダ名。
	 * @return ヘッダ。無ければnull。
	 */
	public String[] getHeader( String name ) {
		return header.get( name.toLowerCase( Locale.ENGLISH ) );
	}

	/**
	 * 品質係数付きヘッダ。
	 */
	public class HeaderQuality {
		String 	val;
		Float	q = null;
		HeaderQuality( String org ) throws NumberFormatException {
			String[]	data = org.split( ";" );
			val = data[0].trim();
			if ( data.length > 1 ) {
				String[]	k = data[1].split( "=" );
				if ( k[0].trim().equals( "q" ) ) {
					if ( k.length > 1 ) {
						q = new Float( data[1] );
					}
				}
			}
		}
		/**
		 * ヘッダ設定値の取得。
		 * en;q=0.7 ならば en を返します。
		 * @return ヘッダ設定値。
		 */
		public String getValue() {
			return val;
		}
		/**
		 * 品質係数の取得。
		 * en;q=0.7 ならば 0.7 を返します。
		 * @return 品質係数。設定されていなければnullです。
		 */
		public Float getQuality() {
			return q;
		}
	}

	/**
	 * リクエストヘッダの取得。品質係数(q)を持つヘッダに使用します。
	 * 戻り値の配列は、カンマで区切られた個数で返します。
	 * @param name ヘッダ名。
	 * @return ヘッダ。無ければnull。
	 *
	 */
	public HeaderQuality[] getHeaderWithQuality( String name ) {
		String[]	org = getHeader( name );
		if ( org == null )	return null;
		ArrayList<HeaderQuality>	head = new ArrayList<HeaderQuality>();
		for ( int i = 0; i < org.length; i++ ) {
			String[]	data = org[i].split( "," );
			for ( int j = 0; j < data.length; j++ ) {
				try {
					head.add( new HeaderQuality( data[j] ) );
				}
				catch( NumberFormatException e ) {}
			}
		}
		return head.toArray( new HeaderQuality[0] );
	}

	/**
	 * リクエスト元のIPアドレスの取得。
	 * @return IPアドレス。
	 */
	public String getRemoteAddr() {
		return remote_addr;
	}

	/**
	 * リクエストされたURIの取得。
	 * @return URI。
	 */
	public URI getURI() {
		return uri;
	}

	/**
	 * クッキーの取得。
	 * @return 全てのクッキー。
	 */
	public Cookie[] getCookie() {
		return in_cookie;
	}

	private void setRequestItem( String k, String v, File fb, String mime, boolean f ) {
		RequestItem	item = item_map.get( k );
		if ( item == null ) {
			item = new RequestItem( k );
			item_map.put( k, item );
		}
		item.value.add( v );
		item.file.add( fb );
		item.mime.add( mime );
		item.file_flg.add( f );
	}

	/**
	 * リクエストパラメーターの設定。ファイル付き。<br>
	 * 既に同名のパラメーター名が存在する場合はそこへ追加され、
	 * 複数の値を持つようになります。
	 * @param k パラメーター名。
	 * @param v パラメーター値。
	 * @param f ファイル。
	 * @param type ファイルのコンテントタイプ。
	 */
	public void addItem( String k, String v, File f, String type ) {
		setRequestItem( k, v, f, type, true );
	}

	/**
	 * リクエストパラメーターの設定。ファイル無し。<br>
	 * 既に同名のパラメーター名が存在する場合はそこへ追加され、
	 * 複数の値を持つようになります。
	 * @param k パラメーター名。
	 * @param v パラメーター値。
	 */
	public void addItem( String k, String v ) {
		setRequestItem( k, v, null, null, false );
	}

	/**
	 * パラメーターの削除。
	 * @param k パラメーター名。
	 */
	public void remove( String k ) {
		item_map.remove( k );
	}

	/**
	 * メソッドの取得。
	 * @return メソッド。
	 */
	public Method getMethod() {
		return method;
	}

	/**
	 * セッションの取得。
	 * @return セッション。セッションが発生していない場合はnull。
	 */
	public HttpSession getSession() {
		return session;
	}

	/**
	 * セッションの再作成。
	 * 既存セッションを破棄し、新しくセッションを開始します。
	 * Paraseleneが管理用に持つセッション情報は、Page#output
	 * からリターンした後に再設定されます。
	 * 履歴等、必要な情報はmakeNewSessionする前に取得して下さい。
	 * @return 新しいセッション。
	 */
	public HttpSession makeNewSession() {
		if ( supervisor != null ) {
			session = supervisor.makeNewSession( session );
		}
		return session;
	}

	/**
	 * 履歴の取得。
	 * @param key 画面遷移履歴キー。
	 * @return 履歴。セッションが発生していない場合はnull。
	 */
	public History getHistory( int key ) {
		HttpSession	session = getSession();
		if ( session == null )	return null;
		SessionData	data = (SessionData)session.getAttribute( Supervisor.SESSION_DATA );
		return data.hist.get( key );
	}

	/**
	 * 履歴の取得。
	 * @return 履歴。セッションが発生していない場合はnull。
	 */
	public History getHistory() {
		return getHistory( SandBox.getCurrentPage().getHistoryKey() );
	}

	/**
	 * リクエスト項目の取得。
	 * @param key リクエスト項目名。
	 * @return リクエスト項目。
	 */
	public RequestItem getItem( String key ) {
		return item_map.get( key );
	}

	/**
	 * リクエスト項目名の列挙。
	 * @return 項目名の配列。
	 */
	public String[] getAllKey() {
		int	cnt = item_map.size();
		String[]	ret = new String[cnt];
		int	i = 0;
		for ( String k: item_map.keySet() ) {
			ret[i] = k;
			i++;
		}
		return ret;
	}

	/**
	 * リクエストクエリーの有無。
	 * @return true:クエリーがある、false:クエリーがない。
	 */
	public boolean isExistRequestItem() {
		return item_map.size() > 0;
	}
}

