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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.SystemManager;
import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.util.Cleanable;
import org.opengion.fukurou.db.ApplicationInfo;

import java.util.Map;
import java.util.WeakHashMap;

/**
 * 事業所(CDJGS) , 年月(YYYYMM) に対応した休日カレンダデータを作成するファクトリクラスです。
 *
 * カレンダデータは、１日(DY1)～３１日(DY31)までの日付け欄に対して、0:平日 1:休日 という
 * データを持っています。実際には、内部では true:平日 false:休日 という持ち方をします。
 * (カレンダＤＢは、文字列ですが、内部で持つ場合は、数字で管理しています。)
 *
 * 通常のカレンダでは、月毎に、(２月は、年によって)最大日付けが変わります。
 * これは、カレンダデータクラスとしては、-1 を設定しておきます。
 *
 * カレンダデータには、３つのレベルのオブジェクト作成方法が適用されます。
 * 他のリソースの３つのレベルとは異なり、エンジンリソースからの読み取りは
 * 存在しなく、第４の方法が加わった事により、３つの方法になっています。
 *
 * まず、読込フラグ(FGLOAD)='1'のカレンダデータは、このCalendarFactoryオブジェクトが
 * 構築された時に、すべてキャッシュとして内部メモリに読み取ります。
 * 読込フラグが、'1' 以外のデータは、初期起動時には、メモリにキャッシュされず
 * 実際に使用されるまで、オブジェクトが作成されません。
 * カレンダの場合、過去の使用される可能性が低いデータまで、キャッシュしない様に、
 * このフラグを使用して、メモリの節約を図ることが可能になります。
 *
 * 読込フラグ(FGLOAD)自動設定機能を使用すれば、読み取ったラベルデータに対して、
 * 読込フラグ(FGLOAD)に '1' を自動的にセットします。この機能により、一度でも
 * 読み取ったことがあるデータに '1' を付け、次回起動時には、メモリキャッシュさせる
 * 事と、一度も読み取っていないデータを判別して、データ削除等のメンテナンスに
 * 使用することが可能です。
 * カレンダに限定すれば、当面使用されない先のカレンダ(１年分など)を登録する時に、
 * 読込フラグ(FGLOAD)='0' にしておけば、実際に読み取られるまで、メモリキャッシュ
 * されないため、さらにメモリ効率が向上します。
 *
 * ２つめの方法は、キャッシュに存在しない場合は、ＤＢから読み取ります。
 *
 * ３つめは、カレンダ特有の方法で、ＤＢにデータが存在しない場合の設定です。
 * これは、カレンダテーブル未設定時でも、カレンダとして使用できるように
 * 自動的にカレンダデータを作成します。日曜日を休日として自動設定します。
 *
 * @og.rev 3.6.0.0 (2004/09/17) 新規作成
 * @og.group リソース管理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class CalendarFactory {
	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理するのではなく、LOCKオブジェクトで、同期処理を行います。  */
	private static final Map<String,CalendarData>  DATA_POOL	= new WeakHashMap<>();		// 6.4.1.1 (2016/01/16) pool           → DATA_POOL  refactoring
	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理するのではなく、LOCKオブジェクトで、同期処理を行います。  */
	private static final Map<String,CalendarQuery> QUERY_POOL	= new WeakHashMap<>();		// 6.4.1.1 (2016/01/16) queryClassPool → QUERY_POOL refactoring
	// 4.0.0.0 PGカレンダーの初期設定をシステムリソースに変更
	private static CalendarData pgCalData	;
	private static final String PG_CALENDAR_DATA_CLASS = HybsSystem.sys( "DEFAULT_CALENDAR_CLASS");
	private static final Object LOCK = new Object();										// 6.4.1.1 (2016/01/16) lock → LOCK refactoring

	// カレンダＤＢの接続先を、取得します。
	private static final String DBID  = HybsSystem.sys( "RESOURCE_CALENDAR_DBID" );

	// カレンダＤＢを使用するかどうかを指定します。(互換モード)
	private static boolean useDB = HybsSystem.sysBool( "USE_CALENDAR_DATABASE" );

	// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	private static final ApplicationInfo APP_INFO;											// 6.4.1.1 (2016/01/16) appInfo → APP_INFO refactoring

	// 4.0.0 (2005/01/31) Cleanable インターフェースによる初期化処理
	static {
		final Cleanable clr = new Cleanable() {
			/**
			 * 初期化(クリア)します。
			 * 主に、キャッシュクリアで利用します。
			 */
			public void clear() {
				CalendarFactory.clear();
			}
		};

		SystemManager.addCleanable( clr );

		/** コネクションにアプリケーション情報を追記するかどうか指定 */
		final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

		// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
		if( USE_DB_APPLICATION_INFO ) {
			final String SYSTEM_ID = HybsSystem.sys( "SYSTEM_ID" );
			APP_INFO = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			APP_INFO.setClientInfo( SYSTEM_ID,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			APP_INFO.setModuleInfo( "CalendarFactory",null,null );
		}
		else {
			APP_INFO = null;
		}
	}

	/**
	 * コンストラクタ
	 * ファクトリクラスの為、private 化しています。
	 *
	 */
	private CalendarFactory() { }

	/**
	 * CalendarData オブジェクトを取得します。
	 * 作成したCalendarDataオブジェクトは，内部にプールしておき，同じリソース要求が
	 * あったときは，プールの CalendarDataを返します。
	 * ＤＢにデータが存在しない場合は、自動でカレンダを作成します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.0.0 (2007/08/29) カレンダーテーブルが存在しない場合のデフォルトのカレンダーデータをシステムリソースで設定する
	 * @og.rev 6.0.4.0 (2014/11/28) NullPointerException が発生するので、事前にチェックします。
	 * @og.rev 6.4.3.3 (2016/03/04) Map#computeIfAbsent で対応するのと、HybsSystem#newInstance(String,String)を利用します。
	 * @og.rev 6.9.9.3 (2018/09/25) 可変引数に変更します(PMD Rather than using a lot of String arguments, consider using a container object for those values.)。
	 *
	 * @param	cls   	CalendarQueryオブジェクトを名称を指定します。
	 * @param	inArgs 	データベース検索時の可変引数
//	 * @param	arg1  	データベース検索時の第１引数
//	 * @param	arg2  	データベース検索時の第２引数
//	 * @param	arg3  	データベース検索時の第３引数
//	 * @param	arg4  	データベース検索時の第４引数
	 *
	 * @return	CalendarDataオブジェクト
	 */
//	public static CalendarData getCalendarData( final String cls,final String arg1,final String arg2,final String arg3,final String arg4 ) {
	public static CalendarData getCalendarData( final String cls,final String... inArgs ) {

		// 4.0.0.0 PGカレンダーの初期設定をシステムリソースに変更
		synchronized( LOCK ) {
			if( pgCalData == null ) {
				pgCalData = HybsSystem.newInstance(PG_CALENDAR_DATA_CLASS);
			}
		}

		if( ! useDB || cls == null ) { return pgCalData; }

		CalendarData calData ;

//		final String key = cls + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 ;
		final String key = cls + ":" + String.join( ":" , inArgs );						// 6.9.9.3 (2018/09/25)
		synchronized( LOCK ) {
			calData = DATA_POOL.get( key ) ;
		}

		if( calData == null ) {
			// 6.4.3.3 (2016/03/04) Map#computeIfAbsent で対応するのと、HybsSystem#newInstance(String,String)を利用します。
			// Map#computeIfAbsent ： 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
			final CalendarQuery query =
						QUERY_POOL.computeIfAbsent( cls , k -> HybsSystem.newInstance( "CalendarQuery_" , k ) );

//			final String[] args   = query.checkArgment( arg1,arg2,arg3,arg4 );
			final String[] args   = query.checkArgment( inArgs );								// 6.9.9.3 (2018/09/25)
			final String[][] vals = DBUtil.dbExecute( query.getQuery(),args,APP_INFO,DBID );	// 3.8.7.0 (2006/12/15)
			final boolean  isFlat = query.isFlatTable();

			if( vals != null && vals.length > 0 ) {
				calData = new CalendarDBData( vals,isFlat );
				// 完全同期ではない。DB処理中に別にもDB処理を行い、
				// 先に put されたとしても、同一キーに同じ属性のオブジェクトを
				// 登録するだけなので、実質的な問題は発生しない。
				// これが、List などの順序が関係すると、このコードは使えない。
				synchronized( LOCK ) {
					DATA_POOL.put( key,calData );
				}
			}
			else {
				// カレンダテーブルにデータが存在しない場合。
				// 4.0.0.0 PGカレンダーの初期設定をシステムリソースに変更
				calData = pgCalData;
			}
		}
		return calData ;
	}

	/**
	 * キャッシュ(プール)から、すべてのオブジェクトをクリアします。
	 *
	 */
	public static void clear() {
		synchronized( LOCK ) {
			DATA_POOL.clear();
			QUERY_POOL.clear();
			useDB = HybsSystem.sysBool( "USE_CALENDAR_DATABASE" );
		}
	}
}
