/*
 * 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.hayabusa.common.HybsSystem;
import org.opengion.fukurou.util.LogWriter;
import org.opengion.fukurou.util.StringUtil;

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

/**
 * DBTableModelを継承した TableModelのソート機能の実装クラスです。
 *
 * ViewFormのヘッダーリンクをクリックすると、その項目について再ソートします。
 * これは、データベースではなく、メモリのDBTableModelにソート用のModelを
 * 用意し、そのModelの行番号のみをソートし、行変換を行います。
 * ソートを利用するかどうかは、システムパラメータ の、VIEW_USE_TABLE_SORTER 属性で
 * 指定します。(内部 システムパラメータ では、false 設定)
 * ヘッダー部に表示するリンクは、command=VIEW&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 = false;		// 3.5.6.3 (2004/07/12)

	/**
	 * DBTableModel を設定し、このオブジェクトを初期化します。
	 *
	 * @param   model DBTableModelオブジェクト
	 */
	public void setModel( final DBTableModel model ) {
		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() {
		int rowCount = super.getRowCount();
		indexes = new int[rowCount];

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

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

		String s1 = super.getValue(row1, column);
		String s2 = super.getValue(row2, column);

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

			double d1 = StringUtil.parseDouble( s1 );
			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の値 < row2の値 : 負            正
	 * row1の値 > 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 ) {
		int result = compareRowsByColumn(row1, row2, sortingColumn);

		if(result != 0) {
			return ascending ? result : -result;
		}
		return 0;
	}

	/**
	 * ソートする内部データが不整合を起こしているかチェックします。
	 * 内部行番号と、テーブルオブジェクトの件数を比較します。
	 *
	 * @og.rev 3.5.6.3 (2004/07/12) チェックエラー時にアベンドせずに再設定する。
	 */
	private void checkModel() {
		if(indexes.length != super.getRowCount()) {
			String errMsg = "内部行番号と、テーブルオブジェクトの件数が不一致です。 " + HybsSystem.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);

		int rowCount = indexes.length;

		List<String[]>		newData		 = new ArrayList<String[]>( rowCount );
		List<DBRowHeader>	newRowHeader = new ArrayList<DBRowHeader>( 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;
	}

	/**
	 * シャトルソートを行います。
	 *
	 * @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;
		}
		int middle = (low + high) >>> 1;	// widely publicized the bug pattern.
		shuttlesort(to, from, low, middle);
		shuttlesort(to, from, middle, high);

		int pp = low;
		int qq = middle;

		if(high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
			for(int i = low; i < high; i++) {
				to[i] = from[i];
			}
			return;
		}

		for(int i = low; i < high; i++) {
			if(qq >= high || (pp < middle && compare(from[pp], from[qq]) <= 0)) {
				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から取得する。
	 *
	 * @param column    カラム番号
	 * @param ascending  ソートの方向[true:昇順/false:降順]
	 */
	public void sortByColumn( final int column, final boolean ascending ) {
		this.ascending = ascending;
		sortingColumn = column;
		isNumberType = "NUMBER".equals( getDBColumn(sortingColumn).getClassName() );
		sort();
		lastColumNo = column;
	}

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