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

import org.opengion.fukurou.util.StringUtil;

import java.util.Arrays;

/**
 * DBTableModelを継承した TableModelのソート機能の実装クラスです。
 *
 * ViewFormのヘッダーリンクをクリックすると、その項目について再ソートします。
 * これは、データベースではなく、メモリのDBTableModelにソート用のModelを
 * 用意し、そのModelの行番号のみをソートし、行変換を行います。
 * ソートを利用するかどうかは、システムパラメータ の、VIEW_USE_TABLE_SORTER 属性で
 * 指定します。(内部 システムパラメータ では、false 設定)
 * ヘッダー部に表示するリンクは、command=VIEW&amp;h_sortColumns=XXXXX で、カラム名を指定します。
 * ※ h_sortColumns 部は、HybsSystemにて定義しますので一般のJSPでは使用しないで下さい。
 *
 * DBTableModel インターフェースは，データベースの検索結果(Resultset)をラップする
 * インターフェースとして使用して下さい。
 *
 * @og.rev 7.0.1.2 (2018/11/04) 新規登録
 * @og.group テーブル管理
 *
 * @version  7.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public class DataModelSorter {
	private DataModel<String>	table		;
	private Integer[]			idx			;
	private int					sortClmNo	= -1;
	private boolean				ascending		= true;
	private boolean				isNumType	;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 7.0.1.2 (2018/11/04) 新規登録
	 */
	public DataModelSorter() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * DBTableModel を設定し、このオブジェクトを初期化します。
	 *
	 * @param   model DBTableModelオブジェクト
	 */
	public void setModel( final DataModel<String> model ) {
		table = model;
	}

	/**
	 * DBTableModel を設定し、このオブジェクトを初期化します。
	 *
	 * @param   clmNo	ソートするカラム番号
	 * @param   numType	ソートするタイプ [true:数値/false:文字]
	 */
	public void sortColumn( final int clmNo , final boolean numType ) {
		final boolean isAsce = sortClmNo == clmNo ? !ascending : true ;		// 同じカラムなら反転
		sortColumn( clmNo , numType , isAsce );
	}

	/**
	 * DBTableModel を設定し、このオブジェクトを初期化します。
	 *
	 * @param   clmNo	ソートするカラム番号
	 * @param   numType	ソートするタイプ [true:数値/false:文字]
	 * @param   isAsce	ソート方向 [true:正方向/false:逆方向
	 */
	public void sortColumn( final int clmNo , final boolean numType , final boolean isAsce ) {
		sortClmNo	= clmNo;
		isNumType	= numType ;
		ascending	= isAsce ;

		reallocateIndexes() ;
		Arrays.sort( idx, (i1,i2) -> compareColumn( i1.intValue(),i2.intValue() ) );
	}

	/**
	 * 行番号インデックスを初期化します。
	 * 行番号をそのまま、順番に設定します。
	 *
	 * @og.rev 7.0.1.2 (2018/11/04) 新規登録
	 */
	private void  reallocateIndexes() {
		final int rowCount = table.getRowCount();
		idx = new Integer[rowCount];

		for( int row=0; row<rowCount; row++ ) {
			idx[row] = Integer.valueOf( row );
		}
	}

	/**
	 * 同一カラム番号に対する、行１と行２の値の大小を比較します。
	 * 比較時に、そのカラムが、NUMBERタイプの場合は、Double に変換後、数字として
	 * 比較します。それ以外の場合は、文字列の比較( row1の値.compareTo(s2) )の
	 * 値を返します。
	 *
	 * row1の値 &lt; row2の値 : 負
	 * row1の値 &gt; row2の値 : 正
	 * row1の値 == row2の値 : 0
	 *
	 * @og.rev 7.0.1.2 (2018/11/04) 新規登録
	 * @og.rev 7.0.1.9 (2019/02/04) 数値タイプでwritableControl機能使用時の対応
	 *
	 * @param   row1	比較元の行番号
	 * @param   row2	比較先の行番号
	 * @param   column	比較するカラム番号
	 *
	 * @return	比較結果[負/0/正]
	 */
	private int compareColumn( final int row1, final int row2 ) {

		// 7.0.1.9 (2019/02/04) 数値タイプでwritableControl機能使用時の対応
		String s1 = table.getValue( row1, sortClmNo );
		String s2 = table.getValue( row2, sortClmNo );

		final int comp ;
		// 3.5.6.3 (2004/07/12) ゼロ文字列時の処理(以下に ゼロ文字列が渡らないことの保障)
		if( s1.isEmpty() || s2.isEmpty() ) {
			comp = s1.length() - s2.length() ;
		}
		else if( isNumType ) {
			if( s1.charAt(0) == '_' ) { s1 = s1.substring(1); }		// 7.0.1.9 (2019/02/04) 先頭に '_' があれば削除
			if( s2.charAt(0) == '_' ) { s2 = s2.substring(1); }		// 7.0.1.9 (2019/02/04) 先頭に '_' があれば削除

			final double d1 = StringUtil.parseDouble( s1 );
			final double d2 = StringUtil.parseDouble( s2 );

			// 注意：引き算をすると、桁あふれする可能性があるため、比較する。
			comp = Double.compare( d1,d2 );
		}
		else {
			comp = s1.compareTo(s2);
		}

		return comp == 0 ? 0 : ascending ? comp : -comp;
	}

	/**
	 * Integerのインデックス配列をint型の配列に変換します。
	 *
	 * @og.rev 7.0.1.2 (2018/11/04) 新規登録
	 *
	 * @return  インデックス配列のint型の配列
	 */
	public int[] getIndexes() {
		final int rowCount = idx.length;
		final int[] rtn = new int[rowCount];
		for( int row=0; row<rowCount; row++ ) {
			rtn[row] = idx[row].intValue();
		}

		return rtn;
	}

	/**
	 * ソートの方向(昇順:true/降順:false)を取得します。
	 *
	 * @og.rev 7.0.1.2 (2018/11/04) 新規登録
	 *
	 * @return  ソートの方向 [true:昇順/false:降順]
	 */
	public boolean isAscending() {
		return ascending;
	}
}
