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

import java.io.UnsupportedEncodingException;
import java.util.Calendar ;							// 7.0.5.0 (2019/09/16)

import org.opengion.fukurou.util.StringUtil;		// 6.9.8.0 (2018/05/28)
import org.opengion.fukurou.util.HybsDateUtil;		// 7.0.5.0 (2019/09/16)

import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;		// 7.2.9.5 (2020/11/28)

/**
 * JavaDB(derby) や､hsqldb に対する､Javaの拡張組込み関数です｡
 *
 * staticﾒｿｯﾄﾞとして､関数を定義します｡引数や返り値は､各ﾃﾞｰﾀﾍﾞｰｽの
 * 定義に準拠します｡
 *
 * <pre>
 * ① JavaDB の場合
 * 【概要】
 *     実行するﾃﾞｰﾀﾍﾞｰｽから見えるところに､ﾌｧｲﾙを配置する必要があります｡
 *     java8 までなら､Javaのｴｸｽﾃﾝｼｮﾝ(JAVA_HOME\)jre\lib\ext などですが､
 *     java9以降は､CLASSPATH に設定します｡
 *     openGionでは､bin/const.bat で､OG_CLASSPATH 環境変数にﾊﾟｽを通して､
 *     使用しています｡
 *     標準の Java staticﾒｿｯﾄﾞを FUNCTION 定義することも出来ます｡
 * 【設定】
 *     JavaDBに FUNCTION を定義します｡(ｻﾝﾌﾟﾙ)
 *      DROP FUNCTION TO_CHAR;
 *
 *      CREATE FUNCTION TO_CHAR ( VAL DOUBLE )
 *      RETURNS VARCHAR(20)
 *      DETERMINISTIC           -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
 *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
 *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.toChar' ;
 *
 * ② HSQLDB の場合
 * 【概要】
 *
 * </pre>
 *
 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
 * @og.group 拡張組込み関数
 *
 * @version  1.1.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public final class Functions {
    private static final String ENCODE = "UTF-8";

	/**
	 *	ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰをprivateにして､
	 *	ｵﾌﾞｼﾞｪｸﾄの生成をさせないようにする｡
	 *
	 * @og.rev 6.9.7.0 (2018/05/14) 新規作成
	 */
	private Functions() {}

	/**
	 * 数値を文字列に変換します｡
	 *
	 * この関数は､引数の double が､小数点を含まない場合は､
	 * 小数点以下を出しません｡
	 * JavaDBの場合､数字と文字列の連結が出来ないため､文字列変換関数を用意します｡
	 *
	 *      DROP FUNCTION TO_CHAR;
	 *
	 *      CREATE FUNCTION TO_CHAR ( VAL DOUBLE )
	 *      RETURNS VARCHAR(20)
	 *      DETERMINISTIC           -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
	 *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
	 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.toChar' ;
	 *
	 * @og.rev 6.7.3.0 (2017/01/27) 新規作成
	 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
	 * @og.rev 6.9.8.0 (2018/05/28) FindBugs:浮動小数点の等価性のためのﾃｽﾄ
	 *
	 * @param	val	文字列に変換すべき数値
	 * @return	変換した文字列
	 */
	public static String toChar( final double val ) {
		//  6.9.8.0 (2018/05/28) FindBugs の警告を避ける方法が､見つかりませんでした｡
		return val == (int)val ? String.valueOf( (int)val ) : String.valueOf( val );

//		final int intVal = (int)val;
//		return ((double)intVal) == val ? String.valueOf( intVal ) : String.valueOf( val );
	}

	/**
	 * 特殊な文字列の連結を行います｡
	 *
	 * これは､第1引数の数字と､第2､第3､第4の文字列をｽﾍﾟｰｽで連結した文字列を返します｡
	 * 引数の個数が､可変に出来ないため､完全に決め打ちです｡
	 *
	 *      DROP FUNCTION JOIN2;
	 *
	 *      CREATE FUNCTION JOIN2 ( INTEGER , VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
	 *      RETURNS VARCHAR(4000)
	 *      DETERMINISTIC			-- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 *      PARAMETER STYLE JAVA	-- 戻り値のﾀｲﾌﾟ
	 *      NO SQL LANGUAGE JAVA	-- 関数の中でSQLは実行しないことを示す
	 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.join2' ;
	 *
	 * @og.rev 6.7.3.0 (2017/01/27) 新規作成
	 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
	 *
	 * @param	no		第1引数の数字
	 * @param	arg2	第2引数
	 * @param	arg3	第3引数
	 * @param	arg4	第4引数
	 * @return	連結したした文字列
	 */
	public static String join2( final int no , final String arg2 , final String arg3 , final String arg4 ) {
		return new StringBuilder( BUFFER_MIDDLE )
				.append( no ).append( ' ' )
				.append( arg2 == null ? "" : arg2 ).append( ' ' )
				.append( arg3 == null ? "" : arg3 ).append( ' ' )
				.append( arg4 == null ? "" : arg4 )
				.toString() ;
	}

	/**
	 * 連結文字列を指定した､文字列の連結を行います｡
	 *
	 * これは､第1引数の連結文字列を使用して､第2､第3､第4の文字列を連結します｡
	 * 引数の個数が､可変に出来ないため､第2､第3､第4の文字列が､null か､ｾﾞﾛ文字列の
	 * 場合は､連結せずにﾊﾟｽします｡
	 *
	 *      DROP FUNCTION JOIN3;
	 *
	 *      CREATE FUNCTION JOIN3 ( VARCHAR(10) , VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
	 *      RETURNS VARCHAR(4000)
	 *      DETERMINISTIC			-- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 *      PARAMETER STYLE JAVA	-- 戻り値のﾀｲﾌﾟ
	 *      NO SQL LANGUAGE JAVA	-- 関数の中でSQLは実行しないことを示す
	 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.join3' ;
	 *
	 * @og.rev 8.0.2.0 (2021/11/30) 新規作成
	 *
	 * @param	sep		連結時の文字列
	 * @param	arg2	第2引数
	 * @param	arg3	第3引数
	 * @param	arg4	第4引数
	 * @return	連結したした文字列
	 */
	public static String join3( final String sep , final String arg2 , final String arg3 , final String arg4 ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) ;

		boolean useSep = false;		// 連結文字列を使うかどうか。直前の文字列が null 等の場合は、連結しません。
		if( arg2 != null && !arg2.isEmpty() ) {
			buf.append( arg2 );
			useSep = true;
		}

		if( arg3 != null && !arg3.isEmpty() ) {
			if( useSep ) { buf.append( sep ); }
			buf.append( arg3 );
			useSep = true;
		}

		if( arg4 != null && !arg4.isEmpty() ) {
			if( useSep ) { buf.append( sep ); }
			buf.append( arg4 );
		}

		return buf.toString() ;
	}

	/**
	 * 対象の文字列の部分文字列を置換します｡
	 *
	 * ただし､source､target､replacement のどれかが､null(ｾﾞﾛ文字列)の場合は､
	 * 処理を実行せず､source をそのまま返します｡
	 *
	 *      DROP FUNCTION REPLACE;
	 *
	 *      CREATE FUNCTION REPLACE ( VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
	 *      RETURNS VARCHAR(4000)
	 *      DETERMINISTIC			-- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 *      PARAMETER STYLE JAVA	-- 戻り値のﾀｲﾌﾟ
	 *      NO SQL LANGUAGE JAVA	-- 関数の中でSQLは実行しないことを示す
	 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.replace' ;
	 *
	 * @og.rev 6.7.3.0 (2017/01/27) 新規作成
	 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
	 * @og.rev 6.9.8.0 (2018/05/28) source､target､replacement のどれかが､null(ｾﾞﾛ文字列)の場合は､source を返す｡
	 *
	 * @param	source 対象の文字列
	 * @param	target 置換したい文字列
	 * @param	replacement 置換する文字列
	 * @return	置換した文字列｡
	 */
	public static String replace( final String source , final String target , final String replacement ) {
		// 6.9.8.0 (2018/05/28) source､target､replacement のどれかが､null(ｾﾞﾛ文字列)の場合は､source を返す｡
		if( StringUtil.isEmpty( source , target , replacement ) ) {		// 一つでも､null の場合､true
			return source;
		}
		else {
			return source.replace( target,replacement );
		}

//		if( source != null && target != null || !target.isEmpty() && replacement != null && !replacement.isEmpty() ) {
//			return source.replace( target,replacement );
//		}
//		else {
//			return source;
//		}
	}

//	/**
//	 * この文字列内にあるすべてのoldCharをnewCharに置換した結果生成される文字列を返します｡
//	 *
//	 * String#replace( char , char )を､繰り返します｡
//	 *
//	 * @og.rev 6.7.9.0 (2017/04/28) 新規作成
//	 *
//	 * @param	source 対象の文字列
//	 * @param	target 以前の文字の集合
//	 * @param	replacement 置換する文字の集合
//	 * @return	置換した文字列｡
//	 */
//	public static String translate( final String source , final String target , final String replacement ) {
//		String rtn = source ;
//
//		if( source != null && target != null && replacement != null && target.length() == replacement.length() ) {
//			for( int i=0; i<target.length(); i++ ) {
//				rtn = rtn.replace( target.charAt(i) , replacement.charAt(i) );
//			}
//		}
//
//		return rtn;
//	}

	/**
	 * substr関数のﾊﾞｲﾄ数版
	 * 過去に､hsqldb 用に作成したJava関数です｡
	 *
	 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
	 * ※ 現在未使用
	 *
	 * @param	value		 変換する文字列
	 * @param	start		 変換開始ｱﾄﾞﾚｽ
	 * @param	length		 変換ﾊﾞｲﾄ数
	 * @return	変換後文字列
	 * @throws	UnsupportedEncodingException	文字のｴﾝｺｰﾃﾞｨﾝｸﾞがｻﾎﾟｰﾄされていません｡
	 */
	public static String substrb( final String value, final int start, final int length ) throws UnsupportedEncodingException {
		String rtn = null;
		final byte[] byteValue = makeByte( value );
		if( byteValue != null ) {
			rtn = new String( byteValue,start-1,length,ENCODE );
		}
		return rtn;
	}

	/**
	 * length関数のﾊﾞｲﾄ数版
	 * 過去に､hsqldb 用に作成したJava関数です｡
	 *
	 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
	 * ※ 現在未使用
	 *
	 * @param	value		ﾊﾞｲﾄ数をｶｳﾝﾄする文字列
	 * @return	ﾊﾞｲﾄ数
	 * @throws	UnsupportedEncodingException	文字のｴﾝｺｰﾃﾞｨﾝｸﾞがｻﾎﾟｰﾄされていません｡
	 */
	public static int lengthb( final String value ) throws UnsupportedEncodingException {
		return makeByte( value ).length;
	}

	/**
	 * 指定の文字列をﾊﾞｲﾄｺｰﾄﾞに変換します｡
	 * 引数の文字列が null の場合は､return は､byte[0] を返します｡
	 *
	 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
	 *
	 * @param	value	 変換するｽﾄﾘﾝｸﾞ値
	 * @param	encode	 変換する文字ｴﾝｺｰﾄﾞ
	 * @return	変換後文字列
	 */
	private static byte[] makeByte( final String value ) throws UnsupportedEncodingException {
		byte[] rtnByte = new byte[0];
		if( value != null ) {
			rtnByte = value.getBytes( ENCODE );
		}
		return rtnByte;
	}

	/**
	 * 日時文字列(yyyyMMddHHmmss)の引数1,2に対して､差分の範囲判定を行います｡
	 *
	 * 範囲判定を行う引数 sec1､sec2､sec3 引数には､それぞれ(秒)ﾚﾍﾞﾙの値を指定します｡
	 * 上記の引数の値が､0 の場合は､判定を回避します｡
	 *
	 * sec1引数と比べて､小さければ 0 を､大きければ 1 を返します｡
	 * sec2引数と比べて､小さければ sec1引数の結果を､大きければ 2 を返します｡
	 * sec3引数と比べて､小さければ sec2引数の結果を､大きければ 3 を返します｡
	 *
	 * 通常､sec1､sec2､sec3 と､順番に大きな値にしておきます｡
	 * 小さいと判定された時点で､判定処理は終了します｡
	 *
	 * date2 - date1 &lt;= sec1 ⇒ 0
	 *               &gt;  sec1 ⇒ 1
	 *               &gt;  sec2 ⇒ 2
	 *               &gt;  sec3 ⇒ 3
	 *
	 * date1,date2 のどちらかが､null,ｾﾞﾛ文字列の場合は､判定しません｡(return "0")
	 *
	 * @og.rev 7.0.5.0 (2019/09/16) 新規作成
	 * ※ 現在未使用
	 *
	 * @param	date1	前比較日時文字列(yyyyMMddHHmmss)
	 * @param	date2	後比較日時文字列(yyyyMMddHHmmss)
	 * @param	sec1	比較する秒
	 * @param	sec2	比較する秒
	 * @param	sec3	比較する秒
	 * @return	判定結果
	 */
	public static String checkDelay( final String date1 , final String date2 , final double sec1 , final double sec2 , final double sec3 ) {
		if( StringUtil.isNull( date1,date2 ) ) { return "0"; }		// どちらかが､null,ｾﾞﾛ文字列の場合は､判定しません｡

		final Calendar cal1 = HybsDateUtil.getCalendar( date1 );
		final Calendar cal2 = HybsDateUtil.getCalendar( date2 );

		final double diff = ( cal2.getTimeInMillis() - cal1.getTimeInMillis() )/1000.0;			// 秒に変換しておきます｡

		String rtn = "0";
		if( sec1 > 0.0 ) {
			if( diff <= sec1 ) { return rtn; }
			else { rtn = "1"; }
		}

		if( sec2 > 0.0 ) {
			if( diff <= sec2 ) { return rtn; }
			else { rtn = "2"; }
		}

		if( sec3 > 0.0 ) {
			if( diff <= sec3 ) { return rtn; }
			else { rtn = "3"; }
		}

		return rtn;
	}

	/**
	 * 部分文字列の出現位置を検索します｡
	 * 第1引数の検索元の文字列と､第2引数の部分文字列を指定します｡
	 * 戻り値は､Javaの indexOf とは異なり､+1 された値が返ります｡
	 *
	 * 	DROP FUNCTION INSTR;
     *
	 * 	CREATE FUNCTION INSTR ( VARCHAR(1000) , VARCHAR(100) )
	 * 	RETURNS INTEGER
	 * 	DETERMINISTIC			-- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 * 	PARAMETER STYLE JAVA	-- 戻り値のﾀｲﾌﾟ
	 * 	NO SQL LANGUAGE JAVA	-- 関数の中でSQLは実行しないことを示す
	 *	EXTERNAL NAME 'org.opengion.fukurou.db.Functions.instr' ;
     *
	 * @og.rev 7.3.0.0 (2021/01/06) 新規追加
	 *
	 * @param	value	 検索元の文字列
	 * @param	sub		 部分文字列
	 * @return	文字列が見つかった位置(最初が1 ､見つからなければ 0 )
	 */
	public static int instr( final String value,final String sub ) {
		if( value == null || value.isEmpty() ) {
			return 0 ;
		}
		else {
			return value.indexOf( sub ) + 1 ;
		}
	}

	/**
	 * 文字列を連結します｡
	 *
	 * 文字列が､NULL の場合は､ｾﾞﾛ文字列として扱います｡
	 * 引数がどちらも NULL の場合は､ｾﾞﾛ文字列が戻されます｡
	 * これは､ORACLEとの互換性を考慮した関数です｡
	 * ORACLE は､NULLとｾﾞﾛ文字列を同等にNULLとして扱うため､
	 * COALESCE( VAL1,'' ) で､VAL1がNULLか､ｾﾞﾛ文字列の場合は､NULLが返されます｡
	 * NULLを含むWHERE条件で､WHERE COALESCE( VAL1,'' ) = COALESCE( VAL2,'' )
	 * としても､一致しないため､WHERE COALESCE( VAL1,'x' ) = COALESCE( VAL2,'x' )
	 * とする必要がありますが､VAL1が､実際の 'x' で､VAL2がNULLの場合もﾏｯﾁします｡
	 * 一方､Derbyでは､NULLとｾﾞﾛ文字列を区別するため､COALESCE( VAL1,'x' ) の
	 * VAL1の値が､NULLとｾﾞﾛ文字列で､結果が変わってしまいます｡
	 * また､|| による文字列連結も､ORACLEとDerbyでは結果が変わるため､
	 * CONCAT関数を作成して､ORACLEと同じSQL文での比較ができるようにしておきます｡
	 *
	 * 	DROP FUNCTION CONCAT;
     *
	 * 	CREATE FUNCTION CONCAT ( VARCHAR(1000) , VARCHAR(1000) )
	 * 	RETURNS VARCHAR(2000)
	 * 	DETERMINISTIC			-- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 * 	PARAMETER STYLE JAVA	-- 戻り値のﾀｲﾌﾟ
	 * 	NO SQL LANGUAGE JAVA	-- 関数の中でSQLは実行しないことを示す
	 *	EXTERNAL NAME 'org.opengion.fukurou.db.Functions.concat' ;
     *
	 * @og.rev 7.3.0.0 (2021/01/06) 新規追加
	 *
	 * @param	val1	文字列1
	 * @param	val2	文字列2
	 * @return	文字列1と文字列2を連結した文字列
	 */
	public static String concat( final String val1,final String val2 ) {
		return new StringBuilder( BUFFER_MIDDLE )
				.append( val1 == null ? "" : val1 )
				.append( val2 == null ? "" : val2 )
				.toString() ;
	}

	/**
	 * NULL文字列の判定変換を行います｡
	 *
	 * 文字列が､NULL(または空文字列)は､文字列2を､NULLでない場合は､文字列1を返します｡
	 * Derbyでは､NULLと空文字列が区別されますが､ORACLE等では､区別されないため､
	 * この関数でも区別しません｡
	 * 空文字列の場合は､NULLと同じになります｡
	 *
	 * 	DROP FUNCTION NVL2;
     *
	 * 	CREATE FUNCTION NVL2 ( VARCHAR(1000) , VARCHAR(1000) , VARCHAR(1000) )
	 * 	RETURNS VARCHAR(1000)
	 * 	DETERMINISTIC			-- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
	 * 	PARAMETER STYLE JAVA	-- 戻り値のﾀｲﾌﾟ
	 * 	NO SQL LANGUAGE JAVA	-- 関数の中でSQLは実行しないことを示す
	 *	EXTERNAL NAME 'org.opengion.fukurou.db.Functions.nvl2' ;
     *
	 * @og.rev 7.3.1.3 (2021/03/09) 新規追加
	 *
	 * @param	inval	判定する文字列
	 * @param	val1	invalがNULL(または空文字列)でない場合に返す文字列1
	 * @param	val2	invalがNULL(または空文字列)の場合に返す文字列2
	 * @return	文字列がNULLの場合は､val2 を､そうでない場合は､val1 を返す｡
	 */
	public static String nvl2( final String inval,final String val1,final String val2 ) {
		return inval == null || inval.isEmpty() ? val2 : val1 ;
	}
}
