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

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

import paraselene.*;

/**
 * 遷移先指定。
 */
public class Forward implements Serializable {
	private static final long serialVersionUID = 212L;
	private int	error = 0;
	private transient URI	redirect_uri = null;
	PageID	out_page = null;

	private AjaxForward[]	ajax = null;
	private AjaxForward		not_ajax = null;

	boolean history_f = true;
	boolean session_clear_f = false;
	transient ArrayList<Cookie>	out_cookie = new ArrayList<Cookie>();
	boolean done_f = false;

	boolean is_refresh() {
		if ( this instanceof Feedback )	return false;
		return out_page != null;
	}

	boolean isRefresh( boolean ajax_f ) {
		if ( !ajax_f )	return true;
		return is_refresh();
	}

	boolean isLeave() {
		return error > 0 || redirect_uri != null;
	}

	/**
	 * クッキーの追加。
	 * クライアントに返したいクッキーを設定します。
	 * @param c クッキー。
	 */
	public void addCookie( Cookie c ) {
		out_cookie.add( c );
	}

	Forward(){}

	private StackTraceElement[]	ste403 = new StackTraceElement[0];

	/**
	 * コンストラクタ。エラーを戻す。
	 * <br>セッションは常に解放されます。
	 * @param stat 404等。
	 */
	public Forward( int stat ) {
		error = stat;
		session_clear_f = true;
		if ( stat == 403 ) {
			ste403 = Thread.currentThread().getStackTrace();
		}
	}

	/**
	 * コンストラクタ。リダイレクト指定。
	 * @param uri 管理外のサイト等。
	 * @param session_off true:セッションを解放する、false:セッションを維持する
	 */
	public Forward( URI uri, boolean session_off ) {
		redirect_uri = uri;
		session_clear_f = session_off;
	}

	/**
	 * コンストラクタ。ページへ遷移。
	 * @param id 指定ページの出力。
	 * @param history_use true:履歴にあれば履歴インスタンスを使用、
	 * false:履歴にあっても新規インスタンスを使用
	 * @param session_off true:セッションを解放する、false:セッションを維持する
	 */
	public Forward( PageID id, boolean history_use, boolean session_off ) {
		if ( !(this instanceof Closure) && !(this instanceof Feedback) ) {
			id = id.getPageFactory().getAjaxSurrogatePage( id );
		}
		out_page = id;
		history_f = history_use;
		session_clear_f = session_off;
	}

	/**
	 * コンストラクタ。ページへ遷移。new Forward( id, true, false )と等価です。
	 * @param id 指定ページの出力。
	 */
	public Forward( PageID id ) {
		this( id, true, false );
	}

	/**
	 * 複数の Ajax 系 Forward を実施します。<br>
	 * Ajax が有効な場合、指定された順番に各ページの output がコールされます。
	 * 同じ動作を複数回指定している場合は、その最初のものだけ実施します。
	 * <br>Ajax が使用できない場合は、以下のように動作します。
	 * <ol>
	 * <li>Popup が指定されていれば、最初に指定された Popup へ画面遷移します。
	 * <li>自ページに対する Closure が存在すれば、それを実施します。
	 * <li>上記の全てに該当しない場合、Feedback 動作を行います。
	 * </ol>
	 * @param af 動作指定。
	 */
	public Forward( AjaxForward ... af ) {
		if ( af.length == 0 ) {
			af = new AjaxForward[]{ new NOP() };
		}
		ajax = af;
		if ( SandBox.isCurrentDaemon() )	return;
		initTrim( SandBox.getCurrentRequestParameter().getHistory() );
	}

	void initTrim( History hist ) {
		if ( ajax == null )	return;
		ajax = trim( ajax, hist, true );
		not_ajax = getNotAjax();
	}

	Forward( History hist, AjaxForward ... af ) {
		ajax = trim( af, hist, false );
		not_ajax = getNotAjax();
	}

	AjaxForward[] getAjaxForward( boolean ajax_f ) {
		if ( ajax_f )	return ajax;
		if ( not_ajax == null )	return null;
		return new AjaxForward[]{ not_ajax };
	}

	private AjaxForward[] trim( AjaxForward[] af, History hist, boolean fb_ok ) {
		if ( af.length < 1 )	return af;
		if ( hist == null )	return null;
		Page[]	page = hist.getBrowsingPage();
		Popup.Type	ptype = Popup.Type.MODELESS;
		if ( Popup.isModelessAlert( ptype, hist, false ) ) {
			ptype = Popup.Type.MODAL;
		}
		ArrayList<AjaxForward>	list = new ArrayList<AjaxForward>();
		for ( int i = 0; i < af.length - 1; i++ ) {
			if ( af[i] == null )	continue;
			for ( int j = i + 1; j < af.length; j++ ) {
				if ( af[i].equals( af[j] ) )	af[j] = null;
			}
			if ( af[i] instanceof Popup ) {
				Popup	pup = (Popup)af[i];
				pup.overwriteType( ptype );
				ptype = pup.getType();
			}
			if ( af[i] instanceof Feedback ) {
				if ( !fb_ok ) {
					af[i] = null;
					continue;
				}
				Feedback	fb = (Feedback)af[i];
				for ( int j = 0; j < af.length; j++ ) {
					if ( !(af[j] instanceof Closure) )	continue;
					if ( fb.isSamePage( (Closure)af[j] ) ) {
						af[i] = null;
						break;
					}
				}
			}
		}
		for ( int i = 0; i < af.length; i++ ) {
			if ( af[i] == null )	continue;
			list.add( af[i] );
		}
		af = list.toArray( new AjaxForward[0] );
		return af;
	}

	private AjaxForward getNotAjax() {
		if ( ajax == null )	return null;
		Feedback	fb = new Feedback();
		AjaxForward	ret = fb;
		for ( int i = 0; i < ajax.length; i++ ) {
			if ( ajax[i] instanceof Popup )	return ajax[i];
			if ( ajax[i] instanceof Closure ) {
				if ( fb.isSamePage( (Closure)ajax[i] ) )	ret = ajax[i];
			}
		}
		return ret;
	}

	Page getPage( Page from ) {
		if ( out_page == null ) {
			Option.trace( "Forward#getPage -> null" );
			return null;
		}
		if ( from != null ) {
			if ( out_page == from.getID() ) {
				Option.trace( "Forward#getPage -> %s", from.getUniqueKey() );
				return from;
			}
		}
		Page	p = out_page.getPageFactory().getPage( out_page.getID() );
		Option.trace( "Forward#getPage -> %s", p.getUniqueKey() );
		return p;
	}

	/**
	 * 遷移先ページの取得。
	 * @return 遷移先のページID。エラー指定やリダイレクトの場合、nullを返します。
	 */
	public PageID getPageID() {
		return out_page;
	}

	/**
	 * リダイレクト先の取得。
	 * @return 遷移先のURI。エラー指定やページ指定の場合、nullを返します。
	 */
	public URI getRedirectURI() {
		return redirect_uri;
	}

	/**
	 * ステータスの取得。
	 * @return 返却ステータス。ページ指定やリダイレクトの場合、0を返します。
	 */
	public int getStatus() {
		return error;
	}

	/**
	 * 画面遷移。
	 * @param res レスポンス。
	 */
	void Action( Supervisor sp, HttpServletResponse res ) throws Exception {
		if ( done_f )	return;
		done_f = true;
		if ( error != 0 ) {
			if ( error == 403 ) {
				System.out.println( "================ 403 Forward  ==================" );
				System.out.println( new java.util.Date().toString() );
				for ( int i = 0; i < ste403.length; i++ ) {
					System.out.println( ste403[i].toString() );
				}
				System.out.println( "================================================" );
			}
			ErrorStatus.send( res, error );
			return;
		}
		if ( redirect_uri != null ) {
			res.sendRedirect( Supervisor.makeWithSessionURI( redirect_uri.toString(), null, null ) );
			return;
		}
	}

	/**
	 * 設定内容取得。
	 * @return 設定値を文字列にして返します。
	 */
	public String toString() {
		StringBuilder	buf = new StringBuilder( "Forward " );
		if ( error != 0 ) {
			buf = buf.append( "status: " ).append( Integer.toString( error ) );
		}
		else if ( redirect_uri != null ) {
			buf = buf.append( "redirect: " ).append( redirect_uri.toString() );
		}
		else if ( ajax != null ) {
			buf = buf.append( " << |" );
			for ( int i = 0; i < ajax.length; i++ ) {
				buf = buf.append( ajax[i].toString() );
				buf = buf.append( "|" );
			}
		}
		else {
			buf = buf.append( "page: " ).append( getPageID().toString() );
		}
		return buf.toString();
	}

	OutputCall makeOutput( int nk, Page lp, RequestParameter r, SessionData sd ) {
		Option.trace( "new LocationOutput <- %s", this );
		return new LocationOutput( nk, lp, this, r, sd );
	}

	CometCall makeCometCall( History h ) {
		Option.trace( "new LocationCometCall <- %s", this );
		return new LocationCometCall( h, this );
	}
}

