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

import org.opengion.hayabusa.common.HybsSystemException;			// 6.4.5.0 (2016/04/08)
// import org.opengion.hayabusa.resource.UserInfo;						// 6.4.5.0 (2016/04/08)

import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring
import java.io.Serializable;

/**
 * ユーザー単位の編集設定情報を管理するためのクラスです。
 *
 * 画面ID+編集名をキーとして編集設定オブジェクトの
 * 追加、削除、参照を行います。
 *
 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
 *
 * @version  5.0
 * @author   Hiroki Nakamura
 * @since    JDK6.0,
 */
public class DBEditConfigManager {

	// 編集設定情報の管理オブジェクト(画面ID+編集名単位で管理)
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
//	private final Map<String,Map<String,DBEditConfig>> editConfigMap = new HashMap<>();
	private final ConcurrentMap<String,Map<String,DBEditConfig>> editConfigMap = new ConcurrentHashMap<>();

	private static final String NAME_KEY = "EDIT_NAME_" ;			// 6.4.5.0 (2016/04/08)
	private static final int    NAME_LEN = NAME_KEY.length();		// 6.4.5.0 (2016/04/08)

	// EDIT_SELECTED_ + 画面ID で、設定される 選択中編集名を記録します。
	private final ConcurrentMap<String,String> editSelMap = new ConcurrentHashMap<>();

	private static final String SLCT_KEY = "EDIT_SELECTED_" ;		// 6.4.5.0 (2016/04/08)
	private static final int    SLCT_LEN = SLCT_KEY.length();		// 6.4.5.0 (2016/04/08)

	/**
	 * デフォルトコンストラクター
	 *
	 * 互換性を考慮し、デフォルトコンストラクターは残しておきます。
	 *
	 * @og.rev 6.0.2.2 (2014/10/03) 新規追加
	 */
	public DBEditConfigManager() { super(); }			// PMD:Document empty constructor 対策

	/**
	 * 引数付コンストラクター
	 *
	 * UserInfo の Map&lt;String,String&gt; attribute から、EDIT_NAME_ で始まるキーワードを
	 * 取り出して、DBEditConfig オブジェクトを作成します。
	 * attribute には、"EDIT_NAME_(画面ID)_(編集名)" というキー情報があるので、
	 * 画面IDと編集名を分離し、DBEditConfig の各キーと再び合成して、attribute から、
	 * 設定値を取り出します。
	 * ただし、画面IDや、編集名 にも、アンダーバーが含まれている可能性がある為、
	 * EDIT_NAME_(画面ID)_(編集名) の値である、編集名 を使用して、分離します。
	 * そのキーと値の配列を元に作成された DBEditConfig オブジェクトを、内部Map に画面IDを
	 * キーに設定します。
	 *
	 * 元々、UserInfo#makeEditConfigMap() で処理していた内容を、こちらに移植しました。
	 *
	 * @og.rev 6.0.2.2 (2014/10/03) 新規追加。DBEditConfig から、移動
	 * @og.rev 6.3.9.1 (2015/11/27) getEditKeys(String,String) は、DBEditConfigManager ⇒ DBEditConfig へ移動。
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。
	 *
	 * @param attribute	UserInfo の 属性Map
	 */
	public DBEditConfigManager( final Map<String,String> attribute ) {
		final String[] keys = attribute.keySet().toArray( new String[attribute.size()] );
//		if( keys == null || keys.length == 0 ) { return ; }

//		final int KEYLEN = "EDIT_NAME_".length();

		for( final String key : keys ) {
			if( key == null ) { continue; }

			// 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。
			if( key.startsWith( SLCT_KEY ) ) {
				final String editName = attribute.get( key );
				if( editName == null || editName.isEmpty() ) {
					attribute.remove( key );		// editName の存在しない EDIT_SELECTED_ キーは削除します。
					continue;
				}

				final String guikey = key.substring( SLCT_LEN );	// "EDIT_SELECTED_" の後ろが画面ID
				if( guikey != null && guikey.length() > 0 ) {
					editSelMap.put( guikey , editName );
				}
			}

//			if( key != null && key.startsWith( "EDIT_NAME_" ) ) {
			if( key.startsWith( NAME_KEY ) ) {
				final String editName = attribute.get( key );
				if( editName == null || editName.isEmpty() ) {
					attribute.remove( key );		// editName の存在しない EDIT_NAME_ キーは削除します。
					continue;
				}

				// "EDIT_NAME_" より後ろで、editName の頭までが、画面ID
				// (ただし、後ろにアンダーバーが付いているので、さらに、1文字削除対象を増やす。)
				final int last = key.lastIndexOf(editName)-1 ;
				if( last < 0 ) {					// 一致しない場合は、キーが違うから。
					attribute.remove( key );		// editName の一致しない EDIT_NAME_ キーは削除します。
					continue;
				}

//				final String guikey = key.substring( KEYLEN,last );
				final String guikey = key.substring( NAME_LEN,last );
				if( guikey != null && guikey.length() > 0 ) {
//						final String[] editKeys = getEditKeys( guikey, editName );
					final String[] editKeys = DBEditConfig.getEditKeys( guikey, editName );
					String[] editVals = new String[editKeys.length];
					for( int i=0; i<editKeys.length; i++ ) {
						editVals[i] = attribute.get( editKeys[i] );
					}
//					addEditConfig( guikey, editName, new DBEditConfig( editVals ) );
					// Map#computeIfAbsent ： 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
					editConfigMap.computeIfAbsent( guikey , k -> new HashMap<>() ).put( editName , new DBEditConfig( editVals ) );
				}
			}
		}
	}

//	/**
//	 * 画面ID、編集名をキーに、編集設定オブジェクトの各設定値の
//	 * 管理キーを指定します。
//	 *
//	 * 編集設定オブジェクトで管理される各キーに対して、
//	 * "EDIT_[KEY]_(画面ID)__(編集名)"というキーを生成し、これを配列にして返します。
//	 *
//	 * @og.rev 6.0.2.2 (2014/10/03) 新規追加。DBEditConfig から、移動
//	 * @og.rev 6.3.9.1 (2015/11/27) getEditKeys(String,String) は、DBEditConfigManager ⇒ DBEditConfig へ移動。
//	 *
//	 * @param guikey 画面ID
//	 * @param editName 編集名
//	 *
//	 * @return 編集設定を管理するためのキー一覧
//	 */
//	public static String[] getEditKeys( final String guikey, final String editName ) {
//		final int size = DBEditConfig.EDIT_KEYS.length;
//		String[] rtn = new String[size];
//		for( int i=0; i<size; i++ ) {
//			rtn[i] = "EDIT_" + DBEditConfig.EDIT_KEYS[i] + "_" + guikey + "_" + editName;
//		}
//		return rtn;
//	}

	/**
	 * 編集設定オブジェクトを追加します。
	 *
	 * ここでの追加はあくまでメモリ上での登録になります。
	 * 登録した内容を永続的に登録する場合は、別途DBへの登録が必要になります。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @param guikey 画面ID
	 * @param editName 編集名
	 * @param config 編集設定オブジェクト
	 */
	public void addEditConfig( final String guikey, final String editName, final DBEditConfig config ) {
		// 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。
		if( guikey != null && editName != null && config != null ) {

			// Map#computeIfAbsent ： 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
			final Map<String,DBEditConfig> confMap = editConfigMap.computeIfAbsent( guikey , k -> new HashMap<>() );
			final DBEditConfig oldConf = confMap.get( editName );

			// 個別設定と共通設定は、同じキーでは登録できません。
			if( oldConf != null && oldConf.isCommon() != config.isCommon() ) {
				final String errMsg = "個別設定と共通設定は、同じキーでは登録できません。";
				throw new HybsSystemException( errMsg );
			}
			confMap.put( editName , config );

//			editConfigMap.computeIfAbsent( guikey , k -> new HashMap<>() ).put( editName , config );
		}

//		if( guikey   == null || guikey.isEmpty()   ) { return; }
//		if( editName == null || editName.isEmpty() ) { return; }
//		if( config   == null ) { return; }

//		synchronized( editConfigMap ) {
//			Map<String,DBEditConfig> map = editConfigMap.get( guikey );
//			if( map == null ) {
//				map = new HashMap<>();
//			}
//			map.put( editName, config );
//
//			editConfigMap.put( guikey, map );
//		}
	}

	/**
	 * 編集設定オブジェクトを削除します。
	 *
	 * ここでの追加はあくまでメモリ上での削除になります。
	 * 登録した内容を永続的に削除する場合は、別途DBへの登録が必要になります。
	 *
	 * @og.rev 6.4.3.2 (2016/02/19) ConcurrentHashMap の同期処理を使用。
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。
	 *
	 * @param guikey 画面ID
	 * @param editName 編集名
	 *
	 * @return 編集設定オブジェクト
	 */
	public DBEditConfig deleteEditConfig( final String guikey, final String editName ) {
		// 6.4.3.2 (2016/02/19) ConcurrentHashMap は、key,val ともに、NOT NULL制限があります。
		if( guikey   == null || guikey.isEmpty() ||
			editName == null || editName.isEmpty() ) { return null; }

		final Map<String,DBEditConfig> ecMap = editConfigMap.get( guikey );

		return ecMap == null ? null : ecMap.remove( editName );

//		if( guikey   == null || guikey.isEmpty()   ) { return null; }
//		if( editName == null || editName.isEmpty() ) { return null; }
//
//		DBEditConfig config = null;
//		synchronized( editConfigMap ) {
//			final Map<String,DBEditConfig> map = editConfigMap.get( guikey );
//			if( map == null ) { return null; }
//			config = map.remove( editName );
//			editConfigMap.put( guikey, map );
//		}
//		return config;
	}

	/**
	 * 編集設定オブジェクトを取得します。
	 *
	 * @og.rev 6.4.3.2 (2016/02/19) ConcurrentHashMap の同期処理を使用。
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。
	 *
	 * @param guikey 画面ID
	 * @param editName 編集名
	 *
	 * @return 編集設定オブジェクト
	 */
	public DBEditConfig getEditConfig( final String guikey, final String editName ) {
		// 6.4.3.2 (2016/02/19) ConcurrentHashMap は、key,val ともに、NOT NULL制限があります。
		if( guikey   == null || guikey.isEmpty() ||
			editName == null || editName.isEmpty() ) { return null; }

		final Map<String,DBEditConfig> ecMap = editConfigMap.get( guikey );

		return ecMap == null ? null : ecMap.get( editName );

//		if( guikey   == null || guikey.isEmpty()   ) { return null; }
//		if( editName == null || editName.isEmpty() ) { return null; }
//
//		synchronized( editConfigMap ) {
//			final Map<String,DBEditConfig> map = editConfigMap.get( guikey );
//			// 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 map == null ? null : map.get( editName );
//
////			if( map == null ) { return null; }
////			return map.get( editName );
//		}
	}

	/**
	 * 画面IDをキーに編集設定の一覧(配列)を返します。
	 * 返される配列は、編集名順にソートされた状態で返されます。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) refactoring: null ではなく長さが0の配列を返す。
	 *
	 * @param guikey 画面ID
	 *
	 * @return 編集設定一覧(配列)
	 * @og.rtnNotNull
	 */
	public DBEditConfig[] getEditConfigs( final String guikey ) {
		if( guikey == null || guikey.isEmpty() ) { return new DBEditConfig[0] ; }	// 6.1.0.0 (2014/12/26)

		final Map<String,DBEditConfig> map = editConfigMap.get( guikey );
		if( map == null ) { return new DBEditConfig[0]; }							// 6.1.0.0 (2014/12/26)

		final DBEditConfig[] configs = map.values().toArray( new DBEditConfig[map.size()] );	// 6.0.2.5 (2014/10/31) refactoring
		// 6.0.2.5 (2014/10/31) findBugs対応:名前付き static 内部クラスにリファクタリングできるかもしれません。
		Arrays.sort( configs , new EditConfigComparator() );

		return configs;
	}

	/**
	 * 指定の画面IDに対して選択済みの編集名を返します。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 6.0.2.2 (2014/10/03) EDIT_NAME_SELECTED_ を、EDIT_SELECTED_ に変更
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。
	 *
	 * @param guikey 画面ID
	 *
	 * @return 選択済み編集名
	 */
	public String getSelectedEdit( final String guikey ) {
		return editSelMap.get( SLCT_KEY + guikey );
	}

	/**
	 * DBEditConfig  比較用の Comparator 実装です。
	 * 名前なしclass から、名前あり staticクラスに格上げです。
	 *
	 * @og.rev 6.0.2.5 (2014/10/31) findBugs対応:新規追加
	 */
	private static final class EditConfigComparator implements Comparator<DBEditConfig>, Serializable {
		private static final long serialVersionUID = 602520141024L ;

		/**
		 * DBEditConfig  比較メソッド
		 * インタフェース Comparable の 実装です。
		 *
		 * @og.rev 6.0.2.5 (2014/10/31) findBugs対応:新規追加
		 *
		 * @param c1 比較対象の最初のオブジェクト
		 * @param c2 比較対象の 2 番目のオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final DBEditConfig c1, final DBEditConfig c2 ) {
			return c1 == null ? -1 : c1.getEditName().compareTo( c2.getEditName() ) ;
		}
	}
}
