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

import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.util.StringUtil;
import static org.opengion.fukurou.system.HybsConst.CR ;		// 6.1.0.0 (2014/12/26)

import java.util.List;
import java.util.ArrayList;

/**
 * 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 3.5.4.7 (2004/02/06) 新規登録
 * @og.group テーブル管理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class DBTableModelSorter extends DBTableModelImpl {
	private int[]		indexes			;
	private int			sortingColumn	;
	private boolean		ascending		= true;
	private int			lastColumNo		= -1;
	private boolean		isNumberType	;		// 3.5.6.3 (2004/07/12)

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

	/**
	 * DBTableModel を設定し、このオブジェクトを初期化します。
	 *
	 * @param   model DBTableModelオブジェクト
	 */
	public void setModel( final DBTableModel model ) {
		final DBTableModelImpl impl = (DBTableModelImpl)model;
		dbColumns		= impl.dbColumns;
		names			= impl.names;
		data			= impl.data;
		rowHeader		= impl.rowHeader;
		columnMap		= impl.columnMap;
		overflow		= impl.overflow;
		numberOfColumns = impl.numberOfColumns;

		// 3.5.5.5 (2004/04/23) 整合性キー(オブジェクトの作成時刻)追加
		consistencyKey  = impl.consistencyKey;

		lastColumNo = -1;
		reallocateIndexes();
	}

	/**
	 * 行番号インデックスを初期化します。
	 * 行番号をそのまま、順番に設定します。
	 *
	 */
	private void  reallocateIndexes() {
		final int rowCount = super.getRowCount();
		indexes = new int[rowCount];

		for( int row=0; row<rowCount; row++ ) {
			indexes[row] = row;
		}
	}

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

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

		if( isNumberType ) {
			// 3.5.6.3 (2004/07/12) 数字型で ゼロ文字列時の処理
			if( s1.isEmpty() || s2.isEmpty() ) {
				return s1.length() - s2.length() ;
			}

			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 );

			// 注意：引き算をすると、桁あふれする可能性があるため、比較する。
			if(      d1 < d2 ) { return -1; }
			else if( d1 > d2 ) { return 1;  }
			else {				 return 0;  }
		}
		else {
			return s1.compareTo(s2);
		}
	}

	/**
	 * 内部指定のカラム(sortingColumn)に対する、行１と行２の値の大小を比較します。
	 * 比較処理は、compareRowsByColumn( int,int,int ) を使用します。
	 * ascending フラグ[true:昇順/false:降順] にしたがって、結果を反転します。
	 *
	 * ascending == true の時        ascending == false の時
	 * row1の値 &lt; row2の値 : 負            正
	 * row1の値 &gt; row2の値 : 正            負
	 * row1の値 == row2の値 : 0             0
	 *
	 * @param	row1	比較元の行番号
	 * @param	row2	比較先の行番号
	 *
	 * @return	比較結果[負/0/正]
	 * @see     #compareRowsByColumn( int,int,int )
	 */
	private int compare( final int row1, final int row2 ) {
		final int result = compareRowsByColumn(row1, row2, sortingColumn);
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		// 条件反転注意
		return result == 0 ? 0 : ascending ? result : -result;
	}

	/**
	 * ソートする内部データが不整合を起こしているかチェックします。
	 * 内部行番号と、テーブルオブジェクトの件数を比較します。
	 *
	 * @og.rev 3.5.6.3 (2004/07/12) チェックエラー時にアベンドせずに再設定する。
	 */
	private void checkModel() {
		if( indexes.length != super.getRowCount() ) {
			final String errMsg = "内部行番号と、テーブルオブジェクトの件数が不一致です。 " + CR
					+ "Index Length=[" + indexes.length + "] , Table Row Count=[" + super.getRowCount() + "]";
			LogWriter.log( errMsg );
			reallocateIndexes();
		}
	}

	/**
	 * ソート処理のトップメソッドです。
	 *
	 */
	private void  sort() {
		checkModel();

		reallocateIndexes();
		shuttlesort(indexes.clone(), indexes, 0, indexes.length);

		final int rowCount = indexes.length;

		final List<String[]>	newData		 = new ArrayList<>( rowCount );
		final List<DBRowHeader>	newRowHeader = new ArrayList<>( rowCount );

		for( int row=0; row<rowCount; row++ ) {
			newData.add( row,data.get( indexes[row] ) );
			newRowHeader.add( row,rowHeader.get( indexes[row] ) );
		}
		data      = newData;
		rowHeader = newRowHeader;
	}

	/**
	 * シャトルソートを行います。
	 *
	 * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
	 *
	 * @param	from	ソート元配列
	 * @param	to		ソート先配列
	 * @param	low		範囲(下位)
	 * @param	high	範囲(上位)
	 */
	private void shuttlesort( final int[] from, final int[] to, final int low, final int high ) {
		if( high - low < 2 ) {
			return;
		}
		final int middle = (low + high) >>> 1;	// widely publicized the bug pattern.
		shuttlesort(to, from, low, middle);
		shuttlesort(to, from, middle, high);

		if( high - low >= 4 && compare(from[middle-1], from[middle]) <= 0 ) {
			// 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
			System.arraycopy( from,low,to,low,high-low );		// 6.3.6.0 (2015/08/16)
			return;
		}

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
		int pp = low;
		int qq = middle;
		for( int i=low; i<high; i++ ) {
			if( qq >= high || pp < middle && compare( from[pp], from[qq] ) <= 0 ) {		// 6.9.7.0 (2018/05/14) PMD Useless parentheses.
				to[i] = from[pp++];
			}
			else {
				to[i] = from[qq++];
			}
		}
	}

	/**
	 * カラム毎ソートのトップメソッドです。
	 * デフォルトで、昇順ソートを行います。
	 * 最後にソートしたカラムと同一のカラムが指定された場合、昇順と降順を
	 * 反転させて、再度ソートを行います。(シャトルソート)
	 *
	 * @param column    カラム番号
	 */
	public void sortByColumn( final int column ) {
		if( lastColumNo == column ) {
			ascending = !ascending ;
		}
		else {
			ascending = true;
		}
		sortByColumn( column,ascending );
	}

	/**
	 * カラム毎ソートのトップメソッドです。
	 * ascending フラグ[true:昇順/false:降順]を指定します。
	 *
	 * @og.rev 3.5.6.3 (2004/07/12) isNumberType 属性を設定する。
	 * @og.rev 4.0.0.0 (2005/01/31) getColumnClassName 廃止。DBColumから取得する。
	 * @og.rev 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
	 * @og.rev 6.4.6.0 (2016/05/27) isNumber , isDate 追加。
	 *
	 * @param column    カラム番号
	 * @param ascending  ソートの方向[true:昇順/false:降順]
	 */
	public void sortByColumn( final int column, final boolean ascending ) {
		this.ascending = ascending;
		sortingColumn = column;
		// 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
		isNumberType = getDBColumn(sortingColumn).isNumberType();			// 6.4.6.0 (2016/05/27)
		sort();
		lastColumNo = column;
	}

	/**
	 * ソートの方向(昇順:true/降順:false)を取得します。
	 *
	 * @return  ソートの方向 [true:昇順/false:降順]
	 */
	public boolean isAscending() {
		return ascending;
	}
}
