/*
 * 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 java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;							// 6.4.3.4 (2016/03/11)

import org.opengion.fukurou.system.OgBuilder ;				// 6.4.4.1 (2016/03/18)
import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.TagBuffer;
import org.opengion.fukurou.util.ToString;					// 6.1.1.0 (2015/01/17)
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBEditConfig;
import org.opengion.hayabusa.db.DBLastSql;
import org.opengion.hayabusa.db.DBTableModel;

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

/**
 * 画面表示、集計に関する設定情報の表示、登録を行うためのタグです。
 * (このタグは標準の設定編集画面に組み込んで使用され、各画面JSPから呼び出すことはありません)
 *
 * このタグは、ユーザー単位に管理される編集設定オブジェクトに対するI/Fの機能を
 * 提供しています。この編集設定オブジェクトについては、画面毎に設定を行うため、
 * タグの呼び出しには、画面IDが必須となっています。
 *
 * 具体的な機能としては、3つの機能を提供します。
 * (1)設定画面表示(command="GET")
 *    ユーザー単位に管理される編集設定オブジェクトをHTMLに変換して表示
 *    また、表示カラムの一覧(CSV形式)については、画面側のJavaScriptで再設定を行うため、
 *    その値を"viewClms"という名前のhiddenタグで出力します。
 * (2)編集名一覧(command="LIST")
 *    指定の画面IDに対して、設定されている編集名の一覧をプルダウン(selectタグ)に
 *    変換して表示します。(name="editName")
 * (3)設定情報登録/削除(command="SET"/"DELETE")
 *    (1)で設定された内容に対して、編集名を指定してその内容を保存/削除します。
 *    情報の保存は、command="GET"で表示される項目名と連動していますので、単独での使用は
 *    できません。
 *
 * @og.formSample
 * ●形式：一般ユーザーが直接組み込むことはありません。
 * ●body：なし
 *
 * ●Tag定義：
 *   &lt;og:editConfig
 *       command          ○【TAG】command を指定します(必須)
 *       gamenId          ○【TAG】画面ID を指定します(必須)
 *       editName           【TAG】編集名 を指定します
 *       orderOnly          【TAG】チェックボックスのリードオンリー化を行います(初期値:false)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   /&gt;
 *
 * ●使用例
 *     &lt;og:editConfig command="{&#064;command}" gamenId="{&#064;GAMENID}" editName="{&#064;editName}" /&gt;
 *
 *     &lt;og:editConfig
 *         command        = command設定 (GET/LIST/SET/REMOVE)
 *         gamenId        = "GE0000"    画面ID
 *       [ editName ]     = "EDITNAME"  編集名
 *     /&gt;
 *
 * @og.group 編集設定
 *
 * @og.rev 5.3.6.0 (2011/06/01)
 *
 * @version  5.0
 * @author	 Hiroki Nakamura
 * @since    JDK6.0,
 */
public class EditConfigTag extends CommonTagSupport {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.4.1 (2016/03/18)" ;
	private static final long serialVersionUID = 644120160318L ;

	private static final String VIEW_PREFIX			= "EDIT_VIEW_";
	private static final String SUM_PREFIX			= "EDIT_SUM_";
	private static final String GROUP_PREFIX		= "EDIT_GROUP_";
	private static final String SUBTOTAL_PREFIX		= "EDIT_SUBTOTAL_";
	private static final String TOTAL_PREFIX		= "EDIT_TOTAL_";
	private static final String ORDERBY_PREFIX		= "EDIT_ORDERBY_";
	private static final String DESC_PREFIX			= "EDIT_DESC_";
	private static final String GRANDTOTAL_PREFIX	= "EDIT_GRANDTOTAL_";
	private static final String COMMON_PREFIX		= "EDIT_COMMON_";
	private static final String FIRSTTOTAL_PREFIX	= "EDIT_FIRSTTOTAL_";		// 6.1.1.0 (2015/01/17)

	private String	command			;			// EW" 、アップロード="COPY|INSERT"
	private String	gamenId			;
	private String	editName		;

	private transient DBTableModel table	;	// 5.5.2.4 (2012/05/16) transient 定義追加
	private transient DBEditConfig config	;	// 5.5.2.4 (2012/05/16) transient 定義追加

	private boolean orderOnly		;			// 5.5.5.2 (2012/08/10)

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public EditConfigTag() {
		super();		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
	 * @og.rev 6.0.2.4 (2014/10/17) JSP修正時の追加カラム対応
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		// 編集情報をHTMLに変換して表示します。
		// 表示に当たって、最後に発行されたQUERYのtableId、scopeをチェックした上で
		// 表示するかを判断します。
		if( "GET".equals( command ) ) {
			final DBLastSql lastSql = (DBLastSql)getSessionAttribute( HybsSystem.DB_LAST_SQL_KEY );
			if( lastSql != null ) {
				// 6.1.1.0 (2015/01/17) PMD Avoid if(x != y) ..; else ..;
				if( lastSql.isViewEditable() && lastSql.isGuiMatch( gamenId ) ) {
					setScope( lastSql.getScope() );
					table = (DBTableModel)getObject( lastSql.getTableId() );
					if( table != null ) {
						config = getUser().getEditConfig( gamenId, editName );
						String viewClms = null;
						if( config == null ) {
							config = new DBEditConfig();
							viewClms = lastSql.getViewClmNames();
						}
						else {
							// 6.0.2.4 (2014/10/17) JSP修正時の追加カラム対応
							viewClms = config.getViewClms( lastSql.getOrgClmNames() );
						}

						buf.append( makeEditTable( viewClms ) );
					}
				}
				else {
					// この画面は、項目の並び替えはできません。
					final String rtn = "<b style=\"font-color:red;\">" + getResource().getLabel( "GEE0003" ) + "</b>";
					jspPrint( rtn );
				}
			}
		}
		// 編集情報を保存します。
		else if( "SET".equals( command ) ) {
			if( editName == null || editName.isEmpty() ) {
				final String errMsg = "編集名が指定されていません。";
				throw new HybsSystemException( errMsg );	// 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
			}
			saveEditConfig();
		}
		// 編集情報を削除します。
		else if( "DELETE".equals( command ) ) {
			if( editName == null || editName.isEmpty() ) {
				final String errMsg = "編集名が指定されていません。";
				throw new HybsSystemException( errMsg );	// 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
			}
			deleteEditConfig();
		}
		// 指定された画面IDに対する編集情報の一覧(プルダウン)を表示します。
		else if( "LIST".equals( command ) ) {
			// 6.1.0.0 (2014/12/26) findBugs: null ではなく長さが0の配列を返す。
			final DBEditConfig[] configs = getUser().getEditConfigs( gamenId );
			if( configs.length > 0 ) {
				buf.append( getEditSelect( configs ) ).append( CR );
			}
		}

		jspPrint( buf.toString() );

		return EVAL_PAGE ;
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 5.5.5.2 (2012/08/10) orderOnly対応
	 */
	@Override
	protected void release2() {
		super.release2();
		command		= "GET";
		gamenId		= null;
		editName	= null;
		table		= null;
		config		= null;
		orderOnly	= false; //5.5.5.2 (2012/08/10)
	}

	/**
	 * 編集情報をHTMLに変換して表示します。
	 *
	 * @og.rev 5.4.2.0 (2011/12/01) 入替え対象のカラム列でのみスクロールが表示されるように対応します。
	 * @og.rev 5.5.5.2 (2012/08/10) orderOnly対応
	 * @og.rev 6.1.1.0 (2015/01/17) 総合計を最初の行に追加するかどうか(FirstTotal)の属性を追加
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @param viewClms 表示カラム(CSV形式)
	 *
	 * @return 編集情報のHTML
	 * @og.rtnNotNull
	 */
	private String makeEditTable( final String viewClms ) {
		final boolean useSum = getUseSum( viewClms );
		final String[] viewGroups = StringUtil.csv2Array( viewClms, '|' );

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "<input type=\"hidden\" name=\"viewClms\" id=\"viewClms\" value=\"" )
			.append( viewClms )
//			.append( "\"/><div /><div style=\"float:left;\">" )
			.append( "\"/><div style=\"float:left;\">" )					// 6.3.9.0 (2015/11/06) 意味不明の div を削除
			.append( makeLabelRow( useSum ) )
			.append( "</div><div id=\"clmLayer\" style=\"float:left; width:670px; overflow-x:scroll;\">" );

		for( int i=0; i<viewGroups.length; i++ ) {
			if( i > 0 ) {
				buf.append( makeSeparateRow( useSum ) );
			}
			buf.append( "<table class=\"clmGroup\" style=\"float:left;\"><tr>" );
			final String[] clms = StringUtil.csv2Array( viewGroups[i] );
			for( int j=0; j<clms.length; j++ ) {
				// 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
				final boolean isView = !StringUtil.startsChar( clms[j] , '!' ) ;					// 6.4.1.1 (2016/01/16) １文字 String.startsWith
				// 6.4.1.1 (2016/01/16) １文字 String.startsWith と、処理順の入れ替え
//				final String clm = clms[j].startsWith( "!" ) ? clms[j].substring( 1 ) : clms[j] ;
				final String clm = isView ? clms[j] : clms[j].substring( 1 ) ;
				if( "rowCount".equals( clm ) ) { continue; }
				// 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
//				final boolean isView = !clms[j].startsWith( "!" ) ;
				buf.append( makeColumnRow( clm, isView, useSum, config ) );
			}
			buf.append( "</tr></table>" );
		}
		buf.append( "</div>" );

		// 6.1.1.0 (2015/01/17) useSum==true の場合のみ、表示する。
		if( useSum ) {
			// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
			if( config == null ) {
				final String errMsg = "DBEditConfigが設定されていません。" ;
				throw new OgRuntimeException( errMsg );
			}

			final String grandTotalLabel = "<b>" + getDBColumn( GRANDTOTAL_PREFIX + "LABEL" ).getLongLabel() + ":</b>";
			final String firstTotalLabel = "<b>" + getDBColumn( FIRSTTOTAL_PREFIX + "LABEL" ).getLongLabel() + ":</b>";	// 6.1.1.0 (2015/01/17)
			buf.append( "<div style=\"clear:both;\"><table>" )
				.append( makeCheckbox( GRANDTOTAL_PREFIX, config.useGrandTotal(), "h", grandTotalLabel, orderOnly ) )	// 5.5.5.2 (2012/08/10)
				.append( makeCheckbox( FIRSTTOTAL_PREFIX, config.useFirstTotal(), "h", firstTotalLabel, orderOnly ) )	// 6.1.1.0 (2015/01/17)
				.append( "</table></div>" );
		}

		return buf.toString();
	}

	/**
	 * 編集情報のヘッダー(ラベル行)のHTMLを生成します。
	 *
	 * @og.rev 5.4.2.0 (2011/12/01) 表示項目の全チェック機能を追加
	 * @og.rev 5.5.5.2 (2012/08/10) orderOnly対応
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @param useSum 集計対象のカラム(=NUMBER型)が存在しているか [true:存在/false:存在しない]
	 *
	 * @return 編集情報のヘッダー(ラベル行)のHTML
	 * @og.rtnNotNull
	 */
	private String makeLabelRow( final boolean useSum ) {
//		final String commonLabel = "<b>" + getDBColumn( COMMON_PREFIX + "LABEL" ).getLongLabel() + ":</b>";
//		final String canEditCommon = HybsSystem.sys( "EDIT_COMMON_ROLES" );

//		final String groupLabel = "<b>" + getDBColumn( GROUP_PREFIX + "LABEL" ).getLongLabel() + "</b>"
//						+ "<img id=\"groupBtn\" src=\"" + HybsSystem.sys( "JSP" ) + "/image/ball-green.gif\" />";

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
									.append( "<table><tr><td style=\"margin:0px; padding:0px;\"><table>" );
//			.append( "<table><tr>" )
//			.append( "<td style=\"margin:0px; padding:0px;\"><table>" );

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
		final String commonLabel = "<b>" + getDBColumn( COMMON_PREFIX + "LABEL" ).getLongLabel() + ":</b>";
		final String canEditCommon = HybsSystem.sys( "EDIT_COMMON_ROLES" );
		if( getUser().isAccess( canEditCommon ) ) {
			// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
			if( config == null ) {
				final String errMsg = "DBEditConfigが設定されていません。" ;
				throw new OgRuntimeException( errMsg );
			}

			buf.append( makeCheckbox( COMMON_PREFIX, config.isCommon(), "h", commonLabel, orderOnly ) ); // 5.5.5.2 (2012/08/10)
		}
		else {
			buf.append( makeLabel( commonLabel ) );
		}
		final String viewLabel = "<b>" + getDBColumn( VIEW_PREFIX + "LABEL" ).getLongLabel() + ":</b>";
		buf.append( makeCheckbox( "VIEW_ALL_CHECK", true, "h", viewLabel, orderOnly ) ); // 5.5.5.2 (2012/08/10)
		if( useSum ) {
			buf.append( makeLabel( SUM_PREFIX		+ "LABEL" ) );
		}

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
		final String groupLabel = "<b>" + getDBColumn( GROUP_PREFIX + "LABEL" ).getLongLabel() + "</b>"
								+ "<img id=\"groupBtn\" src=\"" + HybsSystem.sys( "JSP" ) + "/image/ball-green.gif\" />";
		buf.append(  makeCell	( groupLabel, "h" ) )
			.append( makeLabel	( SUBTOTAL_PREFIX	+ "LABEL" ) )
			.append( makeLabel	( TOTAL_PREFIX		+ "LABEL" ) )
			.append( makeLabel	( ORDERBY_PREFIX	+ "LABEL" ) )
			.append( makeLabel	( DESC_PREFIX		+ "LABEL" ) )
			.append( "</table></td></tr></table>" );
		return buf.toString();
	}

	/**
	 * 編集情報のカラム列のHTMLを生成します。
	 * 
	 * @og.rev 5.5.5.2 (2012/08/10) orderOnly対応
	 * @og.rev 5.7.5.2 (2014/04/11) 降順はorderOnlyに関わらず編集可能にする
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
	 *
	 * @param clm カラム
	 * @param isView 表示のチェックボックス [true:初期ON/false:初期OFF]
	 * @param useSum 集計対象のカラム(=NUMBER型)が存在しているか [true:存在/false:存在しない]
	 * @param config 編集設定オブジェクト
	 *
	 * @return 編集情報のカラム列のHTMLを生成します。
	 * @og.rtnNotNull
	 */
	private String makeColumnRow( final String clm, final boolean isView, final boolean useSum, final DBEditConfig config ) {
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( table == null ) {
			final String errMsg = "DBTableModelが設定されていません。" ;
			throw new OgRuntimeException( errMsg );
		}

		final int clmNo = table.getColumnNo( clm, false  );
		final DBColumn column = ( clmNo < 0 ? getDBColumn( clm ) : table.getDBColumn( clmNo ) );

		return new OgBuilder()
			.append( "<td name=\"" , clm )
			.append( "\" class=\"sortItem\" style=\"margin:0px; padding:0px;\">" )
			.append( "<table>" )
			.append( makeLabel	( column.getLongLabel() ) )
			.append( makeCheckbox( VIEW_PREFIX + clm, isView , "0", "", orderOnly ) )		// 5.5.5.2 (2012/08/10)
			.appendIf( useSum , clm , 
				  		v -> makeCheckbox( SUM_PREFIX + v, config.isSumClm( v ) , "1", "", orderOnly, isNumberClm( v ) ) )	// 5.5.5.2 (2012/08/10)
			.append( makeCheckbox( GROUP_PREFIX		+ clm, config.isGroupClm( clm )		, "0", "", orderOnly ) )	// 5.5.5.2 (2012/08/10)
			.append( makeCheckbox( SUBTOTAL_PREFIX	+ clm, config.isSubTotalClm( clm )	, "1", "", orderOnly ) )	// 5.5.5.2 (2012/08/10)
			.append( makeCheckbox( TOTAL_PREFIX		+ clm, config.isTotalClm( clm )		, "0", "", orderOnly ) )	// 5.5.5.2 (2012/08/10)
			.append( makeInput	 ( ORDERBY_PREFIX	+ clm, config.getOrder( clm )		, "1", "" ) )
			.append( makeCheckbox( DESC_PREFIX		+ clm, config.isOrderByDesc( clm )	, "0", "", false ) )		// 5.7.5.1 (2014/04/11)
			.append( "</table></td>" )
			.toString();

//		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
//			.append( "<td name=\"" ).append( clm )
//			.append( "\" class=\"sortItem\" style=\"margin:0px; padding:0px;\">" )
//			.append( "<table>" )
//			.append( makeLabel	( column.getLongLabel() ) )
//			// 6.1.1.0 (2015/01/17) prefix に、null は禁止
//			.append( makeCheckbox( VIEW_PREFIX + clm, isView , "0", "", orderOnly ) );		// 5.5.5.2 (2012/08/10)
//		if( useSum ) {
//			final boolean isSumClm = isNumberClm( clm );
//			// 6.1.1.0 (2015/01/17) prefix に、null は禁止
//			buf.append( makeCheckbox( SUM_PREFIX	+ clm, config.isSumClm( clm )		, "1", "", orderOnly, isSumClm ) ); // 5.5.5.2 (2012/08/10)
//		}
//		// 6.1.1.0 (2015/01/17) prefix に、null は禁止
//		buf.append(  makeCheckbox( GROUP_PREFIX		+ clm, config.isGroupClm( clm )		, "0", "", orderOnly ) )	// 5.5.5.2 (2012/08/10)
//			.append( makeCheckbox( SUBTOTAL_PREFIX	+ clm, config.isSubTotalClm( clm )	, "1", "", orderOnly ) )	// 5.5.5.2 (2012/08/10)
//			.append( makeCheckbox( TOTAL_PREFIX		+ clm, config.isTotalClm( clm )		, "0", "", orderOnly ) )	// 5.5.5.2 (2012/08/10)
//			.append( makeInput	 ( ORDERBY_PREFIX	+ clm, config.getOrder( clm )		, "1", "" ) )
//			.append( makeCheckbox( DESC_PREFIX		+ clm, config.isOrderByDesc( clm )	, "0", "", false ) )		// 5.7.5.1 (2014/04/11)
//			.append( "</table></td>" );
//
//		return buf.toString();
	}

	/**
	 * チェックボックスのHTML文字列を生成します。
	 * 生成したHTMLは以下のようになります。
	 * 例)&lt;tr&gt;&lt;td class="row_[bgCnt]" ...&gt;[prefix]&lt;input type="checkbox" name="[clm]" ... /&gt;&lt;/td&gt;&lt;/tr&gt;
	 *
	 * @param clm		カラム
	 * @param checked	初期チェック [true:チェック済み/false:通常]
	 * @param bgCnt		ゼブラ指定 [0/1/h]
	 * @param prefix	チェックボックスのタグの前に挿入するHTML文字列
	 * @param readonly	リードオンリー指定 [true:読取専用/false:読書可]
	 * 
	 * @og.rev 5.5.5.2 (2012/08/10) readOnly追加
	 * @og.rev 6.1.1.0 (2015/01/17) 内部構造を見直します。
	 *
	 * @return チェックボックスのHMTL文字列
	 */
	private String makeCheckbox( final String clm, final boolean checked, final String bgCnt, final String prefix, final boolean readonly ) {
		return makeCheckbox( clm, checked, bgCnt, prefix, readonly, true );
	}

	/**
	 * チェックボックスのHTML文字列を生成します。
	 * 生成したHTMLは以下のようになります。
	 * 例)&lt;tr&gt;&lt;td class="row_[bgCnt]" ...&gt;[prefix]&lt;input type="checkbox" name="[clm]" ... /&gt;&lt;/td&gt;&lt;/tr&gt;
	 * 
	 * isChbox(チェックボックス生成) を、true に指定すると、チェックボックスのinputタグを生成します。
	 * 
	 * @og.rev 5.5.5.2 (2012/08/10) readOnly追加
	 * @og.rev 6.1.1.0 (2015/01/17) 内部構造を見直します。
	 *
	 * @param clm		カラム
	 * @param checked	初期チェック [true:チェック済み/false:通常]
	 * @param bgCnt		ゼブラ指定 [0/1/h]
	 * @param prefix	チェックボックスのタグの前に挿入するHTML文字列(nullは禁止)
	 * @param readonly	リードオンリー指定 [true:読取専用/false:読書可]
	 * @param isChbox	チェックボックス生成 [true:生成する/false:生成しない]
	 *
	 * @return チェックボックスのHMTL文字列
	 */
	private String makeCheckbox( final String clm, final boolean checked, final String bgCnt, final String prefix, final boolean readonly, final boolean isChbox ) {
		if( isChbox ) {

			// 6.1.1.0 (2015/01/17) TagBufferの連結記述
			final String tag = new TagBuffer( "input" )
								.add( "type"	, "checkbox" )
								.add( "name"	, clm )
								.add( "value"	, "1" )
								.add( "checked"	, "checked" , checked )
								.add( "disabled", "disabled", readonly )
								.makeTag();

			// 6.1.1.0 (2015/01/17) TagBufferの連結記述
			final String cell = readonly && checked			// 6.1.1.0 (2015/01/17) suffix 処理を変更
					? tag + new TagBuffer( "input" )
								.add( "type", "hidden" )
								.add( "name", clm )
								.add( "value", "1" )
								.makeTag()
					: tag ;

			return makeCell( prefix + cell , bgCnt );			// 6.1.1.0 (2015/01/17) prefix に、null は禁止
		}
		else {
			return makeCell( prefix + "&nbsp;", bgCnt );		// 6.1.1.0 (2015/01/17) prefix に、null は禁止
		}
	}

	/**
	 * テキスト入力HTML文字列を生成します。
	 * 生成したHTMLは以下のようになります。
	 * 例)&lt;tr&gt;&lt;td class="row_[bgCnt]" ...&gt;[prefix]&lt;input type="text" name="[clm]" ... /&gt;&lt;/td&gt;&lt;/tr&gt;
	 *
	 * @param clm		カラム
	 * @param value		値
	 * @param bgCnt		ゼブラ指定 [0/1/h]
	 * @param prefix	チェックボックスのタグの前に挿入するHTML文字列(nullは禁止)
	 *
	 * @return チェックボックスのHMTL文字列
	 */
	private String makeInput( final String clm, final String value, final String bgCnt, final String prefix ) {
		// 6.1.1.0 (2015/01/17) TagBufferの連結記述
		final String tag = new TagBuffer( "input" )
							.add( "type"		, "text" )
							.add( "name"		, clm )
							.add( "value"		, value )
							.add( "style"		, "width:10px; font-size:10px;" )
							.add( "maxlength"	, "2" )
							.add( "class"		, "S9" )
							.makeTag();

		return makeCell( prefix + tag , bgCnt );		// 6.1.1.0 (2015/01/17) prefix に、null は禁止

	}

	/**
	 * 左右分割されている際の分割列のHTML文字列を生成します。
	 *
	 * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
	 *
	 * @param useSum 集計対象のカラム(=NUMBER型)が存在しているか [true:存在している/false:存在していない]
	 *
	 * @return チェックボックスのHMTL文字列
	 * @og.rtnNotNull
	 */
	private String makeSeparateRow( final boolean useSum ) {
		final String ROW_H = makeCell( "&nbsp", "h" ) ;

		return new OgBuilder()
			.append( "<table style=\"float:left;\"><tr><td style=\"margin:0px; padding:0px;\"><table>" )
			.append( ROW_H )			// ラベル
			.append( ROW_H )			// 表示
			.appendIf( useSum , ROW_H )	// 集計項目
			.append( ROW_H )			// 集計キー
			.append( ROW_H )			// 小計キー
			.append( ROW_H )			// 合計キー
			.append( ROW_H )			// 表示順
			.append( ROW_H )			// 昇順・降順
			.append( "</table></td></tr></table>")
			.toString();

//		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
//			// 6.4.2.0 (2016/01/29) PMD refactoring.
//			.append( "<table style=\"float:left;\"><tr><td style=\"margin:0px; padding:0px;\"><table>" )
//			.append( ROW_H )			// ラベル
//			.append( ROW_H );			// 表示
//
////			.append( "<table style=\"float:left;\"><tr>" )
////			.append( "<td style=\"margin:0px; padding:0px;\"><table>" )
////			.append( makeCell( "&nbsp", "h" ) )			// ラベル
////			.append( makeCell( "&nbsp", "h" ) );		// 表示
//
//		if( useSum ) {
//			buf.append( ROW_H );		// 集計項目
////			buf.append( makeCell( "&nbsp", "h" ) );		// 集計項目
//		}
//
//		buf.append(  ROW_H )			// 集計キー
//			.append( ROW_H )			// 小計キー
//			.append( ROW_H )			// 合計キー
//			.append( ROW_H )			// 表示順
//			.append( ROW_H )			// 昇順・降順
//			.append( "</table></td></tr></table>");
//
////		buf.append(  makeCell( "&nbsp", "h" ) )			// 集計キー
////			.append( makeCell( "&nbsp", "h" ) )			// 小計キー
////			.append( makeCell( "&nbsp", "h" ) )			// 合計キー
////			.append( makeCell( "&nbsp", "h" ) )			// 表示順
////			.append( makeCell( "&nbsp", "h" ) )			// 昇順・降順
////			.append( "</table></td></tr></table>");
//
//		return buf.toString();
	}

	/**
	 * ラベルのHTML文字列を生成します。
	 *
	 * @param clm カラム
	 *
	 * @return ラベルのHTML文字列
	 */
	private String makeLabel( final String clm ) {
		return makeCell( getDBColumn( clm ).getLongLabel(), "h" );
	}

	/**
	 * セルのHTML文字列を生成します。
	 *
	 * body 属性は、HTML文字列を指定します。
	 * bgCnt は、背景色のゼブラカラーの指定の為の class 属性で、[0/1/h] が指定できます。
	 * 例えば、"0" を指定した場合は、class="row_0" のように、属性を付与します。
	 * 標準の CSSファイルで設定しているのが、[0/1/h] なだけで、上記 class 属性を
	 * custom.css で設定すれば、どのような文字列でも指定可能です。
	 *
	 * @og.rev 6.1.1.0 (2015/01/17) 内部構造を見直します。
	 *
	 * @param body tdタグ内のHTML文字列
	 * @param bgCnt ゼブラ指定 [0/1/h]
	 *
	 * @return セルのHTML文字列
	 * @og.rtnNotNull
	 */
	private String makeCell( final String body, final String bgCnt ) {
		return "<tr><td align=\"center\" style=\"height:22px;\" class=\"row_" + bgCnt + "\">" + body + "</td></tr>";
	}

	/**
	 * この編集設定で集計対象のカラム(=NUMBER型)が存在しているかを返します。
	 *
	 * @param viewClms カラム
	 *
	 * @return 集計対象のカラム(=NUMBER型)が存在しているか
	 */
	private boolean getUseSum( final String viewClms ) {
		if( viewClms == null ) { return false; }

		final String[] clms = StringUtil.csv2Array( viewClms.replace( '|', ',' ) );
		for( int j=0; j<clms.length; j++ ) {
			// 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
//			final String clm = clms[j].startsWith( "!" ) ? clms[j].substring( 1 ) : clms[j] ;
			final String clm = StringUtil.startsChar( clms[j] , '!' ) ? clms[j].substring( 1 ) : clms[j] ;			// 6.4.1.1 (2016/01/16) １文字 String.startsWith
			if( isNumberClm( clm ) ) { return true; }		// ひとつでも見つかれば、true
		}
		return false;
	}

	/**
	 * 引数のカラムがNUMBER型かどうかをチェックします。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @param clm カラム
	 *
	 * @return NUMBER型かどうか
	 */
	private boolean isNumberClm( final String clm ) {
		if( clm == null ) { return false; }
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( table == null ) {
			final String errMsg = "DBTableModelが設定されていません。" ;
			throw new OgRuntimeException( errMsg );
		}

		final int no = table.getColumnNo( clm, false );
		if( no >= 0 ) {
			final DBColumn dbClm = table.getDBColumn( no ); 
			// 6.0.0.1 (2014/04/25) These nested if statements could be combined
			return dbClm != null && "NUMBER".equals( dbClm.getClassName() );
		}
		return false;
	}

	/**
	 * 編集設定情報を保存します。
	 *
	 * @og.rev 6.1.1.0 (2015/01/17) 総合計を最初の行に追加するかどうか(FirstTotal)の属性を追加
	 */
	private void saveEditConfig() {
		final String viewClms		= getRequest().getParameter( "viewClms" );
		final String sumClms		= getColumns( SUM_PREFIX );
		final String groupClms		= getColumns( GROUP_PREFIX );
		final String subTotalClms	= getColumns( SUBTOTAL_PREFIX );
		final String totalClms		= getColumns( TOTAL_PREFIX );
		final String useGrandTotal	= getRequest().getParameter( GRANDTOTAL_PREFIX );
		final String useFirstTotal	= getRequest().getParameter( FIRSTTOTAL_PREFIX );		// 6.1.1.0 (2015/01/17)
		final String orderByClms	= getOrderByColumns();
		final String isCommon		= getRequest().getParameter( COMMON_PREFIX );

		final DBEditConfig config
			= new DBEditConfig( editName, viewClms, sumClms, groupClms
								, subTotalClms, totalClms, useGrandTotal, useFirstTotal, orderByClms, isCommon );

		getUser().addEditConfig( gamenId, editName, config );
	}

	/**
	 * 編集設定情報を削除します。
	 */
	private void deleteEditConfig() {
		getUser().deleteEditConfig( gamenId, editName );
	}

	/**
	 * パラメーターから引数のプレフィックスをキーに、チェックされたカラム一覧(CSV形式)を返します。
	 *
	 * @param prefixKey 各キーの取得するためのプレフィックス
	 *
	 * @return カラム一覧(CSV形式)
	 * @og.rtnNotNull
	 */
	private String getColumns( final String prefixKey ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		final Enumeration<?> enume = getParameterNames();
		while( enume.hasMoreElements() ) {
			final String key = (String)(enume.nextElement());
			if( key.startsWith( prefixKey ) ) {
				final String val = getRequest().getParameter( key );
				if( "1".equals( val ) ) {
					final String clm = key.substring( prefixKey.length() );
					if( buf.length() > 0 ) { buf.append( ',' ); }		// 6.0.2.5 (2014/10/31) char を append する。
					buf.append( clm );
				}
			}
		}

		return buf.toString();
	}

	/**
	 * 表示順のカラム一覧(CSV形式)を返します。
	 *
	 * @og.rev 6.4.3.4 (2016/03/11) CSV形式の文字連結を、stream 経由で行います。
	 *
	 * @return 表示順のカラム一覧(CSV形式)
	 * @og.rtnNotNull
	 */
	private String getOrderByColumns() {
		final Enumeration<?> enume = getParameterNames();
		final List<Integer> orderNo = new ArrayList<>();
		final Map<Integer,String> odrClmMap = new HashMap<>();
		while( enume.hasMoreElements() ) {
			final String key = (String)(enume.nextElement());
			if( key.startsWith( ORDERBY_PREFIX ) ) {
				final String val = getRequest().getParameter( key );
				if( val != null && val.length() > 0 ) {
					String clm = key.substring( ORDERBY_PREFIX.length() );
					final String desc = getRequest().getParameter( DESC_PREFIX + clm );
					if( "1".equals( desc ) ) {
						clm = "!"  + clm;
					}
					// 数字項目以外が入力された場合は無視
					Integer odno = null;
					try {
						odno = Integer.valueOf( val );
					}
					catch ( NumberFormatException ex ) {
						continue;
					}
					String str = odrClmMap.get( odno );
					// 同じ番号の場合でも重ならないように振り直しする。
					while( str != null ) {
						odno = Integer.valueOf( odno.intValue() + 1 );
						str = odrClmMap.get( odno );
					}
					odrClmMap.put( odno, clm );
					orderNo.add( odno );
				}
			}
		}

		Collections.sort( orderNo );

		// 6.4.3.4 (2016/03/11) CSV形式の文字連結を、stream 経由で行います。
		return orderNo.stream()
						.map( no -> odrClmMap.get( no ) )
						.collect( Collectors.joining( "," ) );

//		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
//		for( final Integer i : orderNo ) {
//			if( buf.length() > 0 ) { buf.append( ',' ); }		// 6.0.2.5 (2014/10/31) char を append する。
//			final String clm = odrClmMap.get( i );
//			buf.append( clm );
//		}
//
//		return buf.toString();
	}

	/**
	 * 編集設定一覧のプルダウンメニューを作成します。
	 *
	 * @param	configs	DBEditConfig配列(可変長引数)
	 *
	 * @return	編集一覧のプルダウン
	 * @og.rtnNotNull
	 */
	private String getEditSelect( final DBEditConfig... configs ) {
		final DBColumn column = getDBColumn( "editName" );

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "<span class=\"label editName\">" )
			.append( column.getLongLabel() )
			.append( ":</span><span class=\"editName\">" )
			.append( "<select name=\"editName\">" )
			.append( "<option />" );
		for( final DBEditConfig config : configs ) {
			final String name = config.getEditName();
			buf.append( "<option value=\"" ).append( name ).append( '"' );		// 6.0.2.5 (2014/10/31) char を append する。
			if( config.isCommon() ) {
				buf.append( " class=\"commonEdit\"" );
			}
			buf.append( "\">" ).append( name ).append( "</option>" );
		}
		buf.append( "</select></span>" );
		return buf.toString();
	}

	/**
	 * 【TAG】command を指定します。
	 *
	 * @og.tag
	 * command を指定します。
	 * [GET/LIST/SET/DELETE]のみが設定可能です。それ以外の場合、何も処理されません。
	 *
	 * @param	cmd コマンド [GET/LIST/SET/DELETE]
	 */
	public void setCommand( final String cmd ) {
		command = nval( getRequestParameter( cmd ),command );
	}

	/**
	 * 【TAG】画面ID を指定します。
	 *
	 * @og.tag
	 * 画面ID を指定します。
	 *
	 * @param	key 画面ID
	 */
	public void setGamenId( final String key ) {
		gamenId = nval( getRequestParameter( key ),gamenId );
	}

	/**
	 * 【TAG】編集名を指定します。
	 *
	 * @og.tag
	 * 編集名 を指定します。
	 * commandがSETまたはDELETEの場合は必須です。
	 * commandがGETまたはLISTの場合は無効です。
	 *
	 * @param	name 編集名
	 */
	public void setEditName( final String name ) {
		editName = nval( getRequestParameter( name ),editName );
	}

	/**
	 * 【TAG】順番の入れ替えと、表示順の設定のみを行う場合にtrueにします(初期値:false)。
	 *
	 * @og.tag
	 * 順番の入れ替えと、表示順の設定のみを行う場合にtrueにします。
	 * 表示/非表示切替や、集計機能は利用できなくなります。
	 * （チェックボックスのリードオンリーはできないため、実際にはdisable+hiddenで出力しています）
	 * 初期値は、false:通常 です。
	 *
	 * @og.rev 5.5.5.2 (2012/08/10) 新規追加
	 *
	 * @param flag	順番入替のみ [true:入替のみ/false:通常]
	 */
	public void setOrderOnly( final String flag ) {
		orderOnly = nval( getRequestParameter( flag ),orderOnly );
	}

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