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

import java.util.Map;
import java.util.HashMap;

import java.util.List;											// 8.0.0.1 (2021/10/08)
import java.util.ArrayList;										// 8.0.0.1 (2021/10/08)

import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.HybsDateUtil;					// 8.0.1.2 (2021/11/19)
// import org.opengion.hayabusa.common.HybsSystem;				// 8.0.0.0 (2021/09/30)
import org.opengion.hayabusa.db.AbstractTableFilter;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBColumnConfig;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBTableModelUtil;
import org.opengion.hayabusa.resource.ResourceManager;
import org.opengion.hayabusa.html.ViewMarker;					// 8.0.0.0 (2021/09/30)

/**
 * TableFilter_ROTATE は､TableFilter ｲﾝﾀｰﾌｪｰｽを継承した､DBTableModel 処理用の
 * 実装ｸﾗｽです｡
 *
 * ここではﾃｰﾌﾞﾙの回転<del>及びその逆回転</del>を行います｡
 *   8.0.0.0 (2021/07/31) 逆回転 廃止
 *
 * ﾊﾟﾗﾒｰﾀは､tableFilterﾀｸﾞの keys, vals にそれぞれ記述するか､BODY 部にCSS形式で記述します｡
 * 【ﾊﾟﾗﾒｰﾀ】
 *  {
 *       KEY_CLM        : ｷｰｶﾗﾑ(複数指定可)                 (必須)
 *       ROTATE_CLM     : 回転するｶﾗﾑ                       (必須)
 *       ROTATE_LBL     : 回転ｶﾗﾑのﾍｯﾀﾞｰﾗﾍﾞﾙ                (任意指定)				8.0.0.1 (2021/10/08)
 *       ADD_CLMS       : 回転するｶﾗﾑ値を外部から与えます   (任意指定)				8.0.1.2 (2021/11/19)
 *       LBL_FORMAT     : ﾍｯﾀﾞｰﾗﾍﾞﾙの日付ﾌｫｰﾏｯﾄ指定(日付)   (任意指定)				8.0.1.2 (2021/11/19)
 *       VALUE_CLM      : 回転ｶﾗﾑの値                       (必須)
 *       USE_LABEL      : 値ﾗﾍﾞﾙのｶﾗﾑを生成するか           (任意指定 初期値:false) 8.0.0.0 (2021/07/31)
 *       USE_RENDERER   : 値の表示に､renndererを使用するか (任意指定 初期値:false)	8.0.0.0 (2021/07/31)
 *       USE_MARKER     : 値の表示に､viewMarkerを使用するか (任意指定 初期値:false)	8.0.0.0 (2021/09/30)
 *  <del>REVERSE        : 回転(false)･逆回転(true)   (任意指定 初期値:false)</del>	8.0.0.0 (2021/07/31) 廃止
 *       MUST_CLM       : 必須属性を定義するｶﾗﾑ      (任意指定 初期値:false)
 *       DEF_CLM        : 初期値を定義するｶﾗﾑ        (任意指定)
 *  }
 *
 *  ※ それぞれに指定されたｶﾗﾑ名が存在しない場合は､処理されませんのでご注意下さい｡
 *
 * ①回転
 *  ｷｰｶﾗﾑ(KEY_CLM)に指定された値が同じN行を1行として回転します｡
 *  (ｷｰｶﾗﾑの値がﾌﾞﾚｲｸしたﾀｲﾐﾝｸﾞで､行を変更します)
 *  このN行に含まれる回転ｶﾗﾑの値がｶﾗﾑ名に､回転ｶﾗﾑ値が各ｶﾗﾑの値になります｡
 *  ｷｰｶﾗﾑは､CSV形式で複数指定可能です｡
 *
 *  生成されたﾃｰﾌﾞﾙﾓﾃﾞﾙのｶﾗﾑは､始めのMｶﾗﾑがｷｰｶﾗﾑに､その後ろのNｶﾗﾑが
 *  回転されたｶﾗﾑになります｡
 *
 *  生成されたﾃｰﾌﾞﾙのｶﾗﾑｵﾌﾞｼﾞｪｸﾄは､ROTATE_CLM で指定されたｷｰで生成されます｡
 *  ﾘｿｰｽに存在しない場合は､ROTATE_LBL で､ﾗﾍﾞﾙを指定することができます｡
 *
 *  また､元ﾃｰﾌﾞﾙにMUST_CLMにより､各ｶﾗﾑの必須属性を定義することが
 *  できます｡(MUST属性は､'1'又は'true'の場合に必須になります｡)
 *
 * 8.0.0.0 (2021/07/31)
 *  USE_LABEL="true" を指定した場合､VALUE_CLM のﾗﾍﾞﾙを､ｷｰｶﾗﾑと回転ｶﾗﾑの間に
 *  追加します｡これは､VALUE_CLM を 複数指定できる機能追加に伴う処置です｡
 *
 * 8.0.0.0 (2021/07/31) 逆回転 廃止
 * <del>②逆回転
 *  回転時の逆の挙動になります｡
 *  "ｷｰｶﾗﾑに指定されたｶﾗﾑ以外"を回転ｶﾗﾑで指定されたｶﾗﾑの値として分解します｡
 *  各回転ｶﾗﾑの値は､回転ｶﾗﾑ値に指定されたｶﾗﾑに格納されます｡
 *
 *  分解後のｶﾗﾑ数は､ｷｰｶﾗﾑ数 + 2 (回転ｶﾗﾑ､回転ｶﾗﾑ値)になります｡
 *  また､行数は､(分解前の行数) x (回転ｶﾗﾑ数)になります｡
 *</del>
 *
 * @og.formSample
 * ●形式：
 *      ① &lt;og:tableFilter classId="ROTATE" selectedAll="true"
 *                   keys="KEY_CLM,ROTATE_CLM,VALUE_CLM" vals='"GOKI,MAX_SID,MAX_TM_RPS",TOKEN,X_VAL' /&gt;
 *
 *      ② &lt;og:tableFilter classId="ROTATE"  selectedAll="true" &gt;
 *               {
 *                   KEY_CLM    : GOKI,MAX_SID,MAX_TM_RPS ;
 *                   ROTATE_CLM : TOKEN ;
 *                   VALUE_CLM  : X_VAL ;
 *               }
 *         &lt;/og:tableFilter&gt;
 *
 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性ﾁｪｯｸを追加
 * @og.rev 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と､USE_LABEL属性の追加
 *
 * @version  0.9.0  2000/10/17
 * @author   Hiroki Nakamura
 * @since    JDK1.1,
 */
public class TableFilter_ROTATE extends AbstractTableFilter {
	/** このﾌﾟﾛｸﾞﾗﾑのVERSION文字列を設定します｡ {@value} */
	private static final String VERSION = "8.0.1.2 (2021/11/19)" ;

	private DBTableModel	table	 ;			// 5.5.2.6 (2012/05/25) 共通に使うため､変数定義
	private ResourceManager	resource ;			// 5.5.2.6 (2012/05/25) 共通に使うため､変数定義

	/**
	 * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) keysMap を､ｻﾌﾞｸﾗｽから設定させるように変更｡
	 */
	public TableFilter_ROTATE() {
		super();
		initSet( "KEY_CLM"   	, "ｷｰｶﾗﾑ(複数指定可)(必須)"					);
		initSet( "ROTATE_CLM"	, "回転するｶﾗﾑ(必須)"						);
		initSet( "ROTATE_LBL"	, "回転ｶﾗﾑのﾍｯﾀﾞｰﾗﾍﾞﾙ"						);		// 8.0.0.1 (2021/10/08)
		initSet( "ADD_CLMS"		, "回転するｶﾗﾑ値を外部から与えます"			);		// 8.0.1.2 (2021/11/19)
		initSet( "LBL_FORMAT"	, "ﾍｯﾀﾞｰﾗﾍﾞﾙのﾌｫｰﾏｯﾄ指定(日付)"				);		// 8.0.1.2 (2021/11/19)
		initSet( "VALUE_CLM"	, "回転ｶﾗﾑの値(複数指定可)(必須)"			);
		initSet( "USE_LABEL"	, "値ﾗﾍﾞﾙのｶﾗﾑを生成するか(初期値:false)"	);		// 8.0.0.0 (2021/07/31)
		initSet( "USE_RENDERER"	, "値の表示にrenndererを使用するか(初期値:false)" );	// 8.0.0.0 (2021/07/31)
		initSet( "USE_MARKER"	, "値の表示にviewMarkerを使用するか(初期値:false)" );	// 8.0.0.0 (2021/09/30)
//		initSet( "REVERSE"		, "回転(false)/逆回転(true) (初期値:false)"	);		// 8.0.0.0 (2021/07/31) 廃止
		initSet( "MUST_CLM"		, "必須属性を定義するｶﾗﾑ (初期値:false)"	);
		initSet( "DEF_CLM"		, "初期値を定義するｶﾗﾑ"						);
	}

	/**
	 * DBTableModel処理を実行します｡
	 *
	 * @og.rev 4.3.7.4 (2009/07/01) 新規追加
	 * @og.rev 5.5.2.6 (2012/05/25) protected変数を､private化したため､getterﾒｿｯﾄﾞで取得するように変更
	 *
	 * @return 処理結果のDBTableModel
	 */
	public DBTableModel execute() {
		table    = getDBTableModel();		// 5.5.2.6 (2012/05/25) ｲﾝﾀｰﾌｪｰｽにgetterﾒｿｯﾄﾞ追加
		resource = getResource();			// 5.5.2.6 (2012/05/25) ｲﾝﾀｰﾌｪｰｽにgetterﾒｿｯﾄﾞ追加

		return getRotateTable();
	}

	/**
	 * 回転後のDBTableModelを返します｡
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) ﾒｿｯﾄﾞ名変更(setDefValue ⇒ setDefault)
	 * @og.rev 6.4.3.4 (2016/03/11) forﾙｰﾌﾟを､forEach ﾒｿｯﾄﾞに置き換えます｡
	 * @og.rev 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と､USE_LABEL属性の追加
	 * @og.rev 8.0.0.1 (2021/10/08) ROTATE_LBL(回転ｶﾗﾑのﾍｯﾀﾞｰﾗﾍﾞﾙ)の追加
	 * @og.rev 8.0.1.2 (2021/11/19) ADD_CLMS、LBL_FORMAT 追加
	 *
	 * @return 回転後のDBTableModel
	 */
	private DBTableModel getRotateTable() {
		// 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と､USE_LABEL属性の追加
		// ｴﾗｰ時の原因表示を入れておきます｡

		final String tmpKeyClm	= getValue( "KEY_CLM" );
		final String[] keyClm	= StringUtil.csv2Array( tmpKeyClm );
		if( keyClm.length == 0 ) { System.out.println( "KEY_CLM is none[" + tmpKeyClm + "]" ); }

		final String tmpRotClm	= getValue( "ROTATE_CLM" );
		final int rotateNo		= table.getColumnNo( tmpRotClm );		// 8.0.0.0 (2021/09/30) ｴﾗｰにします｡

		// 8.0.0.1 (2021/10/08) ROTATE_LBL(回転ｶﾗﾑのﾍｯﾀﾞｰﾗﾍﾞﾙ)の追加
		final String tmpRotLbl	= getValue( "ROTATE_LBL" );
		final int lblNo			= table.getColumnNo( tmpRotLbl, false );	// 任意なので;

		final String tmpValClm	= getValue( "VALUE_CLM" );
		final String[] valClm	= StringUtil.csv2Array(	tmpValClm );
		if( valClm.length == 0 ) { System.out.println( "VALUE_CLM is none[" + tmpValClm + "]" ); }

		int clmCount = 0; // 回転後のｶﾗﾑ数
		// ｷｰｶﾗﾑのｶﾗﾑ番号を求め､ｶﾗﾑ数としてｶｳﾝﾄします｡
		final Map<String, Integer> clmMap = new HashMap<>();
		final int[] keyNos = new int[keyClm.length];
		for( int i=0; i<keyNos.length; i++ ) {
			keyNos[i] = table.getColumnNo( keyClm[i] );		// 8.0.0.0 (2021/09/30) ｴﾗｰにします｡
			clmMap.put( keyClm[i], clmCount++ );			// 固定ｶﾗﾑ
		}

		// 8.0.0.0 (2021/07/31) USE_LABEL属性の追加
		final boolean useValLbl = StringUtil.nval( getValue( "USE_LABEL" ), false );
		if( useValLbl ) {
			clmMap.put( "LABEL", clmCount++ );				// ﾗﾍﾞﾙｶﾗﾑ
		}

		// ============ 以下は回転ｶﾗﾑの処理 ============

		final int valStartNo = clmCount ;					// 値ｶﾗﾑ(回転ｶﾗﾑ)の開始ｱﾄﾞﾚｽ
		final List<String> lblList = new ArrayList<>();		// 8.0.0.1 (2021/10/08) ROTATE_LBL(未使用時は､nullをｾｯﾄする)
		for( int i=0; i<valStartNo; i++ ) { lblList.add( null ); }	// 後の処理を共通にするため｡

		// 8.0.1.2 (2021/11/19) LBL_FORMAT 回転ｶﾗﾑのﾗﾍﾞﾙﾌｫｰﾏｯﾄ(日付)を設定します。
		final String lblFormat	= getValue( "LBL_FORMAT" );		// "MM/dd\n(EEE)" などの日付ﾌｫｰﾏｯﾄを指定します。

		// 8.0.1.2 (2021/11/19) ADD_CLMS 回転ｶﾗﾑを追加します。
		final String tmpAddClms	= getValue( "ADD_CLMS" );
		final String[] addClms	= StringUtil.csv2Array( tmpAddClms );
		for( final String clm : addClms ) {
			clmMap.put( clm, clmCount++ );				// ﾗﾍﾞﾙｶﾗﾑ
			if( lblFormat != null ) {
				lblList.add( HybsDateUtil.getDateFormat( lblFormat,clm ) );	// 後の処理を共通にするため｡
			}
		}

		// 8.0.0.0 (2021/07/31) USE_RENDERER属性の追加
		final boolean useValRend = StringUtil.nval( getValue( "USE_RENDERER" ), false );
		// 8.0.0.0 (2021/09/30) USE_MARKER属性の追加
		final boolean useMarker = StringUtil.nval( getValue( "USE_MARKER" ), false );
		final ViewMarker viewMarker = useMarker ? getViewMarker() : null;

		// 値ｶﾗﾑのｶﾗﾑ番号を求める｡(ｶﾗﾑ数としてｶｳﾝﾄしない=行が増える)
		final int[] valNos = new int[valClm.length];
		final DBColumn[] valClmns = new DBColumn[valClm.length];
		for( int i=0; i<valNos.length; i++ ) {
			valNos[i] = table.getColumnNo( valClm[i] );					// 8.0.0.0 (2021/09/30) ｴﾗｰにします｡
			if( useValRend ) {
				valClmns[i] = table.getDBColumn( valNos[i] );
			}
		}

		int rowCount = 0; // 回転後の行数(KEY_CLMで指定したﾕﾆｰｸになる行数で､実際の行数は､VALUE_CLM 倍になる)
		// 回転ｶﾗﾑの値から回転後のｶﾗﾑ数を求めます｡
		// また同時に､ｷｰｶﾗﾑの値のﾌﾞﾚｲｸ数により行数を求めます｡
		final Map<String, Integer> rowMap		= new HashMap<>();
		final Map<String, Boolean> mustMap		= new HashMap<>();
		final Map<String, String>  defaultMap	= new HashMap<>();
		final int mustNo = table.getColumnNo( getValue( "MUST_CLM"), false );	// 任意なので
		final int defNo  = table.getColumnNo( getValue( "DEF_CLM" ), false );	// 任意なので
		for( int i=0; i<table.getRowCount(); i++ ) {
			final String clmKey = table.getValue( i, rotateNo );
			if( clmMap.get( clmKey ) == null ) {
				clmMap.put( clmKey, clmCount++ );			// 回転ｶﾗﾑ

				if( lblFormat == null ) {
					// 8.0.0.1 (2021/10/08) ROTATE_LBL(未使用時は､nullをｾｯﾄする)
					lblList.add( lblNo >= 0 ? table.getValue( i, lblNo ) : null );	// 後の処理を共通にするため｡
				}
				else {
					// 8.0.1.2 (2021/11/19) LBL_FORMAT 回転ｶﾗﾑのﾗﾍﾞﾙﾌｫｰﾏｯﾄ(日付)を設定します。
					final String fmt = lblNo >= 0 ? table.getValue( i, lblNo ) : clmKey ;
					lblList.add( HybsDateUtil.getDateFormat( lblFormat,fmt ) );	// 後の処理を共通にするため｡
				}
			}
			// 必須ｶﾗﾑ抜き出し
			if( mustNo > -1 && StringUtil.nval( table.getValue( i, mustNo ), false ) ) {
				mustMap.put( clmKey, true );
			}
			// ﾃﾞﾌｫﾙﾄ値を書き換えるｶﾗﾑの抜き出し
			if( defNo > -1 && table.getValue( i, defNo ) != null && table.getValue( i, defNo ).length() > 0 ) {
				defaultMap.put( clmKey, table.getValue( i, defNo ) );
			}

			final String rowKey = getSeparatedValue( i, keyNos );
			// 6.0.0.1 (2014/04/25) These nested if statements could be combined
			if( rowKey != null && rowKey.length() > 0 && rowMap.get( rowKey ) == null ) {
				rowMap.put( rowKey, rowCount++ );
			}
		}

		// 回転後のｶﾗﾑ一覧よりDBTableModelを初期化します｡
		// 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
		final String names[] = new String[clmMap.size()];
		// 6.4.3.4 (2016/03/11) forﾙｰﾌﾟを､forEach ﾒｿｯﾄﾞに置き換えます｡
		clmMap.forEach( (k,v) -> names[v] = k );

		final DBTableModel nTable = DBTableModelUtil.newDBTable();
		nTable.init( names.length );
		for( int i=0; i<names.length; i++ ) {
			if( mustMap.get( names[i] ) != null ) {
				table.addMustType( i, "must" );
			}
//			DBColumn column = resource.makeDBColumn( names[i] );
			DBColumn column = resource.makeDBColumn( names[i],lblList.get(i) );			// 8.0.0.1 (2021/10/08) ﾍｯﾀﾞｰのﾗﾍﾞﾙ｡未使用時は null
			// 8.0.0.0 (2021/07/31) ｵﾘｼﾞﾅﾙが数字ﾀｲﾌﾟだと､ｴﾗｰになる可能性があるため
			final DBColumnConfig dbConfig = column.getConfig();			// 固定ｶﾗﾑより値ｶﾗﾑの方が多いので､処理速度は気にしないことにする｡
			if( valStartNo <= i ) {			// 値ｶﾗﾑ(回転ｶﾗﾑ)
				dbConfig.setRenderer( "LABEL" );						//
			}

//			if( defaultMap.get( names[i] ) != null ) {
//				dbConfig.setDefault( defaultMap.get( names[i] ) );		// 5.1.8.0 (2010/07/01)
//			}
			final String defVal = defaultMap.get( names[i] ) ;
			if( defVal != null ) {
				dbConfig.setDefault( defVal );							// 5.1.8.0 (2010/07/01)
			}
			column = new DBColumn( dbConfig );							// 8.0.0.0 (2021/07/31)
			nTable.setDBColumn( i, column );							// 5.1.8.0 (2010/07/01)
		}

		// 値の一覧を作成し､DBTableModelに値をｾｯﾄします｡
		if( rowCount > 0 ) {
			// 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
			final String[][] vals = new String[rowCount*valClm.length][names.length];	// 行数は､valClm.length 倍になる
			for( int i=0; i<table.getRowCount(); i++ ) {
				final int row = rowMap.get( getSeparatedValue( i, keyNos ) );
				final int clm = clmMap.get( table.getValue( i, rotateNo ) );

				final int nrow = row * valClm.length ;
				// 8.0.0.0 (2021/07/31) VALUE_CLM が複数存在した場合の処理
				for( int k=0; k<valNos.length; k++ ) {
					for( int j=0; j<keyNos.length; j++ ) {
						String val2 = table.getValue( i, keyNos[j] );

						if( viewMarker != null ) {
							val2 = viewMarker.getMarkerString( i,keyNos[j],val2 );			// ViewMarkerを使用
						}
						vals[nrow+k][j] = val2;

		//				vals[nrow+k][j] = table.getValue( i, keyNos[j] );
					}
					if( useValLbl ) {
						// 実際は､上のﾙｰﾌﾟの最後の j ( = keyNos.length )
						vals[nrow+k][keyNos.length] = table.getColumnLabel( valNos[k] );
					}

					String val = table.getValue( i, valNos[k] );
					if( useValRend ) { val = valClmns[k].getRendererValue( val ); }		// Rndererを使用
					if( viewMarker != null ) {
						val = viewMarker.getMarkerString( i,valNos[k],val );			// ViewMarkerを使用
					}
					vals[nrow+k][clm] = val;
				}
			}
			for( int i=0; i<vals.length; i++ ) {
				nTable.addColumnValues( vals[i] );
			}
		}

		return nTable;
	}

	/**
	 * 各行のｷｰとなるｷｰｶﾗﾑの値を連結した値を返します｡
	 *
	 * @param	row		行番号
	 * @param	clmNo	ｶﾗﾑ番号配列(可変長引数)
	 *
	 * @return	各行のｷｰとなるｷｰｶﾗﾑの値を連結した値
	 * @og.rtnNotNull
	 */
	private String getSeparatedValue( final int row, final int... clmNo ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		for( int i=0; i<clmNo.length; i++ ) {
			final String val = table.getValue( row, clmNo[i] );
			if( val != null && val.length() > 0 ) {
				if( i > 0 ) {
					buf.append( "__" );
				}
				buf.append( val );
			}
		}
		return buf.toString();
	}
}
