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

import org.opengion.fukurou.system.OgRuntimeException ;			// 6.4.2.0 (2016/01/29)
import java.io.File;
import java.io.IOException;

import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.system.HybsConst;					// 7.2.3.1 (2020/04/17)
import org.opengion.fukurou.system.ThrowUtil;					// 6.4.2.0 (2016/01/29)
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import static org.opengion.fukurou.system.HybsConst.FS ;		// 8.0.3.0 (2021/12/17)

import com.sun.star.bridge.UnoUrlResolver;
import com.sun.star.bridge.XUnoUrlResolver;
import com.sun.star.comp.helper.Bootstrap;
import com.sun.star.comp.helper.BootstrapException;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XDispatchHelper;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.connection.ConnectionSetupException;		// 6.3.9.0 (2015/11/06)

/**
 * OpenOfficeのﾌﾟﾛｾｽを表すｸﾗｽです｡
 *
 * bootstrap()ﾒｿｯﾄﾞが呼ばれたﾀｲﾐﾝｸﾞでsoffice.binのﾌﾟﾛｾｽを生成します｡
 * soffice.binのﾌﾟﾛｾｽを引数なしで実装した場合､通常は各ﾕｰｻﾞｰで1ﾌﾟﾛｾｽしか
 * 生成されないため､-env:UserInstallationの引数を指定することで､仮想的に別ﾕｰｻﾞｰ
 * として起動しています｡
 * この"ﾕｰｻﾞｰ"を表すｷｰは､ｺﾝｽﾄﾗｸﾀの引数のidです｡
 *
 * また､この仮想ﾕｰｻﾞｰで起動した場合､初回起動時にﾕｰｻﾞｰ登録を促す画面が立ち上がります｡
 * これを回避するため､ﾃﾞﾌｫﾙﾄの環境ﾌｧｲﾙをﾌﾟﾛｾｽ生成前にｺﾋﾟｰすることで､認証済みの
 * 状態で立ち上がるようにしています｡
 *
 * 起動したﾌﾟﾛｾｽとの通知は名前付きﾊﾟｲﾌﾟで行われます｡ﾊﾟｲﾌﾟ名は､"env"+ｺﾝｽﾄﾗｸﾀのidです｡
 * ﾌﾟﾛｾｽ起動と､名前付きﾊﾟｲﾌﾟでの接続は非同期で行われます｡
 * ﾌﾟﾛｾｽ起動後､60秒経過しても接続できない場合は､BootstrapExceptionが発生します｡
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public class SOfficeProcess {
	/** OOoのｲﾝｽﾄｰﾙﾃﾞｨﾚｸﾄﾘ  */
	public static final String OFFICE_HOME =
//		new File( System.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;
//		new File( HybsConst.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;	// 7.2.3.1 (2020/04/17)
		new File( HybsConst.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + FS;				// 8.0.3.0 (2021/12/17)

	/** 環境設定のﾊﾟｽ */
	// 5.1.7.0 (2010/06/01) 複数ｻｰﾊﾞｰ対応漏れ
	public static final String ENV_DIR =
		HybsSystem.url2dir( StringUtil.nval( HybsSystem.sys( "REPORT_FILE_URL" )
							, HybsSystem.sys( "FILE_URL" ) + "REPORT" + FS ) + "oooenv" ) + FS;
	/** 設定ﾌｧｲﾙの雛形 */
	private static final String DEFAULT_ENV_PATH =
//		OFFICE_HOME + "env" + File.separator + "_default";
		OFFICE_HOME + "env" + FS + "_default";					// 8.0.3.0 (2021/12/17)

	/** soffice.binのﾊﾟｽ */
	private static final String SOFFICE_BIN =
//		OFFICE_HOME + File.separator + "program" + File.separator + "soffice.bin";
		OFFICE_HOME + FS + "program" + FS + "soffice.bin";		// 8.0.3.0 (2021/12/17)

	/** ﾛｰｶﾙｺﾝﾃｷｽﾄ */
	private static XComponentContext xLocalContext ;

	private final String envPath;
	private final String envId;				// 環境設定ﾌｧｲﾙのID

	/** ﾘﾓｰﾄﾃﾞｽｸﾄｯﾌﾟｲﾝｽﾀﾝｽ */
	@SuppressWarnings("cast")	// OpenOffice 3.2 での冗長なｷｬｽﾄ警告の抑止｡ｷｬｽﾄをはずすと､旧3.1 では､ｴﾗｰになる｡
	private XDesktop desktop	;

	private XComponentContext remoteContext	;

	/** soffice.binのﾌﾟﾛｾｽ */
	private Process process		;

	static {
		try {
			xLocalContext = Bootstrap.createInitialComponentContext( null );
		}
		catch( final Throwable th ) {
			System.out.println( "[ERROR]PROCESS:Can't start LocalContext,Check OFFICE_HOME!" );
			System.err.println( ThrowUtil.ogStackTrace( th ) );				// 6.4.2.0 (2016/01/29)
		}
	}

	/**
	 * ｺﾝｽﾄﾗｸﾀです｡
	 *
	 * @og.rev 4.3.0.0 (2008/07/15) 設定ﾌｧｲﾙを各ｺﾝﾃｷｽﾄごとに置くように変更
	 * @param	id	ﾌﾟﾛｾｽID
	 */
	protected SOfficeProcess( final String id ) {
		envId = id;
		// envPath = OFFICE_HOME + "env" + File.separator + envId;
		envPath = ENV_DIR + envId;
	}

	/**
	 * OOoへの接続を行います｡
	 *
	 * @og.rev 5.0.0.0 (2009/08/03) Linux対応(ﾊﾟｲﾌﾟ名に":"が含まれていると接続できない)
	 * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
	 */
	@SuppressWarnings("cast")	// OpenOffice 3.2 での冗長なｷｬｽﾄ警告の抑止｡ｷｬｽﾄをはずすと､旧3.1 では､ｴﾗｰになる｡
	protected void bootstrap() {
		System.out.println( "[INFO]OOo:Starting soffice process,ENV-ID=" + envId );

		// check enviroment files, if no files, create from default files
		checkEnv( envPath );

		// pipe name
		// 4.3.3.6 (2008/11/15) ﾏﾙﾁｻｰﾊﾞ対応｡同一ｻｰﾊﾞでの複数実行時不具合のため｡
		// 5.0.0.0 (2009/08/03) Linux対応
		//String sPipeName = "uno" + envId;
		final String sPipeName = "uno" + "_" + HybsSystem.sys("HOST_URL").replace(':','_').replace('/','_') + "_" + envId;

		// start office process
		// 5.5.2.4 (2012/05/16) int priority は使われていないので､削除します｡
		process = execOffice( envPath, sPipeName );
		System.out.println( "[INFO]OOo:Invoke soffice.bin,ENV-ID=" + envId );

		// create a URL resolver
		final XUnoUrlResolver xUrlResolver = UnoUrlResolver.create( xLocalContext );

		// connection string
		// 5.1.7.0 (2010/06/01) TCP接続対応
		final String sConnect = getConnParam( sPipeName );

		// wait until office is started
		try {
			for( int i=0;; ++i ) {
				try {
					final Object context = xUrlResolver.resolve( sConnect );
					remoteContext = (XComponentContext) UnoRuntime.queryInterface( XComponentContext.class, context );
					if( remoteContext == null ) { throw new BootstrapException( "no component context!" ); }
					break;
				}
				catch( final com.sun.star.connection.NoConnectException ex ) {
					System.out.println( "[INFO]OOo:Waiting for Connect soffice process,ENV-ID=" + envId );
					if( i == 60 ) { throw new BootstrapException( ex ); }
					Thread.sleep( 1000 );
				}
			}

			// create desktop instance
			final XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
			desktop = (XDesktop) UnoRuntime.queryInterface( XDesktop.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.Desktop", remoteContext ) );
		}
		catch( final ConnectionSetupException
				| BootstrapException
				| IllegalArgumentException
				| InterruptedException ex ) {
			throw new HybsSystemException( "[ERROR]PROCESS:Can't create Desktop Instance", ex );
		}
//		catch( final Exception ex ) {
		catch( final Throwable th ) {		// PMD : 6.9.9.4 (2018/10/01)
			throw new HybsSystemException( "[ERROR]PROCESS:Can't create XDesktop Instance", th );
		}

		System.out.println( "[INFO]OOo:Connected successful,ENV-ID=" + envId );
	}

	/**
	 * Pipe名をｷｰにOpenOfficeのﾌﾟﾛｾｽに接続するための文字列を生成します｡
	 *
	 * @param key Pipe名
	 *
	 * @return 接続文字列
	 * @og.rtnNotNull
	 */
	protected String getConnParam( final String key ) {
		return "uno:pipe,name=" + key + ";urp;StarOffice.ComponentContext";
	}

	/**
	 * ﾃﾞｽｸﾄｯﾌﾟｲﾝｽﾀﾝｽを返します｡
	 *
	 * @return ﾃﾞｽｸﾄｯﾌﾟｲﾝｽﾀﾝｽ
	 */
	public XDesktop getDesktop() {
		return desktop;
	}

	/**
	 * ﾌﾟﾛｾｽを終了します｡
	 * また､同時に環境設定用のﾌｧｲﾙも削除します｡
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
	 *
	 */
	public void close() {
		// 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
		if( process == null ) {
			final String errMsg = "#bootstrap()を先に実行しておいてください｡" ;
			throw new OgRuntimeException( errMsg );
		}

		process.destroy();
		FileUtil.deleteFiles( new File( envPath ) );
		System.out.println( "[INFO]OOo:Destroy process,ENV-ID=" + envId );
	}

	/**
	 * soffice.binを起動します｡
	 *
	 * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
	 * @og.rev 5.5.2.4 (2012/05/16) int priority は使われていないので､削除します｡
	 *
	 * @param envPath 環境変数ﾊﾟｽ
	 * @param pipeName ﾊﾟｲﾌﾟ名
	 *
	 * @return soffice.binのﾌﾟﾛｾｽ
	 */
	private Process execOffice( final String envPath, final String pipeName ) {
		String[] cmdArray = new String[11];
		cmdArray[0] = SOFFICE_BIN;
		cmdArray[1] = "-nologo";
		cmdArray[2] = "-nodefault";
		cmdArray[3] = "-norestore";
		cmdArray[4] = "-nocrashreport";
		cmdArray[5] = "-nolockcheck";
		cmdArray[6] = "-minimized";
		cmdArray[7] = "-invisible";
		cmdArray[8] = "-headless";
		cmdArray[9] = "-env:UserInstallation=file:///" + ( envPath ).replace( '\\', '/' );
		// 5.1.7.0 (2010/06/01) TCP接続対応
		cmdArray[10] = getProcParam( pipeName );

		Process process;
		try {
			process = Runtime.getRuntime().exec( cmdArray );
		} catch( final IOException ex ) {
			throw new HybsSystemException( "[ERROR]PROCESS:Cant't exec soffice.bin", ex );
		}

		return process;
	}

	/**
	 * Pipe名をｷｰにOpenOfficeのﾌﾟﾛｾｽを生成するためのﾊﾟﾗﾒｰﾀｰ文字列を生成します｡
	 *
	 * @param key Pipe名
	 *
	 * @return ﾌﾟﾛｾｽ生成ﾊﾟﾗﾒｰﾀｰ
	 * @og.rtnNotNull
	 */
	protected String getProcParam( final String key ) {
		return "-accept=pipe,name=" + key + ";urp;";
	}

	/**
	 * OOoの環境設定をﾁｪｯｸします｡
	 *
	 * ※ OFFICE_HOMEが設定されていない場合､HybsSystemException が､throw されます｡
	 *
	 * @og.rev 4.3.0.0 (2008/07/24) OS依存をやめてJavaでｺﾋﾟｰする
	 *
	 * @param envPath 環境設定のﾊﾟｽ
	 */
	private void checkEnv( final String envPath ) {

		if( OFFICE_HOME == null || OFFICE_HOME.isEmpty() ) {
			throw new HybsSystemException( "OFFICE_HOMEが設定されていないため､OpenOfficeを起動できません" );
		}

		// 4.3.0.0 (2008/07/24) OS依存からFileUtilを使うように変更
		FileUtil.copyDirectry( DEFAULT_ENV_PATH, envPath );

		// 5.1.7.0 (2010/06/01) ﾌｧｲﾙﾏｰｼﾞ対応
		if( ! ( new File( getTempPath() ) ).mkdirs() ) {
			System.err.println( "ﾌｧｲﾙﾏｰｼﾞ時のﾃﾝﾎﾟﾗﾘﾌｫﾙﾀﾞを作成できませんでした｡[" + getTempPath() + "]" );
		}
	}

	/**
	 * OpenOfficeのﾛｰｶﾙｺﾝﾎﾟｰﾈﾝﾄｺﾝﾃｷｽﾄを返します｡
	 *
	 * @og.rev 5.1.7.0 (2010/06/01) 新規作成
	 * @og.rev 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
	 *
	 * @return ﾛｰｶﾙｺﾝﾎﾟｰﾈﾝﾄｺﾝﾃｷｽﾄ
	 */
	@SuppressWarnings("cast")	// OpenOffice 3.2 での冗長なｷｬｽﾄ警告の抑止｡ｷｬｽﾄをはずすと､旧3.1 では､ｴﾗｰになる｡
	public XDispatchHelper getDispatcher() {
		// 6.3.9.0 (2015/11/06) ｺﾝｽﾄﾗｸﾀで初期化されていないﾌｨｰﾙﾄﾞを null ﾁｪｯｸなしで利用している(findbugs)
		if( remoteContext == null ) {
			final String errMsg = "#bootstrap()を先に実行しておいてください｡" ;
			throw new OgRuntimeException( errMsg );
		}

		final XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
		XDispatchHelper dispatcher = null;
		try {
			dispatcher = (XDispatchHelper) UnoRuntime.queryInterface( XDispatchHelper.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.DispatchHelper", remoteContext ) );
		}
		catch( final com.sun.star.uno.Exception ex ) {
			throw new HybsSystemException( "ﾃﾞｨｽﾊﾟｯﾁｬｰの取得に失敗しました｡", ex );
		}
		return dispatcher;
	}

	/**
	 * このﾌﾟﾛｾｽに対して固有に使用できる一時ﾌｧｲﾙのﾊﾟｽを指定します｡
	 *
	 * @og.rev 5.1.7.0 (2010/06/01) 新規作成
	 *
	 * @return 一時ﾌｧｲﾙのﾊﾟｽ
	 * @og.rtnNotNull
	 */
	public String getTempPath() {
//		return envPath + File.separator + "temp" + File.separator;
		return envPath + FS + "temp" + FS;		// 8.0.3.0 (2021/12/17)
	}
}
