/*
 * 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 java.io.File;
import java.io.IOException;

import org.opengion.fukurou.util.FileUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

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.lang.XMultiComponentFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;

/**
 * OpenOfficeのプロセスを表すクラスです。
 *
 * bootstrap()メソッドが呼ばれたタイミングでsoffice.binのプロセスを生成します。
 * soffice.binのプロセスを引数なしで実装した場合、通常は各ユーザーで1プロセスしか
 * 生成されないため、-env:UserInstallationの引数を指定することで、仮想的に別ユーザー
 * として起動しています。
 * この"ユーザー"を表すキーは、コンストラクタの引数のidです。
 *
 * また、この仮想ユーザーで起動した場合、初回起動時にユーザー登録を促す画面が立ち上がります。
 * これを回避するため、デフォルトの環境ファイルをプロセス生成前にコピーすることで、認証済みの
 * 状態で立ち上がるようにしています。
 *
 * 起動したプロセスとの通知は名前付きパイプで行われます。パイプ名は、"env"+コンストラクタのidです。
 * プロセス起動と、名前付きパイプでの接続は非同期で行われます。
 * プロセス起動後、60秒経過しても接続できない場合は、BootstrapExceptionが発生します。
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public final class SOfficeProcess {

	/**
	 * OOoのインストールディレクトリ
	 */
	public static final String OFFICE_HOME =
		( new File ( System.getenv( "OFFICE_HOME" ) ).getAbsolutePath() ) + File.separator;

	/** 設定ファイルの雛形 */
	private static final String DEFAULT_ENV_PATH =
		OFFICE_HOME + "env" + File.separator + "_default";

	/** soffice.binのパス */
	private static final String SOFFICE_BIN =
		OFFICE_HOME + File.separator + "program" + File.separator + "soffice.bin";
	
	/** ローカルコンテキスト */
	private static XComponentContext xLocalContext = null;
	static {        
		try {
			xLocalContext = Bootstrap.createInitialComponentContext( null );
		}
		catch( Throwable th ) {
			System.out.println( "[ERROR]OOo:Can't start LocalContext,Check OFFICE_HOME!" );
			th.printStackTrace();
		}
	}

	/** リモートデスクトップインスタンス */
	private XDesktop desktop	= null;

	/** soffice.binのプロセス */
	private Process process		= null;
		
	/** 環境設定のパス */
	public static final String ENV_DIR = HybsSystem.url2dir( HybsSystem.sys( "FILE_URL" ) + "oooenv" ) + File.separator;
	private final String envPath;
		
	/** 環境設定ファイルのID */
	private final String envId;

	/**
	 * @og.rev 4.3.0.0 (2008/07/15) 設定ファイルを各コンテキストごとに置くように変更
	 * @param 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対応(パイプ名に":"が含まれていると接続できない)
	 * 
	 * @throws Exception
	 */
	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;
		String sPipeName = "uno" + "_" + HybsSystem.sys("HOST_URL").replace(':','_').replace('/','_') + "_" + envId;

		// start office process
		process = execOffice( envPath, sPipeName, 0 );
		System.out.println( "[INFO]OOo:Invoke soffice.bin,ENV-ID=" + envId );

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

		// connection string
		String sConnect = "uno:pipe,name=" + sPipeName + ";urp;StarOffice.ComponentContext";

		// wait until office is started
		XComponentContext xContext = null;
		try {
			for( int i = 0;; ++i ) {
				try {
					Object context = xUrlResolver.resolve( sConnect );
					xContext = (XComponentContext) UnoRuntime.queryInterface( XComponentContext.class, context );
					if( xContext == null ) { throw new BootstrapException( "no component context!" ); }
					break;
				}
				catch( 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
			XMultiComponentFactory componentFactory = xContext.getServiceManager();
			desktop = (XDesktop) UnoRuntime.queryInterface( XDesktop.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.Desktop", xContext ) );
		}
		catch ( Exception ex ) {
			throw new HybsSystemException( "[ERROR] Can't create Desktop Instance", ex );
		}

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

	/**
	 * デスクトップインスタンスを返します
	 * 
	 * @return デスクトップインスタンス
	 */
	public XDesktop getDesktop() {
		return desktop;
	}

	/**
	 * プロセスを終了します。
	 * また、同時に環境設定用のファイルも削除します。
	 */
	public void close() {
		process.destroy();
		FileUtil.deleteFiles( new File( envPath ) );
		System.out.println( "[INFO]OOo:Destroy process,ENV-ID=" + envId );
	}

	/**
	 * OOoの環境設定ファイルをコピーします。
	 * 
	 * @og.rev 4.3.0.0 (2008/07/24) OS依存をやめてJavaでコピーする
	 * @param envPath
	 * @throws Exception
	 */
	private static void checkEnv( final String envPath ) {

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

//		File file = new File( envPath ); // 万が一ファイルが消えていなかった時のため、常にコピー
//		if( !file.exists() || !file.isDirectory() ) {
//			String[] cmdArray = new String[7];
//			cmdArray[0] = "xcopy";
//			cmdArray[1] = "/e";
//			cmdArray[2] = "/q";
//			cmdArray[3] = "/y";
//			cmdArray[4] = "/i";
//			cmdArray[5] = DEFAULT_ENV_PATH;
//			cmdArray[6] = envPath;
//
//			Process proc = Runtime.getRuntime().exec( cmdArray );
//			proc.waitFor();
			
//		}
		// 4.3.0.0 (2008/07/24) OS依存からFileUtilを使うように変更
		FileUtil.copyDirectry( DEFAULT_ENV_PATH, envPath );
	}

	/**
	 * soffice.binを起動します。
	 * 
	 * @param envPath
	 * @param pipeName
	 * @param priority
	 * @return soffice.binのプロセス
	 */
	private static Process execOffice( final String envPath, final String pipeName, final int priority ) {
		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( '\\', '/' );
		cmdArray[10] = "-accept=pipe,name=" + pipeName + ";urp;";

		Process process;
		try {
			process = Runtime.getRuntime().exec( cmdArray );
		} catch ( IOException ex ) {
			throw new HybsSystemException( "[ERROR] Cant't exec soffice.bin", ex );
		}
//		pipe( process.getInputStream(), System.out, "CO> " );
//		pipe( process.getErrorStream(), System.err, "CE> " );

		return process;
	}

//	private static void pipe( final InputStream in, final PrintStream out, final String prefix ) {
//		new Thread( "Pipe: " + prefix ) {
//			public void run() {
//				BufferedReader r = new BufferedReader( new InputStreamReader( in ) );
//				try {
//					for( ;; ) {
//						String s = r.readLine();
//						if( s == null ) {
//							break;
//						}
//						out.println( prefix + s );
//					}
//				}
//				catch( java.io.IOException e ) {
//					e.printStackTrace( System.err );
//				}
//			}
//		}.start();
//	}
}

