/*
 * 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 java.util.Map;

import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.JSONScan;
import org.opengion.fukurou.util.ToString;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBTableModel;

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

/**
 * IOr (Information Organizer) に接続し、ﾃﾞｰﾀﾍﾞｰｽに追加/更新/削除を行うﾀｸﾞです。
 *
 * DBTableModel内のﾃﾞｰﾀを JSON形式 の文字列に出力し、IOr へﾃﾞｰﾀ登録を要求します。
 * DBTableModel内のﾃﾞｰﾀより loadFile が優先されます。
 *
 * 実行後にﾘｸｴｽﾄﾊﾟﾗﾒｰﾀに以下の値がｾｯﾄされます。
 *   DB.COUNT    : 実行結果の件数
 *   DB.ERR_CODE : 実行結果のｴﾗｰｺｰﾄﾞ
 *   DB.ERR_MSG  : 実行結果のｴﾗｰﾒｯｾｰｼﾞ
 *
 * ※ このﾀｸﾞは、Transaction ﾀｸﾞの対象です。
 *
 * @og.formSample
 * ●形式：
 *     &lt;og:iorUpdate
 *         url           = "http://･･･ "    必須
 *         authURL       = "http://･･･ "    必須
 *         authUserPass  = "admin:******"   必須
 *         appliName     = "ﾃﾞｰﾀﾃｰﾌﾞﾙ名"
 *         sqlType       = "UPDATE"
 *     &gt;
 *     &lt;/og:iorUpdate&gt;
 *
 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *
 * ●Tag定義：
 *   &lt;og:iorUpdate
 *       url              ○【TAG】ｱｸｾｽする URL を指定します (必須)
 *       proxyHost          【TAG】ﾌﾟﾛｷｼ経由で接続する場合の、ﾌﾟﾛｷｼﾎｽﾄ名を指定します
 *       proxyPort          【TAG】ﾌﾟﾛｷｼ経由で接続する場合の、ﾌﾟﾛｷｼﾎﾟｰﾄ番号を指定します
 *       timeout            【TAG】通信ﾘﾝｸのｵｰﾌﾟﾝ時に、指定された秒単位のﾀｲﾑ･ｱｳﾄ値を使用します
 *                                  (初期値:URL_CONNECT_TIMEOUT[={@og.value SystemData#URL_CONNECT_TIMEOUT}])
 *       authURL          ○【TAG】JSONｺｰﾄﾞで認証するURLを指定します (必須)
 *       authUserPass     ○【TAG】Basic認証を使用して接続する場合のﾕｰｻﾞｰ:ﾊﾟｽﾜｰﾄﾞを指定します (必須)
 *       companyId          【TAG】企業IDを指定します
 *       appliName        ○【TAG】ｱﾌﾟﾘｹｰｼｮﾝの名前を指定します
 *       sqlType          ○【TAG】SQLﾀｲﾌﾟを指定します(INSERT,COPY,UPDATE,MODIFY,DELETE)
 *       display            【TAG】接続の結果を表示するかどうかを指定します (初期値:false)
 *       loadFile           【TAG】ﾌｧｲﾙからURL接続結果に相当するﾃﾞｰﾀを読み取ります
 *       scope              【TAG】ｷｬｯｼｭする場合のｽｺｰﾌﾟ[request/page/session/application]を指定します (初期値:session)
 *       selectedAll        【TAG】ﾃﾞｰﾀを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
 *       selectedOne        【TAG】ﾃﾞｰﾀを1件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
 *       displayMsg         【TAG】実行結果を画面上に表示するﾒｯｾｰｼﾞﾘｿｰｽIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
 *       tableId            【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのｷｰを指定します
 *       stopError          【TAG】処理ｴﾗｰの時に処理を中止するかどうか[true/false]を設定します (初期値:true)
 *       dispError          【TAG】ｴﾗｰ時にﾒｯｾｰｼﾞを表示するか[true/false]を設定します。通常はstopErrorと併用 (初期値:true)
 *       quotCheck          【TAG】ﾘｸｴｽﾄ情報の ｼﾝｸﾞﾙｸｫｰﾄ(') 存在ﾁｪｯｸを実施するかどうか[true/false]を設定します
 *                                  (初期値:USE_SQL_INJECTION_CHECK)
 *       useTimeView        【TAG】処理時間を表示する TimeView を表示するかどうかを指定します
 *                                  (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])
 *       caseKey            【TAG】このﾀｸﾞ自体を利用するかどうかの条件ｷｰを指定します (初期値:null)
 *       caseVal            【TAG】このﾀｸﾞ自体を利用するかどうかの条件値を指定します (初期値:null)
 *       caseNN             【TAG】指定の値が、null/ｾﾞﾛ文字列 でない場合(Not Null=NN)は、このﾀｸﾞは使用されます (初期値:判定しない)
 *       caseNull           【TAG】指定の値が、null/ｾﾞﾛ文字列 の場合は、このﾀｸﾞは使用されます (初期値:判定しない)
 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このﾀｸﾞは使用されます (初期値:判定しない)
 *       debug              【TAG】ﾃﾞﾊﾞｯｸﾞ情報を出力するかどうか[true/false]を指定します (初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:iorUpdate&gt;
 *
 * ●使用例
 *     &lt;og:iorUpdate
 *         url           = "http://･･･ "
 *         authURL       = "http://･･･ "
 *         authUserPass  = "admin:******"
 *         appliName     = "ﾃﾞｰﾀﾃｰﾌﾞﾙ名"
 *         sqlType       = "MODIFY"
 *     &gt;
 *         &lt;og:iorParam
 *             key  = "update_set"  value  = "{'SUNYSY':'%(SUNYSY)s','TANTO':'%(TANTO)s'}"  /&gt;
 *         &lt;og:iorParam
 *             key  = "where_lk"    value  = "{'PN':'%(PN)s','TANI':'%(TANI)s'}"  /&gt;
 *     &lt;/og:iorUpdate&gt;
 *
 * @og.rev 8.0.2.0 (2021/11/30) 新規作成
 * @og.group その他部品
 *
 * @version  8.0
 * @author   LEE.M
 * @since    JDK17.0,
 */
public class IorUpdateTag extends IorQueryTag {
	/** このﾌﾟﾛｸﾞﾗﾑのVERSION文字列を設定します。 {@value} */
	private static final String VERSION = "8.0.2.0 (2021/11/30)" ;
	private static final long serialVersionUID = 802020211130L ;

	/** SQLﾀｲﾌﾟ */
	private String	sqlType	;
	/** ﾃﾞｰﾀの全件選択済 */
	private boolean	selectedAll	;
	/** ﾃﾞｰﾀの1件選択済 */
	private boolean	selectedOne	;
	/** ｸｵｰﾄﾁｪｯｸ */
	private boolean	quotCheck	;

	/**
	 * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ
	 *
	 */
	public IorUpdateTag() { super(); }	// これも、自動的に呼ばれるが、空のﾒｿｯﾄﾞを作成すると警告されるので、明示的にしておきます。

	/**
	 * Taglibの開始ﾀｸﾞが見つかったときに処理する doStartTag() を ｵｰﾊﾞｰﾗｲﾄﾞします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doStartTag() {
		if( useTag() ) {
			dyStart = System.currentTimeMillis();								// 現在時刻

			table = (DBTableModel)getObject( tableId );
			startQueryTransaction( tableId );

			if( table == null || table.getRowCount() == 0 ) { return SKIP_BODY ; }

			super.quotCheck = quotCheck;
		}

		// ﾕｰｻﾞｰ:ﾊﾟｽﾜｰﾄﾞ から ﾕｰｻﾞｰとﾊﾟｽﾜｰﾄﾞを取得します。
		checkUsrPw();

		// 読取ﾌｧｲﾙ指定無し
		if( loadFile == null ) {
			// JSON形式の共通要求ｷｰを設定します。
			setComJson();
		}
		return EVAL_BODY_BUFFERED;												// Body を評価する (extends BodyTagSupport 時)
	}

	/**
	 * Taglibの終了ﾀｸﾞが見つかったときに処理する doEndTag() を ｵｰﾊﾞｰﾗｲﾄﾞします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();
		if( !useTag() ) { return EVAL_PAGE; }

		// 読取ﾌｧｲﾙ指定無し
		if( loadFile == null ) {
			// ﾃｰﾌﾞﾙﾓﾃﾞﾙ から JSON形式 に変更します。
			postData = tbl2Json();
		}
		// 読取ﾌｧｲﾙ指定有り
		else {
			// ﾌｧｲﾙ からﾃﾞｰﾀを読取ります。
			postData = outJson();
		}

		// URLに対して応答結果を取得します。
		rtnData = retResponse();

		int errCode = ErrorMessage.OK;											// ｴﾗｰｺｰﾄﾞ
		// 応答結果の HTTPｽﾃｰﾀｽｺｰﾄﾞ を設定します。
		errCode = getStatus( rtnData );

		int rtnCode = EVAL_PAGE;
		// 正常／警告
		if( errCode < ErrorMessage.NG ) {
			// 応答結果の 実行件数 を取得します。
			executeCount = getCount( rtnData );
			if( executeCount > 0 ){
				// 処理後のﾒｯｾｰｼﾞを作成します。
				errCode = makeMessage();
			}
		}
		// 異常
		else {
			rtnCode = stopError ? SKIP_PAGE : EVAL_PAGE ;
		}

		// 処理時間(queryTime)などの情報出力の有効/無効を指定します。
		if( useTimeView ) {
			final long dyTime = System.currentTimeMillis() - dyStart;
			jspPrint( "<div id=\"queryTime\" value=\"" + (dyTime) + "\"></div>" );
		}
		return rtnCode;
	}

	/**
	 * ﾀｸﾞﾘﾌﾞｵﾌﾞｼﾞｪｸﾄをﾘﾘｰｽします。
	 * ｷｬｯｼｭされて再利用されるので、ﾌｨｰﾙﾄﾞの初期設定を行います。
	 */
	@Override
	protected void release2() {
		super.release2();
		sqlType		= null;														// SQLﾀｲﾌﾟ
		selectedAll	= false;													// ﾃﾞｰﾀの全件選択済
		selectedOne	= false;													// ﾃﾞｰﾀの1件選択済
		quotCheck	= false;													// ｸｵｰﾄﾁｪｯｸ
	}

	/**
	 * ﾃｰﾌﾞﾙﾓﾃﾞﾙ から JSON形式 に変更します。
	 *
	 * @return	JSON形式の文字列
	 */
	private String tbl2Json() {
		final int[] rowNo = getParameterRows();
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		if( rowNo.length > 0 ) {
			// ---------------------------------------------------------------------
			// 共通要求ｷｰを設定します。
			// 例：{"company_id":"XXXXX","user_id":"admin","session_id":"$session_id$","report_name":"YYYYY" …}
			// ---------------------------------------------------------------------
			// ﾒｿｯﾄﾞ
			if( !sqlType.isEmpty() ) { mapParam.put( "method", sqlType ); }
			buf.append( JSONScan.map2Json( mapParam ) );

			// ---------------------------------------------------------------------
			// 共通要求ｷｰ以外の ｷｰを設定します。
			// ---------------------------------------------------------------------
			// 共通要求ｷｰの末尾に ,"data":{ 文字列を追加します。
			// 例：{"company_id":"XXXXX", …}を {"company_id":"XXXXX", … ,"data":{ …} に
			buf.setLength(buf.length()-1);
			buf.append( ",\"data\":{\n\"headers\":[\n" );

			// ---------------------------------------------------------------------
			// ﾃｰﾌﾞﾙﾓﾃﾞﾙの列を追加します。
			// 例："headers": [{"display_label": "品目番号", "display": "PN"}, … ],"rows" …
			// ---------------------------------------------------------------------
			final DBColumn[] clms = table.getDBColumns();

			for( final DBColumn clm : clms ) {
				// ｷｰと値をﾏｯﾋﾟﾝｸﾞします。
				final Map<String,String> mapHeader = Map.of( IOR_DISP_LBL , clm.getLabel() , IOR_DISP_KEY ,clm.getName() );

				buf.append( JSONScan.map2Json( mapHeader ) )
					.append( ',' );
			}

			// ---------------------------------------------------------------------
			// ﾃｰﾌﾞﾙﾓﾃﾞﾙの行を追加します。
			// 例："rows": [{"cols": [1, "GEN", "20211130", 32.4, "kg"]}, … ]}}
			// ---------------------------------------------------------------------
			buf.setLength(buf.length()-1);
			buf.append( "],\n\"rows\":[\n" );

			int row;
			for( int i=0; i<rowNo.length; i++ ) {
				row = rowNo[i];

				buf.append( i == 0 ? "{\"cols\":[" : ",\n{\"cols\":[" );
				for( int j=0; j<clms.length; j++ ) {
					buf.append( '"' )
						.append(  nval(table.getValue( row, j ),"") )
						.append( "\"," );
				}
				buf.setLength(buf.length()-1);
				buf.append( "]}" );
			}
			buf.append( "\n]}}" );
		}
		return buf.toString();
	}

	/**
	 * 表示ﾃﾞｰﾀの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行番号の
	 * 配列を返します。
	 * なにも選ばれていない場合は、ｻｲｽﾞ０の配列を返します。
	 *
	 * @return	選ばれた 行番号 (選ばれていない場合は、ｻｲｽﾞ０の配列を返す)
	 * @og.rtnNotNull
	 */
	@Override
	protected int[] getParameterRows() {
		final int[] rowNo;
		if( selectedAll ) {
			final int rowCnt = table.getRowCount();
			rowNo = new int[ rowCnt ];
			for( int i=0; i<rowCnt; i++ ) {
				rowNo[i] = i;
			}
		}
		else if( selectedOne ) {
			rowNo = new int[] {0};
		}
		else {
			rowNo = super.getParameterRows();
		}
		return rowNo;
	}

	/**
	 * 【TAG】SQLﾀｲﾌﾟを指定します｡
	 *
	 * @og.tag
	 * SQLﾀｲﾌﾟは、INSERT,COPY,UPDATE,MODIFY,DELETE の中から指定する必要があります｡
	 * なお、COPY と MODIFY は、command で指定できる簡易機能として用意しています｡
	 *
	 * @param	type	SQLﾀｲﾌﾟ [INSERT/COPY/UPDATE/MODIFY/DELETE]
	 */
	public void setSqlType( final String type ) {
		sqlType = nval( getRequestParameter( type ),sqlType );
	}

	/**
	 * 【TAG】ﾃﾞｰﾀを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * 全てのﾃﾞｰﾀを選択済みﾃﾞｰﾀとして扱って処理します。
	 * 全件処理する場合に、(true/false)を指定します。
	 * 初期値は false です。
	 *
	 * changeOnly よりも selectedAll="true" が優先されます。
	 *
	 * @param	all	ﾃﾞｰﾀを全件選択済み [true:全件選択済み/false:通常]
	 */
	public void setSelectedAll( final String all ) {
		selectedAll = nval( getRequestParameter( all ),selectedAll );
	}

	/**
	 * 【TAG】ﾃﾞｰﾀを1件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * 先頭行の1件だけを選択済みとして処理します。
	 * まとめ処理のﾃﾞｰﾀを処理する場合などに使われます。
	 * 初期値は false です。
	 *
	 * @param	one	先頭行の1件だけを選択済みとして処理するかどうか [true:処理する/false:通常]
	 */
	public void setSelectedOne( final String one ) {
		selectedOne = nval( getRequestParameter( one ),selectedOne );
	}

	/**
	 * このｵﾌﾞｼﾞｪｸﾄの文字列表現を返します。
	 * 基本的にﾃﾞﾊﾞｯｸﾞ目的に使用します。
	 *
	 * @return このｸﾗｽの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title( this.getClass().getName() )
				.println( "VERSION"		,VERSION	)
				.println( "sqlType"		,sqlType	)
				.fixForm().toString()
			+ CR
			+ super.toString() ;
	}
}
