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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.fukurou.security.HybsCryptography;				// 5.8.8.0 (2015/06/05)
import org.opengion.fukurou.security.URLHashMap;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.Attributes;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.fukurou.model.Formatter;
import static org.opengion.fukurou.util.HybsConst.BUFFER_LARGE;		// 6.1.0.0 (2014/12/26) refactoring

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays ;

/**
 * ViewLink インターフェース の実装オブジェクトです。
 * これを,共通のスーパークラスとして 各種表示フォーム(例：HTML表示等)に使います。
 *
 * このクラスは、setter/getterメソッドのデフォルト実装を提供しています。
 * 各種表示フォームに対応したサブクラス上で, create() をオーバーライドして下さい。
 *
 * @og.rev 2.1.0.3 (2002/11/08) エンコードの開始/終了アドレスを求める処理の修正
 * @og.rev 4.0.0.0 (2005/08/31) 同一カラムの複数登録を許可します。
 * @og.group 画面表示
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class ViewLink_LINK implements ViewMarker {
	private static final String REQ_KEY = HybsSystem.URL_HASH_REQ_KEY ;

	private static final int	ACCS_LVL		= HybsSystem.sysInt( "URL_ACCESS_SECURITY_LEVEL" );
	private static final int	MARK_NULL		= -1;	// リンク未設定
	private static final int	MARK_TRUE		= 1;	// リンク作成
	private static final int	MARK_FALSE		= 0;	// リンク作成せず

	private List<Attributes>		markData	;		// 4.0.0 (2005/08/31)
	private Map<Integer,Formatter>	formMap		= new HashMap<Integer,Formatter>();
	private DBTableModel		table			;
	private int[]				markCmlNo		;
	private int[]				isMark			;
	// 2.1.0.3 (2002/11/08) エンコードの開始/終了アドレスを求める処理の修正
	// 6.2.0.1 (2015/03/06) hrefアドレスのASCII以外の文字の、URLエンコードを行う。
	private int[]				hrefIn			;		// 初期値:範囲外 6.2.0.1 (2015/03/06)
	private int[]				encodeIn		;		// 初期値:範囲外
	private int[]				encodeOut		;		// 初期値:範囲外
	// 3.5.2.0 (2003/10/20)
	private String[]			markKey			;
	private String[]			markLists		;
	private int[]				markListNo		;

	private boolean[]			useURLCheck	 	;		// 4.3.7.1 (2009/06/08)
	private String[]			urlCheckUser 	;		// 4.3.7.1 (2009/06/08)
	private long[]				urlCheckTime	;		// 4.3.7.1 (2009/06/08)
	private HybsCryptography[]	urlCheckCrypt	;		// 5.8.8.0 (2015/06/05)
	private boolean[]			useHrefEncode	;		// 6.0.2.0 (2014/09/19)

	private String[]			extToken		;		// 5.8.2.1 (2014/12/13)

	private Map<Integer,List<Integer>>	clmMap	= new HashMap<Integer,List<Integer>>();	// 4.0.0 (2005/08/31)

	/**
	 * 内容をクリア(初期化)します。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
	 * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
	 * @og.rev 3.5.6.1 (2004/06/25) formMap属性を追加
	 * @og.rev 4.3.7.1 (2009/06/08) URLチェック属性追加
	 * @og.rev 6.0.2.0 (2014/09/19) useHrefEncode属性追加
	 * @og.rev 5.8.2.1 (2014/12/13) extToken追加
	 * @og.rev 6.2.0.1 (2015/03/06) hrefアドレスのASCII以外の文字の、URLエンコードを行う。
	 * @og.rev 5.8.8.0 (2015/06/05) urlCheckCrypt追加
	 */
	public void clear() {
		markData	= null;			// 4.0.0 (2005/08/31)
		formMap		= new HashMap<Integer,Formatter>();
		table		= null;
		isMark		= null;
		hrefIn		= null;			// 6.2.0.1 (2015/03/06)
		encodeIn	= null;
		encodeOut	= null;
		markKey		= null;
		markLists	= null;
		markListNo	= null;
		clmMap		= new HashMap<Integer,List<Integer>>();	// 4.0.0 (2005/08/31)
		useURLCheck		= null;		// 4.3.7.1 (2009/06/08)
		urlCheckUser	= null;		// 4.3.7.1 (2009/06/08)
		urlCheckTime	= null;		// 4.3.7.1 (2009/06/08)
		urlCheckCrypt	= null;		// 5.8.8.0 (2015/06/05)
		useHrefEncode	= null;		// 6.0.2.0 (2014/09/19)
		extToken	 	= null;		// 5.8.2.1 (2014/12/14)
	}

	/**
	 * カラムに対するリンクアトリビュートをセットします。
	 *
	 * @og.rev 3.1.0.0 (2003/03/20) Hashtable を使用している箇所で、非同期でも構わない箇所を、HashMap に置換え。
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
	 * @og.rev 4.0.0.0 (2005/08/31) 同一カラムの複数登録を許可します。
	 *
	 * @param	attri	リンクアトリビュート
	 */
	public void addAttribute( final Attributes attri ) {
		if( markData == null ) { markData = new ArrayList<Attributes>(); }
		markData.add( attri );
	}

	/**
	 * 内部に DBTableModel をセットします。
	 *
	 * @og.rev 2.1.0.3 (2002/11/08) エンコードの開始/終了アドレスを求める処理の修正
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
	 * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
	 * @og.rev 3.5.5.0 (2004/03/12) xlink 属性によるリンク情報作成方法の分岐を追加
	 * @og.rev 3.5.6.1 (2004/06/25) DBTableModel の再設定に対応。
	 * @og.rev 3.5.6.2 (2004/07/05) linkFormat をパラメータで取得するように変更。
	 * @og.rev 3.8.1.1 (2005/11/21) linkFormat が "[","]" をエンコードしてしまった場合に元に戻します。
	 * @og.rev 4.0.0.0 (2005/08/31) 同一カラムの複数登録を許可します。
	 * @og.rev 4.3.7.1 (2009/06/08) URLチェック機能追加
	 * @og.rev 6.0.2.0 (2014/09/19) useHrefEncode属性追加
	 * @og.rev 6.2.0.1 (2015/03/06) hrefアドレスのASCII以外の文字の、URLエンコードを行う。
	 * @og.rev 5.8.8.0 (2015/06/05) urlCheckCrypt対応
	 *
	 * @param  tbl DBTableModelオブジェクト
	 */
	public void setDBTableModel( final DBTableModel tbl ) {
		table = tbl;
		final int count = markData.size();		// 4.0.0 (2005/08/31)

		isMark		= new int[count];
		markKey		= new String[count];
		markCmlNo	= new int[count];
		markLists	= new String[count];
		markListNo	= new int[count];
		hrefIn		= new int[count];					// 6.2.0.1 (2015/03/06)
		encodeIn	= new int[count];
		encodeOut	= new int[count];
		useURLCheck	= new boolean[count];				// 4.3.7.1 (2009/06/08)
		urlCheckUser	= new String[count];			// 4.3.7.1 (2009/06/08)
		urlCheckTime	= new long[count];				// 4.3.7.1 (2009/06/08)
		urlCheckCrypt	= new HybsCryptography[count];	// 5.8.8.0 (2015/06/05)
		useHrefEncode	= new boolean[count];			// 6.0.2.0 (2014/09/19)
		extToken		= new String[count];			// 5.8.2.1 (2014/12/13)

		Arrays.fill( isMark,MARK_FALSE );		// リンクの表示可否
		Arrays.fill( markCmlNo,-1 );			// リンクの可否を判断するカラム番号
		Arrays.fill( hrefIn   ,10000 );			// 初期値:範囲外 6.2.0.1 (2015/03/06)
		Arrays.fill( encodeIn ,10000 );			// 初期値:範囲外
		Arrays.fill( encodeOut,-1 );			// 初期値:範囲外
		Arrays.fill( useURLCheck, false );		// 4.3.7.1 (2009/06/08)
		Arrays.fill( urlCheckTime, 0L );		// 4.3.7.1 (2009/06/08)
		Arrays.fill( useHrefEncode, false );	// 6.0.2.0 (2014/09/19) 決め打ちに近いがとりあえず初期化は false

	 	// 4.0.0 (2005/08/31) 同一カラムの複数登録を許可します。
		for( int intKey=0; intKey<count; intKey++ ) {
			final Attributes attri = markData.get( intKey );

			final String column = attri.get( "column" );
			final int clm = table.getColumnNo( column );
			List<Integer> list = clmMap.get( clm );
			if( list == null ) { list = new ArrayList<Integer>(); }
			list.add( intKey );
			clmMap.put( clm,list );

			String linkFormat = attri.get( "linkFormat" );
			linkFormat = StringUtil.replace( linkFormat,"%5B","[" );	// 3.8.1.1 (2005/11/21)
			linkFormat = StringUtil.replace( linkFormat,"%5D","]" );	// 3.8.1.1 (2005/11/21)

			final Formatter formatter = new Formatter( table );
			formatter.setFormat( linkFormat );
			formMap.put( intKey, formatter );

			// URLエンコード用の範囲設定。この範囲内のデータをURLエンコードする。
			final String[] format = formatter.getFormat();
			boolean findHref = false;
			for( int j=0; j<format.length; j++ ) {
				// 6.2.0.1 (2015/03/06) hrefアドレスのASCII以外の文字の、URLエンコードを行う。
				if( format[j] != null && format[j].indexOf( "href" ) >= 0 ) { findHref = true; hrefIn[intKey] = j; }
				if( findHref && format[j].indexOf( '?'   ) >= 0 ) { encodeIn[intKey]  = j; }
				if( findHref && format[j].indexOf( "\" " ) >= 0 ) { encodeOut[intKey] = j; findHref = false; }
			}

			// 4.3.7.1 (2009/06/08)
			useURLCheck[intKey]  = StringUtil.nval( attri.get( "useURLCheck"  ), false );
			urlCheckUser[intKey] = StringUtil.nval( attri.get( "urlCheckUser" ), null );
			urlCheckTime[intKey] = StringUtil.nval( attri.get( "urlCheckTime" ), 0L   );
			final String cryptKey= StringUtil.nval( attri.get( "urlCheckCrypt" ), null );	// 5.8.8.0 (2015/06/05)
			urlCheckCrypt[intKey]= new HybsCryptography( cryptKey );						// 5.8.8.0 (2015/06/05)
			useHrefEncode[intKey]= StringUtil.nval( attri.get( "useHrefEncode"), false );	// 6.0.2.0 (2014/09/19)
			extToken[intKey]     = StringUtil.nval( attri.get( "extToken" )    , null  );	// 5.8.2.1 (2014/12/14)
			makeOnLinkFormat( intKey,attri );
		}
	}

	/**
	 * 指定の行列に対するマーカー文字列を返します。
	 * この値は,すでにマーカー文字列処理されている為, RendererValue で
	 * 変換する必要はありません。
	 * 引数の value はそのカラムの値として利用されます。この値は、修飾済みの
	 * 値を与えることが可能です。
	 *
	 * @og.rev 2.1.0.3 (2002/11/08) エンコードの開始/終了アドレスを求める処理の修正
	 * @og.rev 3.0.0.0 (2002/12/25) URLEncoder.encode を StringUtil#urlEncode に置換え
	 * @og.rev 3.0.0.1 (2003/02/14) リンクの引数部分に、RendererValue が適用される箇所を修正
	 * @og.rev 3.0.0.1 (2003/02/14) リンクの引数部分に、RendererValue が適用される箇所を修正
	 * @og.rev 3.5.6.1 (2004/06/25) formMap属性を使用します。
	 * @og.rev 3.7.0.3 (2005/03/01) "{I}" 文字列に、行番号(row)を割り当てます。
	 * @og.rev 3.8.5.0 (2006/03/20) "{I}" ⇒ "%7BI%7D" として、行番号(row)を割り当てます。
	 * @og.rev 4.3.7.1 (2009/06/08) URLチェック機能追加
	 * @og.rev 4.3.7.4 (2009/07/01) 循環参照を解消
	 * @og.rev 5.2.3.0 (2010/12/01) URLのハッシュ化/暗号化を行います。
	 * @og.rev 6.0.2.0 (2014/09/19) useHrefEncode属性追加
	 * @og.rev 5.8.2.1 (2014/12/13) トークンプラグイン対応
	 * @og.rev 6.2.0.1 (2015/03/06) ASCII以外の文字の、URLエンコードを行う。
	 * @og.rev 6.2.4.0 (2015/05/15) エンコード範囲内の value は、値を使う。(元に戻す)
	 * @og.rev 5.8.8.0 (2015/06/05) urlCheckCrypt対応
	 *
	 * @param   row 指定の行
	 * @param   clm 指定の列
	 * @param   value カラムの値
	 *
	 * @return  row行，colum列 のマーカー文字列
	 */
	public String getMarkerString( final int row,final int clm,final String value ) {
		final int intKey = isOnLink(row,clm) ;
		if( intKey < 0 ) { return value; }

		final Formatter formatter = formMap.get( intKey );
		final int[]    clmNo  = formatter.getClmNos();
		final String[] format = formatter.getFormat();

		final StringBuilder strLink = new StringBuilder( BUFFER_LARGE );
		int j=0;
		for( ; j<clmNo.length; j++ ) {

			strLink.append( format[j] );

			// 6.2.4.0 (2015/05/15) エンコード範囲内の value は、値を使う。(元に戻す)
			String val = formatter.getValue(row,clmNo[j]);

			// '?' が無ければ、encodeIn は有効にならない。encodeOut は、href終了で必ず設定される。
			if( hrefIn[intKey] <= j && j < encodeOut[intKey] ) {			// (href) encode範囲内
				if( useHrefEncode[intKey] && val != null ) {				// 6.0.2.0 (2014/09/19) useHrefEncode属性追加
					val = StringUtil.urlEncode2( val );		// ファイル名などは、部分エンコード
				}
			}
			else if( encodeIn[intKey] <= j && j < encodeOut[intKey] ) {		// (パラメータ) encode範囲内
				val = StringUtil.urlEncode( val );			// パラメータはフルエンコード
			}
			else {															// encode範囲外
				// なにもしない。
				if( clm == clmNo[j] ) { val = value; }		// 一致する場合の valueは、通常レンデラー
			}
			strLink.append( val );
		}
		strLink.append( format[j] );

		// 3.8.5.0 (2006/03/27) "{I}" と そのエンコード文字 "%7BI%7D" に、行番号(row)を割り当てます。
		String rtn = strLink.toString();
		final String sRow = String.valueOf( row );
		rtn = StringUtil.replace( rtn,"{I}",sRow );
		rtn = StringUtil.replace( rtn,"%7BI%7D",sRow );

		// 4.3.7.1 (2009/06/08)
		if( useURLCheck[intKey] ) {
			// 4.3.7.4 (2009/07/01)
			rtn = XHTMLTag.embedURLCheckKey( rtn, HybsSystem.URL_CHECK_KEY, urlCheckUser[intKey], urlCheckTime[intKey], urlCheckCrypt[intKey] ); // 5.8.8.0 (2015/06/05)
		}

		// 5.8.2.1 (2014/12/13) トークンプラグイン対応
		if( extToken[intKey] != null && extToken[intKey].length() > 0 ){
			final String[] tokens = StringUtil.csv2Array( extToken[intKey] );
			for( final String tk :tokens ){
				final String cls = HybsSystem.sys( "CreateToken_" + tk ) ;	
				final CreateToken ct = (CreateToken)HybsSystem.newInstance( cls );
				rtn = ct.embedToken( rtn, urlCheckTime[intKey], null );
			}
		}

		// 5.2.3.0 (2010/12/01) URLのハッシュ化/暗号化
		if( ACCS_LVL == 2 ) {
			// ACCS_LVL == 2 の場合は、外部のみ処理するので、extOnly=true をセットする。
			rtn = URLHashMap.makeUrlChange( rtn,REQ_KEY,true );
		}
		else if( ACCS_LVL == 3 ) {
			rtn = URLHashMap.makeUrlChange( rtn,REQ_KEY,false );
		}

		return rtn ;
	}

	/**
	 * リンクを張る/張らないの指定カラム番号を求めます。
	 * また、int[列番号] isMark を初期化します。
	 *
	 * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
	 *
	 * @param	intKey	カラムキーの番号
	 * @param	attri	アトリビュート
	 */
	private void makeOnLinkFormat( final int intKey,final Attributes attri ) {
		final String onMark   = attri.get( "onLink" );
		final String markList = attri.get( "markList" );

		// 3.5.6.0 (2004/06/18) nullポインタの参照外しバグの対応
		// このロジックで値が設定済みであれば、以下の処理は不要である。
		isMark[intKey] = MARK_NULL;
		if( onMark == null || onMark.isEmpty() ||
			markList == null || markList.isEmpty() ) {
				isMark[intKey] = MARK_FALSE;
				return ;	// 3.5.6.0 (2004/06/18)
		}
		else if( onMark.charAt(0) != '[' && markList.charAt(0) != '[' ) {
			isMark[intKey] = ( markList.indexOf( onMark ) >= 0 ) ? MARK_TRUE : MARK_FALSE;
			return ;	// 3.5.6.0 (2004/06/18)
		}

		if( onMark.charAt(0) == '[' ) {
			markCmlNo[intKey] = table.getColumnNo( onMark.substring( 1,onMark.length()-1 ));
		}
		else {
			markCmlNo[intKey]  = -1;
			markKey[intKey]    = onMark ;
		}

		if( markList.charAt(0) == '[' ) {
			markListNo[intKey] = table.getColumnNo( markList.substring( 1,markList.length()-1 ));
		}
		else {
			markListNo[intKey] = -1;
			markLists[intKey] = markList;
		}
	}

	/**
	 * リンクを張るかどうかを判断します。
	 * int[列番号] isMark には、 未設定 FALSE TRUE の状態を持っており、
	 * 列でリンクを張る状態が固定の場合(例えば,onLink属性がデフォルト "true" の場合)
	 * カラムに関係なく、同じ値を返すときに、使用します。
	 *
	 * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
	 * @og.rev 3.5.4.0 (2003/11/25) onMark ,markList が null(またはゼロストリング)の場合は、false とする。
	 * @og.rev 4.0.0.0 (2005/08/31) 同一カラムの複数登録を許可します。
	 *
	 * @param	row	列番号
	 * @param	clm	カラムキーの名称
	 *
	 * @return	処理するリスト番号、-1 の場合は、該当なし
	 */
	private int isOnLink( final int row,final int clm ) {

		final List<Integer> list = clmMap.get( clm );
		if( list == null ) { return -1; }

		for( int i=0; i<list.size(); i++ ) {
			final int intKey = list.get( i );
			if( isMark[intKey] != MARK_NULL ) {
				if( isMark[intKey] == MARK_TRUE ) { return intKey; }
				else { continue; }
			}

			String onMark ;
			if( markCmlNo[intKey] < 0 ) { onMark = markKey[intKey] ; }
			else { onMark = table.getValue( row,markCmlNo[intKey] ); }

			// 3.5.4.0 (2003/11/25) 追加
			if( onMark == null || onMark.isEmpty() ) { continue; }

			String markList ;
			if( markListNo[intKey] < 0 ) { markList = markLists[intKey] ; }
			else { markList = table.getValue( row,markListNo[intKey] ); }

			// 3.5.4.0 (2003/11/25) 修正
			if( markList == null || markList.isEmpty() ) { continue; }

			if( markList.indexOf( onMark ) >= 0 ) { return intKey; }
		}
		return -1;
	}

	/**
	 * マーカーされたカラム番号の配列を返します。
	 *
	 * これは特殊処理で、Edit機能で、カラム列をキャッシュしているときに、
	 * JSPのソース等の変更時に、変更が反映されない対応を行う場合、
	 * 通常の ViewFormのサブクラスから、Edit専用の ViewForm_HTMLSeqClmTable で
	 * 制御する場合、ViewMarkerのEditMarkerでは、通常非表示（検索の場合）ですが
	 * Editのポップアップ画面に、表示されてしまうのを避けるため、noDisplay に
	 * 強制的にするカラム番号が必要です。
	 * あくまで、暫定処置です。Edit機能を改修するときに、この機能は削除します。
	 *
	 * ※ この処理は、EditMarkerでのみ有効にします。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) Edit機能で、JSPソース変更時の対応
	 *
	 * @return  マーカーされたカラム番号の配列(常に、長さ０の配列を返す)
	 */
	public int[] getColumnNos() {
		return new int[0];
	}
}
