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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.resource.GUIInfo;
import org.opengion.fukurou.util.ToString;						// 6.1.1.0 (2015/01/17)
import org.opengion.fukurou.util.ArraySet;						// 6.4.3.4 (2016/03/11)

import static org.opengion.fukurou.util.StringUtil.nval ;

import javax.servlet.ServletRequest ;
import javax.servlet.http.HttpSession ;

import java.util.Set;											// 6.4.3.4 (2016/03/11)
import java.util.Map;
import java.util.HashMap;

/**
 * 戻るリンクで戻る場合に使用する、検索時の request 情報をキャッシュするタグです(通常はquery.jsp に組込み)。
 *
 * requestタグをキャッシュすることにより、 再検索時や、各画面遷移時の項目の持ち回りを行います。
 * command = "NEW" で、キャッシュし、"RENEW" で、取り出します。
 * 暫定的にこのタグは、共通JSPファイルに設定し、HTMLそのもののキャッシュ制御も
 * 行うように設定しています。
 *
 * @og.formSample
 * ●形式：&lt;og:requestCache cacheKey="[･･･]" /&gt;
 * ●body：なし
 *
 * ●Tag定義：
 *   &lt;og:requestCache
 *       cacheKey           【TAG】キャッシュするサブキーを指定します(初期値:"")
 *       action             【TAG】アクション(SET,DELETE)をセットします
 *       keys               【TAG】リンク先に渡すキーを指定します
 *       vals               【TAG】keys属性に対応する値をCSV形式で複数指定します
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   /&gt;
 *
 * ●使用例
 *       &lt;og:requestCache
 *                  cacheKey="{&#064;GUI.KEY}"     キャッシュするサブキーを指定します。
 *       /&gt;
 *
 * @og.group 画面制御
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class RequestCacheTag extends CommonTagSupport {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.3.4 (2016/03/11)" ;
	private static final long serialVersionUID = 643420160311L ;

	private static final String CACHE_KEY = HybsSystem.REQ_CACHE_KEY;

	/** command 引数に渡す事の出来る アクションコマンド   ニュー {@value} */
	public static final String CMD_NEW   = "NEW" ;
	/** command 引数に渡す事の出来る アクションコマンド   レニュー {@value} */
	public static final String CMD_RENEW = "RENEW" ;
	/** command 引数に渡す事の出来る アクションコマンド   イニット {@value} */
	public static final String CMD_INIT = "INIT" ;
	/** command 引数に渡す事の出来る アクションコマンド   リセット {@value} */
	public static final String CMD_RESET = "RESET" ;	// 3.5.5.0 (2004/03/12) 追加

	// 3.8.8.0 (2006/12/22)
	/** action 引数に渡す事の出来る アクション  設定 {@value} */
	public static final String ACT_SET = "SET" ;
	/** action 引数に渡す事の出来る アクション  削除 {@value} */
	public static final String ACT_DELETE = "DELETE" ;

	// 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
	private static final Set<String> ACTION_SET = new ArraySet<>( ACT_SET , ACT_DELETE );

	private String	cacheKey	= "";
	private String	action		;
	private String[] keys		;
	private String[] vals		;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public RequestCacheTag() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 * @og.rev 3.1.1.2 (2003/04/04) 継承元を、CommonTagSupport から TagSupport に変更する。
	 * @og.rev 3.1.3.0 (2003/04/10) Cache-Control ヘッダーのセットを削除します。
	 * @og.rev 3.1.6.0 (2003/04/24) キャッシュすべき値を、キー毎に指定できるように、cacheKey 属性を追加。
	 * @og.rev 3.1.7.0 (2003/05/02) command=INIT または、null のときに、キャッシュを削除するように変更する。
	 * @og.rev 3.1.7.0 (2003/05/02) command=INIT または、null のときに、キャッシュを削除するように変更する。
	 * @og.rev 3.1.8.0 (2003/05/16) BACK_GAMENID のキャッシュの設定先を変更する。
	 * @og.rev 3.5.1.0 (2003/10/03) GAMENID 情報を取得し、backGamenIdSetメソッドに渡すように変更。
	 * @og.rev 3.7.0.3 (2005/03/01) BACK_ROW 情報を取得し、backGamenIdSetメソッドに渡すように変更。
	 * @og.rev 4.0.1.0 (2007/12/17) BackAddress対応
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)
		// request をキャッシュするタグなので、直接取り込む。
		final ServletRequest request = pageContext.getRequest();
		final String command	 = request.getParameter( "command" );
		final String backGamenId = request.getParameter( "BACK_GAMENID" );
		final String gamenId	 = request.getParameter( "GAMENID" );			// 3.5.1.0 (2003/10/03)
		final String backRow	 = request.getParameter( "BACK_ROW" );		// 3.7.0.3 (2005/03/01)
		final String backAdrs	 = request.getParameter( "BACK_ADDRESS" );	// 4.0.1.0 (2007/12/17)

		commandExec( command,request );
		backGamenIdSet( command,backGamenId,gamenId,backRow,backAdrs );	// 4.0.1.0 (2007/12/17)

		return EVAL_PAGE ;		// ページの残りを評価する。
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 *
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 3.5.5.3 (2004/04/09) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 * @og.rev 3.8.8.0 (2006/12/22) action,keys,vals 追加
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		cacheKey	= "";
		action		= null;
		keys		= null;
		vals		= null;
	}

	/**
	 * アクションを実行します。
	 * アクションは,指定のアクションコマンドに対応する処理を入力データに
	 * 対して行います。
	 *
	 * @og.rev 3.1.1.2 (2003/04/04) 継承元を、CommonTagSupport から TagSupport に変更する。
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。HybsRequestWrapper 廃止。直接 Mapでキャッシュする。
	 * @og.rev 3.1.6.0 (2003/04/24) キャッシュすべき値を、キー毎に指定できるように、cacheKey 属性を追加。
	 * @og.rev 3.1.7.0 (2003/05/02) RENEW のときに、キャッシュを削除しないように変更する。
	 * @og.rev 3.1.7.0 (2003/05/02) command=INIT または、null のときに、キャッシュを削除するように変更する。
	 * @og.rev 3.5.5.0 (2004/03/12) command=RESET時にも、キャッシュを取り出すように変更する。
	 * @og.rev 3.8.6.3 (2006/11/30) debug 処理を追加
	 * @og.rev 3.8.8.0 (2006/12/22) actionCacheData 処理を追加
	 * @og.rev 5.6.8.1 (2013/09/13) UserInfo に、request 情報を渡してキャッシュ対象情報をセットします。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 * @og.rev 6.4.3.3 (2016/03/04) ServletRequest#getParameterMap() が、not null保証は無いので、ConcurrentHashMap に置き換えできない。
	 *
	 * @param	command	アクションコマンド (public static final 宣言されている文字列)
	 * @param	request	リクエストオブジェクト
	 */
	@SuppressWarnings(value={"unchecked"})
	private void commandExec( final String command,final ServletRequest request ) {
		final String key = CACHE_KEY + cacheKey ;
		final HttpSession session = pageContext.getSession();
		String msg = null;	// 3.8.6.3 (2006/11/30)

		if( CMD_NEW.equals( command ) ) {
			// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
			final Map<String,String[]> map = actionCacheData( new HashMap<>( request.getParameterMap() ) );					// 6.4.3.3 (2016/03/04)
			session.setAttribute( key,map );

	 		// 5.6.8.1 (2013/09/13) UserInfo に、request 情報を渡してキャッシュ対象情報をセットします。
			getUser().setLastRequestMap( map );
			msg = "command=[NEW] CACHE=[Set]" ;
		}
		else if( cacheKey.length() > 0 &&
					( CMD_RENEW.equals( command ) || CMD_RESET.equals( command ) ) ) {
			Map<String,String[]> map = (Map<String,String[]>)session.getAttribute( key );
			if( map != null ) {
				map = actionCacheData( map );
				session.setAttribute( CACHE_KEY,map );		// 共有キャッシュにセット
				msg = "command=[" + command + "] CACHE=[Load]" ;
			}
		}
		else if( CMD_INIT.equals( command ) || command == null || command.isEmpty() ) {
			session.removeAttribute( key );
			msg = "command=[" + command + "] CACHE=[remove]" ;
		}

		// 3.8.6.3 (2006/11/30)
		if( isDebug() ) {
			jspPrint( msg + "<br />" );
		}
	}

	/**
	 * キャッシュデータに対して、追記、削除を行います。
	 *
	 * @og.rev 3.8.8.0 (2006/12/22) 新規追加
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMapではないが、not null制限のチェック追加
	 *
	 * @param	map	キャッシュデータ
	 *
	 * @return	追記、削除のキャッシュデータのマップ(入力のMapと同一)
	 */
	private Map<String,String[]> actionCacheData( final Map<String,String[]> map ) {
		if( action != null && map != null && keys != null && vals != null ) {
			if( ACT_SET.equalsIgnoreCase( action ) ) {
				for( int i=0; i<keys.length; i++ ) {
					final String[] val = new String[] { vals[i] } ;
					if( keys[i] != null ) { map.put( keys[i],val ); }			// 6.4.3.3 (2016/03/04)
				}
			}
			else if( ACT_DELETE.equalsIgnoreCase( action ) ) {
				for( int i=0; i<keys.length; i++ ) {
					if( keys[i] != null ) { map.remove( keys[i] ); }			// 6.4.3.3 (2016/03/04)
				}
			}
		}

		return map ;
	}

	/**
	 * BACK_GAMENID のキャッシュの設定先を変更します。
	 *
	 * @og.rev 3.1.8.0 (2003/05/16) BACK_GAMENID のキャッシュの設定先を変更する。
	 * @og.rev 3.5.1.0 (2003/10/03) BACK_GAMENID のリクエストがNULLのときの処理(バグ)訂正。
	 * @og.rev 3.7.0.3 (2005/03/01) BACK_ROW 情報を取得し、backGamenIdSetメソッドに渡すように変更。
	 * @og.rev 3.8.6.3 (2006/11/30) debug 処理を追加
	 * @og.rev 4.0.1.0 (2007/12/17) BACK_ADDRESSを追加
	 *
	 * @param	command	アクションコマンド (public static final 宣言されている文字列)
	 * @param	backGamenId	リクエストから取得したBACK_GAMENID
	 * @param	gamenId	画面ID
	 * @param   backRow Stringリクエストから取得したBACK_ROW
	 * @param   backAdrs Stringリクエストから取得したBACK_ADDRESS
	 */
	private void backGamenIdSet( final String command,final String backGamenId,final String gamenId,final String backRow,final String backAdrs) {		// 3.5.1.0 (2003/10/03)
		final HttpSession session = pageContext.getSession();

		// この画面自身のＩＤ
		final GUIInfo guiInfo = (GUIInfo)session.getAttribute( HybsSystem.GUIINFO_KEY );
		final String guiId = guiInfo.getAttribute( "KEY" );

		final String guikey = HybsSystem.BACK_GAMENID_KEY + guiId ;
		final String rowkey = HybsSystem.BACK_ROW_KEY + guiId ;		// 3.7.0.3 (2005/03/01)
		String msg = null;	// 3.8.6.3 (2006/11/30)
		final String adrskey = HybsSystem.BACK_ADDRESS_KEY + guiId;	// 4.0.1.0 (2007/12/17)
		if( CMD_NEW.equals( command ) && backGamenId != null && ! backGamenId.equals( guiId ) ) {
			session.setAttribute( guikey,backGamenId );
			session.setAttribute( rowkey,backRow );			// 3.7.0.3 (2005/03/01)
			session.setAttribute( adrskey,backAdrs );		// 4.0.1.0 (2007/12/17)
			msg = "command=[" + command + "] backGamenId=[Set]" ;
		}
		else if( CMD_INIT.equals( command ) || command == null || command.isEmpty() ) {
			session.removeAttribute( guikey );
			session.removeAttribute( rowkey );		// 3.7.0.3 (2005/03/01)
			msg = "command=[" + command + "] backGamenId=[command Remove]" ;
			session.removeAttribute( adrskey );		// 4.0.1.0 (2007/12/17)
		}
		// 以下 追加 3.5.1.0 (2003/10/03)
		// 変更 4.0.1.0 (2007/12/17)
		else if( ( gamenId == null || gamenId.isEmpty() ) &&
				 ( backGamenId == null || backGamenId.isEmpty() ) &&
				 ( backAdrs == null || backAdrs.isEmpty() )) {
					session.removeAttribute(guikey );
					session.removeAttribute( rowkey );		// 3.7.0.3 (2005/03/01)
					msg = "command=[" + command + "] backGamenId=[null Remove] " ;
					session.removeAttribute( adrskey );		// 4.0.1.0 (2007/12/17)
		}

		// 3.8.6.3 (2006/11/30)
		if( isDebug() ) {
			jspPrint( msg + "<br />" );
		}
	}

	/**
	 * 【TAG】キャッシュするサブキーを指定します(初期値:"")。
	 *
	 * @og.tag
	 * キャッシュすべき値を、キー毎に指定できるようにします。
	 * 例えば、これに、画面IDを追加しておけば、画面ごとに、自分のリクエスト情報を
	 * キャッシュしておき、自分の画面が呼ばれたら、再度使用することができる様になります。
	 * NEW でキャッシュ登録を行い、RENEW で、通常のキャッシュキーに値を取り出します。
	 *
	 * @og.rev 3.1.6.0 (2003/04/24) キャッシュすべき値を、キー毎に指定できるように、cacheKey 属性を追加。
	 *
	 * @param	ck キャッシュするサブキー
	 */
	public void setCacheKey( final String ck ) {
		cacheKey = nval( getRequestParameter( ck ),cacheKey );
	}

	/**
	 * 【TAG】アクション(SET,DELETE)をセットします。
	 *
	 * @og.tag
	 * アクションは,HTMLから(get/post)指定されますので,ACT_xxx で設定される
	 * フィールド定数値のいづれかを、指定できます。
	 * 無指定の場合は、なにもしません。
	 *
	 * <table border="1" frame="box" rules="all" >
	 *   <caption>アクションの説明</caption>
	 *   <tr><th>action </th><th>名称</th><th>機能</th></tr>
	 *   <tr><td>SET     </td><td>登録</td><td>指定の keys のキーに vals のキャッシュをセットします。</td></tr>
	 *   <tr><td>DELETE  </td><td>削除</td><td>指定の keys のキャッシュを削除します。                </td></tr>
	 * </table>
	 *
	 * @og.rev 6.3.4.0 (2015/08/01) Arrays.toString から String.join に置き換え。
	 * @og.rev 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
	 *
	 * @param	act アクション (public static final 宣言されている文字列)
	 * @see		<a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.RequestCacheTag.ACT_DELETE">アクション定数</a>
	 */
	public void setAction( final String act ) {
		action = nval( getRequestParameter( act ),action );

		if( action != null && !check( action, ACTION_SET ) ) {
			final String errMsg = "指定のアクションは実行できません。アクションエラー"	+ CR
							+ "action=[" + action + "] "								+ CR
							+ "actionList=" + String.join( ", " , ACTION_SET ) ;
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】リンク先に渡すキーを指定します。
	 *
	 * @og.tag
	 * 戻る時に、検索時のキャッシュに指定した引数以外に指定したり、別の値に置き換えたり
	 * する場合のキーを設定できます。CSV形式で複数指定できます。
	 * vals 属性には、キーに対応する値を、設定してください。
	 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。
	 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。
	 *
	 * @og.rev 3.8.8.0 (2006/12/22) 新規追加
	 *
	 * @param	key リンク先に渡すキー
	 */
	public void setKeys( final String key ) {
		keys = getCSVParameter( key );
	}

	/**
	 * 【TAG】keys属性に対応する値をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * キーに設定した値を、CSV形式で複数して出来ます。
	 * 指定順序は、キーと同じにしておいて下さい。
	 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。
	 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。
	 *
	 * @og.rev 3.8.8.0 (2006/12/22) 新規追加
	 *
	 * @param	val keys属性に対応する値
	 */
	public void setVals( final String val ) {
		vals = getCSVParameter( val );
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title( this.getClass().getName() )
				.println( "VERSION"		,VERSION	)
				.println( "cacheKey"	,cacheKey	)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
