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

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.xml.HybsXMLSave;

/**
 * システムの自動インストールと自動更新を行います。
 * 自動インストールを行うには、起動時の環境変数にINSTALL_CONTEXTSが設定されている
 * 必要があります。
 * この環境変数が設定されている場合、システムリソーステーブル(GE12)が存在しなければ、
 * エンジンがインストールされていないと判断し、自動インストールを行います。
 * INSTALL_CONTEXTSにge,gfが指定されている場合は、開発環境を含めたフルバージョンが
 * インストールされます。
 * geのみが指定されている場合は、コアモジュールであるge4のみがインストールされます。
 * インストールスクリプトは、
 *  webapps/[CONTEXT]/db/[DBNAME]/xml/install
 * 以下にあるXMLファイルが全て実行されます。
 * また、同時に
 *  webapps/[CONTEXT]/db/xml
 * 以下にあるデータロードスクリプトも全て実行されます。
 * 
 * 自動更新については、システムリソーステーブル(GE12)の更新と、各システムの更新の2つが
 * あります。
 * GE12更新の判断基準は、システムID='**'に格納されているバージョン(同一のGE12を使用し
 * ているシステムの最大バージョン番号)がアップした場合です。
 * この場合に、エンジン内部で保持しているXMLファイルよりシステムリソースの再ロードを
 * 行います。
 * 各システムの更新の判断基準は、システムID=各システムのバージョン番号がアップされた
 * 場合です。
 * 
 * 更新スクリプトは、
 *  webapps/[CONTEXT]/db/[DBNAME]/xml/update
 * 以下にあるXMLファイルが全て実行されます。
 * また、同時に
 *  webapps/[CONTEXT]/db/xml
 * 以下にあるデータロードスクリプトも全て実行されます。
 * 
 * @og.rev 4.3.6.6 (2009/05/15) 新規作成
 * @og.group 初期化
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public final class SystemInstaller {
	private final String VERSION;
	private final String SYSTEM_ID;
	private final String CONTEXT_NAME;
	private final String HOST_URL;
	private final Connection connection ;

	/** エンジン共通パラメータ(SYSTEM_ID='**' KBSAKU='0')のXML ファイルの指定	{@value}	*/
	public static final String GE12_XML = "org/opengion/hayabusa/common/GE12.xml";

	/** エンジン共通パラメータ(SYSTEM_ID='**' KBSAKU='0')のENGINE_INFO 読み取りクエリー	{@value}	*/
	public static final String SEL_MAX_ENG = "select PARAM from GE12"
									+ " where SYSTEM_ID='**' and PARAM_ID='ENGINE_INFO'"
									+ " and FGJ='1' and KBSAKU='0'" ;

	/** エンジン個別(SYSTEM_ID='個別' KBSAKU='0'  CONTXT_PATH='自身')のバージョン情報を取得するクエリーー{@value} 4.3.6.6 (2009/05/15) */
	public static final String SEL_SYS_ENG = "select PARAM from GE12"
									+ " where SYSTEM_ID=? and PARAM_ID='ENGINE_INFO' and KBSAKU='0' and CONTXT_PATH=? and FGJ='1'";


	/**
	 * システムインストール・更新クラスのコンストラクタです
	 *
	 * @param version String バージョン
	 * @param systemId String システムID
	 * @param context String コンテキスト
	 * @param hostUrl String ホスト名を表す文字列
	 * @param conn Connection  登録用コネクション
	 */
	public SystemInstaller( final String version, final String systemId
			, final String context, final String hostUrl, final Connection conn ) {
		VERSION = version;
		SYSTEM_ID = systemId;
		CONTEXT_NAME = context;
		HOST_URL = hostUrl;
		connection = conn;
	}

	/**
	 * システムの自動インストール、自動更新を行います。
	 *
	 * @throws SQLException
	 * @throws UnsupportedEncodingException 
	 */
	public void insupd() throws SQLException, UnsupportedEncodingException {
		System.out.println( "    Start System Version Check ( " + SYSTEM_ID + " ) : loadVersion [ " + VERSION + " ]" );

		String oldMaxVersion = getOldMaxVersion();
		System.out.println( "     max old version = [ " + oldMaxVersion + " ]" );

		if( "none".equalsIgnoreCase( oldMaxVersion ) ) {
			String INSTALL_CONTEXTS = System.getenv( "INSTALL_CONTEXTS" );
			if( INSTALL_CONTEXTS != null && INSTALL_CONTEXTS.length() > 0 ) {
				System.out.println( "      Start System Install : install type ( " + INSTALL_CONTEXTS + " )" );
				String[] insSys = StringUtil.csv2Array( INSTALL_CONTEXTS );
				for( int i=0; i<insSys.length; i++ ) {
					System.out.println( "       install ( " + insSys[i] + " )" );
					loadXMLScript( "install", insSys[i] );
					connection.commit();
				}
				dbXMLResourceInsert();
				connection.commit();
			}
			return;
		}

		if( oldMaxVersion == null || oldMaxVersion.compareTo( VERSION ) < 0 ){
			System.out.println( "       Start SystemParameter reload" );
			dbXMLResourceInsert();
			connection.commit();
		}

		String oldSystemVersion = getOldSystemVersion();
		System.out.println( "     system old version = [ " + oldSystemVersion + " ]" );
		if ( oldSystemVersion == null || oldSystemVersion.compareTo( VERSION ) < 0 ){
			System.out.println( "       update ( " + CONTEXT_NAME + " )" );
			loadXMLScript( "update", CONTEXT_NAME );
			connection.commit();
		}
	}

	/**
	 * インストール、更新用のXMLスクリプトをロードします。
	 * 
	 * @og.rev 5.0.0.2 (2009/09/15) .xmlファイル以外は読み込まないように修正
	 * @og.rev 5.1.1.0 (2009/12/01) コメントを出して、処理中ということが判る様にします。
	 * 
	 * @param type String 更新タイプ( install or update )
	 * @param context String コンテキスト名
	 */
	private void loadXMLScript( final String type, final String context ) throws SQLException {
		final String FS       = File.separator ;
		final String APP_BASE = System.getenv( "APP_BASE" );
		final String DBNAME   = connection.getMetaData().getDatabaseProductName().toLowerCase( Locale.JAPAN );

		// DB名からスクリプトを格納しているフォルダを探します。
		String scriptBase = APP_BASE + FS + context.toLowerCase( Locale.JAPAN ) +FS + "db";
		File[] dbDir = new File( scriptBase ).listFiles();
		if( dbDir == null || dbDir.length == 0 ) {
			System.out.println( "       DB Folder not found. [" + scriptBase + "]"  );
			return;
		}

		String scriptPath = null;
		for ( int i = 0; i < dbDir.length; i++ ) {
			if ( DBNAME.indexOf( dbDir[i].getName() ) >= 0 ) {
				scriptPath = dbDir[i].getAbsolutePath();
				break;
			}
		}
		if( scriptPath == null ) {
			System.out.println( "       Script Folder for [ " + DBNAME + " ] not found " );
			return;
		}

		// webapps/[CONTEXT]/db/[DBNAME]/xml/(install|update) 内のスクリプトを実行します
		List<String> list = new ArrayList<String>();
		FileUtil.getFileList( new File( scriptPath  + FS + "xml" + FS + type ), true, list );
		if( ! list.isEmpty() ) {
			String dir1 = null;		// 5.1.1.0 (2009/12/01)
			for ( String name : list ) {
				if( name.endsWith( ".xml" ) ) {		// 5.0.0.2 (2009/09/15)
					File xml = new File( name );
					// 5.1.1.0 (2009/12/01) 処理中コメント：フォルダ単位に表示
					String dir2 = xml.getParent();
					if( dir1 == null || !dir1.equalsIgnoreCase( dir2 ) ) {
						System.out.println( "          " + dir2 );
						dir1 = dir2;
					}

					Reader reader = new BufferedReader( FileUtil.getBufferedReader( xml, "UTF-8" ) );
					HybsXMLSave save = new HybsXMLSave( connection, xml.getName() );
					save.insertXML( reader );
				}
			}
			System.out.println( "        DB Enviroment Installed , [ " + list.size() + " ] scripts loaded " );
		}

		// webapps/[CONTEXT]/db/xml 内のスクリプトを実行します
		File[] dataDir = new File( scriptBase + FS + "xml" ).listFiles();
		if( dataDir != null && dataDir.length > 0 ) {
			for ( int i=0; i<dataDir.length; i++ ) {
				String dtNm = dataDir[i].getName() ;
				if( dtNm.endsWith( ".xml" ) ) {		// 5.0.0.2 (2009/09/15)
					Reader reader = new BufferedReader( FileUtil.getBufferedReader( dataDir[i], "UTF-8" ) );
					HybsXMLSave save = new HybsXMLSave( connection, dtNm );
					save.insertXML( reader );
				}
			}
			System.out.println( "        DB Data Files Installed , [ " + dataDir.length + " ] files loaded " );
		}
		System.out.println( "       .completed");
	}

//	private void loadXMLScript( final String type, final String context ) throws SQLException {
//		String APP_BASE = System.getenv( "APP_BASE" );
//		String dbName = connection.getMetaData().getDatabaseProductName();
//
//		// DB名からスクリプトを格納しているフォルダを探します。
//		String scriptBase = APP_BASE + File.separator + context.toLowerCase( Locale.JAPAN ) + File.separator;
//		String scriptPath = null;
//		File[] dbDir = new File( scriptBase + "db" ).listFiles();
//		if( dbDir == null || dbDir.length == 0 ) {
//			System.out.println( "       DB Folder not found. [" + scriptBase + "db]"  );
//			return;
//		}
//
//		for ( int i = 0; i < dbDir.length; i++ ) {
//			if ( dbName.toLowerCase( Locale.JAPAN ).indexOf( dbDir[i].getName() ) >= 0 ) {
//				scriptPath = dbDir[i].getAbsolutePath();
//				break;
//			}
//		}
//		if( scriptPath == null ) {
//			System.out.println( "       Script Folder for [ " + dbName + " ] not found " );
//			return;
//		}
//
//		// webapps/[CONTEXT]/db/[DBNAME]/xml/(install|update) 内のスクリプトを実行します
//		List<String> list = new ArrayList<String>();
//		FileUtil.getFileList( new File( scriptPath  + File.separator + "xml" + File.separator + type ), true, list );
////		if( list.size() > 0 ) {
//		if( ! list.isEmpty() ) {
//			for ( String name : list ) {
//				File xml = new File( name );
//				// 5.0.0.2 (2009/09/15)
//				if( name.endsWith( ".xml" ) ) {
//					Reader reader = new BufferedReader( FileUtil.getBufferedReader( xml, "UTF-8" ) );
//					HybsXMLSave save = new HybsXMLSave( connection, xml.getName() );
//					save.insertXML( reader );
//				}
//			}
//			System.out.println( "        DB Enviroment Installed , [ " + list.size() + " ] scripts loaded " );
//		}
//
//		// webapps/[CONTEXT]/db/xml 内のスクリプトを実行します
//		File[] dataDir = new File( scriptBase + "db" + File.separator + "xml" ).listFiles();
//		if( dataDir != null && dataDir.length > 0 ) {
//			for ( int i=0; i<dataDir.length; i++ ) {
//				// 5.0.0.2 (2009/09/15)
//				if( dataDir[i].getName().endsWith( ".xml" ) ) {
//					Reader reader = new BufferedReader( FileUtil.getBufferedReader( dataDir[i], "UTF-8" ) );
//					HybsXMLSave save = new HybsXMLSave( connection, dataDir[i].getName() );
//					save.insertXML( reader );
//				}
//			}
//			System.out.println( "        DB Data Files Installed , [ " + dataDir.length + " ] files loaded " );
//		}
//		System.out.println( "       .completed");
//	}

	/**
	 * 最後に起動された際のバージョン番号を取得します。(システムID='**')
	 *
	 * エンジンがまだインストールされていない等の原因でエラーが発生した場合は、
	 * "none"という文字列を返します。
	 * 
	 * @og.rev 5.1.1.0 (2009/12/01) 実行エラー時に、rollback を追加(PostgreSQL対応)
	 * 
	 * @return String バージョン番号
	 */
	private String getOldMaxVersion() {
		// エンジンパラメータのエンジン情報(バージョン番号 + ビルドタイプ)を取得します。
		Statement 			stmt		= null;
		ResultSet			resultSet	= null;
		String				ver 		= null;
		try {
			stmt = connection.createStatement();
			resultSet = stmt.executeQuery( SEL_MAX_ENG );
			while( resultSet.next() ) {
				ver = resultSet.getString(1);
			}
		}
		catch( SQLException ex ) {
			ver = "none";
			Closer.rollback( connection );		// 5.1.1.0 (2009/12/01)
		}
		finally {
			Closer.resultClose( resultSet );
			Closer.stmtClose( stmt );
		}
		return ver;
	}
	
	/**
	 * 最後に起動された際のバージョン番号を取得します。(システムID=各システム)
	 * 
	 * @og.rev 5.1.1.0 (2009/12/01) 実行エラー時に、rollback を追加(PostgreSQL対応)
	 * 
	 * @return String バージョン番号
	 */
	private String getOldSystemVersion() throws SQLException {
		// エンジンパラメータのエンジン情報(バージョン番号 + ビルドタイプ)を取得します。
		PreparedStatement	pstmt		= null;
		ResultSet			resultSet	= null;
		String				ver 		= null;
		try {
			pstmt = connection.prepareStatement( SEL_SYS_ENG );
			pstmt.setString( 1, SYSTEM_ID );
			pstmt.setString( 2, HOST_URL ); 
			resultSet = pstmt.executeQuery();
			while( resultSet.next() ) {
				ver = resultSet.getString(1);
			}
		}
		catch( SQLException ex ) {
			Closer.rollback( connection );		// 5.1.1.0 (2009/12/01)
		}
		finally {
			Closer.resultClose( resultSet );
			Closer.stmtClose( pstmt );
		}
		return ver;
	}

	/**
	 * エンジン内部定義の初期リソース情報をDB(GE12)に登録します。
	 *
	 * 初期リソース情報は、KBSAKU='0' で登録されている情報で、一旦すべて削除
	 * してから、全てのリソース情報を追加するという形をとります。
	 * リソースは、すでに、Oracle XDK により XMLファイル化してあります。
	 * なお、この情報をDB登録する理由は、リソースの設定値を変えたい場合に、
	 * キーが判らない（JavaDOCからしか読み取れない）のでは不便な為に
	 * 用意しておくだけで、内部では SystemData オブジェクトとして定義
	 * されている値を使用するため、このデータベース値は、使用していません。
	 * 
	 * @og.rev 4.3.6.6 (2009/05/15) バージョン判定部分を分離
	 *
	 * @param connection Connection  登録用コネクション
	 */
	private void dbXMLResourceInsert()
				throws UnsupportedEncodingException {
		// 新設定値を全件INSERTします。
		// common フォルダにセットして、ClassLoader で読み取る方法
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		InputStream stream = loader.getResourceAsStream( GE12_XML );

		Reader reader = new BufferedReader( new InputStreamReader( stream,"UTF-8" ) );
		HybsXMLSave save = new HybsXMLSave( connection,"GE12" );
		save.insertXML( reader );
		int insCnt = save.getInsertCount();
		int delCnt = save.getDeleteCount();

		System.out.print( "        XML Engine Resource Reconfiguration " );
		System.out.println( "DELETE=[" + delCnt + "],INSERT=[" + insCnt + "] finished." );
		System.out.println( "      .completed" );
	}

}
