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

import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.db.AbstractTableFilter;
import org.opengion.hayabusa.db.DBTableModel;

/**
 * TableFilter_NORMALIZE は、TableFilter インターフェースを継承した、DBTableModel 処理用の
 * 実装クラスです。
 * 指定の数値データ(横並び)を、最小値「0」～最大値「1」に正規化(スケーリング)します。
 * 値 = (値 - 最小値) / (最大値 - 最小値) を計算します。
 *
 * 正規化するｶﾗﾑは、VAL_CLMS で指定します。これは、数値カラムで、CSV形式で指定します。
 *
 * ｶﾗﾑの値が null または、ゼロ文字列の場合は、計算から除外します。
 *
 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
 * 【パラメータ】
 *  {
 *       VAL_CLMS   : 正規化(スケーリング)を行うカラム列
 *       FORMAT     : 数値のフォーマット (初期値:%.3f ･･･ 小数第３位以下を、四捨五入する)
 *  }
 *
 * @og.formSample
 * ●形式：
 *      ① &lt;og:tableFilter classId="NORMALIZE" selectedAll="true"
 *                       keys="VAL_CLMS"
 *                       vals='"USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY"' /&gt;
 *
 *      ② &lt;og:tableFilter classId="NORMALIZE"  selectedAll="true" &gt;
 *               {
 *                   VAL_CLMS  : USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY ;
 *               }
 *         &lt;/og:tableFilter&gt;
 *
 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
 *
 * @version  8.4 (2023/03/10)
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.11,
 */
public class TableFilter_NORMALIZE extends AbstractTableFilter {
	/** このプログラムのVERSION文字列を設定します。 {@value} */
	private static final String VERSION = "8.4.1.0 (2023/02/10)" ;

	private DBTableModel	table	;

	/**
	 * デフォルトコンストラクター
	 */
	public TableFilter_NORMALIZE() {
		super();
		initSet( "VAL_CLMS"	, "正規化(スケーリング)を行うカラム列" );
		initSet( "FORMAT"	, "数値のフォーマット (初期値:%.3f ･･･ 小数代３位以下を、四捨五入する)"	);
	}

	/**
	 * DBTableModel処理を実行します。
	 *
	 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
	 *
	 * @return 処理結果のDBTableModel
	 */
	public DBTableModel execute() {
		table = getDBTableModel();

		final int ROW_CNT = table.getRowCount();
		if( ROW_CNT == 0 ) { return table; }			// 0 は、処理なし

		final String[] valClms = StringUtil.csv2Array( getValue( "VAL_CLMS" ) );
		// 最大、最小判定を行うカラム番号を求めます。
		final int[] clmNos = getTableColumnNo( valClms );

		final String	fmt		= getValue( "FORMAT" );
		final String	format	= fmt == null || fmt.isEmpty() ? "%.3f" : fmt ;		// 初期値が、"%.3f"

		final Normalization normal = new Normalization( clmNos , format );

		// １回目は最小値、最大値を求める処理
		for( int row=0; row<ROW_CNT; row++ ) {
			final String[] vals = table.getValues( row );
			normal.check( vals );
		}

		// ２回目は(値 - 最小値) / (最大値 - 最小値) を計算します。
		for( int row=0; row<ROW_CNT; row++ ) {
			final String[] vals = table.getValues( row );
			normal.chenge( vals );				// vals の配列の中身を直接書き換えています。
		}

		return table;
	}

	/**
	 * 実際の間引き処理を行うクラス。
	 *
	 * 最大、最少を求めて、正規化処理部分だけを分離しました。
	 *
	 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
	 */
	private static final class Normalization {
		private final int[]			clmNos ;		// 数値ｶﾗﾑのｶﾗﾑ番号
		private final int			clmSize;		// 数値ｶﾗﾑの個数
		private final double[]		minVals;		// 最小値
		private final double[]		maxVals;		// 最大値
		private final String		format;			// 文字列化する時の数値ﾌｫｰﾏｯﾄ

		/**
		 * 正規化処理を行うクラスのコンストラクター
		 *
		 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
		 *
		 * @param	clmNos	最大最小処理を行うカラム番号配列
		 * @param	format	初期値が、"%.3f"
		 */
		public Normalization( final int[] clmNos , final String format ) {
			this.clmNos 	= clmNos;
			this.clmSize	= clmNos.length;		// 値部分のみの配列番号
			this.format		= format;

			minVals = new double[clmSize];
			maxVals = new double[clmSize];

			Arrays.fill( minVals,Double.POSITIVE_INFINITY );	// 最小値の初期値は、正の無限大
			Arrays.fill( maxVals,Double.NEGATIVE_INFINITY );	// 最大値の初期値は、負の無限大
		}

		/**
		 * 正規化処理のための最大最小のデータを求めます。
		 *
		 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
		 *
		 * @param	vals	ﾁｪｯｸする行配列ﾃﾞｰﾀ
		 */
		public void check( final String[] vals ) {
			for( int i=0; i<clmSize; i++ ) {
				final String val = vals[clmNos[i]];
				if( !StringUtil.isNull( val ) ) {						// データが null の場合は、置換が発生しません。
					final double dval = Double.parseDouble( val );
					if( minVals[i] > dval ) { minVals[i] = dval; }		// 最小値の入れ替え
					if( maxVals[i] < dval ) { maxVals[i] = dval; }		// 最大値の入れ替え
				}
			}
		}

		/**
		 * 正規化処理のための最大最小のデータ変換を行います。
		 *
		 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
		 *
		 * @param	vals	変換する行配列ﾃﾞｰﾀ
		 */
		public void chenge( final String[] vals ) {
			for( int i=0; i<clmSize; i++ ) {
				final String val = vals[clmNos[i]];
				if( !StringUtil.isNull( val ) ) {						// データが null の場合は、置換が発生しません。
					final double dval = Double.parseDouble( val );
					vals[clmNos[i]] = String.format( format,( dval - minVals[i] ) / (maxVals[i] - minVals[i]) );	// 正規化（Normalization）
				}
			}
		}
	}
}
