/*
 * 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.util.Locale;
import java.util.Arrays;					// 6.3.9.1 (2015/11/27)
import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)

/**
 * 各データベースに対応するenum名を返します。
 * 主に、各データベースにおける関数名の差異を吸収するためのenumです。
 * 本来は、互換性のあるファンクション以外、使用しないようにしましょう。
 * また、無ければ互換性パックなどで、ファンクションを定義してしまうのも
 * 一つの方法です。
 *
 * <table class="plain">
 *  <caption>各データベースにおける関数</caption>
 *  <tr><th>データベース名 </th><th>連結</th><th>部分文字列</th><th>日付関数         </th><th>DUAL検索         </th></tr>
 *  <tr><td>{&#064;DBF.XXX}</td><td>CON </td><td>SUBSTR    </td><td>SYSDATE          </td><td>FROM_DUAL        </td></tr>
 *  <tr><td>ORACLE         </td><td>||  </td><td>SUBSTR    </td><td>SYSDATE          </td><td>FROM DUAL        </td></tr>
 *  <tr><td>HSQL           </td><td>||  </td><td>SUBSTR    </td><td>CURRENT_TIMESTAMP</td><td>FROM DUAL        </td></tr>
 *  <tr><td>POSTGRES       </td><td>||  </td><td>SUBSTR    </td><td>CURRENT_DATE     </td><td>                 </td></tr>
 *  <tr><td>MYSQL          </td><td>||  </td><td>SUBSTR    </td><td>now()            </td><td>FROM DUAL        </td></tr>
 *  <tr><td>SQLSERVER      </td><td>+   </td><td>SUBSTRING </td><td>GETDATE()        </td><td>                 </td></tr>
 *  <tr><td>FIREBIRD       </td><td>||  </td><td>SUBSTR    </td><td>CURRENT_DATE     </td><td>FROM RDB$DATABASE</td></tr>
 *  <tr><td>DERBY          </td><td>||  </td><td>SUBSTR    </td><td>CURRENT_TIMESTAMP</td><td>FROM SYSIBM.SYSDUMMY1</td></tr>
 *  <tr><td>CACHE          </td><td>||  </td><td>SUBSTRING </td><td>SYSDATE          </td><td>FROM DUAL        </td></tr>
 *  <tr><td>H2             </td><td>||  </td><td>SUBSTR    </td><td>SYSDATE          </td><td>FROM DUAL        </td></tr>
 *  <tr><td>OTHER          </td><td>||  </td><td>SUBSTR    </td><td>SYSDATE          </td><td>FROM DUAL        </td></tr>
 * </table>
 *
 * ※ MySQLでは、通常文字列連結は、CONCAT関数を使います。パイプ(||)で結合するには、
 * SET sql_mode='PIPES_AS_CONCAT' しておく必要があります。
 * JDBCで設定する場合は、jdbc:mysql://《サーバー》/《DB名》?sessionVariables=sql_mode='PIPES_AS_CONCAT'
 * と指定します。
 *
 * @og.rev 5.1.4.0 (2010/03/01) 新規作成
 * @og.rev 5.8.5.0 (2015/03/06) CACHE追加
 * @og.rev 8.0.1.1 (2021/11/12) FROM DUAL 項目追加
 *
 * @version  5.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public enum DBFunctionName {
	// 引数付きenum定義：ここに、必要な関数が増えるたびに、追加していきます。
	// 6.2.2.1 (2015/03/31) SYSDATE 項目追加
	//			  CON	SUBSTR		  SYSDATE
	  /** ファンクション名 */ ORACLE	( "||" , "SUBSTR"		, "SYSDATE"				,"FROM DUAL" )
	, /** ファンクション名 */ HSQL		( "||" , "SUBSTR"		, "CURRENT_TIMESTAMP"	,"FROM DUAL" )	// 良く判らん
	, /** ファンクション名 */ POSTGRES	( "||" , "SUBSTR"		, "CURRENT_DATE"		,""	)
	, /** ファンクション名 */ MYSQL		( "||" , "SUBSTR"		, "now()"				,"FROM DUAL" )	// SYSDATE() でもほとんど同じ
	, /** ファンクション名 */ SQLSERVER	( "+"  , "SUBSTRING"	, "GETDATE()"			,""	)
	, /** ファンクション名 */ FIREBIRD	( "||" , "SUBSTR"		, "CURRENT_DATE"		,"FROM RDB$DATABASE" )
	, /** ファンクション名 */ DERBY		( "||" , "SUBSTR"		, "CURRENT_TIMESTAMP"	,"FROM SYSIBM.SYSDUMMY1" )	// 6.4.9.3 (2016/08/26)
	, /** ファンクション名 */ CACHE		( "||" , "SUBSTRING"	, "SYSDATE"				,"FROM DUAL" )	// 良く判らん
	, /** ファンクション名 */ H2		( "||" , "SUBSTR"		, "SYSDATE"				,"FROM DUAL" )	// 6.8.3.0 (2017/11/27) 追加
	, /** ファンクション名 */ OTHER		( "||" , "SUBSTR"		, "SYSDATE"				,"FROM DUAL" ) ; 	// 6.4.5.0 (2016/04/08) 適当

	private final String dbfCON ;
	private final String dbfSUBSTR ;
	private final String dbfSYSDATE ;							// 6.2.2.1 (2015/03/31) SYSDATE 項目追加
	private final String dbfFROM_DUAL ;							// 8.0.1.1 (2021/11/12) FROM DUAL 項目追加

	/**
	 * コンストラクター(enum の場合は、private宣言される)
	 *
	 * @og.rev 5.1.4.0 (2010/03/01) 新規作成
	 * @og.rev 6.2.2.1 (2015/03/31) SYSDATE 項目追加
	 * @og.rev 8.0.1.1 (2021/11/12) FROM DUAL 項目追加
	 *
	 * @param	con   	第１引数にて指定(CON)
	 * @param	substr	第２引数にて指定(SUBSTR)
	 * @param	sysdt	第３引数にて指定(SYSDATE)
	 * @param	dual	第４引数にて指定(FROM DUAL)
	 */
//	private DBFunctionName( final String con , final String substr , final String sysdt ) {
//	DBFunctionName( final String con , final String substr , final String sysdt ) {
	DBFunctionName( final String con , final String substr , final String sysdt , final String dual ) {
		dbfCON    = con;
		dbfSUBSTR = substr;
		dbfSYSDATE= sysdt;
		dbfFROM_DUAL= dual;
	}

	/**
	 * 共通ファンクションに対応するデータベース個別のファンクション名を返します。
	 *
	 * 現時点では、NAME,CON,SUBSTR のみ使用できます。
	 *
	 * @og.rev 5.1.4.0 (2010/03/01) 新規作成
	 * @og.rev 6.2.2.1 (2015/03/31) SYSDATE 項目追加
	 * @og.rev 6.3.9.1 (2015/11/27) メソッドの出口は、最後の１か所にすべきです(PMD)。
	 * @og.rev 5.9.19.1 (2017/04/14) DBF.TYPE追加
	 * @og.rev 8.0.1.1 (2021/11/12) FROM DUAL 項目追加
	 *
	 * @param   func 共通ファンクション
	 *
	 * @return  ファンクション名
	 */
	public String getFunctionName( final String func ) {

		final String rtn ;
		if(      "NAME"     .equalsIgnoreCase( func ) ) { rtn = toString();	}		// この、enum の NAME
		else if( "CON"      .equalsIgnoreCase( func ) ) { rtn = dbfCON;		}
		else if( "SUBSTR"   .equalsIgnoreCase( func ) ) { rtn = dbfSUBSTR;	}
		else if( "SYSDATE"  .equalsIgnoreCase( func ) ) { rtn = dbfSYSDATE;	}
		else if( "FROM_DUAL".equalsIgnoreCase( func ) ) { rtn = dbfFROM_DUAL;	}	// 8.0.1.1 (2021/11/12)
		else if( "TYPE"     .equalsIgnoreCase( func ) ) { rtn = toString().toLowerCase( Locale.JAPAN );	}		// 5.9.19.1 (2017/04/14)
		else {											rtn = func;			}

		return rtn ;
	}

	/**
	 * 各データベースに対応するファンクション名を返します。
	 *
	 * @og.rev 4.3.8.0 (2009/08/01) SUBSTRを追加
	 * @og.rev 5.1.2.0 (2010/01/01) MySQL対応,SUBSTRB廃止(帳票データの分割の内部処理化に伴う)
	 * @og.rev 5.1.4.0 (2010/03/01) データベース名 ではなく、dbid で判断するように変更
	 * @og.rev 5.7.7.2 (2014/06/20) DBF.NAME 時の処理の簡素化
	 * @og.rev 6.2.1.0 (2015/03/13) NAME だけ特殊処理するのではなく、統一する。
	 *
	 * @param   func ファンクション名(定義文字)
	 * @param   dbid 接続先ID
	 *
	 * @return  実ファンクション名
	 */
	public static String getFunctionName( final String func ,final String dbid ) {

		// 5.7.7.2 (2014/06/20) DBF.NAME 時の処理の簡素化
		final String dbName = ConnectionFactory.getDBName( dbid );
	// 6.2.1.0 (2015/03/13) NAME だけ特殊処理するのではなく、統一する。
	//	if( "NAME".equals( func ) ) { return dbName; }
	//	else {
			return getDBName( dbName ).getFunctionName( func );
	//	}
	}

	/**
	 * シーケンス名よりシーケンスオブジェクトを検索し、次の値を取り出します。
	 * DBに対するシーケンスオブジェクトは予め作成されている必要があります。
	 *
	 * また、MySQLの場合は、シーケンスオブジェクトが実装されていないため、
	 * 内部的には、引数のシーケンス名と同じ名前のテーブルから、Integer型の
	 * "SEQID"という項目名を検索することにより、シーケンスをエミュレートしています。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 * @og.rev 5.8.5.0 (2015/03/06) CACHE追加
	 * @og.rev 5.9.31.1 (2018/04/13) DBID追加対応
	 *
	 * @param seqName シーケンス名
	 * @param tran トランザクション
	 *
	 * @return シーケンス番号
	 */
	public int getSequence( final String seqName, final Transaction tran ) {
		return getSequence( seqName, tran, null );
	}

	/**
	 * シーケンス名よりシーケンスオブジェクトを検索し、次の値を取り出します。
	 * DBに対するシーケンスオブジェクトは予め作成されている必要があります。
	 *
	 * また、MySQLの場合は、シーケンスオブジェクトが実装されていないため、
	 * 内部的には、引数のシーケンス名と同じ名前のテーブルから、Integer型の
	 * "SEQID"という項目名を検索することにより、シーケンスをエミュレートしています。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 * @og.rev 5.8.5.0 (2015/03/06) CACHE追加
	 * @og.rev 6.4.9.3 (2016/08/26) JavaDB(APACHE DERBY) の追加
	 * @og.rev 5.9.31.1 (2018/04/13) DBID追加対応
	 * @og.rev 6.9.8.0 (2018/05/28) DBID 対応漏れ,arg引数がないことを明確にするため、nullを渡します。
	 *
	 * @param seqName シーケンス名
	 * @param tran トランザクション
	 * @param DBID DBID
	 *
	 * @return シーケンス番号
	 */
//	public int getSequence( final String seqName, final Transaction tran ) {
	public int getSequence( final String seqName, final Transaction tran, final String DBID ) {
		String sql = null;
		// 6.0.2.5 (2014/10/31) refactoring:無効な代入です。
		switch ( this ) {
			case ORACLE:
				sql = "select " + seqName + ".nextval from dual";
				break;
			case HSQL:
				sql = "select next value for " + seqName + " from dual";
				break;
			case POSTGRES:
				sql = "select nextval('" + seqName + "')";
				break;
			case MYSQL:
				sql = "update " + seqName + " set SEQID = last_insert_id(SEQID+1)";
//				DBUtil.dbExecute( sql, new String[0], tran );
				DBUtil.dbExecute( sql, null, tran, DBID );		// 6.9.8.0 (2018/05/28) DBID 対応漏れ
				sql = "select last_insert_id()";
				break;
			case SQLSERVER:
				throw new OgRuntimeException( "現在、SQLSERVERではシーケンス機能はサポートされていません。" );
			case FIREBIRD:
				sql = "select gen_id(" + seqName + ", 1) from rdb$database";
				break;
			// 6.4.9.3 (2016/08/26)
			case DERBY:
				throw new OgRuntimeException( "現在、DERBYではシーケンス機能はサポートされていません。" );
			// 5.8.5.0 (2015/03/06) CACHE追加
			case CACHE:
				throw new OgRuntimeException( "現在、CACHEではシーケンス機能はサポートされていません。" );
			default:
				throw new OgRuntimeException( "現在、このデータベースではシーケンス機能はサポートされていません。" );
		}

		// 6.0.2.5 (2014/10/31) refactoring:無効な代入です。
//		final String[][] rtn = DBUtil.dbExecute( sql, new String[0], tran );
//		final String[][] rtn = DBUtil.dbExecute( sql, new String[0], tran, DBID );		// 5.9.31.1 (2018/04/13) DBID追加対応
		final String[][] rtn = DBUtil.dbExecute( sql, null, tran, DBID );				// 6.9.8.0 (2018/05/28)

		return Integer.parseInt( rtn[0][0] );		// 6.0.2.4 (2014/10/17) メソッド間違い
	}

	/**
	 * 各データベースに対応するenum名を返します。
	 *
	 * @og.rev 5.1.4.0 (2010/03/01) 新規作成
	 * @og.rev 5.8.5.0 (2015/03/06) CACHE追加
	 * @og.rev 6.3.9.1 (2015/11/27) メソッドの出口は、最後の１か所にすべきです(PMD)。
	 * @og.rev 6.4.5.0 (2016/04/08) 規定以外のデータベースでも、動作するように、OTHER を用意する。
	 * @og.rev 6.4.9.3 (2016/08/26) JavaDB(APACHE DERBY) の追加
	 * @og.rev 6.8.3.0 (2017/11/27) H2 の追加
	 *
	 * @param   dbName データベース名(null不可)
	 *
	 * @return  データベースに対応するenum名
	 * @og.rtnNotNull
	 */
	public static DBFunctionName getDBName( final String dbName ) {
		final String dbn = dbName.toUpperCase( Locale.JAPAN );

		final DBFunctionName rtn ;
		if( 	 dbn.contains( "ORACLE"		) ) { rtn = DBFunctionName.ORACLE;		}
		else if( dbn.contains( "HSQL"		) ) { rtn = DBFunctionName.HSQL;		}
		else if( dbn.contains( "POSTGRES"	) ) { rtn = DBFunctionName.POSTGRES;	}
		else if( dbn.contains( "MYSQL"		) ) { rtn = DBFunctionName.MYSQL;		}
		else if( dbn.contains( "SQLSERVER"	) ) { rtn = DBFunctionName.SQLSERVER;	}
		else if( dbn.contains( "FIREBIRD"	) ) { rtn = DBFunctionName.FIREBIRD;	}
		else if( dbn.contains( "DERBY"		) ) { rtn = DBFunctionName.DERBY;		}	// 6.4.9.3 (2016/08/26)
		else if( dbn.contains( "CACHE"		) ) { rtn = DBFunctionName.CACHE;		}	// 5.8.5.0 (2015/03/06)
		else if( dbn.contains( "H2"			) ) { rtn = DBFunctionName.H2;			}	// 5.8.5.0 (2015/03/06)
		else {
			rtn = DBFunctionName.OTHER;			// 6.4.5.0 (2016/04/08)
			final String errMsg = "初期化時に、指定の dbName キーが存在しません。"
							+ "[" + dbName + "] キーは、" + Arrays.toString( DBFunctionName.values() ) ;
			System.out.println( errMsg );
		}

		return rtn;
	}
}
