/*
 * 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.hayabusa.resource.UserInfo;
import org.opengion.hayabusa.resource.FavoriteGUIData;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.fukurou.util.ToString;							// 6.1.1.0 (2015/01/17)
import org.opengion.fukurou.util.StringUtil ;						// 6.2.2.0 (2015/03/27)

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

import javax.servlet.http.HttpServletRequest ;
import javax.servlet.http.HttpServletResponse;

import java.util.Enumeration;
import java.util.ArrayList;
import java.util.Map;
// import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring

import java.io.IOException;

/**
 * お気に入りリンクを作成するタグです(通常はresult.jspに組込み)。
 *
 * 画面検索時の引数やユーザー情報を元にして、ダイレクトに再検索できる
 * リンクを作成します。このリンクをお気に入り等にセーブしておき、次回検索時にも
 * 使用できるようにします。
 *
 * @og.formSample
 * ●形式：&lt;og:favoriteLink direct="true" target="_blank" method="GET" /&gt;
 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *
 * ●Tag定義：
 *   &lt;og:favoriteLink
 *       target             【TAG】リンク先の文書を表示させるフレーム、またはウィンドウの名前を指定します(初期値:_blank)
 *       direct             【TAG】直接アクセスできる形式のリンクを作成するかどうか[true/false]を指定します(初期値:false)
 *       method             【TAG】リンクの作成元となるメソッド[POST/GET/ALL]を指定します(初期値:GET)
 *       href               【TAG】リンクを作成する時の転送先アドレスを指定します(初期値:index.jsp)
 *       lbl                【TAG】ラベルリソースのラベルIDを指定します
 *       linkCache          【TAG】リンクをキャッシュするかどうか[true/false]を指定します(初期値:false)
 *       lastQueryRedirect  【TAG】キャッシュされたリンク先に転送するかどうか[true/false]を指定します(初期値:false)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *       useIcon            【TAG】お気に入りアイコンリンクを作成するかどうか[true/false]を指定します(初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:favoriteLink&gt;
 *
 * ●使用例：
 *    直接お気に入りのリンクを作成する。
 *    デフォルト属性を使用(direct="true" target="_blank" method="GET")
 *    &lt;og:favoriteLink &gt; Favorite Link &lt;/og:favoriteLink &gt;
 *
 * @og.group 画面部品
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FavoriteLinkTag extends CommonTagSupport {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.3.1 (2016/02/12)" ;
	private static final long serialVersionUID = 643120160212L ;

	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
//	private static final Map<String,String>	LAST_QUERY	= new HashMap<>();				// 6.4.1.1 (2016/01/16) lastQuery → LAST_QUERY  refactoring
	private static final Map<String,String>	LAST_QUERY	= new ConcurrentHashMap<>();	// 6.4.1.1 (2016/01/16) lastQuery → LAST_QUERY  refactoring

	private boolean		direct		;				// 3.0.0.0 初期値変更
	private String		target		= "_blank";		// 3.6.0.7 (2004/11/06)
	private String		method		= "GET";
	private String		href		= "index.jsp";	// 3.8.8.2 (2007/01/26)
	private boolean		linkCache	;				// 3.5.5.9 (2004/06/07)
	private boolean		redirect	;				// 3.5.5.9 (2004/06/07)
	private boolean		useIcon		;				// 4.1.1.0 (2008/02/13)

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。ボディが存在する場合のみボディが呼ばれる対応。
	 * @og.rev 3.5.5.9 (2004/06/07) リンクキャッシュより最終画面を転送表示します。
	 * @og.rev 5.0.0.2 (2009/09/15) xss対応⇒チェックする
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doStartTag() {
		// 5.0.0.2 (2009/09/15) 強制False
		// useXssCheck( false );

		// 3.5.5.9 (2004/06/07) リンクキャッシュより最終画面を転送表示
		if( redirect ) {
			// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。key,val は、not null 制限。
			final String uid = getUserInfo( "ID" );

			final String page = uid == null ? null : LAST_QUERY.get( uid ) ;

//			final String page ;
//			synchronized( LAST_QUERY ) {
//				page = LAST_QUERY.get( getUserInfo( "ID" ) ) ;
//			}
			if( page != null ) {
				final HttpServletResponse response = (HttpServletResponse)pageContext.getResponse();
				final String url = response.encodeRedirectURL( page );
				try {
					response.sendRedirect( url );
				}
				catch( final IOException ex ) {
					final String errMsg = "最終画面の転送時のリダイレクトエラー" + toString();
					throw new HybsSystemException( errMsg,ex );
				}

				return SKIP_BODY ;
			}
		}

		set( "body",getMsglbl() );
		return EVAL_BODY_BUFFERED ;	// Body を評価する。( extends BodyTagSupport 時)
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) ボディの内容を取得する処理を、CommonTagSupport で行う。
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。ボディが存在する場合のみボディが呼ばれる対応。
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doAfterBody() {

		final String label = getBodyString();

		if( label != null && label.length() > 0 ) {
			set( "body",label );
		}

		return SKIP_BODY ;
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 * @og.rev 4.1.1.0 (2008/0213) お気に入りアイコンリンクの作成
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)

		// method による条件判断。
		final String requestMethod = ((HttpServletRequest)getRequest()).getMethod();
		if( method != null &&
			( "ALL".equalsIgnoreCase( method ) ||
			  method.equalsIgnoreCase( requestMethod ) ) ) {
				// 4.1.1.0 (2008/0213) お気に入りアイコンリンクの作成
				if( useIcon ) {
					jspPrint( getFavoriteIcon() );
				}
				else {
					jspPrint( makeTag() );
				}
		}

		return EVAL_PAGE ;
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 2.0.0.4 (2002/09/27) カスタムタグの release() メソッドを、追加
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 * @og.rev 3.5.5.9 (2004/06/07) linkCache , redirect 属性を追加
	 * @og.rev 3.6.0.7 (2004/11/06) target 属性の初期値を _new から _blank に変更
	 * @og.rev 3.8.8.2 (2007/01/26) href 属性を追加
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		direct		= false;
		target		= "_blank";		// 3.6.0.7 (2004/11/06)
		method		= "GET";
		href		= "index.jsp";	// 3.8.8.2 (2007/01/26)
		linkCache	= false;		// 3.5.5.9 (2004/06/07)
		redirect	= false;		// 3.5.5.9 (2004/06/07)
	}

	/**
	 * お気に入りリンクを作成します。
	 *
	 * @og.rev 3.8.8.2 (2007/01/26) href 属性を追加
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @return	お気に入りリンクタグ文字列
	 * @og.rtnNotNull
	 */
	protected String makeTag() {
		final HttpServletRequest request = (HttpServletRequest)getRequest();

		// ダイレクトリンク時の設定
		// リンクの作成
		// http://C00000:C00000@hn50g5:8080/dbdef/jsp/index.jsp?GAMENID=xxx&command=NEW&key=val
		final StringBuilder link = new StringBuilder( BUFFER_MIDDLE )
			.append( "http://" )
			.append( request.getServerName() ).append( ':' )	// hn50g5:		// 6.0.2.5 (2014/10/31) char を append する。
			.append( request.getServerPort() )					// 8823
			.append( request.getContextPath() )					// /dbdef
			.append( "/jsp/" );

		// 4.0.0 (2005/01/31)
		final String direct_jsp  = getGUIInfoAttri( "ADDRESS" ) + "/" + (String)getSessionAttribute( "JSPID" );

		final String hrefUrl = link.toString() ;
		if( direct ) {
			set( "href" , hrefUrl + direct_jsp );
		}
		else {
			set( "href" , hrefUrl + href );		// 3.8.8.2 (2007/01/26)
		}

		set( "target" ,target );
		set( "title"  ,getGUIInfoAttri( "LABEL" ) );	// 4.0.0 (2005/01/31)

		final String urlEnc = makeUrlEncode( request );

		// linkCache による、最終リクエストのアドレスを格納しておきます。
		if( linkCache ) {
			// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。key,val は、not null 制限。
			final String uid = getUserInfo( "ID" );
			if( uid != null ) {
				final String val = hrefUrl + direct_jsp + "?" + urlEnc ;
				LAST_QUERY.put( uid,val );
			}

//			final String key = getUserInfo( "ID" );
//			final String val = hrefUrl + direct_jsp + "?" + urlEnc ;
//			synchronized( LAST_QUERY ) {
//				LAST_QUERY.put( key,val );
//			}
		}

		return XHTMLTag.link( getAttributes(),urlEnc ) ;
	}

	/**
	 * URLエンコードを行ったリンク情報を作成します。
	 *
	 * @og.rev 3.1.0.0 (2003/03/20) Vector を使用している箇所で、非同期でも構わない箇所を、ArrayList に置換え。
	 * @og.rev 3.1.2.0 (2003/04/07) 画面IDと実画面ディレクトリとの関連見直し(DIRの代りにGAMENIDを渡すように変更)
	 * @og.rev 3.1.8.0 (2003/05/16) 内部で作成している GAMENID 属性をセットしないように変更。
	 * @og.rev 4.0.0.0 (2007/07/11) submitTag で作成されたボタンパラメータは、次ページへ転送しません。
	 * @og.rev 5.0.0.2 (2009/09/15) xssチェック対応
	 *
	 * @param	request	HttpServletRequestオブジェクト
	 *
	 * @return	URLエンコードを行ったリンク情報
	 * @og.rtnNotNull
	 */
	private String makeUrlEncode( final HttpServletRequest request ) {
		final Enumeration<?> enume = request.getParameterNames();		// 4.3.3.6 (2008/11/15) Generics警告対応
		final ArrayList<String> v_keys = new ArrayList<>();
		final ArrayList<String> v_vals = new ArrayList<>();
		while( enume.hasMoreElements() ) {
			final String key = String.valueOf( enume.nextElement() );
			if( key != null && key.startsWith( HybsSystem.NO_XFER_KEY ) ) {
				continue;
			}

			// String val = getRequestValue( key );
			final String val = getRequestValue( key, false ); // 5.0.0.2 (2009/09/15)
			if( val != null && val.length() > 0 ) {
				v_keys.add( key );
				v_vals.add( val );
			}
		}

		final String[] keys = v_keys.toArray( new String[v_keys.size()] );
		final String[] vals = v_vals.toArray( new String[v_vals.size()] );

		return XHTMLTag.urlEncode( keys,vals );
	}

	/**
	 * お気に入りアイコンを取得します。
	 *
	 * @og.rev 4.1.1.0 (2008/02/12) 新規追加
	 * @og.rev 6.2.2.0 (2015/03/27) BRと\nを相互に変換する処理を追加
	 * @og.rev 6.2.2.3 (2015/04/10) htmlフィルターに、BR→改行処理機能を追加。
	 *
	 * @return	お気に入りアイコンのリンクを返します。
	 * @og.rtnNotNull
	 */
	private String getFavoriteIcon() {
		final GUIInfo guiInfo		= ( GUIInfo )getSessionAttribute( HybsSystem.GUIINFO_KEY );
		final String mscVal		= getRequestValue( "MSC" );
		final StringBuilder buf	= new StringBuilder( BUFFER_MIDDLE );

		if( guiInfo.isRead() && mscVal.length() != 0 ) {
			final String gamenId		= guiInfo.getKey();
			final UserInfo userInfo	= getUser();
			final Map<String,FavoriteGUIData> favoriteMap = userInfo.getFavoriteMap();

			// 6.3.9.0 (2015/11/06) Use one line for each declaration, it enhances code readability.(PMD)
//			String fgEdit,image,updateLabel;
			final String fgEdit;
			final String image;
			final String updateLabel;
			if( favoriteMap.containsKey( gamenId ) ){
				fgEdit = "0";
				image  = "FAV_MENU_OUT.gif";
				updateLabel  = getLabel( "DELETE" );
			}
			else {
				fgEdit = "1";
				image  = "FAV_MENU_IN.gif";
				updateLabel  = getLabel( "INSERT" );
			}

			final String geContextName = HybsSystem.sys( "GE_CONTEXT_NAME" );
			if( geContextName.isEmpty() ) {
				final String errMsg = "システムパラメータ GE_CONTEXT_NAME が設定されていません。";
				throw new HybsSystemException( errMsg );
 			}

			buf.append( "<a href=\"/").append( geContextName )
				.append("/jsp/GE0014/update.jsp?fgEdit=" ).append( fgEdit )
				.append( "&command=NEW&SYSTEM_ID="	).append( userInfo.getParameter( "SYSTEM_ID" ) )
				.append( "&GUIKEY="			).append( gamenId )
				.append( "&CONTEXT_URL="	).append( userInfo.getParameter( "CONTEXT_URL" ) )
				.append( "\"><img src=\""	).append( userInfo.getParameter( "JSP" ) )
				.append( "/image/"			).append( image )
				.append( "\" title=\""		).append( StringUtil.htmlFilter( getLabel( "FAVORITE_MENU" ),true ) )	// 6.2.2.0 (2015/03/27)
				.append( ' '				).append( updateLabel )
				.append( "\" /></a>"		);
		}

		return buf.toString();
	}

	/**
	 * 【TAG】直接アクセスできる形式のリンクを作成するかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * trueは、指定の画面のフレームレベルでの指定になります。false は、トップフレームを
	 * 含む形なので、通常の登録画面と同じ形式になります。
	 * 初期値は、false(直接リンクしない)です。
	 *
	 * @param	flag 作成可否 [true:する/false:しない]
	 */
	public void setDirect( final String flag ) {
		direct = nval( getRequestParameter( flag ),direct );
	}

	/**
	 * 【TAG】リンク先の文書を表示させるフレーム、またはウィンドウの名前を指定します(初期値:_blank)。
	 *
	 * @og.tag リンク先のフレーム名(ターゲット属性)を設定します。
	 *
	 * @param	name リンク先の文書のフレーム名(ターゲット属性)
	 */
	public void setTarget( final String name ) {
		target = nval( getRequestParameter( name ),target );
	}

	/**
	 * 【TAG】リンクを作成する時の転送先アドレスを指定します(初期値:index.jsp)。
	 *
	 * @og.tag
	 * direct="false"(初期値)に使用されるリンクの転送先アドレスを指定します。
	 * 初期値は、index.jspです。
	 *
	 * @param	url 転送先アドレス
	 */
	public void setHref( final String url ) {
		href = nval( getRequestParameter( url ),href );
	}

	/**
	 * 【TAG】リンクの作成元となるメソッド[POST/GET/ALL]を指定します(初期値:GET)。
	 *
	 * @og.tag
	 * ここで指定したメソッドでリクエストされた場合のみ、リンクを作成します。
	 * 初期値は、GET です。(つまり GET のみリンクを作成します。)
	 * これは、POST では、引数が付かない為です。(実際は付ける事ができますが・・・)
	 * ALL は、どちらの場合でもリンクを作成しますが、先に述べたように POST では
	 * 引数がつきません。
	 * 初期値は、GETです。
	 *
	 * @param	flag リンクの作成元となるメソッド [POST/GET/ALL]
	 */
	public void setMethod( final String flag ) {
		method = nval( getRequestParameter( flag ),method );
	}

	/**
	 * 【TAG】リンクをキャッシュするかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * この、favoriteLink で指定された画面を、ユーザー毎にキャッシュします。
	 * キャッシュされた画面は、lastQuery を指定することで、取り出し(転送)
	 * することが出来ます。
	 * ここでのキャッシュは、direct="true" を指定した場合のアドレスです。
	 * direct="false" は、index.jsp からのフレーム形式の為、メール等で
	 * 送り、後ほど再開するような使い方(または、ワークフロー的な使い方)
	 * を想定していますが、direct="true" により単独フレームデータを、
	 * リアルタイムで使用するケース(EXCELのWebクエリーなど)で使用します。
	 * 初期値は、falseです。
	 *
	 * @og.rev 3.5.5.9 (2004/06/07) リンクキャッシュより最終画面を転送表示します。
	 *
	 * @param	flag リンクキャッシュ [true:する/false:しない]
	 */
	public void setLinkCache( final String flag ) {
		linkCache = nval( getRequestParameter( flag ),linkCache );
	}

	/**
	 * 【TAG】キャッシュされたリンク先に転送するかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * この、favoriteLink で指定された画面を、キャッシュします。
	 * キャッシュされた画面は、lastQuery を指定することで、取り出し(転送)
	 * することが出来ます。
	 * ここでのキャッシュは、direct="true" を指定した場合のアドレスです。
	 * direct="false" は、index.jsp からのフレーム形式の為、メール等で
	 * 送り、後ほど再開するような使い方(または、ワークフロー的な使い方)
	 * を想定していますが、direct="true" により単独フレームデータを、
	 * リアルタイムで使用するケース(EXCELのWebクエリーなど)で使用します。
	 * 初期値は、falseです。
	 *
	 * @og.rev 3.5.5.9 (2004/06/07) リンクキャッシュより最終画面を転送表示します。
	 *
	 * @param	flag リンク先転送 [true:する/false:しない]
	 */
	public void setLastQueryRedirect( final String flag ) {
		redirect = nval( getRequestParameter( flag ),redirect );
	}

	/**
	 * 【TAG】お気に入りアイコンリンクを作成するかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * 初期値は、falseです。
	 *
	 * @og.rev 4.1.1.0 (2008/02/13) 新規追加。
	 *
	 * @param	flag お気に入りアイコンリンク作成 [true:する/false:しない]
	 */
	public void setUseIcon( final String flag ) {
		useIcon = nval( getRequestParameter( flag ),useIcon );
	}

	/**
	 * リンクキャッシュをクリアします。
	 * この時、poolされているオブジェクトは、ResourceManager#clear() メソッドを
	 * 呼び出します。
	 *
	 * @og.rev 3.5.5.9 (2004/06/07) 新規作成
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 */
	public static void clear() {
//		synchronized( LAST_QUERY ) {
			LAST_QUERY.clear();
//		}
	}

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