/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

//
//	LO共通ログのSQL作成クラスです。
//			変更履歴
//				2004.01.08	新規作成	二條　智文


package jp.co.ntt.lms.lo.util;

import jp.co.ntt.lms.lo.*;
import jp.co.ntt.lms.xmo.util.XMODate;
import java.util.*;
import java.text.*;
import jp.co.ntt.lms.xmo.Environment;

/**
 * 	LO共通ログのSQL作成クラスです。<BR>
 * 以下の画面に関して、SQLを動的に作成します。
 * <PRE>
 *   グループ/カリキュラム 教材利用数一覧画面 (クエリーID　LOG0101)
 *   グループ/教材         教材利用数一覧画面 (クエリーID　LOG0201)
 * </PRE>
 * @author T.Nijou
 * @version 0.0.0.1 2004/01/08
 */
public class LoLogSQLMaker {

	// エラー文言の定義
	private static final String ERR_00 = "検索条件に誤りがあります。";
	private static final String ERR_01 = "粒度の値が不正です。";
	private static final String ERR_02 = "カウント対象の値が不正です。";
	private static final String ERR_03 = "FROM日付の値が不正です。";
	private static final String ERR_04 = "TO日付の値が不正です。";

	// インデックス定義
	private static final int FROM_DATE_INDEX = 3;
	private static final int TO_DATE_INDEX = 4;
	private static final int PRART_INDEX = 5;
	private static final int COUNT_TYPE_INDEX = 6;

	// 内部変数
	/** クエリーIDを保持します */
	private String mstrQueryID;
	/** クエリーを保持します */
	private String mstrQuery;
	/** クエリー条件を保持します */
	private Vector mvecCondition;

	/** 検索条件の検索期間FROMを保持します */
	private Calendar mobjFromCal;

	/** 検索条件の検索期間TOを保持します */
	private Calendar mobjToCal;

	/**
	 * 粒度を保持します
	 * <PRE>
	 *     PART_YEAR: 年
	 *     PART_MONTH: 月
	 *     PART_WEEK: 週
	 *     PART_DATE: 日
	 * </PRE>
	 */
	private String mstrParticle;

	/**
	 * カウント対象を保持します
	 * <PRE>
	 *     ACCESS_COUNT: アクセスカウント
	 *     USER_COUNT: ユーザカウント
	 * </PRE>
	 */
	private String mstrCountType;

	/**
	 * カウント対象がUSERの場合、DISTINCTを付加するときに使用します
	 *  デフォルトでは空文字です。
	 */
	private String mstrDistinct = "";

	// 定数
	/** SQL置換文字列です。*/
	private static final String QUERY_SELECT = "QUERY_SELECT";

	/** 粒度が年の場合のMax (5年) */
	private static final int MAX_YEAR = 5;
	/** 粒度が月の場合のMax (12ヶ月) */
	private static final int MAX_MONTH = 12;
	/** 粒度が週の場合のMax (12週) */
	private static final int MAX_WEEK = 12;
	/** 粒度が日の場合のMax (62日) */
	private static final int MAX_DATE = 62;

	/** 粒度　年 */
	private static final String PART_YEAR = "year";
	/** 粒度　月 */
	private static final String PART_MONTH = "month";
	/** 粒度　週 */
	private static final String PART_WEEK = "week";
	/** 粒度　日 */
	private static final String PART_DATE = "date";

	/** カウント対象 アクセスカウント */
	private static final String ACCESS_COUNT = "access";
	/** カウント対象 ユーザカウント */
	private static final String USER_COUNT = "user";

	/**
	 * コンストラクタです。<BR>
	 * クエリーIDと対応するマクロ展開前のSQL文と検索条件Vectorを設定します
	 * @param strQueryID　クエリーID
	 * @param strQuery マクロ展開前のクエリー
	 * @param vecCondition 検索条件
	 */
	public LoLogSQLMaker(String strQueryID, String strQuery, Vector vecCondition) {
		// 内部変数に設定します
		mstrQueryID = strQueryID;
		mstrQuery = strQuery;
		mvecCondition = vecCondition;
	}

	/**
	 * SQL文を作成します。
	 * @return String SQL
	 */
	public String makeQuery()
		throws LOException
	{

		try {
			// 検索条件のFROM日付を取得する
			try {
				String strFromDate = (String)mvecCondition.get(FROM_DATE_INDEX);
				DateFormat objFormat = new SimpleDateFormat( "yyyy/MM/dd" );
				Date objDate = objFormat.parse( strFromDate );
				mobjFromCal = new GregorianCalendar();
				mobjFromCal.setTime( objDate );

			}
			catch( ParseException pe ) {
				throw new LOException( ERR_03 );
			}

			// 検索条件のTO日付を取得する
			try {
				String strToDate = (String)mvecCondition.get(TO_DATE_INDEX);
				DateFormat objFormat = new SimpleDateFormat( "yyyy/MM/dd" );
				Date objDate = objFormat.parse( strToDate );
				mobjToCal = new GregorianCalendar();
				mobjToCal.setTime( objDate );
			}
			catch( ParseException pe ) {
				throw new LOException( ERR_04 );
			}

			// 粒度を取得します
			mstrParticle = (String)mvecCondition.get(PRART_INDEX);
			// 粒度のチェック
			if( !mstrParticle.equals( PART_YEAR ) &&
				!mstrParticle.equals( PART_MONTH ) &&
				!mstrParticle.equals( PART_WEEK ) &&
				!mstrParticle.equals( PART_DATE ) )
			{
				throw new LOException( ERR_01 );
			}

			// カウント対象のチェック
			mstrCountType = (String)mvecCondition.get(COUNT_TYPE_INDEX);
			// カウント対象のチェック
			if( !mstrCountType.equals( ACCESS_COUNT ) &&
				!mstrCountType.equals( USER_COUNT ) )
			{
				throw new LOException( ERR_02 );
			}

			// 配列の５，６番目を削除します
			mvecCondition.remove(COUNT_TYPE_INDEX);
			mvecCondition.remove(PRART_INDEX);
		}
		catch( ArrayIndexOutOfBoundsException ae ) {
			throw new LOException( ERR_00 );
		}

		// カウント対象がユーザ数の場合は検索取得結果にDISTINCTを付加します
		if( mstrCountType.equals( USER_COUNT ) ) {
			mstrDistinct = "DISTINCT ";
		}

		// 粒度により組み立てるSQLを変更します
		String strSelectString = "";
		if( mstrParticle.equals( PART_YEAR ) ) {
			strSelectString = makeYearSQL();
		}
		else if( mstrParticle.equals( PART_MONTH ) ) {
			strSelectString = makeMonthSQL();
		}
		else if( mstrParticle.equals( PART_WEEK ) ) {
			strSelectString = makeWeekSQL();
		}
		else if( mstrParticle.equals( PART_DATE ) ) {
			strSelectString = makeDateSQL();
		}

		// コンストラクタで受け取った文字列をマクロ展開します
		Hashtable htbMacro = new Hashtable();
		htbMacro.put( QUERY_SELECT, strSelectString );

		return expand( mstrQuery, htbMacro );
	}


	/**
	 * 粒度が年の場合のSQLを組み立てます。
	 */
	private String makeYearSQL() {
		// SQL参照StringBuffer
		StringBuffer sbSQL = new StringBuffer();


		int intYear = 0;
		while( mobjFromCal.before( mobjToCal ) && intYear <= MAX_YEAR ) {
			if( intYear > 0 ) {
				sbSQL.append( "," );
			}

			makeYearSQL( sbSQL );
			intYear++;

			mobjFromCal.add( Calendar.YEAR, 1 );
		}
		return sbSQL.toString();
	}

	/**
	 * 粒度が年の場合のSQLを組み立てます。
	 * @param sbSQL SQL保持用
	 */
	private void makeYearSQL( StringBuffer sbSQL ) {
		try{
			//XMS.iniファイルロードクラスのインスタンス
			Environment objEnv = new Environment();
			sbSQL.append( " COUNT(" );
			sbSQL.append( mstrDistinct );
			//SQLServerの場合	
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "CASE WHEN Year(LOLogSession.study_start_date)='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "CASE WHEN TO_CHAR(LOLogSession.study_start_date,'YYYY')='" );
			}
			sbSQL.append( mobjFromCal.get( Calendar.YEAR ) );
			sbSQL.append( "' THEN LOLogSession.UserID END) AS ");
			sbSQL.append( "a" + mobjFromCal.get( Calendar.YEAR ) );
		}
		catch(Exception ex)
		{

		}
	}

	/**
	 * 粒度が月の場合のSQLを組み立てます。
	 */
	private String makeMonthSQL() {
		// SQL参照StringBuffer
		StringBuffer sbSQL = new StringBuffer();


		int intMonth = 0;
		while( mobjFromCal.before( mobjToCal ) && intMonth <= MAX_MONTH ) {
			if( intMonth > 0 ) {
				sbSQL.append( "," );
			}

			makeMonthSQL( sbSQL );

			mobjFromCal.add( Calendar.MONTH, 1 );
			intMonth++;

		}
		return sbSQL.toString();
	}

	/**
	 * 粒度が月の場合のSQLを組み立てます。
	 * @param sbSQL SQL保持用
	 */
	private void makeMonthSQL( StringBuffer sbSQL ) {
		try{
			//XMS.iniファイルロードクラスのインスタンス
			Environment objEnv=new Environment();

			sbSQL.append( " COUNT(" );
			sbSQL.append( mstrDistinct );
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "CASE WHEN Year(LOLogSession.study_start_date)='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "CASE WHEN TO_CHAR(LOLogSession.study_start_date,'YYYY')='" );
			}
			sbSQL.append( mobjFromCal.get( Calendar.YEAR ) );
			//SQLSeverの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "' AND Month(LOLogSession.study_start_date)='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "' AND TO_CHAR(LOLogSession.study_start_date,'MM')='" );
			}
			//SQLSeverの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( (mobjFromCal.get( Calendar.MONTH ) + 1 ));
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( (convertAddZero(mobjFromCal.get( Calendar.MONTH ) + 1) ));
			}
			sbSQL.append( "' THEN LOLogSession.UserID END) AS ");
			sbSQL.append( "a" + mobjFromCal.get( Calendar.YEAR ) + ( mobjFromCal.get( Calendar.MONTH ) + 1 ) );
		}
		catch(Exception ex)
		{

		}
	}

	/**
	 * 粒度が週の場合のSQLを組み立てます。
	 */
	private String makeWeekSQL() {
		// SQL参照StringBuffer
		StringBuffer sbSQL = new StringBuffer();


		int intWeek = 0;
		while( (mobjFromCal.before( mobjToCal ) || mobjFromCal.equals( mobjToCal )) && intWeek <= MAX_WEEK ) {
			if( intWeek > 0 ) {
				sbSQL.append( "," );
			}

			makeWeekSQL( sbSQL );

			mobjFromCal.add( Calendar.DAY_OF_YEAR, 1 );
			intWeek++;

		}
		return sbSQL.toString();
	}

	/**
	 * 粒度が月の場合のSQLを組み立てます。
	 * @param sbSQL SQL保持用
	 */
	private void makeWeekSQL( StringBuffer sbSQL ) {
		Date objWeekFirst = mobjFromCal.getTime();
		// Fromカレンダーを週末(土曜日)にします
		mobjFromCal = moveWeekEnd( mobjFromCal );
		Date objWeekEnd = mobjFromCal.getTime();

		// 日付型をyyyy/mm/dd形式に変換します
		String strWeekFirst = XMODate.parseYYYYMMDD( objWeekFirst ) + " 00:00:00";
		String strWeekEnd = XMODate.parseYYYYMMDD( objWeekEnd ) + " 23:59:59";
		// 日付型をyyyymmdd形式に変換します
		String strOraWeekFirst = XMODate.parseOraYYYYMMDD( objWeekFirst ) + "000000";
		String strOraWeekEnd = XMODate.parseOraYYYYMMDD( objWeekEnd ) + "235959";
		try{
			//XMS.iniファイルロードクラスのインスタンス
			Environment objEnv=new Environment();
			sbSQL.append( " COUNT(" );
			sbSQL.append( mstrDistinct );
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "CASE WHEN LOLogSession.study_start_date >='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "CASE WHEN TO_CHAR(LOLogSession.study_start_date,'YYYY/MM/DD HH24:MI:SS') >='" );
			}
			sbSQL.append( strWeekFirst );
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "' AND LOLogSession.study_start_date <='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "' AND TO_CHAR(LOLogSession.study_start_date,'YYYY/MM/DD HH24:MI:SS') <='" );
			}
			sbSQL.append( strWeekEnd );
			sbSQL.append( "' THEN LOLogSession.UserID END) AS ");
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "[" + strWeekFirst + " 〜 " + strWeekEnd + "]" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "a" + strOraWeekFirst + "a" + strOraWeekEnd );
			}
		}
		catch(Exception ex)
		{

		}
	}

	/**
	 * カレンダーを週末（土曜日）にします
	 */
	private Calendar moveWeekEnd( Calendar objCal ) {
		Calendar objCalRes = (Calendar)objCal.clone();
		int intAddDate = 0;
		switch( objCalRes.get( Calendar.DAY_OF_WEEK ) ) {
			// 日曜日
			case Calendar.SUNDAY:
				intAddDate = 6;
				break;
			// 月曜日
			case Calendar.MONDAY:
				intAddDate = 5;
				break;
			// 火曜日
			case Calendar.TUESDAY:
				intAddDate = 4;
				break;
			// 水曜日
			case Calendar.WEDNESDAY:
				intAddDate = 3;
				break;
			// 木曜日
			case Calendar.THURSDAY:
				intAddDate = 2;
				break;
			// 金曜日
			case Calendar.FRIDAY:
				intAddDate = 1;
				break;
			// 土曜日
			case Calendar.SATURDAY:
				intAddDate = 0;
				break;
		}

		objCalRes.add( Calendar.DAY_OF_YEAR, intAddDate );

		if( objCalRes.after( mobjToCal ) ) {
			objCalRes = mobjToCal;
		}

		return objCalRes;
	}

	/**
	 * 粒度が日の場合のSQLを組み立てます。
	 */
	private String makeDateSQL() {
		// SQL参照StringBuffer
		StringBuffer sbSQL = new StringBuffer();


		int intDate = 0;
		// 日付の場合のみ当日を含める
		while( (mobjFromCal.before( mobjToCal ) || mobjFromCal.equals( mobjToCal ) ) && intDate <= MAX_DATE ) {

			if( intDate > 0 ) {
				sbSQL.append( "," );
			}

			makeDateSQL( sbSQL );

			mobjFromCal.add( Calendar.DAY_OF_YEAR, 1 );
			intDate++;

		}

		return sbSQL.toString();
	}

	/**
	 * 粒度が日の場合のSQLを組み立てます。
	 * @param sbSQL SQL保持用
	 */
	private void makeDateSQL( StringBuffer sbSQL ) {
		try{
			//XMS.iniファイルロードクラスのインスタンス
			Environment objEnv=new Environment();

			sbSQL.append( " COUNT(" );
			sbSQL.append( mstrDistinct );
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "CASE WHEN Year(LOLogSession.study_start_date)='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "CASE WHEN TO_CHAR(LOLogSession.study_start_date,'YYYY')='" );
			}
			sbSQL.append( mobjFromCal.get( Calendar.YEAR ) );
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "' AND Month(LOLogSession.study_start_date)='" );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "' AND TO_CHAR(LOLogSession.study_start_date,'MM')='" );
			}
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( (mobjFromCal.get( Calendar.MONTH ) + 1) );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( (convertAddZero(mobjFromCal.get( Calendar.MONTH ) + 1) ));
			}
			//SQLServerの場合
			if(objEnv.getDBType().equals( objEnv.DB_SQLSERVER )){
				sbSQL.append( "' AND Day(LOLogSession.study_start_date)='" );
				sbSQL.append( mobjFromCal.get( Calendar.DAY_OF_MONTH ) );
			//Oracleの場合 OR PostgreSQLの場合
			}else if(objEnv.getDBType().equals( objEnv.DB_ORACLE ) || objEnv.getDBType().equals( objEnv.DB_POSTGRE )){
				sbSQL.append( "' AND TO_CHAR(LOLogSession.study_start_date,'DD')='" );
				sbSQL.append( convertAddZero(mobjFromCal.get( Calendar.DAY_OF_MONTH ) ) );
			}
			sbSQL.append( "' THEN LOLogSession.UserID END) AS ");
			sbSQL.append( "a" + mobjFromCal.get( Calendar.YEAR ) + ( mobjFromCal.get( Calendar.MONTH ) + 1 ) + mobjFromCal.get( Calendar.DAY_OF_MONTH ) );
		}
		catch(Exception ex)
		{

		}

	}

	/**
	 * 文字列をマクロ展開します<BR>
	 * '$'で囲まれた文字列を取得し、引数で渡されたHashtableから
	 * 取得した文字列をキーにして、値を取得し、入れ替えて返します。
	 * @param strBase 変換前
	 * @param htbTable マクロ情報
	 * @return String 変換後文字列
	 */
	private String expand(String strBase , Hashtable htbTable ) {
		// 返還前文字列をchar配列に変換します
		char[] chrBase = strBase.toCharArray();

		// 変換後の文字列を格納します
		StringBuffer sbBase = new StringBuffer();
		// '$'で囲まれてた文字列を格納します
		StringBuffer sbMacro = new StringBuffer();
		// マクロ文字列読み取り中か、普通の文字列読み取り中かを判断する
		boolean blMacro = false;
		// charの配列文ループします
		for( int i = 0; i < chrBase.length; i++ ) {
			// '$'の部分はマクロ文字列として格納します
			if( chrBase[i] == '$' ) {
				if( blMacro ) {
					blMacro = false;
					// マクロ文字列をキーにして、値を入れる
					String strMacro = (String)htbTable.get( sbMacro.toString() );
					// 取得した文字列がnull以外なら、設定
					if( strMacro != null ) {
						sbBase.append( (String)htbTable.get( sbMacro.toString() ) );
					}
					// マクロ文字列を初期化します
					sbMacro = new StringBuffer();
				}
				else {
					blMacro = true;
				}
				continue;
			}

			// マクロ読み取り中の場合
			if( blMacro ) {
				sbMacro.append( chrBase[i] );
			}
			// 普通の文字列読み取り中
			else {
				sbBase.append( chrBase[i] );
			}
		}
		return sbBase.toString();
	}
	/**
	 * 月又は日が10未満の時、2桁表示に変換します。<BR>
	 * 例.月が7の時、07に変換
	 * @param MonthDateNo 変換前の月又は日
	 * @return String 変換後文字列
	 */

	private String convertAddZero(int MonthDateNo) {
		String strRes = new Integer(MonthDateNo).toString();;
		if(MonthDateNo < 10) {
			strRes = "0" + strRes;
		}
		
		return strRes;
		
	}
}
