/*
 * 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.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.system.ThrowUtil;							// 6.4.2.0 (2016/01/29)

/**
 * OpenOfficeのプロセスを表すクラスです。
 *
 * このクラスでは、TCPによりプロセスに接続を行います。
 * 基本的には、パイプ名による接続({@link SOfficeProcess})を利用すべきですが、
 * x64環境で、64Bit版のJavaを起動した場合、パイプ接続では、UnsatisfiedLinkErrorが発生します。
 * このような場合では、TCP接続を利用することで、上記エラーを回避することができます。
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public final class SOfficeProcessTcp extends SOfficeProcess {

	private static final boolean[] PORTS 	= new boolean[512];		// 6.4.1.1 (2016/01/16) ports → PORTS refactoring
	private static final Object LOCK		= new Object();			// 6.4.1.1 (2016/01/16) lock  → LOCK refactoring

	private final int initPort;
	private final int thisPort;

	/**
	 * コンストラクタです。
	 *
	 * @param	id			プロセスID
	 * @param	initPort	初期ポート
	 */
	protected SOfficeProcessTcp( final String id, final int initPort ) {
		super( id );

		this.initPort = initPort;
		this.thisPort = getThisPort();
	}

	/**
	 * TCP接続ポート番号を取得します。
	 *
	 * @return TCP接続ポート番号
	 */
	private int getThisPort() {
		try {
			Thread.sleep( 100 ); // 切断後すぐにopenされると、ポートチェックで引っかかるため100msWAIT
		}
		catch( final InterruptedException ex ) {
			// ここの Exception は、無視します。
		}

		int port = -1;
		synchronized( LOCK ) {
			for( int i=0; i<PORTS.length; i++ ) {
				// 6.0.0.1 (2014/04/25) These nested if statements could be combined
				if( !PORTS[i] && checkPort( initPort + i ) ) {
					PORTS[i] = true;
					port = initPort + i;
					break;
				}
			}
		}
		if( port < 0 ) {
			throw new HybsSystemException( "TCP接続ポートを取得することができません" );
		}

		return port;
	}

	/**
	 * 引数のポートが使用中かどうかを調べます。
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。
	 *
	 * @param port ポート番号
	 *
	 * @return	使用中かどうか
	 */
	private boolean checkPort( final int port ) {
		boolean flg = false;
		Socket sk = null;
		try {
			sk = new Socket();
			sk.connect( new InetSocketAddress( "localhost", port ) );
		}
		catch( final IOException ex ) {
			flg = true;
		}
		finally {
			try {
				if( sk != null ) { sk.close(); }	// 5.5.2.6 (2012/05/25) findbugs対応
			}
			catch( final IOException ex ) {
//				ex.printStackTrace();
				System.err.println( ThrowUtil.ogStackTrace( ex ) );				// 6.4.2.0 (2016/01/29)
			}
		}
		return flg;
	}

	/**
	 * Pipe名をキーにOpenOfficeのプロセスに接続するための文字列を生成します。
	 * ※TCP接続の場合、キーのPipe名は無視され、内部的に管理されるポート番号一覧より
	 *   接続ポートを取得します。
	 *
	 * @param key Pipe名(無視されます)
	 *
	 * @return 接続文字列
	 * @og.rtnNotNull
	 */
	@Override
	protected String getConnParam( final String key ) {
		System.out.println( "[INFO]OOo:TCP Connection Start,port=" + thisPort );
		return "uno:socket,host=localhost,tcpNoDelay=1,port=" + thisPort + ";urp;StarOffice.ComponentContext";
	}

	/**
	 * Pipe名をキーにOpenOfficeのプロセスを生成するためのパラメーター文字列を生成します。
	 * ※TCP接続の場合、キーのPipe名は無視され、内部的に管理されるポート番号一覧より
	 *   接続ポートを取得します。
	 *
	 * @param key Pipe名(無視されます)
	 *
	 * @return プロセス生成パラメーター
	 * @og.rtnNotNull
	 */
	@Override
	protected String getProcParam( final String key ) {
		return "-accept=socket,host=localhost,port=" + thisPort + ";urp;";
	}

	/**
	 * プロセスを終了します。
	 * また、同時に環境設定用のファイルも削除します。
	 * ここでは、プロセスを終了すると同時に、そのプロセスのポート番号を開放し、
	 * 次に起動されるプロセスで利用できるようにします。
	 */
	@Override
	public void close() {
		super.close();
		synchronized( LOCK ) {
			PORTS[thisPort-initPort] = false;
		}
		System.out.println( "[INFO]OOo:TCP Connection End(Release),port=" + thisPort );
	}
}
