/*
 * 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 java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.opengion.fukurou.util.StringUtil;

/**
 * エディット設定情報を管理するためのデータ管理クラスです。
 * ここで管理される各パラメーターの意味は以下の通りです。
 * (各インデックス番号は、内部的に管理されているインデックス番号を意味します)
 *
 * ・0:エディット名
 *       このエディット設定オブジェクトの名称です。
 * ・1:表示カラム
 *       表示対象となるカラム一覧です。カンマ区切りで指定します。
 *       この一覧には、非表示のカラムも合わせて管理され、非表示カラムについては、
 *       カラム名の先頭に"!"をつけます。
 *       例) AAA,!BBB,CCC ⇒ AAA,CCCの順に表示(BBBは非表示)
 * ・2:集計カラム
 *       各値をSUMする対象となるカラムです。(カンマ区切りで複数指定が可能)
 *       ここで指定されたカラムは数値型である必要があります。
 *       SQL構文における、SUM関数の引数として指定するカラムに相当します。
 * ・3:グループカラム
 *       集計カラムの各値をグルーピングするためのカラムです。(カンマ区切りで複数指定が可能)
 *       SQL構文における、GROUP BYに指定するカラムに相当します。
 * ・4:小計カラム
 *       集計カラムの各値に対し、小計行を付加するためのブレイクキーを指定します。(カンマ区切りで複数指定が可能)
 * ・5:合計カラム
 *       集計カラムの各値に対し、合計行を付加するためのブレイクキーを指定します。(カンマ区切りで複数指定が可能)
 * ・6:総合計フラグ
 *       集計カラムの各値に対し、総合計行を付加するかどうかを指定します。(0以外:追加する 0:追加しない)
 * ・7:表示順カラム
 *       データの表示順をその順番にカンマ区切りで指定します。
 *       カラム名の先頭に"!"をつけた場合は、そのカラムは降順で表示されます。
 *       SQL構文における、orderby句に相当します。
 * ・8:共通フラグ
 *       このエディット設定オブジェクトが、共通(全ユーザー公開)エディットかどうかを
 *       指定します。(0以外:共通 0:個人のみ)
 *
 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
 *
 * @version  5.0
 * @author   Hiroki Nakamura
 * @since    JDK6.0,
 */
public class DBEditConfig {

	private static final int EDIT_KEY_NAME		= 0;
	private static final int EDIT_KEY_VIEW		= 1;
	private static final int EDIT_KEY_SUM		= 2;
	private static final int EDIT_KEY_GROUP		= 3;
	private static final int EDIT_KEY_SUBTOTAL	= 4;
	private static final int EDIT_KEY_TOTAL		= 5;
	private static final int EDIT_KEY_GRANDTOTAL= 6;
	private static final int EDIT_KEY_ORDERBY	= 7;
	private static final int EDIT_KEY_COMMON	= 8;

	private static final String[] EDIT_KEYS
		= { "NAME", "VIEW", "SUM", "GROUP", "SUBTOTAL", "TOTAL", "GRANDTOTAL", "ORDERBY", "COMMON" };

	private static final int EDIT_KEYS_LENGTH	= EDIT_KEYS.length;

	private final String[] editVals = new String[EDIT_KEYS_LENGTH];

	private int sumClmCount;
	private int groupClmCount;
	private int subTotalClmCount;
	private int totalClmCount;
	private final Map<String,String> orderMap = new HashMap<String,String>();
	private String orderByDescClms;

	/**
	 * コンストラクタ
	 *
	 * 空のエディット設定オブジェクトを構築します。
	 */
	public DBEditConfig() {
	}

	/**
	 * コンストラクタ
	 *
	 * 各種パラメーターを指定してエディット設定オブジェクトを構築します。
	 *
	 * @param editName エディット名称
	 * @param viewClms 画面表示カラム
	 * @param sumClms 集計カラム
	 * @param groupClms グループカラム
	 * @param subTotalClms 小計カラム
	 * @param totalClms 合計カラム
	 * @param useGrandTotal 総合計行を追加するか(1:追加する 1以外:追加しない)
	 * @param orderByClms 表示順
	 * @param isCommon 共通エディットかどうか(1:共通 1以外:個人のみ)
	 */
	public DBEditConfig( final String editName, final String viewClms
						, final String sumClms, final String groupClms
						, final String subTotalClms, final String totalClms
						, final String useGrandTotal, final String orderByClms
						, final String isCommon ) {

		editVals[EDIT_KEY_NAME]			= editName;
		editVals[EDIT_KEY_VIEW]			= viewClms;
		editVals[EDIT_KEY_SUM]			= sumClms;
		editVals[EDIT_KEY_GROUP]		= groupClms;
		editVals[EDIT_KEY_SUBTOTAL]		= subTotalClms;
		editVals[EDIT_KEY_TOTAL]		= totalClms;
		editVals[EDIT_KEY_GRANDTOTAL]	= useGrandTotal;
		editVals[EDIT_KEY_ORDERBY]		= orderByClms;
		editVals[EDIT_KEY_COMMON]		= isCommon;

		init();
	}

	/**
	 * コンストラクタ
	 *
	 * 各種パラメーターを配列で指定してエディット設定オブジェクトを構築します。
	 * 各パラメータの配列インデックスは、{@link #getEditKeys(String,String)}で返される
	 * キー一覧の配列インデックスと一致します。
	 * 各パラメーターの意味については、クラスのJavadoc{@link DBEditConfig}を参照して下さい。
	 *
	 * @param editVals 設定値(配列)
	 * @see DBEditConfig
	 * @see #getEditKeys(String,String)
	 **/
	public DBEditConfig( final String[] editVals ) {
		System.arraycopy( editVals, 0, this.editVals, 0, editVals.length );
		init();
	}

	/**
	 * エディット設定オブジェクト作成時の初期化処理です。
	 * コンストラクタの引数に基づき内部変数の初期設定を行います。
	 */
	private void init() {
		sumClmCount = StringUtil.csv2Array( editVals[EDIT_KEY_SUM] ).length;
		groupClmCount = StringUtil.csv2Array( editVals[EDIT_KEY_GROUP] ).length;
		subTotalClmCount = StringUtil.csv2Array( editVals[EDIT_KEY_SUBTOTAL] ).length;
		totalClmCount = StringUtil.csv2Array( editVals[EDIT_KEY_TOTAL] ).length;

		if( editVals[EDIT_KEY_ORDERBY] != null ) {
			StringBuilder buf = new StringBuilder();
			String[] ary = StringUtil.csv2Array( editVals[EDIT_KEY_ORDERBY] );
			for( int i=0; i<ary.length ;i++ ) {
				String str = ary[i];
				if( str.startsWith( "!" ) ) {
					str = str.substring( 1 );
					if( buf.length() > 0 ) { buf.append( "," ); }
					buf.append( str );
				}
				orderMap.put( str, String.valueOf( i+1 ) );
			}
			orderByDescClms = buf.toString();
		}
		else {
			orderByDescClms = null;
		}
	}

	/**
	 * キー配列から画面IDとエディット名称のペアの一覧を取り出します。
	 *
	 * キー配列から"EDIT_NAME_"で始まるキーを検索し、"EDIT_NAME_(画面ID)_(エディット名)"
	 * と言う形式に基づき、画面IDとエディット名称のペアを取り出します。
	 *
	 * 画面IDとエディット名称は配列として保存され(インデックス番号は 0:画面ID、1:エディット名)
	 * その一覧がされらに配列に格納されて返されます。
	 *
	 * @param keys キー配列
	 *
	 * @return 画面IDとエディット名称のペアの一覧
	 */
	public static String[][] getKeySet( final String[] keys ) {
		if( keys == null || keys.length == 0 ) { return null; }

		Set<String[]> keySet = new HashSet<String[]>();
		for( String key : keys ) {
			if ( key != null && key.startsWith( "EDIT_NAME_" ) ) {
				String guikeyEditName = key.substring( ( "EDIT_NAME_" ).length() );
				if( guikeyEditName.indexOf( '_' ) >= 0 ) {
					String guikey = guikeyEditName.substring( 0, guikeyEditName.indexOf( '_' ) );
					String editName = guikeyEditName.substring( ( guikey + "_" ).length() );
					if( guikey != null && guikey.length() > 0 && editName != null && editName.length() > 0 ) {
						String[] set = { guikey, editName };
						keySet.add( set );
					}
				}
			}
		}
//		return keySet.toArray( new String[0][] );
		return keySet.toArray( new String[keySet.size()][] );
	}

	/**
	 * 画面ID、エディット名をキーに、エディット設定オブジェクトの各設定値の
	 * 管理キーを指定します。
	 *
	 * エディット設定オブジェクトで管理される各キーに対して、
	 * "EDIT_[KEY]_(画面ID)_(エディット名)"というキーを生成し、これを配列にして返します。
	 *
	 * @param guikey 画面ID
	 * @param editName エディット名
	 *
	 * @return エディット設定を管理するためのキー一覧
	 */
	public static String[] getEditKeys( final String guikey, final String editName ) {
		String[] rtn = new String[EDIT_KEYS_LENGTH];
		for( int i=0; i<EDIT_KEYS_LENGTH; i++ ) {
			rtn[i] = "EDIT_" + EDIT_KEYS[i] + "_" + guikey + "_" + editName;
		}
		return rtn;
	}

	/**
	 * エディット設定オブジェクトの各設定値を配列にして返します。
	 *
	 * 配列のインデックス番号は、{@link #getEditKeys(String,String)}で生成されるキーの
	 * インデックス番号と一致します。
	 *
	 * @return エディット設定オブジェクトの設定値一覧(配列)
	 * @see #getEditKeys(String,String)
	 */
	public String[] getEditVals() {
		String[] rtn = new String[editVals.length];
		System.arraycopy( editVals, 0, rtn, 0, editVals.length );
		return rtn;
	}

	/**
	 * エディット名を返します。
	 *
	 * @return エディット名
	 */
	public String getEditName() {
		return editVals[EDIT_KEY_NAME];
	}

	/**
	 * 表示カラム名の一覧をカンマ区切りで返します。
	 * 非表示カラムについては、カラム名の先頭に"!"をつけて返されます。
	 * 例) AAA,!BBB,CCC ⇒ AAA,CCCの順に表示(BBBは非表示)
	 *
	 * @return 表示カラム名一覧(カンマ区切り)
	 */
	public String getViewClms() {
		return editVals[EDIT_KEY_VIEW];
	}

	/**
	 * 表示カラム(CSV形式)をチェックし、変更があれば、反映したカラムを作成します。
	 *
	 * 表示カラムは、並び順や非表示マーカー(!)などが加味され、ユーザー、画面ごとに
	 * データベースに記録されています。JSPソースを修正した場合、データベースに
	 * 書き込まれた表示カラムは、反映されないため、カラム選択画面等に表示されません。
	 * そこで、オリジナルのカラムに追加された場合は、カラムを比較することで、
	 * 追加分のカラムを、非表示カラムとして、後ろに追記します。
	 * 削除された場合は、ViewForm で警告表示することで、ユーザーに変更を促します。
	 *
	 * @og.rev 5.8.2.0 (2014/12/05) JSP修正時の追加カラム対応
	 * @og.rev 5.9.32.0 (2018/05/02) spritView使用時の対応
	 *
	 * @param	orgClms		オリジナルのカラム(CSV形式)
	 *
	 * @return	変更後の表示カラム(CSV形式)
	 */
	public String getViewClms( final String orgClms ) {
		String viewClms = editVals[EDIT_KEY_VIEW];

		if( orgClms == null || orgClms.isEmpty() ) { return viewClms; }		// orgClms がなければ、viewClms を返す。
		// 基本的には、両者のカラムは、一致するはず。
//		String[] vclms = viewClms.split( "," );		// 表示順、非表示処理を行ったカラム 5.9.32.0 DELETE
		Pattern pattern1 = Pattern.compile("[,|]");	// spritView使用を考慮して、「 , or | 」で分割します。 5.9.32.0 ADD
		String[] vclms = pattern1.split(viewClms);

//		String[] fclms = orgClms.split( "," );		// 元々の表示可能カラムすべて(fullClms) 5.9.32.0 DELETE
		String[] fclms = vclms.clone();				// spritView使用時は未固定の列情報のみ渡されるため、vclmsの値から取得するように変更。5.9.32.0 ADD
		for(int i=0; i<fclms.length; i++) {
			fclms[i] = fclms[i].charAt(0) == '!' ? fclms[i].substring(1) : fclms[i]; // 非表示の(!)は削除します。
		}

		// 表示可能カラムすべての Set を作成します。
		Set<String> fset = new HashSet<String>();
		for( int i=0; i<fclms.length; i++ ) {		// orgClms をSet に追加します。
			fset.add( fclms[i] );
		}

		// 非表示カラムの内、表示可能カラムに存在しない分だけの Set を作成します。
		// また、表示可能カラムから、順番に、viewClms の値を削除していきます。
		Set<String> vset = new HashSet<String>();
		StringBuilder vbuf = new StringBuilder();	// 新しい viewClms 作成用
		for( int i=0; i<vclms.length; i++ ) {		// viewClms をSet に追加します。
			String clm = vclms[i];
			if( clm == null || clm.isEmpty() ) { continue; }			// 6.0.2.5 (2014/10/31) 潜在バグ？ 先頭に"," が来るとアベンドする。
			clm = clm.charAt(0) == '!' ? clm.substring(1) : clm ;		// 非表示の (!) は削除します。
			if( fset.remove( clm ) ) {				// fullSet にあれば、削除するとともに、新viewClmsを作成する。
				if( vbuf.length() > 0 ) { vbuf.append(','); }	// 最初以降は、カンマで連結する。		// 6.0.2.5 (2014/10/31) char を append する。
				vbuf.append( vclms[i] );			// append するのは、(!) 付のカラム
			}
			else {
				vset.add( clm );					// fullSet になければ、viewSet に追加
			}
		}

		// この段階で、fset、vset ともに、それぞれ独自のカラムが残っている。
		// どちらも、残っていなければ、正常なので、viewClms を返す。
		if( vset.isEmpty() && fset.isEmpty() ) { return viewClms; }

		// fullSet にカラムが残っていれば、非表示で、新viewClmsに、追加する。
		if( !fset.isEmpty() ) {
			String[] defClms = fset.toArray( new String[fset.size()] );
			for( int i=0; i<defClms.length; i++ ) {
				if( vbuf.length() > 0 ) { vbuf.append(','); }	// 6.0.2.5 (2014/10/31) 最初以降は、カンマで連結する。
				vbuf.append('!').append( defClms[i] );						// 非表示カラムとして、後ろに追加する。
			}
		}
		viewClms = vbuf.toString();

		editVals[EDIT_KEY_VIEW] = viewClms;
		return viewClms;
	}

	/**
	 * 集計カラムの一覧をカンマ区切りで返します。
	 *
	 * @return 集計カラムの一覧(カンマ区切)
	 */
	public String getSumClms() {
		return editVals[EDIT_KEY_SUM];
	}

	/**
	 * 集計処理を行うかどうかを返します。
	 * これは、集計カラムが指定されているか、と同じ意味です。
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean useSum() {
		return ( editVals[EDIT_KEY_SUM] != null && editVals[EDIT_KEY_SUM].length() > 0 );
	}

	/**
	 * 指定されたカラムが集計対象のカラムかどうかを返します。
	 *
	 * @param clm カラム
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean isSumClm( final String clm ) {
		if( clm == null || editVals[EDIT_KEY_SUM] == null ) { return false; }
		return ( ( ","+editVals[EDIT_KEY_SUM]+"," ).indexOf( ","+clm+"," ) >= 0 );
	}

	/**
	 * 集計カラムのカラム数を返します。
	 *
	 * @return 集計カラムのカラム数
	 */
	public int getSumClmCount() {
		return sumClmCount;
	}

	/**
	 * グループカラムの一覧をカンマ区切りで返します。
	 *
	 * @return グループカラムの一覧(カンマ区切)
	 */
	public String getGroupClms() {
		return editVals[EDIT_KEY_GROUP];
	}

	/**
	 * グループ処理を行うかどうかを返します。
	 * これは、グループカラムが指定されているか、と同じ意味です。
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean useGroup() {
		return ( editVals[EDIT_KEY_GROUP] != null && editVals[EDIT_KEY_GROUP].length() > 0 );
	}

	/**
	 * 指定されたカラムがグループ対象のカラムかどうかを返します。
	 *
	 * @param clm カラム
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean isGroupClm( final String clm ) {
		if( clm == null || editVals[EDIT_KEY_GROUP] == null ) { return false; }
		return ( ( ","+editVals[EDIT_KEY_GROUP]+"," ).indexOf( ","+clm+"," ) >= 0 );
	}

	/**
	 * グループカラムのカラム数を返します。
	 *
	 * @return グループカラムのカラム数
	 */
	public int getGroupClmCount() {
		return groupClmCount;
	}

	/**
	 * 小計カラムの一覧をカンマ区切りで返します。
	 *
	 * @return 小計カラムの一覧(カンマ区切)
	 */
	public String getSubTotalClms() {
		return editVals[EDIT_KEY_SUBTOTAL];
	}

	/**
	 * 小計処理を行うかどうかを返します。
	 * これは、小計カラムが指定されているか、と同じ意味です。
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean useSubTotal() {
		return ( editVals[EDIT_KEY_SUBTOTAL] != null && editVals[EDIT_KEY_SUBTOTAL].length() > 0 );
	}

	/**
	 * 指定されたカラムが小計対象のカラムかどうかを返します。
	 *
	 * @param clm カラム
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean isSubTotalClm( final String clm ) {
		if( clm == null || editVals[EDIT_KEY_SUBTOTAL] == null ) { return false; }
		return ( ( ","+editVals[EDIT_KEY_SUBTOTAL]+"," ).indexOf( ","+clm+"," ) >= 0 );
	}

	/**
	 * 小計カラムのカラム数を返します。
	 *
	 * @return グループカラムのカラム数
	 */
	public int getSubTotalClmCount() {
		return subTotalClmCount;
	}

	/**
	 * 合計カラムの一覧をカンマ区切りで返します。
	 *
	 * @return 合計カラムの一覧(カンマ区切)
	 */
	public String getTotalClms() {
		return editVals[EDIT_KEY_TOTAL];
	}

	/**
	 * 合計処理を行うかどうかを返します。
	 * これは、合計カラムが指定されているか、と同じ意味です。
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean useTotal() {
		return ( editVals[EDIT_KEY_TOTAL] != null && editVals[EDIT_KEY_TOTAL].length() > 0 );
	}

	/**
	 * 指定されたカラムが合計対象のカラムかどうかを返します。
	 *
	 * @param clm カラム
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean isTotalClm( final String clm ) {
		if( clm == null || editVals[EDIT_KEY_TOTAL] == null ) { return false; }
		return ( ( ","+editVals[EDIT_KEY_TOTAL]+"," ).indexOf( ","+clm+"," ) >= 0 );
	}

	/**
	 * 合計カラムのカラム数を返します。
	 *
	 * @return グループカラムのカラム数
	 */
	public int getTotalClmCount() {
		return totalClmCount;
	}

	/**
	 * 総合計行を付加するかどうかを返します。
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean useGrandTotal() {
		return StringUtil.nval( editVals[EDIT_KEY_GRANDTOTAL], false );
	}

	/**
	 * 表示順カラムをカンマ区切りで返します。
	 * カラムの並び順が表示順としての優先順になります。
	 * また、降順で表示するカラムについては、カラム名の先頭に"!"が付加されます。
	 *
	 * @return 標準順カラムの一覧(カンマ区切)
	 */
	public String getOrderByClms() {
		return editVals[EDIT_KEY_ORDERBY];
	}

	/**
	 * 指定されたカラムの表示順の優先番号を返します。
	 * 指定カラムが標準として指定されていない場合は、""(ゼロストリング)を返します。
	 *
	 * @param clm カラム
	 *
	 * @return 表示順の優先番号
	 */
	public String getOrder( final String clm ) {
		if( clm == null || editVals[EDIT_KEY_ORDERBY] == null ) { return ""; }

		String rtn = orderMap.get( clm );
		return ( rtn == null ? "" : rtn );
	}

	/**
	 * 指定されたカラムの表示順指定が降順であるかどうかを返します。
	 * 標準と指定されていない場合は、falseを返します。
	 *
	 * @param clm カラム
	 *
	 * @return true:降順 false:昇順
	 */
	public boolean isOrderByDesc( final String clm ) {
		if( clm == null || orderByDescClms == null ) { return false; }
		return ( ( ","+orderByDescClms+"," ).indexOf( ","+clm+"," ) >= 0 );
	}

	/**
	 * 並び替え処理を行うかどうかを返します。
	 * これは、表示順カラムが指定されているか、と同じ意味です。
	 *
	 * @return true:対象 false:非対象
	 */
	public boolean useOrderBy() {
		return ( editVals[EDIT_KEY_ORDERBY] != null && editVals[EDIT_KEY_ORDERBY].length() > 0 );
	}

	/**
	 * このエディット設定オブジェクトが、共通(全ユーザー公開)エディットか
	 * どうかを返します。
	 *
	 * @return 0以外:共通 0:個人のみ
	 */
	public boolean isCommon() {
		return StringUtil.nval( editVals[EDIT_KEY_COMMON], false );
	}
}
