/*
 * 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 java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

// import org.opengion.fukurou.system.Closer;
import org.opengion.fukurou.db.ResultSetValue;		// 6.0.4.0 (2014/11/28)

import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.Second;			// Second(int second, int minute, int hour, int day, int month, int year) 

/**
 * HybsTimeSeriesCollection は、org.jfree.data.time.TimeSeriesCollection を継承したサブクラスで、
 * オブジェクト作成とともに JDBC接続して、TimeSeries データを作成し、セットします。
 * TimeSeriesCollection は、XYDataset のサブクラスです。
 *
 * TimeSeriesLineV、TimeSeriesBarV、StackedTimeSeriesLineV の場合は縦持です。
 *  １．select series,x(時間),y(値) from XX order by series,x(時間) の縦持ちで、series のキーブレイク処理
 * TimeSeriesLineH、TimeSeriesBarH、StackedTimeSeriesLineH の場合は横持です。
 *  ２．select x(時間),y1(値),y2(値),・・・ from XX  order by x(時間) の横持
 * series のキーブレイク処理されます。
 *
 * Stacked**** は、各シリーズのy(値)を、次々に加算します。各時間で実績数をセットし、最終時刻に
 * どれだけ出来上がったかを表示するのに便利です。
 *
 * @og.rev 5.6.1.0 (2013/02/01) 新規作成
 *
 * @version  0.9.0  2001/05/05
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.1,
 */
public class HybsTimeSeriesCollection extends TimeSeriesCollection {
	private static final long serialVersionUID = 561020130201L ;

	private final boolean vhFlag ;		// select文で series を縦持V(true)か横持H(false)かを指定。
	private final boolean isStacked ;	// データの加算処理を行うかどうか？   true:行う/false:行わない

	private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ;	// 6.0.2.5 (2014/10/31) equals,hashCode

	/**
	 * チャートタイプを引数にとる、コンストラクター
	 *
	 * TimeSeriesLineV、TimeSeriesBarV、StackedTimeSeriesLineV の場合は縦持です。
	 *  １．select series,x(時間),y(値) from XX order by series,x(時間) の縦持ちで、series のキーブレイク処理
	 * TimeSeriesLineH、TimeSeriesBarH、StackedTimeSeriesLineH の場合は横持です。
	 *  ２．select x(時間),y1(値),y2(値),・・・ from XX  order by x(時間) の横持
	 * series のキーブレイク処理されます。
	 *
	 * Stacked**** は、各シリーズのy(値)を、次々に加算します。各時間で実績数をセットし、最終時刻に
	 * どれだけ出来上がったかを表示するのに便利です。
	 *
	 * @param type  チャートタイプ
	 */
	public HybsTimeSeriesCollection( final String type ) {
		super();
		vhFlag    = type.endsWith( "V" ) ;				// V:縦持 = true / H:横持 = false
		isStacked = type.startsWith( "Stacked" ) ;		// Stacked:積み上げ = true
	}

	/**
	 * HybsTimeSeriesCollection オブジェクトの内部に、DB検索結果のデータを設定します。
	 *
	 * このメソッドは、series の 縦持/横持を、コンストラクターで判定しています。
	 * TimeSeriesLineV、TimeSeriesBarV、StackedTimeSeriesLineV の場合は縦持です。
	 *  １．select series,x(時間),y(値) from XX order by series,x(時間) の縦持ちで、series のキーブレイク処理
	 * TimeSeriesLineH、TimeSeriesBarH、StackedTimeSeriesLineH の場合は横持です。
	 *  ２．select x(時間),y1(値),y2(値),・・・ from XX  order by x(時間) の横持
	 * series のキーブレイク処理されます。
	 * (独自メソッド)
	 *
	 * @param con  the connection.
	 * @param query  the query.
	 * @throws SQLException データベース実行エラーが発生した場合
	 *
	 */
	public void executeQuery( final Connection con, final String query ) throws SQLException {
		if( vhFlag ) { innerQueryV( con,query ); }
		else		 { innerQueryH( con,query ); }
	}

	/**
	 * HybsTimeSeriesCollection オブジェクトの内部に、DB検索結果のデータを設定します(縦持)。
	 * このメソッドが呼ばれるのは、TimeSeriesLineV、TimeSeriesBarV、StackedTimeSeriesLineV の場合です。
	 *
	 * このメソッドは、series の 縦持を想定しています。
	 *  １．select series,x(時間),y(値) from XX order by series,x(時間) の縦持ちで、series のキーブレイク処理
	 * series のキーブレイク処理されます。
	 * (独自メソッド)
	 *
	 * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
	 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。
	 *
	 * @param con  the connection.
	 * @param query  the query.
	 *
	 */
	private void innerQueryV( final Connection con, final String query ) throws SQLException {

//		Statement statement = null;
//		ResultSet resultSet = null;
//		try {
//			statement = con.createStatement();
//			resultSet = statement.executeQuery(query);
		// 6.4.2.1 (2016/02/05) try-with-resources 文
		try( final Statement statement = con.createStatement();
			 final ResultSet resultSet = statement.executeQuery(query) ) {

			// 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
			final ResultSetValue rsv = new ResultSetValue( resultSet );

			final int columnCount = rsv.getColumnCount();

			if( columnCount < 3 ) {
				final String errMsg = "HybsTimeSeriesCollection.innerQueryV() : 実行できません。\n"
							+ "select series,x(時間),y(値) は、最低必要です。それ以降は無視します。"
							+ " SQL=" + query ;
				throw new SQLException( errMsg );
			}

			String bkSeries = null;		// キーブレイクのための過去のSeries
			double bkyn     = 0.0;

			TimeSeries timeSeries = null;
			while( rsv.next() ) {
				// first column contains the row key...
				final String seriVal	= rsv.getValue(0);								// 縦持ちの場合は、データの値がシリーズ名になる。
				if( seriVal != null && !seriVal.equals( bkSeries ) ) {
					if( timeSeries != null ) { addSeries( timeSeries ); }		// キーブレイクでセット
					timeSeries = new TimeSeries( seriVal );
					bkSeries = seriVal ;
					bkyn     = 0.0;
				}

				final String dateVal	= rsv.getValue(1);					// x(時間)
				final RegularTimePeriod timep = getTimePeriod( dateVal );

				final double yn = Double.parseDouble( rsv.getValue(2) );	// y(値)
				bkyn = isStacked ? bkyn + yn : yn ;					// isStacked = true の場合は、加算していく

				timeSeries.add( timep, bkyn );
			}
			if( timeSeries != null ) { addSeries( timeSeries ); }		// キーブレイクでセット
		}
//		finally {
//			Closer.resultClose( resultSet ) ;
//			Closer.stmtClose( statement ) ;
//		}
	}

	/**
	 * HybsTimeSeriesCollection オブジェクトの内部に、DB検索結果のデータを設定します(横持)。
	 * このメソッドが呼ばれるのは、TimeSeriesLineH、TimeSeriesBarH、StackedTimeSeriesLineH の場合です。
	 *
	 * このメソッドは、series の 横持を想定しています。
	 *  ２．select x(時間),y1(値),y2(値),・・・ from XX  order by x(時間) の横持
	 * で、y1, y2 ・・・ が series として処理されます。
	 * series のラベルは、y1, y2 ・・・のカラム名になります。
	 * (独自メソッド)
	 *
	 * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
	 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。
	 *
	 * @param con  the connection.
	 * @param query  the query.
	 */
	private void innerQueryH( final Connection con, final String query ) throws SQLException {

//		Statement statement = null;
//		ResultSet resultSet = null;
//		try {
//			statement = con.createStatement();
//			resultSet = statement.executeQuery(query);
		// 6.4.2.1 (2016/02/05) try-with-resources 文
		try( final Statement statement = con.createStatement();
			 final ResultSet resultSet = statement.executeQuery( query ) ) {

			// 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
			final ResultSetValue rsv = new ResultSetValue( resultSet );

			final int columnCount = rsv.getColumnCount();

			if( columnCount < 2 ) {
				final String errMsg = "HybsTimeSeriesCollection.innerQueryH() : 実行できません。\n"
							+ "select x(時間),y1(値),y2(値) , ・・・・ は、最低必要です。"
							+ " SQL=" + query ;
				throw new SQLException( errMsg );
			}

			// 各シリーズに対するカラムタイプを先に求めておく
			final int seriSu = columnCount-1;				// カラム数-1( x(時間) )
			TimeSeries[] timeSeries = new TimeSeries[seriSu];
			double[]     bkyn       = new double[seriSu];
			for( int j=0; j<seriSu; j++ ) {
				timeSeries[j] = new TimeSeries( rsv.getColumnName(j+1) );			// 横持の場合は、カラム名をシリーズ名にする。
				bkyn[j]       = 0.0;
			}

			while( rsv.next() ) {
				// first column contains the row key...
				final String dateVal	= rsv.getValue(0);					// x(時間)
				final RegularTimePeriod timep = getTimePeriod( dateVal );

				for( int j=0; j<seriSu; j++ ) {
					final double yn = Double.parseDouble( rsv.getValue(j+1) );	// y(値)
					bkyn[j]   = isStacked ? bkyn[j] + yn : yn ;		// isStacked = true の場合は、加算していく
					timeSeries[j].add( timep, bkyn[j] );
				}
			}

			for( int j=0; j<seriSu; j++ ) {
				addSeries( timeSeries[j] );
			}
		}
//		finally {
//			Closer.resultClose( resultSet ) ;
//			Closer.stmtClose( statement ) ;
//		}
	}

	/**
	 * 日付文字列 から、RegularTimePeriodオブジェクト を生成します。
	 *
	 * このメソッドでは、日付文字列 として、"yyyyMMdd" 形式と "yyyyMMddhhmmss" 形式のみ認めています。
	 *  １．８文字以上ある場合、yyyyMMdd 部分を切り出して、年月日情報を作成します。
	 *  ２．１４文字以上ある場合、残りの、hhmmss 部分を切り出して、時分秒情報を作成します。
	 *  ３．それ以外の場合は、"20100101000000" として、処理します。
	 *
	 * @param	dateVal	日付文字列(yyyyMMddhhmmss 形式)
	 *
	 * @return	RegularTimePeriodオブジェクト(Secondオブジェクト)
	 */
	private RegularTimePeriod getTimePeriod( final String dateVal ) {
		// 6.3.9.0 (2015/11/06) Use one line for each declaration, it enhances code readability.(PMD)
//		int second=0, minute=0, hour=0, day=1, month=1, year=2010 ;
		int second	= 0;
		int minute	= 0;
		int hour	= 0;
		int day		= 1;
		int month	= 1;
		int year	= 2010 ;

		if( dateVal != null ) {
			if( dateVal.length() >= 8 ) {
				year  = Integer.parseInt( dateVal.substring( 0,4 ) );
				month = Integer.parseInt( dateVal.substring( 4,6 ) );
				day   = Integer.parseInt( dateVal.substring( 6,8 ) );
			}
			if( dateVal.length() >= 14 ) {
				hour   = Integer.parseInt( dateVal.substring( 8,10 ) );
				minute = Integer.parseInt( dateVal.substring( 10,12 ) );
				second = Integer.parseInt( dateVal.substring( 12,14 ) );
			}
		}

		return new Second( second,minute,hour,day,month,year ) ;
	}

	/**
	 * この文字列と指定されたオブジェクトを比較します。
	 *
	 * 親クラスで、equals メソッドが実装されているため、警告がでます。
	 *
	 * @og.rev 6.0.2.5 (2014/10/31) findbug対応
	 *
	 * @param	object	比較するオブジェクト
	 *
	 * @return	Objectが等しい場合は true、そうでない場合は false
	 */
	@Override
	public boolean equals( final Object object ) {
		// 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 super.equals( object ) && hsCode == ((HybsTimeSeriesCollection)object).hsCode;

//		if( super.equals( object ) ) {
//			return hsCode == ((HybsTimeSeriesCollection)object).hsCode;
//		}
//		return false;
	}

	/**
	 * このオブジェクトのハッシュコードを取得します。
	 *
	 * @og.rev 6.0.2.5 (2014/10/31) findbug対応
	 *
	 * @return	ハッシュコード
	 */
	@Override
	public int hashCode() { return hsCode ; }
}
