/*
 * 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.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Stream;
import java.util.stream.Collectors;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.util.JSONScan;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.ToString;

/**
 * Ior のﾘｸｴｽﾄJSON に対して､Select文を生成するｸﾗｽです｡
 *
 * 通常は､POSTﾃﾞｰﾀの JSONか､loadFileで指定したﾌｧｲﾙ形式の JSONを読み込んで､
 * SQL文を作成するため､この結果を､queryﾀｸﾞ等で実行して､DBTableModel を生成します｡
 *
 * @og.formSample
 * ●形式：&lt;og:iorSQL /&gt;
 * ●body：なし
 *
 * POSTﾃﾞｰﾀ､または､loadFile に記載する JSON は､下記の形式です｡
 * {
 *   "report_name": "≪ﾃｰﾌﾞﾙ名≫",
 *   "select_cols": "PN,sum(SUNYSY) AS AAA,max(sys_index),COUNT(*) as cnt,TANI as TANIIII",
 *   "where_lk": "{'TANI':'%%g'}",
 *   "where_gt": "{'DYNYSY':'20210101','SUNYSY':'1'}",
 *   "where_lt": "{'SUNYSY':2000}",
 *   "group_by":"PN,TANI",
 *   "order_by":"TANIIII"
 * }
 *
 * ●Tag定義：
 *   &lt;og:iorSQL
 *       reqKey             【TAG】JSONﾃﾞｰﾀを取り出すPOSTﾘｸｴｽﾄのｷｰ
 *       loadFile           【TAG】ﾌｧｲﾙからJSONﾃﾞｰﾀを読み取ります(指定された場合､ﾌｧｲﾙ優先)
 *       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;
 *
 * @og.group 画面部品
 * @og.rev 8.1.1.0 (2022/02/04) 新規追加
 *
 * @version  8.1
 * @author	 Kazuhiko Hasegawa
 * @since    JDK17,
 */
public class IorSQLTag extends CommonTagSupport {
	/** このﾌﾟﾛｸﾞﾗﾑのVERSION文字列を設定します｡	{@value} */
	private static final String VERSION = "8.1.1.0 (2022/02/04)" ;
	private static final long serialVersionUID = 811020220204L ;

	private String	fileURL = HybsSystem.sys( "FILE_URL" );		// ﾌｧｲﾙURL

	private String	jsonVal	;
	private String	reqKey;			// POSTﾘｸｴｽﾄから読み込む場合のｷｰ
	private String	loadFile;		// ﾌｧｲﾙから読み込む場合のﾌｧｲﾙ名

	/**
	 * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public IorSQLTag() { super(); }		// これも､自動的に呼ばれるが､空のﾒｿｯﾄﾞを作成すると警告されるので､明示的にしておきます｡

	/**
	 * Taglibの開始ﾀｸﾞが見つかったときに処理する doStartTag() を ｵｰﾊﾞｰﾗｲﾄﾞします｡
	 *
	 * @return	後続処理の指示( EVAL_BODY_BUFFERED )
	 */
	@Override
	public int doStartTag() {
		if( useTag() ) {
			useXssCheck(  false );
			useQuotCheck( false );
		}

		return SKIP_BODY ;		// Body を評価しない
	}

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

		// 優先順位は、loadFile があればloadFileから。
		if( loadFile != null ) {
			// Files.lines には､try-with-resources文 が必要って…こんな感じ？
			try( Stream<String> srm = Files.lines( Paths.get(loadFile) , StandardCharsets.UTF_8 ) ) {
				jsonVal = srm.collect( Collectors.joining() );
			}
			catch( final IOException ex ) {
				final String errMsg = "loadFile 処理中でｴﾗｰが発生しました｡"	+ CR
							+ "\t " + ex.getMessage()							+ CR ;
				throw new HybsSystemException( errMsg, ex );
			}
		}
		else if( reqKey != null ) {
			jsonVal = getRequest().getParameter( reqKey );
		}
		else {
			final String errMsg = "reqKey か、loadFile は指定してください。" ;
			throw new HybsSystemException( errMsg );
		}

		printSQL() ;			// 画面上に生成したSQL文を出力します。

		return EVAL_PAGE ;
	}

	/**
	 * ﾀｸﾞﾘﾌﾞｵﾌﾞｼﾞｪｸﾄをﾘﾘｰｽします｡
	 * ｷｬｯｼｭされて再利用されるので､ﾌｨｰﾙﾄﾞの初期設定を行います｡
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		jsonVal		= null;
		reqKey		= null;		// POSTﾘｸｴｽﾄから読み込む場合のｷｰ
		loadFile	= null;		// ﾌｧｲﾙから読み込む場合のﾌｧｲﾙ名
	}

	/**
	 * ﾀｸﾞﾘﾌﾞｵﾌﾞｼﾞｪｸﾄをﾘﾘｰｽします｡
	 * ｷｬｯｼｭされて再利用されるので､ﾌｨｰﾙﾄﾞの初期設定を行います｡
	 *
	 * @param	jsonVal	読み込むｷｰ
	 */
	private void printSQL() {
		final Map<String,String> jsonMap = JSONScan.json2Map( jsonVal );

		if( isDebug() ) {
			// SQL文を画面に出す関係で、ｺﾏﾝﾄﾞﾌﾟﾛﾝｳﾄに出すことにする。
			jsonMap.forEach( (k,v) -> System.out.println( k + ":" + v ) );
		}

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		final String cols = jsonMap.getOrDefault( "select_cols" , "*" );
		buf.append( "select " ).append( cols ).append( " from " );
		final String tbl = jsonMap.get( "report_name" );
		if( StringUtil.isNull(tbl) ) {
			final String errMsg = "IOr 検索用 JSON では、report_name(=ﾃｰﾌﾞﾙ名)は必須です。" ;
			throw new HybsSystemException( errMsg );
		}
		buf.append( tbl );

		boolean useWhere = true ;		// 最初に where ｷｰを出すため
		useWhere = whereStr( buf , jsonMap.get( "where_lk" ) , " like " , useWhere ) ;	// LIKE 検索
		useWhere = whereStr( buf , jsonMap.get( "where_gt" ) , " > "    , useWhere ) ;	// GT(>)検索
		useWhere = whereStr( buf , jsonMap.get( "where_lt" ) , " < "    , useWhere ) ;	// LT(<)検索

		final String gby = jsonMap.get( "group_by" );			// GROUP BY
		if( StringUtil.isNotNull(gby) ) {
			buf.append( " group by " ).append( gby );
		}

		final String oby = jsonMap.get( "order_by" );			// ORDER BY
		if( StringUtil.isNotNull(oby) ) {
			buf.append( " order by " ).append( oby );
		}

		jspPrint( buf.toString() );
	}

	/**
	 * 指定の文字列の前後のｼﾝｸﾞﾙｸｵｰﾄを削除します。
	 *
	 * @param	buf			書き込む文字列ﾊﾞｯﾌｧ
	 * @param	wkey		WHERE条件の指定ｷｰ
	 * @param	operator	WHERE条件式(前後にｽﾍﾟｰｽを付けておいてください)
	 * @param	useWhere	文字列連結時に、whereを使用済みかどうか
	 * @return	where の書き込みがあれば、false を、なければ、引数のuseWhere を返す。
	 */
	private boolean whereStr( final StringBuilder buf , final String wkey , final String operator , final boolean useWhere ) {
		boolean rtnWhere = useWhere ;
		if( StringUtil.isNotNull(wkey) ) {
			final String[] ary = JSONScan.json2Array( wkey );	// ｶﾝﾏで分割,JSONScan.json2Trim 済
			for( final String str : ary ) {
				final String[] kv = str.split(":", 2);
				if( kv.length == 2 ) {
					if( rtnWhere ) { buf.append( " where " ); rtnWhere=false; }
					else           { buf.append( " and " ); }
					buf.append( quotTrim(kv[0]) ).append( operator ).append( kv[1] );
				}
			}
		}
		return rtnWhere ;
	}

	/**
	 * 指定の文字列の前後のｼﾝｸﾞﾙｸｵｰﾄを削除します。
	 *
	 * @param	str	読み込む文字列
	 * @return	key	前後のｼﾝｸﾞﾙｸｵｰﾄを削除した文字列
	 */
	private String quotTrim( final String str ) {
		if( str.length() >= 2 && str.charAt(0) == '\'' && str.charAt(str.length()-1) == '\'' ) {
			return str.substring(1,str.length()-1);
		}
		else {
			return str ;
		}
	}

	/**
	 * 【TAG】POSTﾘｸｴｽﾄから読み込む場合のｷｰを指定します｡
	 *
	 * @og.tag
	 *  POSTﾘｸｴｽﾄから読み込む場合のｷｰを指定
	 *
	 * @param	key	読み込むｷｰ
	 */
	public void setReqKey( final String key ) {
		reqKey = StringUtil.nval( getRequestParameter( key ),reqKey );
	}

	/**
	 * 【TAG】ﾌｧｲﾙからURL接続結果に相当するﾃﾞｰﾀを読み取ります｡
	 *
	 * @og.tag
	 * 主にﾃﾞﾊﾞｯｸﾞ用として使われます｡
	 *
	 * @param	file	検索するﾌｧｲﾙ
	 */
	public void setLoadFile( final String file ) {
		loadFile = StringUtil.nval( getRequestParameter( file ),loadFile );
		if( loadFile != null ) {
			loadFile = HybsSystem.url2dir( StringUtil.urlAppend( fileURL,loadFile ) );
		}
	}

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