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

import org.opengion.fukurou.util.Closer ;
import org.opengion.fukurou.util.LogWriter;

import java.sql.Connection;
import java.sql.Date;
import java.sql.Timestamp;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;

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

import org.jfree.data.jdbc.JDBCCategoryDataset;
import org.jfree.data.Range;

/**
 * HybsJDBCCategoryDataset は、org.jfree.data.jdbc.JDBCCategoryDataset を継承したサブクラスで、
 * executeQuery(Connection , String )  をオーバーライドしています。
 * これは、元のソースのデータベース検索結果を内部で持っておき、getValue(int row, int column)
 * メソッドで直接値を返します。
 * series の横持ち(標準と同じ) 対応です。
 * 参考:JFreeChart : a free chart library for the Java(tm) platform(jfreechart-1.0.6)
 *
 * @og.rev 3.8.9.2 (2007/07/28) 新規作成
 *
 * @version  0.9.0  2001/05/05
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.1,
 */
public class HybsJDBCCategoryDataset2 extends JDBCCategoryDataset {
	private static final long serialVersionUID = 562120130308L ;

	private Number[][] numdata = null;
	private Range      range   = null;
	private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ;	// 5.1.9.0 (2010/08/01) equals,hashCode

	// 4.3.5.0 (2009/02/01) 各オブジェクトを特定するためのユニークな番号
//	private final int uniqNo  = Double.valueOf( Math.random() ).hashCode() ;	// 5.1.8.0 (2010/07/01) 廃止

	/**
	 * Creates a new dataset with the given database connection, and executes
	 * the supplied query to populate the dataset.
	 *
	 * @param connection  the connection.
	 * @param query  the query.
	 *
	 * @throws SQLException if there is a problem executing the query.
	 */
	public HybsJDBCCategoryDataset2( final Connection connection, final String query ) throws SQLException {
		super( connection );
		innerQuery( connection,query );
	}

	/**
	 * Populates the dataset by executing the supplied query against the
	 * existing database connection.  If no connection exists then no action
	 * is taken.
	 *
	 * The results from the query are extracted and cached locally, thus
	 * applying an upper limit on how many rows can be retrieved successfully.
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) new Long(long) ⇒ Long.valueOf(long) 変更
	 * @og.rev 4.0.0.0 (2007/11/28) resultSet,statement を Closer でclose する。
	 * @og.rev 4.0.0.0 (2007/11/28) Range 求めで nullポインタを参照外しの修正
	 * @og.rev 4.0.0.0 (2007/11/30) public な executeQuery メソッドを private 化します。
	 *
	 * @param con  the connection.
	 * @param query  the query.
	 *
	 * @throws SQLException if there is a problem executing the query.
	 */
	@Override
	public void executeQuery( final Connection con, final String query ) throws SQLException {
		innerQuery( con,query );
	}

	/**
	 * Populates the dataset by executing the supplied query against the
	 * existing database connection.  If no connection exists then no action
	 * is taken.
	 *
	 * The results from the query are extracted and cached locally, thus
	 * applying an upper limit on how many rows can be retrieved successfully.
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) new Long(long) ⇒ Long.valueOf(long) 変更
	 * @og.rev 4.0.0.0 (2007/11/28) resultSet,statement を Closer でclose する。
	 * @og.rev 4.0.0.0 (2007/11/28) Range 求めで nullポインタを参照外しの修正
	 * @og.rev 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
	 *
	 * @param con  the connection.
	 * @param query  the query.
	 *
	 * @throws SQLException if there is a problem executing the query.
	 */
	private void innerQuery( final Connection con, final String query ) throws SQLException {

		Statement statement = null;
		ResultSet resultSet = null;
		try {
			statement = con.createStatement();
			resultSet = statement.executeQuery(query);
			ResultSetMetaData metaData = resultSet.getMetaData();

			// Range を予め求めておきます。
			double minimum = Double.POSITIVE_INFINITY;
			double maximum = Double.NEGATIVE_INFINITY;

			int columnCount = metaData.getColumnCount();
			if(columnCount < 2) {
				String errMsg = "JDBCCategoryDataset.executeQuery() : insufficient columns "
							+ "returned from the database. \n"
							+ " SQL=" + query ;
				throw new SQLException( errMsg );
			}

			List<Number[]> rowList = new ArrayList<Number[]>();
			while (resultSet.next()) {
				Number[] clmList = new Number[columnCount-1];
				// first column contains the row key...
		//		Comparable rowKey = resultSet.getString(1);
				String rowKey = resultSet.getString(1);			// 4.3.3.6 (2008/11/15) Generics警告対応
				for( int column=2; column<=columnCount; column++ ) {

				//	Comparable columnKey = metaData.getColumnName(column);
					String columnKey = metaData.getColumnName(column);		// 4.3.3.6 (2008/11/15) Generics警告対応
					int columnType = metaData.getColumnType(column);

					Number value = null;
					switch (columnType) {
						case Types.TINYINT:
						case Types.SMALLINT:
						case Types.INTEGER:
						case Types.BIGINT:
						case Types.FLOAT:
						case Types.DOUBLE:
						case Types.DECIMAL:
						case Types.NUMERIC:
						case Types.REAL: {
							value = (Number)resultSet.getObject(column);
							break;
						}
						case Types.DATE:
						case Types.TIME:  {
							Date date = (Date) resultSet.getObject(column);
							value = Long.valueOf(date.getTime());
							break;
						}
						// 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
						case Types.TIMESTAMP: {
							Timestamp time = (Timestamp) resultSet.getObject(column);
							value = Long.valueOf(time.getTime());
							break;
						}
						case Types.CHAR:
						case Types.VARCHAR:
						case Types.LONGVARCHAR: {
							String string = (String)resultSet.getObject(column);
							try {
								value = Double.valueOf(string);
							}
							catch (NumberFormatException ex) {
								LogWriter.log( ex );
								// suppress (value defaults to null)
							}
							break;
						}
						default:
							// not a value, can't use it (defaults to null)
							break;
					}
					clmList[column-2] = value;
					setValue(value, columnKey, rowKey);

					// Range 求め
					if( value != null ) {	// 4.0.0.0 (2007/11/28)
						double dbl = value.doubleValue();
						if( dbl     < minimum ) { minimum = dbl; }
						if( maximum < dbl     ) { maximum = dbl; }
					}
				}
				rowList.add( clmList );
			}
			numdata = rowList.toArray( new Number[columnCount-1][rowList.size()] );

			range = new Range( minimum, maximum );
		}
		finally {
			Closer.resultClose( resultSet );
			Closer.stmtClose( statement );
		}
	}

	/**
	 * 指定された行列から、数字オブジェクトを取得します。
	 *
	 * @param	row 	行番号
	 * @param	column	カラム番号(列番号)
	 *
	 * @return	指定の行列の値
	 */
	@Override
	public Number getValue( final int row, final int column ) {
		// 注意：行列の順序が逆です。
		return numdata[column][row];
	}

	/**
	 * レンジオブジェクトを取得します。(独自メソッド)
	 *
	 * @return	レンジオブジェクト
	 */
	public Range getRange() {
		return range;
	}

	/**
	 * この文字列と指定されたオブジェクトを比較します。
	 *
	 * 親クラスで、equals メソッドが実装されているため、警告がでます。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) findbug対応
	 * @og.rev 5.1.9.0 (2010/08/01) findbug対応
	 *
	 * @param	object	比較するオブジェクト
	 *
	 * @return	Objectが等しい場合は true、そうでない場合は false
	 */
	@Override
	public boolean equals( final Object object ) {
//		return super.equals( object );
		if( super.equals( object ) ) {
			return hsCode == ((HybsJDBCCategoryDataset2)object).hsCode;
		}
		return false;
	}

	/**
	 * このオブジェクトのハッシュコードを取得します。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) findbug対応
	 * @og.rev 5.1.9.0 (2010/08/01) findbug対応
	 *
	 * @return	ハッシュコード
	 */
//	public int hashCode() { return super.hashCode() ; }
	@Override
	public int hashCode() { return hsCode ; }

	/**
	 * このオブジェクトと指定されたオブジェクトを比較します。
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) 新規追加
	 * @og.rev 4.3.5.0 (2009/02/01) 同一オブジェクトかどうかの判定方法変更
	 * @og.rev 5.1.8.0 (2010/07/01) 廃止
	 *
	 * @param anObject Object 比較されるオブジェクト
	 *
	 * @return	指定されたオブジェクトが等しい場合は true、そうでない場合は false
	 */
//	public boolean equals( final Object anObject ) {
//	//	if( anObject instanceof HybsJDBCCategoryDataset2 ) {
//	//		HybsJDBCCategoryDataset2 other = (HybsJDBCCategoryDataset2)anObject ;
//	//		return ( uniqNo == other.uniqNo ) ;
//	//	}
//	//	return false ;
//
//		if( super.equals( anObject ) ) {
//			HybsJDBCCategoryDataset2 other = ((HybsJDBCCategoryDataset2)anObject);
//			if( numdata != null && numdata == other.numdata &&
//				range   != null && range.equals( other.range ) ) {
//				return true;
//			}
//		}
//		return false;
//	}

	/**
	 * このオブジェクトのハッシュコードを返します。
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) 新規追加
	 * @og.rev 4.3.5.0 (2009/02/01) ハッシュの求め方を変更
	 * @og.rev 5.1.8.0 (2010/07/01) 廃止
	 *
	 * @return	このオブジェクトのハッシュコード値
	 */
//	public int hashCode() {
//	//	return uniqNo;
//		return super.hashCode() +
//				((numdata == null) ? 0 : numdata.hashCode()) +
//				((range   == null) ? 0 : range.hashCode()) ;
//	}
}
