/*
 * 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     = "データテーブル名"
 *         callMethod    = "saveReport"
 *     /&gt;
 *
 * ●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】アプリケーションの名前を指定します
 *       callMethod         【TAG】関数名を指定します
 *       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;
 *
 * ●使用例
 *     &lt;og:iorUpdate
 *         url           = "http://･･･ "
 *         authURL       = "http://･･･ "
 *         authUserPass  = "admin:******"
 *         appliName     = "データテーブル名"
 *         callMethod    = "saveReport"
 *   /&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 ;

	private boolean	selectedAll	;												// データの全件選択済
	private boolean	selectedOne	;												// データの1件選択済
	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;
		}
		return SKIP_BODY;														// Body を評価しない
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {

		debugPrint();
		if( !useTag() ) { return EVAL_PAGE ; }

		// ユーザー:パスワード から ユーザーとパスワードを取得します。
		checkUsrPw();

		// 読取ファイル指定無し
		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 ) {
			// 処理後のメッセージを作成します。
			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();
		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" …}
			// ---------------------------------------------------------------------
			setComJson();
			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( 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】データを全件選択済みとして処理するかどうか[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	)
				.fixForm().toString()
			+ CR
			+ super.toString() ;
	}
}
