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

import paraselene.*;
import paraselene.tag.*;
import paraselene.css.*;
import paraselene.supervisor.*;
import java.util.*;



/**
 * タブコントロールを持った div タグです。
 * タブのラベルは、{@link Page#getTitleLabel()}を使用します。
 */
public class Tab extends Tag implements PageHooker {
	private ArrayList<Page>	page_list = new ArrayList<Page>();
	private HashMap<String, Boolean>	page_enable = new HashMap<String, Boolean>();
	private Color	selected;
	private Color	unselected;
	private Tag	head = new Tag( "div", false );
	private PageBypassedDiv	body = new PageBypassedDiv();
	private int	select_no = -1;
	private History	hist;

	/**
	 * コンストラクタ。
	 */
	public Tab() {
		super( "div", false );
		setColor( new Color( WebColor.DARKORANGE ), new Color( WebColor.PALETURQUOISE ) );
		addHTMLPart( head, body );
		body.setAttribute( new Attribute( "class", "paraseleneTabBody" ) );
		makeID();
	}

	protected Tag newReplica() {
		return new Tab();
	}

	private static final CSSValuable	px0 = new NumberValue( 0, NumberValue.Unit.PIXEL );
	private static final CSSValuable	px1 = new NumberValue( 1, NumberValue.Unit.PIXEL );
	private static final CSSValuable	none = new Text( "none" );
	private static final CSSValuable	solid = new Text( "solid" );

	/**
	 * 色の指定。
	 * @param selected_top 選択中タブの強調色。
	 * @param unselected_back 非選択タブの背景色。
	 */
	public void setColor( Color selected_top, Color unselected_back ) {
		selected = selected_top;
		unselected = unselected_back;
		setAttribute( new Attribute( "style", new Style(
			new Property( "border-width", px1 ),
			new Property( "border-style", solid )
		) ) );
	}

	/**
	 * タブ表示するページの追加。
	 * <br>必ず、タブを何れかのページに所属(どれかのタグに addHTMLPart
	 * されている状態)させてから addPage を行って下さい。例外が発生します。
	 * @param start 挿入位置。0が先頭です。
	 * @param history 履歴。
	 * @param page ページインスタンス。
	 */
	public void addPage( int start, History history, Page ... page ) {
		hist = history;
		for ( int i = 0; i < page.length; i++, start++ ) {
			page_list.add( start, page[i] );
			page_enable.put( page[i].getUniqueKey(), true );
		}
		if ( select_no < 0 )	select( 0 );
		else	if ( select_no >= start )	select( select_no + page.length );
		else	select( select_no );
	}

	/**
	 * タブ表示するページの追加。末尾に追加されます。
	 * <br>必ず、タブを何れかのページに所属(どれかのタグに addHTMLPart
	 * されている状態)させてから addPage を行って下さい。例外が発生します。
	 * @param history 履歴。
	 * @param page ページインスタンス。
	 */
	public void addPage( History history, Page ... page ) {
		addPage( getPageCount(), history, page );
	}

	/**
	 * タブ表示するページの追加。
	 * <br>必ず、タブを何れかのページに所属(どれかのタグに addHTMLPart
	 * されている状態)させてから addPage を行って下さい。例外が発生します。
	 * @param start 挿入位置。0が先頭です。
	 * @param history 履歴。
	 * @param id ページID。
	 */
	public void addPage( int start, History history, PageID ... id ) {
		Page[]	page = new Page[id.length];
		for ( int i = 0; i < id.length; i++ ) {
			page[i] = id[i].getPageFactory().getPage( id[i] );
		}
		addPage( start, history, page );
	}

	/**
	 * タブ表示するページの追加。末尾に追加されます。
	 * <br>必ず、タブを何れかのページに所属(どれかのタグに addHTMLPart
	 * されている状態)させてから addPage を行って下さい。例外が発生します。
	 * @param history 履歴。
	 * @param id ページID。
	 */
	public void addPage( History history, PageID ... id ) {
		addPage( getPageCount(), history, id );
	}

	/**
	 * タブ表示ページの削除。
	 * @param no タブ番号。
	 */
	public void removePage( int no ) {
		page_enable.remove( page_list.remove( no ).getUniqueKey() );
		if ( select_no >= no )	select( select_no - 1 );
		else	select( select_no );
	}

	/**
	 * タブ表示ページの削除。
	 * @param page ページインスタンス。
	 */
	public void removePage( Page page ) {
		removePage( getTabNo( page ) );
	}

	/**
	 * タブ表示ページの全削除。
	 */
	public void removePage() {
		select_no = -1;
		page_enable.clear();
		page_list.clear();
	}

	/**
	 * タブ番号の取得。
	 * @param page 探すページ。
	 * @return タブ番号。無ければ -1。
	 */
	public int getTabNo( Page page ) {
		int	cnt = getPageCount();
		for ( int i = 0; i < cnt; i++ ) {
			if ( page_list.get( i ) == page ) {
				return i;
			}
		}
		return -1;
	}

	private boolean	select_pending = false;

	/**
	 * タブの選択。
	 * @param no タブ番号。
	 */
	public void select( int no ) {
		int	cnt = getPageCount();
		if ( no < 0 || no >= cnt )	no = 0;
		select_no = no;
		if ( select_pending )	return;

		head.removeHTMLPart();
		body.setBypassPage( hist, null );
		if ( cnt == 0 ) {
			select_no = -1;
			return;
		}
		else {
			page_enable.put( getPage( select_no ).getUniqueKey(), true );
		}

		for ( int i = 0; i < cnt; i++ ) {
			Page	page = page_list.get( i );
			Tag	div = new Tag( "div", false );
			div.setAttribute( new Attribute( "class", "paraseleneTab" ) );
			Tag	in = null;
			if ( i == select_no ) {
				in = new Tag( "span", false );
				in.setAttribute( new Attribute( "class", "paraselene_selected" ) );
				div.setAttribute( new Attribute( "style", 
						new Style(
							new Property( "border-top-color", selected ),
							new Property( "border-width", new NumberValue( 6, NumberValue.Unit.PIXEL ), px1, px0, px0 ),
							new Property( "border-style", solid, solid, none, none ),
							new Property( "margin-top", new NumberValue( NumberValue.Sign.MINUS, 5, NumberValue.Unit.PIXEL ) )
						)
					)
				);
				body.setBypassPage( hist, page );
			}
			else {
				if ( getEnable( i ) ) {
					in = new Anchor();
					in.setAttribute( new Attribute( "href",
						URIValue.pageToURI( getAssignedPage().getID(), null,
							new QueryItem( getIDAttribute(), Integer.toString( i ) )
						)
					) );
				}
				else {
					in = new Tag( "span", false );
					in.setAttribute( new Attribute( "class", "paraselene_disable" ) );
				}
				div.setAttribute( new Attribute( "style",
						new Style(
							new Property( "background-color", unselected ),
							new Property( "border-width", px0, px1, px1, px0 ),
							new Property( "border-style", none, solid, solid, none )
						)
					)
				);
			}
			in.addHTMLPart( page.getTitleLabel() );
			div.addHTMLPart( in );
			head.addHTMLPart( div );
		}
	}

	/**
	 * タブの選択。
	 * @param page ページインスタンス。
	 */
	public void select( Page page ) {
		select( getTabNo( page ) );
	}

	/**
	 * 管理しているページ総数。
	 * @return ページ数。
	 */
	public int getPageCount() {
		return page_list.size();
	}

	public Forward beforeInput( Page page, RequestParameter req, Forward fw ) throws Exception {
		RequestItem	item = req.getItem( getIDAttribute() );
		if ( item == null )	return fw;
		try {
			int	no = Integer.parseInt( item.getValue( 0 ) );
			select( no );
			fw = new Feedback();
		}
		catch( Exception e ) {}
		return fw;
	}

	public void afterOutput( Page from, Page to, RequestParameter req ) throws Exception {
	}

	public void commitDaemon() {
	}

	/**
	 * 現在選ばれているタブ番号を返します。
	 * @return タブ番号。0が先頭です。
	 */
	public int getSelected() {
		return select_no;
	}

	/**
	 * 設定ページの取得。
	 * @param no タブ番号。
	 */
	public Page getPage( int no ) {
		return page_list.get( no );
	}

	/**
	 * タブの入れ替え。
	 * <br>必ず、タブを何れかのページに所属(どれかのタグに addHTMLPart
	 * されている状態)させてから swap を行って下さい。例外が発生します。
	 * @param no1 入れ替えるタブ番号。
	 * @param no2 入れ替えるタブ番号。
	 */
	public void swap( int no1, int no2 ) {
		if ( no1 == no2 )	return;
		int	old = select_no;

		int	a = (no1 < no2)?	no1:	no2;
		int	b = (no1 < no2)?	no2:	no1;
		Page[]	page = new Page[]{ getPage( b ), getPage( a ) };
		select_pending = true;
		try {
			removePage( b );
			removePage( a );
			addPage( a, hist, page[0] );
			addPage( b, hist, page[1] );
		}
		finally{
			select_pending = false;
		}
		if ( old == a )	select( b );
		else if ( old == b )	select( a );
		else	select( select_no );
	}

	/**
	 * タブの入れ替え。
	 * @param p1 入れ替えるページ。
	 * @param p2 入れ替えるページ。
	 */
	public void swap( Page p1, Page p2 ) {
		swap( getTabNo( p1 ), getTabNo( p2 ) );
	}

	/**
	 * タブの選択不可指定。選択中のタブの状態は変更できません。
	 * @param no タブ番号。
	 * @param flag true:選択可能、false:選択不可。
	 */
	public void setEnable( int no, boolean flag ) {
		if ( no == select_no )	return;
		page_enable.put( getPage( no ).getUniqueKey(), flag );
		select( select_no );
	}

	/**
	 * タブの選択不可指定。選択中のタブの状態は変更できません。
	 * @param page ページ。
	 * @param flag true:選択可能、false:選択不可。
	 */
	public void setEnable( Page page, boolean flag ) {
		setEnable( getTabNo( page ), flag );
	}

	/**
	 * タブの選択不可状態の取得。
	 * @param page ページ。
	 * @return true:選択可能、false:選択不可。
	 */
	public boolean getEnable( Page page ) {
		return page_enable.get( page.getUniqueKey() );
	}

	/**
	 * タブの選択不可状態の取得。
	 * @param no タブ番号。
	 * @return true:選択可能、false:選択不可。
	 */
	public boolean getEnable( int no ) {
		return getEnable( getPage( no ) );
	}

	/**
	 * クッキーからの値を復元。
	 * 以前選択されていたタブ番号を復元します。
	 * @param req リクエスト内容。
	 */
	public void importFromCookie( RequestParameter req ) {
		try {
			Integer	no = (Integer)getCookieValue( req );
			if ( no != null ) {
				select( no );
			}
		}
		catch( Exception e ){}
	}

	/**
	 * クッキーへ値の保存。
	 * 選択されているタブ番号をクッキーへ設定します。ただし、<ul>
	 * <li>ページに属していないタグ
	 * <li>name 属性を持たないタグ
	 * </ul>はクッキーへ保存できません。
	 * @param req リクエスト内容。
	 * @param fw 設定先インスタンス。
	 * @param expiry クッキー存続時間(秒)。
	 * @param ssl_f true:HTTPSの場合のみクッキーを返すようにする、
	 * false:全てのプロトコルでクッキーを返すようにする。
	 * @return fw をそのまま返します。
	 */
	public Forward exportToCookie( RequestParameter req, Forward fw, int expiry, boolean ssl_f ) {
		try {
			fw.addCookie( makeCookieValue( req, new Integer( getSelected() ), expiry, ssl_f ) );
		}
		catch( Exception e ) {}
		return fw;
	}
}


