/*
 * 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 org.opengion.fukurou.util.ToString;						// 6.1.1.0 (2015/01/17)

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：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *
 * ●Tag定義：
 *   &lt;og:fileOption
 *       from               【TAG】ファイルの検索元となるディレクトリを指定します (初期値:FILE_URL[=filetemp/])
 *       value              【TAG】Optionの初期値で選ばれる値を指定します
 *       useDir             【TAG】optionリストの作成を、ディレクトリの値で行います。
 *       groupDir           【TAG】optgroupを、ディレクトリの値で作成します(１レベルのみ)。
 *       orderBy            【TAG】検索した結果を表示する表示順をファイル属性名で指定します(初期値:自然順序)
 *       desc               【TAG】表示順を逆転するかどうか[true/false]を指定します(初期値:false)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:fileOption&gt;
 *
 * ●使用例
 *      ・&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.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 = "6.3.4.0 (2015/08/01)" ;
	private static final long serialVersionUID = 634020150801L ;

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

	private static final String[] ORDER_BY = { "NAME","LASTMODIFIED","FILE_LENGTH","LENGTH" };	// 5.3.4.0 (2011/04/01) FILE_LENGTH 追加

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

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

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

		return EVAL_PAGE ;
	}

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

	/**
	 * オプションを作成します。
	 *
	 * ファイル名を "value" に、
	 * BODY属性 に登録するOptionを作成します。
	 *
	 * @og.rev 5.3.4.0 (2011/04/01) FILE_LENGTH 追加
	 *
	 * @param	orderBy	ソートする属性 [NAME/LASTMODIFIED/FILE_LENGTH/LENGTH]
	 * @param	desc	並び順 [true:昇順/false:降順]
	 *
	 * @return	ファイル比較用のComparatorオブジェクト
	 */
	private Comparator<File> makeComparator( final String orderBy,final boolean desc ) {
		if( orderBy == null ) { return null; }

		Comparator<File> comp = null ;

		if( "NAME".equalsIgnoreCase( orderBy ) ) {
			comp = new NameComparator( desc );
		}
		else if( "LASTMODIFIED".equalsIgnoreCase( orderBy ) ) {
			comp = new ModifiedComparator( desc );
		}
		// "LENGTH" を残すのは、互換性のため
		else if( "FILE_LENGTH".equalsIgnoreCase( orderBy ) || "LENGTH".equalsIgnoreCase( orderBy ) ) {
			comp = new LengthComparator( desc );
		}

		return comp ;
	}

	/**
	 * オプションを作成します。
	 *
	 * ﾌｧｲﾙ名を "value" と、BODY属性 に登録するOptionを作成します。
	 *
	 * @og.rev 3.8.0.9 (2005/10/17) 複数選択可能時に全選択を設定する。
	 * @og.rev 6.3.4.0 (2015/08/01) useDir 属性と、groupDir 属性 を追加します。
	 *
	 * @param	path	処理の開始パス名(ディレクトリ)
	 * @param	select	SelectTagオブジェクト
	 * @param	comp	並び順を指定するためのComparatorオブジェクト
	 * @param	grpDir	フォルダ階層を、optgroup タグで表すかどうか [true:階層/false:１層]
	 */
//	private void makeLabel( final OptionAncestorIF select,final Comparator<File> comp ) {
	private void makeLabel( final File path , final OptionAncestorIF select , final Comparator<File> comp
							, final boolean grpDir ) {
//		final File path = new File( from );

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

		final 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++ ) {
				final String value = list[i].getName();
				// 6.3.4.0 (2015/08/01) useDir 属性と、groupDir 属性 を追加
//				if( list[i].isDirectory() ) { continue; }	// ディレクトリは除外
				if( grpDir && list[i].isDirectory() ) {
					select.addOption( "<optgroup label=\"" + value + "\">" );
					makeLabel( list[i] , select , comp , false );	// ２階層目は、grpDir=false 指定。
					select.addOption( "</optgroup>" );
					continue;
				}
				else {
					// ディレクトリ時のuseDir=true と、ﾌｧｲﾙ時useDir=false でない場合( XOR )時は、処理しない。
					if( list[i].isDirectory() ^ useDir ) { continue; }
				}

				// 6.1.1.0 (2015/01/17) Attributesの連結記述
				final String selected = ( selValue != null && selValue.equalsIgnoreCase( value ) ) || multipleAll
											? "selected" : null ;

				select.addOption(
					XHTMLTag.option(
						new Attributes()
							.set( "value"	, value )
							.set( "selected", selected )
							.set( "body"	, value )
					)
				);
			}
		}
	}

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

	/**
	 * 【TAG】optionリストの作成を、ディレクトリの値で行います(初期値:false)。
	 *
	 * @og.tag
	 * ファイル検索で、ディレクトリ名のリストで、オプションを作成します。
	 * 初期値は、false (ファイル名でリスト) です。
	 *
	 * @og.rev 6.3.4.0 (2015/08/01) useDir 属性の追加
	 *
	 * @param	flag ディレクトリ名のリストで、オプションを作成するかどうか [true:ディレクトリ/false:ファイル]
	 */
	public void setUseDir( final String flag ) {
		useDir = nval( getRequestParameter( flag ),useDir );
	}

	/**
	 * 【TAG】optgroupを、ディレクトリの値で作成します(１レベルのみ)(初期値:false)。
	 *
	 * @og.tag
	 * optgroupをディレクトリで作成することで、階層のメニューを作成します。
	 * 初期値は、false(通常) です。
	 *
	 * @og.rev 6.3.4.0 (2015/08/01) groupDir 属性の追加
	 *
	 * @param	flag ディレクトリで階層メニューを作成するかどうか [true:階層/false:通常]
	 */
	public void setGroupDir( final String flag ) {
		groupDir = nval( getRequestParameter( flag ),groupDir );
	}

	/**
	 * 【TAG】ファイルの検索元となるディレクトリを指定します
	 *		(初期値:FILE_URL[={@og.value SystemData#FILE_URL}])。
	 *
	 * @og.tag ファイルの検索元となるディレクトリを指定します。
	 * (初期値:システム定数のFILE_URL[={@og.value SystemData#FILE_URL}])。
	 *
	 * @og.rev 4.0.0.0 (2007/11/20) 指定されたディレクトリ名の最後が"\"or"/"で終わっていない場合に、"/"を付加する。
	 *
	 * @param	url ファイルの検索元となるディレクトリ
	 * @see		org.opengion.hayabusa.common.SystemData#FILE_URL
	 */
	public void setFrom( final String url ) {
		String furl = nval( getRequestParameter( url ),null );
		if( furl != null ) {
			final 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】検索した結果を表示する表示順をファイル属性名[null/NAME/LASTMODIFIED/FILE_LENGTH]で指定します(初期値:自然順序)。
	 *
	 * @og.tag
	 * ファイルをソートする順(Comparator)を指定します。ソートに指定できる
	 * ファイル属性名は、"NAME","LASTMODIFIED","FILE_LENGTH" の内のどれかひとつです。
	 * 何も指定しない場合は、Fileオブジェクトの自然順序でのソートになります。
	 * (※ 下位互換性のため、LENGTH も残しますが、廃止予定です。)
	 *
	 * @og.rev 3.5.6.2 (2004/07/05) 文字列の連結にStringBuilderを使用します。
	 * @og.rev 4.0.0.0 (2005/01/31) 新規ロジックで改定
	 * @og.rev 5.3.4.0 (2011/04/01) ORDER_BYリストの出力方法 見直し
	 * @og.rev 6.3.4.0 (2015/08/01) Arrays.toString から String.join に置き換え。
	 *
	 * @param	ordr  ソートキー [null/NAME/LASTMODIFIED/FILE_LENGTH]
	 */
	public void setOrderBy( final String ordr ) {
		orderBy = nval( getRequestParameter( ordr ),orderBy );

		if( orderBy != null && ! check( orderBy, ORDER_BY ) ) {
			final String errMsg = "orderBy 属性に、下記の属性名以外の値が設定されました。"	+ CR
							+ "orderBy=[" + orderBy + "] " 								+ CR
							+ "orderBy List=" + String.join( ", " , ORDER_BY ) ;
			throw new HybsSystemException( errMsg );

//			final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
//				.append( "orderBy 属性に、下記の属性名以外の値が設定されました。" )
//				.append( CR )
//				.append( " orderBy=[" ).append( orderBy ).append( ']' )		// 6.0.2.5 (2014/10/31) char を append する。
//				.append( CR )
//				.append( " orderBy List=[" )
//				.append( StringUtil.array2csv( ORDER_BY ) )
//				.append( ']' );												// 6.0.2.5 (2014/10/31) char を append する。
//			throw new HybsSystemException( errMsg.toString() );
		}
	}

	/**
	 * 【TAG】表示順を逆転するかどうか[true/false]を指定します(初期値: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	オブジェクト
	 */
	protected void setFileFilter( final FileFilter filter ) {
		this.filter = filter;
	}

	/**
	 * 名前順でのソート順を指定する Comparator の実体内部クラス
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private static final class に変更。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
//	static class NameComparator implements Comparator<File>,Serializable {
	private static final class NameComparator implements Comparator<File>,Serializable {
		private static final long serialVersionUID = 400020050131L ;	// 4.0.0.0 (2005/01/31)

		private final boolean desc ;

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

		/**
		 * Comparator インターフェースの compare( File,File ) メソッド。
		 *
		 * @param o1 比較元１のファイルオブジェクト
		 * @param o2 比較元２のファイルオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final File o1, final File o2 ) {
			final File f1 = desc ? o2 : o1 ;
			final File f2 = desc ? o1 : o2 ;
			return f1.getName().compareTo( f2.getName() ) ;
		}
	}

	/**
	 * 更新日順でのソート順を指定する Comparator の実体内部クラス
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private static final class に変更。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
//	static class ModifiedComparator implements Comparator<File>,Serializable {
	private static final class ModifiedComparator implements Comparator<File>,Serializable {
		private static final long serialVersionUID = 400020050131L ;	// 4.0.0.0 (2005/01/31)

		private final boolean desc ;

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

		/**
		 * Comparator インターフェースの compare( File,File ) メソッド。
		 *
		 * @param o1 比較元１のファイルオブジェクト
		 * @param o2 比較元２のファイルオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final File o1, final File o2 ) {
			final File f1 = desc ? o2 : o1 ;
			final File f2 = desc ? o1 : o2 ;
			return (int)( f1.lastModified() - f2.lastModified() ) ;
		}
	}

	/**
	 * ファイルサイズ順でのソート順を指定する Comparator の実体内部クラス
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private static final class に変更。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
//	static class LengthComparator implements Comparator<File>,Serializable {
	private static final class LengthComparator implements Comparator<File>,Serializable {
		private static final long serialVersionUID = 400020050131L ;	// 4.0.0.0 (2005/01/31)

		private final boolean desc ;

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

		/**
		 * Comparator インターフェースの compare( File,File ) メソッド。
		 *
		 * @param o1 比較元１のファイルオブジェクト
		 * @param o2 比較元２のファイルオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final File o1, final File o2 ) {
			final File f1 = desc ? o2 : o1 ;
			final File f2 = desc ? o1 : o2 ;
			return (int)( f1.length() - f2.length() ) ;
		}
	}

//	/**
//	 * シリアライズ用のカスタムシリアライズ書き込みメソッド。
//	 *
//	 * @og.rev 4.0.0.0 (2006/09/31) 新規追加
//	 * @serialData 一部のオブジェクトは、シリアライズされません。
//	 *
//	 * @param	strm	ObjectOutputStreamオブジェクト
//	 * @throws IOException	シリアライズに関する入出力エラーが発生した場合
//	 */
//	private void writeObject( final ObjectOutputStream strm ) throws IOException {
//		strm.defaultWriteObject();
//	}

//	/**
//	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
//	 *
//	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
//	 *
//	 * @og.rev 4.0.0.0 (2006/09/31) 新規追加
//	 * @serialData 一部のオブジェクトは、シリアライズされません。
//	 *
//	 * @param	strm	ObjectInputStreamオブジェクト
//	 * @see #release2()
//	 * @throws IOException	シリアライズに関する入出力エラーが発生した場合
//	 * @throws ClassNotFoundException	クラスを見つけることができなかった場合
//	 */
//	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
//		strm.defaultReadObject();
//	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return 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() ;
	}
}
