/*
 * 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 org.opengion.fukurou.db.DBUtil;
import org.opengion.hayabusa.common.HybsSystem;

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 java.util.Arrays;
import java.util.Locale;

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)
 * メソッドで直接値を返します。
 * select category,series1,series2,series3,･･･ from ･･･
 * 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 = 602120140926L ;

	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

	/**
	 * Creates a new dataset with the given database connection, and executes
	 * the supplied query to populate the dataset.
	 *
	 * @og.rev 6.0.2.0 (2014/09/19) シリーズのラベル名配列追加
	 *
	 * @param connection  the connection.
	 * @param query  the query.
	 * @param lbls  シリーズのラベル名配列
	 *
	 * @throws SQLException if there is a problem executing the query.
	 */
//	public HybsJDBCCategoryDataset2( final Connection connection, final String query ) throws SQLException {
	public HybsJDBCCategoryDataset2( final Connection connection, final String query, final String[] lbls ) throws SQLException {
		super( connection );
//		innerQuery( connection,query );
		innerQuery( connection,query,lbls );
	}

	/**
	 * 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 化します。
	 * @og.rev 6.0.2.0 (2014/09/19) シリーズのラベル名配列追加
	 *
	 * @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 );
		innerQuery( con,query,null );
	}

	/**
	 * 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 で処理を分けます。
	 * @og.rev 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。
	 * @og.rev 6.0.2.0 (2014/09/19) columnKeyは、getColumnLabel で取得する。
	 *
	 * @param con  the connection.
	 * @param query  the query.
	 * @param seriesLabels  シリーズのラベル名配列
	 *
	 * @throws SQLException if there is a problem executing the query.
	 */
//	private void innerQuery( final Connection con, final String query ) throws SQLException {
	private void innerQuery( final Connection con, final String query, final String[] seriesLabels ) 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 );
			}

			// 6.0.2.0 (2014/09/19) シリーズのラベル名配列を使うときは、必ずカラム数ー１
			if( seriesLabels != null && seriesLabels.length != columnCount-1 ) {
				String errMsg = "seriesLabels を使用する場合は、必ず(カラム数ー１)にしてください。"
								+ HybsSystem.CR
								+ " seriesLabels.length=" + seriesLabels.length
								+ " columnCount=" + columnCount
								+ HybsSystem.CR
								+ " seriesLabels=" + Arrays.toString( seriesLabels )
								+ HybsSystem.CR ;
				throw new IllegalArgumentException( errMsg );
			}

			String[] series  = new String[columnCount];
			int[] columnType = new int[columnCount];
			// ORACLEの引数は、配列＋１から始まる。series と seriesLabels も配列の開始が異なる。
			for( int i=2; i<=columnCount; i++ ) {
				series[i-1] = seriesLabels != null
									? seriesLabels[i-2]
									: metaData.getColumnLabel(i).toUpperCase( Locale.JAPAN );
				columnType[i-1] = metaData.getColumnType(i);
			}

			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);
	 			// 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。
//				String rowKey = resultSet.getString(1);				// 4.3.3.6 (2008/11/15) Generics警告対応
				String category = resultSet.getString(1);			// 4.3.3.6 (2008/11/15) Generics警告対応
				for( int column=2; column<=columnCount; column++ ) {

				//	Comparable columnKey = metaData.getColumnName(column);
					// 6.0.2.0 (2014/09/19) columnKeyは、getColumnLabel で取得する。
	 				// 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。
//					String columnKey = metaData.getColumnName(column);		// 4.3.3.6 (2008/11/15) Generics警告対応
//					int columnType = metaData.getColumnType(column);		// 6.0.2.0 (2014/09/19) int[] columnType化

					Number value = null;
//					switch (columnType) {
//					switch (columnType[column-1]) {
//						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;
//					}

					// 6.0.2.1 (2014/09/26) org.opengion.fukurou.db.DBUtil に、移動
					try {
						value = DBUtil.getNumber( columnType[column-1],resultSet.getObject(column) );
					}
					catch( RuntimeException ex ) {
						LogWriter.log( ex );
					}

					clmList[column-2] = value;
//					setValue(value, columnKey, rowKey);
					addValue(value, series[column-1], category);	// 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。

					// 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;
	}

	/**
	 * パレート図用のDatasetに値を書き換えます。(独自メソッド)
	 *
	 * 色々と方法はあると思いますが、簡易的に、内部の Number配列を
	 * 積上げ計算して、パレート図用のデータを作成します。
	 * レンジオブジェクト も変更します。
	 * なお、行列の順序が、イメージと異なりますので、注意願います。
	 * (columnは、series , row は、category で、シリーズを積み上げます)
	 *
	 * @og.rev 6.0.2.1 (2014/09/26) 新規追加
	 */
	protected void changeParetoData() {
		if( numdata == null || numdata.length == 0 || numdata[0].length == 0 ) { return; }

		int rowCnt = numdata[0].length ;
		int clmCnt = numdata.length ;

		double maximum = Double.NEGATIVE_INFINITY;
		for( int rowNo=0; rowNo<rowCnt; rowNo++ ) {			// 行列が逆。
			double val = 0.0;		// 初期値
			for( int clmNo=0; clmNo<clmCnt; clmNo++ ) {		// 積上げ計算するカラムでループを回す。
				Number v1Num = numdata[clmNo][rowNo];
				if(v1Num != null) {
					val += v1Num.doubleValue();
				}
				numdata[clmNo][rowNo] = new Double(val);
			}
			if( maximum < val ) { maximum = val; }				// パレート図用の積上げなので、最大値は、最後のデータ
		}

		range = new Range( range.getLowerBound(), maximum );
	}

	/**
	 * この文字列と指定されたオブジェクトを比較します。
	 *
	 * 親クラスで、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 ) {
		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	ハッシュコード
	 */
	@Override
	public int hashCode() { return hsCode ; }
}
