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

import org.opengion.fukurou.util.Closer;

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:行わない

	/**
	 * チャートタイプを引数にとる、コンストラクター
	 *
	 * 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 のキーブレイク処理されます。
	 *
	 * @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);
			ResultSetMetaData metaData = resultSet.getMetaData();

			int columnCount = metaData.getColumnCount();

			if(columnCount < 3) {
				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 (resultSet.next()) {
				// first column contains the row key...
				String seriVal	= resultSet.getString(1);						// 縦持ちの場合は、データの値がシリーズ名になる。
				if( seriVal != null && !seriVal.equals( bkSeries ) ) {
					if( timeSeries != null ) { addSeries( timeSeries ); }		// キーブレイクでセット
					timeSeries = new TimeSeries( seriVal );
					bkSeries = seriVal ;
					bkyn     = 0.0;
				}

				String dateVal	= resultSet.getString(2);			// x(時間)
				RegularTimePeriod timep = getTimePeriod( dateVal );

				double yn = resultSet.getDouble(3);					// 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 ・・・のカラム名になります。
	 *
	 * @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);
			ResultSetMetaData metaData = resultSet.getMetaData();

			int columnCount = metaData.getColumnCount();

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

			// 各シリーズに対するカラムタイプを先に求めておく
			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( metaData.getColumnLabel(j+2) );		// 横持の場合は、カラム名をシリーズ名にする。
				bkyn[j]       = 0.0;
			}

			while (resultSet.next()) {
				// first column contains the row key...
				String dateVal	= resultSet.getString(1);			// x(時間)
				RegularTimePeriod timep = getTimePeriod( dateVal );

				for( int j=0; j<seriSu; j++ ) {
					double yn = resultSet.getDouble(j+2);
					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 ) {
		int second=0, minute=0, hour=0, day=1, month=1, 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 ) ;
	}
}
