/*
 * 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.plugin.view;

import java.util.List;

import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.TagBuffer;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.html.FormatterType;
import org.opengion.hayabusa.html.TableFormatter;
import org.opengion.hayabusa.html.ViewAjaxTreeTableParam;

/**
 * JavaScript のツリー階層を持ったテーブル表示を行う、ツリーテーブル表示クラスです。
 *
 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。
 * 各HTMLのタグに必要な setter/getterメソッドのみ，追加定義しています。
 *
 * AbstractViewForm を継承している為,ロケールに応じたラベルを出力させる事が出来ます。
 *
 * @og.rev 7.3.2.3 (2021/04/09) ｼｽﾃﾑ定数のJSP_IMGを使用します。(※ SYS.JSP + SYS.IMAGE_DIR)
 * @og.group 画面表示
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public class ViewForm_HTMLAjaxTreeTable extends ViewForm_HTMLCustomTable  {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.8.1.1 (2017/07/22)" ;

//	// 6.4.4.2 (2016/04/01) JSP + "/image/" にする。
//	private static final String JSPIMG = HybsSystem.sys( "JSP" ) + "/image/" ;
	// 7.3.2.3 (2021/04/09) ｼｽﾃﾑ定数のJSP_IMGを使用します。(※ SYS.JSP + SYS.IMAGE_DIR)
	private static final String JSPIMG = HybsSystem.sys( "JSP_IMG" ) + "/" ;	// 互換性の関係で最後に"/"を追加

	private int[]			childSearchKeys ;
	private String			childSearchJsp	;
	private String			levelClm		;
	private int				levelClmPos		= -1;
	private String			imgCollapsed    ;
	private String			imgExpanded     ;
	private String			imgNoSub        ;
	private boolean			expandAll		;		// 4.3.3.0 (2008/10/01)
	private int				childViewStartNo= -1;	// 4.3.3.0 (2008/10/01)
	private int				expCtrlClmPos	= -1;	// 4.3.5.0 (2008/02/01)

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

	/**
	 * DBTableModel から HTML文字列を作成して返します。
	 * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
	 * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
	 *
	 * @og.rev 4.3.3.0 (2008/10/01) noTransition属性,childViewStartNo属性対応
	 * @og.rev 4.3.7.4 (2009/07/01) tbodyタグの入れ子を解消(FireFox対応)
	 * @og.rev 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。
	 * @og.rev 6.4.4.2 (2016/04/01) TableFormatterのタイプ別値取得処理の共通部をまとめる。
	 * @og.rev 6.4.5.0 (2016/04/08) メソッド変更( getColumnDbType(int) → getClassName(int) )
	 * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、&lt;td&gt; から &lt;td に変更します(タグの最後が記述されていない状態でもらう)。
	 *
	 * @param  strNo     表示開始位置
	 * @param  pageSize  表示件数
	 *
	 * @return  DBTableModelから作成された HTML文字列
	 * @og.rtnNotNull
	 */
	@Override
	public String create( final int strNo, final int pageSize )  {
		if( getRowCount() == 0 ) { return ""; }	// 暫定処置

		initParam();

		// 4.3.3.0 (2008/10/01) 子データ差分取得用
		// 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
		final int startNo = childViewStartNo >= 0 ? childViewStartNo : strNo;

		if( headerFormat == null ) {
			makeDefaultFormat();
		}

		headerFormat.makeFormat( getDBTableModel() );

		final StringBuilder out = new StringBuilder( BUFFER_LARGE )
			.append( getCountForm( startNo,pageSize ) )
			.append( getHeader() );

		if( bodyFormatsCount == 0 ) {
			bodyFormats = new TableFormatter[BODYFORMAT_MAX_COUNT];
			bodyFormats[0] = headerFormat ;
			bodyFormatsCount ++ ;
		}
		else {
			for( int i=0; i<bodyFormatsCount; i++ ) {
				bodyFormats[i].makeFormat( getDBTableModel() );
			}
		}

		int bgClrCnt = 0;
		final int lastNo = getLastNo( startNo, pageSize );				// 6.3.9.1 (2015/11/27) forループの近くに移動
		for( int row=startNo; row<lastNo; row++ ) {
			if( isSkip( row ) || isSkipNoEdit( row ) ) { continue; }	// 4.3.1.0 (2008/09/08)
			for( int i=0; i<bodyFormatsCount; i++ ) {
				final TableFormatter bodyFormat = bodyFormats[i];
				if( ! bodyFormat.isUse( row,getDBTableModel() ) ) { continue; }		// 3.5.4.0 (2003/11/25)
				out.append("<tbody").append( getBgColorCycleClass( bgClrCnt++,row ) );
				if( isNoTransition() ) { // 4.3.3.0 (2008/10/01)
					out.append( getHiddenRowValue( row ) );
				}
				out.append('>')		// 6.0.2.5 (2014/10/31) char を append する。
					.append( bodyFormat.getTrTag() );

				if( isNumberDisplay() ) {
					final String ckboxTD = "<td" + bodyFormat.getRowspan();		// 6.8.1.1 (2017/07/22)
					out.append( makeCheckbox( ckboxTD,row,0 ) );
				}

				int cl = 0;
				for( ; cl<bodyFormat.getLocationSize(); cl++ ) {
					String fmt = bodyFormat.getFormat(cl);
					final int loc = bodyFormat.getLocation(cl);
					if( ! bodyFormat.isNoClass() && loc >= 0 ) {
						// 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。
						final int idx = fmt.lastIndexOf( "<td" );
						if( idx >= 0 ) {	// matchしてるので、あるはず
							final String tdclass = " class=\"" + getClassName(loc) + "\" ";			// 6.4.5.0 (2016/04/08)
							fmt = fmt.substring( 0,idx+3 ) + tdclass + fmt.substring( idx+3 ) ;
						}
					}
					out.append( fmt );
					if( loc >= 0 ) {
						if( levelClm != null && levelClm.equals( getDBColumn( loc ).getName() ) ) {
							out.append( getLvlClmTag( row ) );
						}
						else {
							// 6.4.4.2 (2016/04/01) 処理の共通部をまとめる。
							out.append( getTypeCaseValue( bodyFormat.getType(cl),row,loc ) );
						}
					}
					else {
						out.append( bodyFormat.getSystemFormat(row,loc) );
					}
				}
				out.append( bodyFormat.getFormat(cl) )
					.append("</tbody>").append( CR );
			}
		}

		if( footerFormat != null ) {
			// 6.3.9.0 (2015/11/06) 引数にTableFormatterを渡して、処理の共有化を図る。
			out.append( getTableFoot( footerFormat ) );
		}

		out.append("</table>").append( CR )
			.append( getScrollBarEndDiv() )
			.append( getParameterTag() );

		return out.toString();
	}

	/**
	 * フォーマットを設定します。
	 *
	 * @param	list	TableFormatterのリスト
	 */
	@Override
	public void setFormatterList( final List<TableFormatter> list ) {		// 4.3.3.6 (2008/11/15) Generics警告対応
		bodyFormats = new TableFormatter[BODYFORMAT_MAX_COUNT];

		bodyFormatsCount = 0;
		// 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
		for( final TableFormatter format : list ) {
//		for( int i=0; i<list.size(); i++ ) {
//			final TableFormatter format = list.get( i );		// 4.3.3.6 (2008/11/15) Generics警告対応
			switch( format.getFormatType() ) {
				case TYPE_HEAD : headerFormat = format; break;
				case TYPE_BODY : bodyFormats[bodyFormatsCount++] = format; break;
				case TYPE_FOOT : footerFormat = format; break;
				default : final String errMsg = "FormatterType の定義外の値が指定されました。";
				// 4.3.4.4 (2009/01/01)
				  throw new HybsSystemException( errMsg );
			}
		}
	}

	/**
	 * フォーマッターが設定されていない場合は、DBTableModelの情報からデフォルトの
	 * フォーマッターを作成します。
	 *
	 * @og.rev 4.3.3.6 (2008/11/15) columnDisplay,noDisplay対応
	 * @og.rev 4.3.5.0 (2008/02/01) 全展開コントロール用カラムへの対応
	 */
	private void makeDefaultFormat() {
		final String[] clms = getDBTableModel().getNames();
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
					.append( "<tr>" );
		for( int i=0; i<clms.length; i++ ) {
			if( isColumnDisplay( i ) && i != expCtrlClmPos ) {	// 4.3.3.6 (2008/11/15) // 4.3.5.0 (2008/02/01)
				buf.append( "<td>[" ).append( clms[i] ).append( "]</td>" );	// 6.4.4.2 (2016/04/01)
			}
		}
		buf.append( "</tr>" );

		final TableFormatter formatter = new TableFormatter();
		formatter.setFormat( buf.toString() );
		formatter.setFormatType( FormatterType.TYPE_HEAD );

		headerFormat = formatter;
	}

	/**
	 * フォーマットメソッドを使用できるかどうかを問い合わせます。
	 *
	 * @return	フォーマットメソッドを使用できるか
	 */
	@Override
	public boolean canUseFormat() {
		return true;
	}

	/**
	 * 初期パラメーターを設定します。
	 *
	 * @og.rev 4.3.3.0 (2008/10/01) 初期全展開の属性追加
	 * @og.rev 4.3.5.0 (2008/02/01) 全展開時の状態をコントロールするためのフラグを追加
	 */
	private void initParam() {
		final String[] tmp	= StringUtil.csv2Array( getParam( ViewAjaxTreeTableParam.CHILD_SEARCH_KEYS, "" ) );
		childSearchKeys	= new int[tmp.length];
		for( int i=0; i<tmp.length; i++ ) {
			childSearchKeys[i] = getDBTableModel().getColumnNo( tmp[i] );
		}
		childSearchJsp	= getParam( ViewAjaxTreeTableParam.CHILD_SEARCH_JSP, "getChildTag.jsp" );
		levelClm		= getParam( ViewAjaxTreeTableParam.LVL_CLM_KEY, "LVL" );
		levelClmPos		= getDBTableModel().getColumnNo( levelClm );
		imgCollapsed    = getParam( ViewAjaxTreeTableParam.IMG_COLLAPSED, "collapsed.gif" );
		imgExpanded     = getParam( ViewAjaxTreeTableParam.IMG_EXPANDED, "expanded.gif" );
		imgNoSub        = getParam( ViewAjaxTreeTableParam.IMG_NO_SUB, "nosub.gif" );
		expandAll		= Boolean.valueOf( getParam( ViewAjaxTreeTableParam.EXPAND_ALL, "false" ) );		// 4.3.2.0 (2008/09/11)
		childViewStartNo= Integer.parseInt( getParam( ViewAjaxTreeTableParam.CHILD_VIEW_START_NO, "-1" ) );	// 6.0.2.4 (2014/10/17) メソッド間違い
		final String expCtrlClm	= getParam( ViewAjaxTreeTableParam.EXPAND_CONTROL_CLM_KEY, "EXPAND_CONTROL" );	// 4.3.5.0 (2008/02/01)
		expCtrlClmPos	= getDBTableModel().getColumnNo( expCtrlClm, false );
	}

	/**
	 * JavaScriptに渡すためのパラメータをhiddenタグで出力します。
	 *
	 * @og.rev 4.3.3.0 (2008/10/01) 初期全展開対応
	 * @og.rev 4.3.5.0 (2008/02/01) 全展開時の状態をコントロールするためのフラグを追加
	 * @og.rev 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
	 * @og.rev 6.4.3.4 (2016/03/11) forループを、２回まわさずに、１回にまとめます。
	 *
	 * @param row 行番号
	 *
	 * @return HTMLタグ
	 * @og.rtnNotNull
	 */
	private String getLvlClmTag( final int row ) {
		// 6.4.2.0 (2016/01/29) ソースを見ていたら、共通値が結構あったので、再利用します。
		final String lvlClmVal = getValue( row, levelClmPos );

		// 6.4.3.4 (2016/03/11) forループを、２回まわさずに、１回にまとめます。
		final StringBuilder keys = new StringBuilder( BUFFER_MIDDLE ).append( "command," ).append( levelClm );
		final StringBuilder vals = new StringBuilder( BUFFER_MIDDLE ).append( "NEW,"     ).append( lvlClmVal );

		// 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
		for( final int clm : childSearchKeys ) {
//		for( int i=0; i<childSearchKeys.length; i++ ) {
//			final int clm = childSearchKeys[i];
			keys.append( ',' ).append( getColumnName( clm ) );		// 6.0.2.5 (2014/10/31) char を append する。
			vals.append( ',' ).append( getValue( row, clm ) );		// 6.0.2.5 (2014/10/31) char を append する。
		}

		// 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
		final String imgsrc ;
		final StringBuilder clazz = new StringBuilder( BUFFER_MIDDLE );
		clazz.append( "lvlctl unreplaceable" );
		if( expandAll ) { // 4.3.3.0 (2008/10/01)
			if( row == getRowCount() - 1
					|| Integer.parseInt( lvlClmVal ) >= Integer.parseInt( getValue( row+1, levelClmPos ) ) ) {				// 6.4.2.0 (2016/01/29)
				final boolean isExp = expCtrlClmPos > -1 && StringUtil.nval( getValue( row, expCtrlClmPos ), false ) ;
				if( isExp ) {
					imgsrc = JSPIMG + imgCollapsed;
				}
				else {
					imgsrc = JSPIMG + imgNoSub;
					clazz.append( " fetched nosub" );
				}
			}
			else {
				imgsrc = JSPIMG + imgExpanded;
				clazz.append( " fetched expanded" );
			}
		}
		else {
			imgsrc = JSPIMG + imgCollapsed;
		}

		// 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
		final String tag = new TagBuffer( "img" )			// 6.1.1.0 (2015/01/17) refactoring. 連結記述
			.add( "class"	, clazz.toString() )
			.add( "src"		, imgsrc )
			.add( "alt"		, "Level " + lvlClmVal )		// 6.4.2.0 (2016/01/29) 共通値を設定する。
			.add( "title"	, "Level " + lvlClmVal )		// 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
			.add( "lvl"		, lvlClmVal )					// 6.4.2.0 (2016/01/29) 共通値を設定する。
			.add( "keys"	, keys.toString() )
			.add( "vals"	, vals.toString() )
			.makeTag();

		return getRendererValue( row, levelClmPos ) + tag;
	}

	/**
	 * JavaScriptに渡すためのパラメーターをhiddenタグをして出力します。
	 *
	 * @return hiddenタグ
	 * @og.rtnNotNull
	 */
	private String getParameterTag() {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( XHTMLTag.hidden( ViewAjaxTreeTableParam.CHILD_SEARCH_JSP, childSearchJsp ) )
			.append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_COLLAPSED,	JSPIMG + imgCollapsed ) )
			.append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_EXPANDED,	JSPIMG + imgExpanded ) )
			.append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_NO_SUB,	JSPIMG + imgNoSub ) );
		return buf.toString();
	}
}
