/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.taglib;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.fukurou.util.Attributes;
import org.opengion.fukurou.util.StringUtil;

import static org.opengion.fukurou.util.StringUtil.nval ;

import java.io.File;
import java.io.FileFilter;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;

/**
 * ファイルのプルダウンリストの作成するタグです。
 *
 * SelectタグのBODY部に指定します。
 * 並び替えについては、このタグで指定しますが、ファイルの選別は、
 * BODY 部に記述する fileWhere タグで指定します。
 *
 * @og.formSample
 * ●形式：&lt;og:fileOption from="…" value="[…]" ･･･ &gt;･･･&lt;/og:fileOption&gt;
 * ●body：あり
 *
 * ●使用例
 *      ・&lt;og:fileOption val1="ABCD" val2="{&#064;value}" &gt;
 *            &lt;og:fileWhere startsWith="ABCD" ･･･ /&gt;
 *        &lt;/og:fileOption&gt;
 *
 * @og.rev 2.1.1.0 (2002/11/11) 新規作成
 * @og.rev 4.0.0 (2005/01/31) 内部ロジック改定
 * @og.group その他入力
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FileOptionTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0 (2005/08/31)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/01/31)

	private String		orderBy		= null;		// ｿｰﾄ項目
	private boolean		desc		= false;	// 降順ﾌﾗｸﾞ
	private String      from		= HybsSystem.sys( "FILE_URL" );	// 検索起点ﾌｧｲﾙ
	private String		selValue	= null;		// 選択済み初期値にする場合
	private transient FileFilter	filter		= null;							// FileWhere で指定したフィルター

	private static final String[] ORDER_BY = new String[] {
									"NAME","LASTMODIFIED","LENGTH" };

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return	int
	 */
	@Override
	public int doStartTag() {
		return( EVAL_BODY_BUFFERED );	// Body を評価する。（ extends BodyTagSupport 時）
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @return	int
	 */
	@Override
	public int doAfterBody() {
		return(SKIP_BODY);
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 *
	 * @return  int
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)
		SelectTag select = (SelectTag)findAncestorWithClass( this, SelectTag.class );
		if( select == null ) {
			String errMsg = "このタグは、SelectTag のBODY に記述する必要があります。";
			throw new HybsSystemException( errMsg );
		}
		Comparator<File> comp = makeComparator( orderBy,desc );
		makeLabel( select,comp );

		return(EVAL_PAGE);
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		orderBy		= null;		// ｿｰﾄ項目
		desc		= false;	// 降順ﾌﾗｸﾞ
		from		= HybsSystem.sys( "FILE_URL" );
		filter		= null;
		selValue	= null;
	}

	/**
	 * オプションを作成します。
	 *
	 * ﾌｧｲﾙ名を "value" に、
	 * BODY属性 に登録するOptionを作成します。
	 *
	 * @param   orderBy String
	 * @param   desc    boolean
	 * @return Comparator<File>
	 */
	private Comparator<File> makeComparator( final String orderBy,final boolean desc ) {
		if( orderBy == null ) { return null; }

		Comparator<File> comp = null ;

		if( "NAME".equals( orderBy ) ) {
			comp = new NameComparator( desc );
		}
		else if( "LASTMODIFIED".equals( orderBy ) ) {
			comp = new ModifiedComparator( desc );
		}
		else if( "LENGTH".equals( orderBy ) ) {
			comp = new LengthComparator( desc );
		}

		return comp ;
	}

	/**
	 * オプションを作成します。
	 *
	 * ﾌｧｲﾙ名を "value" に、
	 * BODY属性 に登録するOptionを作成します。
	 *
	 * @og.rev 3.8.0.9 (2005/10/17) 複数選択可能時に全選択を設定する。
	 *
	 * @param   select SelectTag
	 * @param   comp Comparator<File>
	 */
	private void makeLabel( final SelectTag select,final Comparator<File> comp ) {
		File path = new File( from );

		File[] list = path.listFiles( filter );

		boolean multipleAll = select.isMultipleAll();		// 3.8.0.9 (2005/10/17)
		if( list != null )  {
			Arrays.sort( list, comp );
			for( int i = 0; i < list.length; i++ ) {
				if( list[i].isDirectory() ) { continue; }	// ディレクトリは除外
				Attributes attri = new Attributes();
				String value = list[i].getName();
				attri.set( "value", value );
				if( ( selValue != null && selValue.equalsIgnoreCase( value ) ) || multipleAll ) {
					attri.set( "selected", "selected" );
				}
				attri.set( "body", value );
				select.addOption( XHTMLTag.option( attri ) );
			}
		}
	}

	/**
	 * 【TAG】Optionの初期値で選ばれる値を指定します。
	 *
	 * @og.tag
	 * キーになるのは、ファイル属性の NAME です。(ディレクトリなしのファイル名)
	 * ここで value属性に指定した場合、このファイル名と（大文字小文字を無視して）
	 * 一致する場合に、プルダウンの初期値に表示されます。(selected 属性が設定される。)
	 *
	 * @param   val  初期値で選ばれる値
	 */
	public void setValue( final String val ) {
		selValue = nval( getRequestParameter( val ),selValue );
	}

	/**
	 * 【TAG】ファイルの検索元となるディレクトリを指定します(初期値:システムパラメータのFILE_URL)。
	 *
	 * @og.tag ファイルの検索元となるディレクトリを指定します。
	 *
	 * @og.rev 4.0.0.0 (2007/11/20) 指定されたディレクトリ名の最後が"\"or"/"で終わっていない場合に、"/"を付加する。
	 *
	 * @param	url ファイルの検索元となるディレクトリ
	 */
	public void setFrom( final String url ) {
		String furl = nval( getRequestParameter( url ),null );
		if( furl != null ) {
			char ch = furl.charAt( furl.length()-1 );
			if( ch != '/' && ch != '\\' ) { furl = furl + "/"; }
		}
		furl = StringUtil.urlAppend( from,furl );
		furl = StringUtil.urlAppend( furl,"." );
		from = HybsSystem.url2dir( furl );
	}

	/**
	 * 【TAG】検索した結果を表示する表示順をファイル属性名で指定します(初期値:自然順序)。
	 *
	 * @og.tag
	 * ファイルをソートする順(Comparator)を指定します。ソートに指定できる
	 * ファイル属性名は、"NAME","LASTMODIFIED","LENGTH" の内のどれかひとつです。
	 * 何も指定しない場合は、Fileオブジェクトの自然順序でのソートになります。
	 *
	 * @og.rev 3.5.6.2 (2004/07/05) 文字列の連結にStringBuilderを使用します。
	 * @og.rev 4.0.0 (2005/01/31) 新規ロジックで改定
	 *
	 * @param	ordr  ソートキー("NAME","LASTMODIFIED","LENGTH")
	 */
	public void setOrderBy( final String ordr ) {
		orderBy = nval( getRequestParameter( ordr ),orderBy );

		if( orderBy != null && ! check( orderBy, ORDER_BY ) ) {
			StringBuilder errMsg = new StringBuilder();
			errMsg.append( "orderBy 属性に、下記の属性名以外の値が設定されました。" );
			errMsg.append( HybsSystem.CR );
			errMsg.append( " orderBy=[" ).append( orderBy ).append( "]" );
			errMsg.append( HybsSystem.CR );
			errMsg.append( " orderBy List=[" );
			for( int i=0; i<ORDER_BY.length; i++ ) {
				errMsg.append( ORDER_BY[i] );
				if( i == ORDER_BY.length-1 ) { errMsg.append( "]" ); }
				else {						   errMsg.append( "," ); }
			}
			throw new HybsSystemException( errMsg.toString() );
		}
	}

	/**
	 * 【TAG】表示順を逆転するかどうかを指定します(初期値:false)。
	 *
	 * @og.tag
	 * orderBy 属性で指定した表示順を、逆順にするかどうかを指定できます。
	 * 初期値は、false (昇順) です。
	 *
	 * @param	flag 表示順を逆転するかどうか (true/false)
	 */
	public void setDesc( final String flag ) {
		desc = nval( getRequestParameter( flag ),desc );
	}

	/**
	 * FileFilterオブジェクトをセットします。
	 * これは、BODY 部に登録した、FileWhereタグによって設定された
	 * ファイルフィルターです。
	 *
	 * @param	 filter	FileFilter オブジェクト
	 */
	protected void setFileFilter( final FileFilter filter ) {
		this.filter = filter;
	}

	/**
	 * 名前順でのソート順を指定する Comparator の実体内部クラス
	 *
	 * @og.group その他入力
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	static class NameComparator implements Comparator<File>,Serializable {
		private static final long serialVersionUID = 4000 ;

		private final boolean desc ;

		/**
		 * 名前順での比較を行うオブジェクトを作成します。
		 *
		 * @param desc boolean true:昇順 / false:降順
		 */
		public NameComparator( final boolean desc ) { this.desc = desc; }

		/**
		 * Comparator インターフェースの compare( File,File ) メソッド
		 *
		 * @param o1 File 比較元１のファイルオブジェクト
		 * @param o2 File 比較元２のファイルオブジェクト
		 */
		public int compare( final File o1, final File o2 ) {
			File f1 = (desc) ? o2 : o1 ;
			File f2 = (desc) ? o1 : o2 ;
			return (f1.getName()).compareTo( f2.getName() ) ;
		}
	}

	/**
	 * 更新日順でのソート順を指定する Comparator の実体内部クラス
	 *
	 * @og.group その他入力
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	static class ModifiedComparator implements Comparator<File>,Serializable {
		private static final long serialVersionUID = 4000 ;

		private final boolean desc ;

		/**
		 * 更新日順での比較を行うオブジェクトを作成します。
		 *
		 * @param desc boolean true:昇順 / false:降順
		 */
		public ModifiedComparator( final boolean desc ) { this.desc = desc; }

		/**
		 * Comparator インターフェースの compare( File,File ) メソッド
		 *
		 * @param o1 File 比較元１のファイルオブジェクト
		 * @param o2 File 比較元２のファイルオブジェクト
		 */
		public int compare( final File o1, final File o2 ) {
			File f1 = (desc) ? o2 : o1 ;
			File f2 = (desc) ? o1 : o2 ;
			return (int)( f1.lastModified() - f2.lastModified() ) ;
		}
	}

	/**
	 * ファイルサイズ順でのソート順を指定する Comparator の実体内部クラス
	 *
	 * @og.group その他入力
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	static class LengthComparator implements Comparator<File>,Serializable {
		private static final long serialVersionUID = 4000 ;

		private final boolean desc ;

		/**
		 * ファイルサイズでの比較を行うオブジェクトを作成します。
		 *
		 * @param desc boolean true:昇順 / false:降順
		 */
		public LengthComparator( final boolean desc ) { this.desc = desc; }

		/**
		 * Comparator インターフェースの compare( File,File ) メソッド
		 *
		 * @param o1 File 比較元１のファイルオブジェクト
		 * @param o2 File 比較元２のファイルオブジェクト
		 */
		public int compare( final File o1, final File o2 ) {
			File f1 = (desc) ? o2 : o1 ;
			File f2 = (desc) ? o1 : o2 ;
			return (int)( f1.length() - f2.length() ) ;
		}
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectOutputStream
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectInputStream
	 * @see #release2()
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
		strm.defaultReadObject();
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"		,VERSION	)
				.println( "orderBy"		,orderBy	)
				.println( "desc"		,desc		)
				.println( "from"		,from		)
				.println( "selValue"	,selValue	)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
