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

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import com.jacob.com.ComThread;

import java.io.File;

import org.opengion.fukurou.util.FileInfo;
import org.opengion.fukurou.util.StringUtil;
import static org.opengion.fukurou.util.HybsConst.CR;

/**
 * JACOB - Java COM Bridge を使用した、ユーティリティークラスです。
 *
 * JavaからのCOMオートメーションコンポーネント呼び出しを可能とする 
 * JAVA-COMブリッジです。COMライブラリのネイティブな呼び出しにJNIを使います。
 * JACOBは、32bitおよび64bitのJVMをサポートするX86およびx64環境で動作します。 
 * http://sourceforge.jp/projects/sfnet_jacob-project/  (日本語プロジェクト)
 * http://sourceforge.net/projects/jacob-project/       (本家)
 *
 * まずは、EXCEL 関連のみ用意しています。
 *
 * 設定：
 *    jacob-1.18-M2.zip をダウンロードし、
 *      ①jacob-1.18-M2-x64.dll または、jacob-1.18-M2-x86.dll を、
 *        Windowsのシステムディレクトリにコピーします。 （例：C:\Windows\System32）
 *      ②jacob.jar を、クラスパスに登録します。
 *        ここでは、名称を、jacob-1.18-M2.jar に変更し、jre\lib\ext にコピーしています。
 *
 * @og.rev 6.2.4.0 (2015/05/15) 新規作成
 * @og.group その他
 *
 * @version  6.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public final class JacobUtil {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.3.9.0 (2015/11/06)" ;

	private static final String PDF_SUFIX = "pdf" ;					// 6.2.6.0 (2015/06/19)
	private static final String XLS_SUFIX = "xls,xlsx,xlsm" ;		// 6.2.6.0 (2015/06/19)

	/**
	 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
	 */
	private JacobUtil() {}

	/**
	 * Excelファイルを、別の名前（または、同じ名前）にコピーします。
	 *
	 * これは、EXCELか、PDFかを判別して、それぞれ、copyExcel か、excel2PDF を
	 * 呼び出します。
	 * 拡張子のチェックも行います。
	 *
	 * セーブ先のファイルが既に存在した場合の警告は表示しません。
	 *
	 * @og.rev 6.2.6.0 (2015/06/19) 新規追加
	 *
	 * @param	inFile  入力ファイル
	 * @param	outFile 出力ファイル
	 */
	public static void saveAs( final File inFile , final File outFile ) {
		final String inSfx  = FileInfo.getSUFIX( inFile  );
		final String outSfx = FileInfo.getSUFIX( outFile );

		if( XLS_SUFIX.contains( inSfx ) ) {
			if( XLS_SUFIX.contains( outSfx ) ) {
				copyExcel( inFile,outFile );
			}
			else if( PDF_SUFIX.contains( outSfx ) ) {
				excel2PDF( inFile,outFile );
			}
			else {
				final String errMsg = "出力ファイルの拡張子が、サポートされていません。" + CR
										+ " inFile=[" + inFile + "] , outFile=[" + outFile + "]" + CR
										+ " , OUT SUFIX=" + XLS_SUFIX + "," + PDF_SUFIX + " のうち、どれか" + CR;
				throw new RuntimeException( errMsg );
			}
		}
		else {
			final String errMsg = "入力ファイルの拡張子が、サポートされていません。" + CR
									+ " inFile=[" + inFile + "] , outFile=[" + outFile + "]" + CR
									+ " , IN SUFIX=" + XLS_SUFIX + " のうち、どれか" + CR;
			throw new RuntimeException( errMsg );
		}
	}

	/**
	 * Excelファイルを、別の名前（または、同じ名前）にコピーします。
	 *
	 * ファイルのコピーではなく、オープンして、セーブします。
	 * xlsxファイルで、ファイル形式が、おかしいケース(zipで開けない)場合に、
	 * このメソッドで開いてセーブすれば、正しい形式に直せます。
	 *
	 * セーブ先のファイルが既に存在した場合の警告は表示しません。
	 *
	 * @og.rev 6.2.4.0 (2015/05/15) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) synchronized 処理を外す。
	 *
	 * @param	inFile  入力ファイル
	 * @param	outFile 出力ファイル
	 */
//	public static synchronized void copyExcel( final File inFile , final File outFile ) {
	public static void copyExcel( final File inFile , final File outFile ) {
		final String inPath  = inFile.getAbsolutePath() ;
		final String outPath = outFile.getAbsolutePath() ;

		final ActiveXComponent excl = new ActiveXComponent( "Excel.Application" );
		try {
			ComThread.InitSTA();
		//	excl.setProperty( "Visible",	   Variant.VT_TRUE  );		// 表示 ＯＮ
			excl.setProperty( "DisplayAlerts", Variant.VT_FALSE );		// 警告メッセージをON/OFF

			final Dispatch workbooks = excl.getProperty( "Workbooks" ).toDispatch();
			final Dispatch workbook = Dispatch.call( workbooks, "Open", inPath ).toDispatch();

			Dispatch.call( workbook, "SaveAs", outPath );
			Dispatch.call( workbook, "Close" , Variant.VT_FALSE );
		}
		catch ( Throwable th ) {
			final String errMsg = "ActiveXComponent Copy処理中にエラーが発生しました。" + CR
									+ th.getMessage() + CR
									+ " inFile=[" + inFile + "] , outFile=[" + outFile + "]" ;
			System.err.println( errMsg );
			System.err.println( StringUtil.ogStackTrace( th ) );
			throw new RuntimeException( errMsg,th );
		}
		finally {
			excl.invoke( "Quit", new Variant[] {} );
			ComThread.Release();
		}
	}

	/**
	 * Excelファイルを、PDFファイルに変換します。
	 *
	 * ファイルをオープンして、PDFでセーブします。
	 * 例えば、セーブ先のファイルが既に存在した場合、上書き許可のダイアログが
	 *
	 * @og.rev 6.2.4.0 (2015/05/15) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) synchronized 処理を外す。
	 *
	 * @param	inFile  入力ファイル
	 * @param	outPdf  出力PDFファイル
	 */
//	public static synchronized void excel2PDF( final File inFile , final File outPdf ) {
	public static void excel2PDF( final File inFile , final File outPdf ) {
		final String inPath  = inFile.getAbsolutePath() ;
		final String outPath = outPdf.getAbsolutePath() ;

		final ActiveXComponent excl = new ActiveXComponent( "Excel.Application" );
		try {
			ComThread.InitSTA();
		//	excl.setProperty( "Visible",	   Variant.VT_TRUE  );		// 表示 ＯＮ
			excl.setProperty( "DisplayAlerts", Variant.VT_FALSE );		// 警告メッセージをON/OFF

			final Dispatch workbooks = excl.getProperty( "Workbooks" ).toDispatch();
			final Dispatch workbook = Dispatch.call( workbooks, "Open", inPath ).toDispatch();

			Dispatch.call( workbook, "SaveAs", outPath, new Variant(57) );
			Dispatch.call( workbook, "Close" , Variant.VT_FALSE );
		}
		catch ( Throwable th ) {
			final String errMsg = "ActiveXComponent PDF変換処理中にエラーが発生しました。" + CR
									+ th.getMessage() + CR
									+ " inFile=[" + inFile + "] , outPdf=[" + outPdf + "]" ;
			System.err.println( errMsg );
			System.err.println( StringUtil.ogStackTrace( th ) );
			throw new RuntimeException( errMsg,th );
		}
		finally {
			excl.invoke( "Quit", new Variant[] {} );
			ComThread.Release();
		}
	}

	/**
	 * Excelファイルを、指定のプリンタに印刷します。
	 *
	 * @og.rev 6.2.6.0 (2015/06/19) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) synchronized 処理を外す。
	 *
	 * @param	inFile  入力ファイル
	 * @param	printer プリンタ名
	 */
//	public static synchronized void toPrint( final File inFile , final String printer ) {
	public static void toPrint( final File inFile , final String printer ) {
		final String inPath  = inFile.getAbsolutePath() ;

		final ActiveXComponent excl = new ActiveXComponent( "Excel.Application" );
		try {
			ComThread.InitSTA();
		//	excl.setProperty( "Visible",	   Variant.VT_TRUE  );		// 表示 ＯＮ
			excl.setProperty( "DisplayAlerts", Variant.VT_FALSE );		// 警告メッセージをON/OFF

			final Dispatch workbooks = excl.getProperty( "Workbooks" ).toDispatch();
			final Dispatch workbook = Dispatch.call( workbooks, "Open", inPath ).toDispatch();
	//		final Dispatch sheet    = Dispatch.call( workbook , "ActiveSheet" ).toDispatch();

	//		Dispatch.put( excl, "ActivePrinter", new Variant( printer ) );
	//		Dispatch.call( sheet, "PrintOut" );

			Dispatch.call( workbook, "PrintOut", Variant.DEFAULT ,Variant.DEFAULT, Variant.DEFAULT, Variant.VT_FALSE, new Variant( printer ) );
			Dispatch.call( workbook, "Close" , Variant.VT_FALSE );
		}
		catch ( Throwable th ) {
			final String errMsg = "ActiveXComponent 印刷処理中にエラーが発生しました。" + CR
									+ th.getMessage() + CR
									+ " inFile=[" + inFile + "] , printer=[" + printer + "]" ;
			System.err.println( errMsg );
			System.err.println( StringUtil.ogStackTrace( th ) );
			throw new RuntimeException( errMsg,th );
		}
		finally {
			excl.invoke( "Quit", new Variant[] {} );
			ComThread.Release();
		}
	}

	/**
	 * アプリケーションのサンプルです。
	 *
	 * 入力ファイル名 は必須で、第一引数固定です。
	 * 第二引数は、出力ファイル名で、無ければ、第一引数と同じファイル名で、上書き保存されます。
	 * 出力ファイル名の拡張子を、PDF に指定すれば、PDF 変換を行います。
	 *
	 * Usage: java org.opengion.fukurou.model.JacobUtil  [-P(DF)] 入力ファイル名 [出力ファイル名]
	 *
	 *   ※ 
	 *      例) java org.opengion.fukurou.model.JacobUtil AAA.xlsx BBB.pdf
	 *                 AAA.xlsx → BBB.pdf
	 *
	 *      例) java org.opengion.fukurou.model.JacobUtil AAA.xlsx
	 *                 AAA.xlsx → AAA.xlsx (Officeの正式な形に成形される)
	 *
	 *   ※ -P(DF)を指定する場合は、入力ファイルに拡張子を指定せず、複数引数に記述します。
	 *      その場合、.xlsx を自動的に付けて読取り、.pdf を自動的に付けて出力します。
	 *      例) java org.opengion.fukurou.model.JacobUtil -PDF AAA BBB CCC
	 *                 AAA.xlsx → AAA.pdf , BBB.xlsx → BBB.pdf , CCC.xlsx → CCC.pdf
	 *
	 * @og.rev 6.2.4.0 (2015/05/15) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) PDF変換を一括処理できるように、コマンド追加。
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		final String usageMsg = "Usage: java org.opengion.fukurou.model.JacobUtil [-P(DF)] 入力ファイル名 [出力ファイル名]" ;
		if( args.length == 0 ) {
			System.err.println( usageMsg );
			return ;
		}

		if( args[0].indexOf( "-P" ) >= 0 ) {
			// 実験的にマルチスレッドでPDF作成を行ってみます。
			final java.util.concurrent.ExecutorService service = java.util.concurrent.Executors.newCachedThreadPool();
	//		final java.util.concurrent.ExecutorService service = java.util.concurrent.Executors.newFixedThreadPool(args.length-1);
	//		System.out.println("task start");
			final java.util.List<java.util.concurrent.Future<String>> list = new java.util.ArrayList<>();
			for( int i=1; i<args.length; i++ ) {
				final File inFile = new File( args[i]+".xlsx" );
				final File otFile = new File( args[i]+".pdf" );

				list.add(
					service.submit( () -> { JacobUtil.saveAs( inFile , otFile ); return otFile.getName(); } )
				);
			}

			// 一応、Future で、結果を受け取っているが、エラー時は、RuntimeException が発生しているハズ。
			for( final java.util.concurrent.Future<String> future : list) {
				try {
		//			System.out.println( future.get() );
					System.out.println( future.get( 10 , java.util.concurrent.TimeUnit.MINUTES ) );		// 10分
				}
				catch( InterruptedException
							| java.util.concurrent.TimeoutException
							| java.util.concurrent.ExecutionException ex ) {
					ex.printStackTrace();
				}
			}

	//		System.out.println("task end");
			service.shutdown();
		}
		else {
			final File inFile  = new File( args[0] );
			final File outFile = args.length == 1 ? inFile : new File( args[1] );

			JacobUtil.saveAs( inFile , outFile );
		}
	}
}
