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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

import com.sun.star.beans.PropertyValue;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XStorable;
import com.sun.star.io.IOException;
import com.sun.star.lang.EventObject;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.lang.XComponent;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.CloseVetoException;
import com.sun.star.util.XCloseable;
import com.sun.star.view.PrintJobEvent;
import com.sun.star.view.PrintableState;
import com.sun.star.view.XPrintJobBroadcaster;
import com.sun.star.view.XPrintJobListener;
import com.sun.star.view.XPrintable;

/**
 * ODSファイルの変換を行うためのユーティリティクラスです。
 *
 * @og.group 帳票システム
 *
 * @version  4.0
 * @author   Hiroki.Nakamura
 * @since    JDK1.6
 */
public class OdsConverter {

	/**
	 * インスタンスの生成を抑止します。
	 */
	private OdsConverter() {
		// 何もありません。(PMD エラー回避)
	}

	/**
	 * Calcコンポーネントを起動します。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。
	 * 
	 * @param desktop
	 * @param file
	 * @return Calcコンポーネント
	 */
	public static final XComponent open( final XDesktop desktop, final String file ) {
		PropertyValue[] calcProps = new PropertyValue[1];
		calcProps[0] = new PropertyValue();
		calcProps[0].Name = "Hidden";
		calcProps[0].Value = true;

		String url = "file:///" + ( file ).replace( '\\', '/' );
		XComponentLoader cloader = (XComponentLoader) UnoRuntime.queryInterface( XComponentLoader.class, desktop );
		XComponent calc;
        try {
	        calc = cloader.loadComponentFromURL( url, "_default", 0, calcProps );
        }
        catch ( IOException ex ) {
			throw new HybsSystemException( "Calc立ち上げ時にエラーが発生しました(入出力エラー)。", ex );
        }
        catch ( IllegalArgumentException ex ) {
			throw new HybsSystemException( "印刷時にエラーが発生しました(パラメーター不正)。", ex );
        }

		return calc;
	}

	/**
	 * Calcコンポーネントをクローズします。
	 * クローズのコマンドは、実際にファイルがクローズされる前に応答を返すため、
	 * 内部的には、実際にファイルがクローズされるまでループしています。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。
	 * 
	 * @og.rev 4.2.4.1 (2008/07/07 ) 終了処理を60回で終わるように修正
	 * @og.rev 4.3.0.0 (2008/07/15 ) ↑は6秒しか待っていなかったので、60秒待つように修正
	 * 
	 * @param calc
	 */
	public static final void close( final XComponent calc ) {

		XCloseable closeable = null;
		for( int i = 0;; ++i ) {
			try {
				closeable = (XCloseable) UnoRuntime.queryInterface( XCloseable.class, calc );
				closeable.close( true );
				break;
			}
			catch( CloseVetoException ex ) {
				// 4.2.4.1 (2008/07/07 )
				// 4.3.4.4 (2009/01/01)
				if( i == 600 ) { throw new HybsSystemException ( "sofficeプロセスに接続できません。", ex ); }
				try {
	                Thread.sleep( 100 );
                }
                catch ( InterruptedException ex2 ) {
                	throw new HybsSystemException ( ex2 );
                }
			}
		}
	}

	/**
	 * 印刷を行います。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。
	 * 
	 * @og.rev 4.3.0.0 (2008/07/16) スプールが終わるまでwaitし、さらにプリンタ発行の状況を監視し、正常終了かどうかを判断
	 * @og.rev 4.3.7.3 (2009/06/22) 存在しないプリンターを指定した場合のエラーハンドリングを追加
	 * @og.rev 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
	 * 
	 * @param calc
	 * @param printer
	 */
	public final static void print( final XComponent calc, final String printer ) {
		if( printer == null || printer.length() == 0 ) {
			throw new HybsSystemException( "プリンターが指定されていません。" );
		}

		XPrintable xprintable = (XPrintable) UnoRuntime.queryInterface( XPrintable.class, calc );
		XPrintJobBroadcaster selection = (XPrintJobBroadcaster) UnoRuntime.queryInterface(XPrintJobBroadcaster.class, xprintable); 
		MyPrintJobListener listener = new MyPrintJobListener ();
		selection.addPrintJobListener( listener );
		
		PropertyValue[] tmpProps = new PropertyValue[1];
		tmpProps[0] = new PropertyValue();
		tmpProps[0].Name = "Name";
		// 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
		// OSがLinuxの場合は、プリンタ名称の前後に"<",">"を付加
//		tmpProps[0].Value = "Linux".equals( HybsSystem.sys( "OS_INFO" ) ) ? "<" + printer + ">" : printer;
		tmpProps[0].Value = "LINUX".indexOf( HybsSystem.sys( "OS_INFO" ).toUpperCase( Locale.JAPAN ) ) >= 0 ? "<" + printer + ">" : printer;

		// 4.3.4.4 (2009/01/01)
		try {
			xprintable.setPrinter( tmpProps );
		}
		catch ( IllegalArgumentException ex ) {
			throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
		}

		// 4.3.7.3 (2009/06/22) 存在しないプリンタを指定した場合は、PropertyValueに
		// デフォルトプリンターが入るため、引数の値と合致しているかで正しく設定されたかを確認
		String curPrinter = null;
		PropertyValue[] chkProps = xprintable.getPrinter();
		for( int i=0; i<chkProps.length; i++ ) {
			if( "Name".equals( chkProps[i].Name) ) {
				curPrinter = (String)chkProps[i].Value;
				break;
			}
		}
		if( !(printer.equalsIgnoreCase( curPrinter ) ) ) {
			String msg = "プリンター[" + printer + "]を発行先に指定できませんでした。" + HybsSystem.CR
							+ "存在しないプリンタ名が指定されている可能性があります。";
			throw new HybsSystemException( msg );
		}

		// 4.3.0.0 (2008/07/16)
		PropertyValue[] printProps = new PropertyValue[1];
		printProps[0] = new PropertyValue();
		printProps[0].Name = "Wait";
		printProps[0].Value = true;

		// 4.3.4.4 (2009/01/01)
		try {
			xprintable.print( printProps );
		}
		catch ( IllegalArgumentException ex ) {
			throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
		}

		// 4.3.0.0 (2008/07/16)
		if( listener.getStatus() == null
				|| ( listener.getStatus() != PrintableState.JOB_COMPLETED && listener.getStatus() != PrintableState.JOB_SPOOLED ) ){
			throw new HybsSystemException ( "Error Occured while spooling print job. Check Spooler-Service!!!");
		}
	}
	
	/**
	 * プリンタジョブの状況を監視するリスナーです。
	 * 
	 * @author Hiroki.Nakamura
	 */
	private static class MyPrintJobListener implements XPrintJobListener {
		private PrintableState status = null;
		
		@Override
		public void printJobEvent( final PrintJobEvent event ) {
			status = event.State;
		}

		@Override
		public void disposing( final EventObject event ) {
			// 何もありません。(PMD エラー回避)
		}

		public PrintableState getStatus() {
			return status;
		}
	}

	/**
	 * PDF出力を行います。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。
	 * 
	 * @param calc
	 * @param file
	 * @param pdfPasswd
	 */
	public final static void pdf( final XComponent calc, final String file, final String pdfPasswd ) {

		PropertyValue[] storeProps;
		if( pdfPasswd == null || pdfPasswd.length() == 0 ) {
			storeProps = new PropertyValue[1];
			storeProps[0] = new PropertyValue();
			storeProps[0].Name = "FilterName";
			storeProps[0].Value = "calc_pdf_Export";
		}
		// 帳票要求テーブルでPDFパスワードが設定されている場合
		else {
			PropertyValue[] filterProps = new PropertyValue[2];
			filterProps[0] = new PropertyValue();
			filterProps[0].Name = "EncryptFile";
			filterProps[0].Value = true;
			filterProps[1] = new PropertyValue();
			filterProps[1].Name = "DocumentOpenPassword";
			filterProps[1].Value = pdfPasswd;

			storeProps = new PropertyValue[2];
			storeProps[0] = new PropertyValue();
			storeProps[0].Name = "FilterName";
			storeProps[0].Value = "calc_pdf_Export";
			storeProps[1] = new PropertyValue();
			storeProps[1].Name = "FilterData";
			storeProps[1].Value = filterProps;
		}

		String url = "file:///" + file.replace( '\\', '/' );
		XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, calc );
		// 4.3.4.4 (2009/01/01)
		try {
			xstorable.storeToURL( url, storeProps );
		}
        catch ( IOException ex ) {
	        throw new HybsSystemException( "PDFファイルへの変換時にエラーが発生しました。", ex );
        }
	}

	/**
	 * Excel出力を行います。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。 
	 * 
	 * @param calc
	 * @param file
	 */
	public final static void excel( final XComponent calc, final String file ){
		PropertyValue[] storeProps = new PropertyValue[1];
		storeProps[0] = new PropertyValue();
		storeProps[0].Name = "FilterName";
		storeProps[0].Value = "MS Excel 97";

		String url = "file:///" + file.replace( '\\', '/' );
		XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, calc );
		// 4.3.4.4 (2009/01/01)
		try {
			xstorable.storeAsURL( url, storeProps );
		}
        catch ( IOException ex ) {
	        throw new HybsSystemException( "Excelファイルへの変換時にエラーが発生しました。", ex );
        }
	}

	/**
	 * ODS出力を行います。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。
	 * 
	 * @param calc
	 * @param file
	 */
	public final static void ods( final XComponent calc, final String file ) {
		PropertyValue[] storeProps = new PropertyValue[1];
		storeProps[0] = new PropertyValue();
		storeProps[0].Name = "FilterName";
		storeProps[0].Value = "calc8";

		String url = "file:///" + file.replace( '\\', '/' );
		XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, calc );
		// 4.3.4.4 (2009/01/01)
		try {
	        xstorable.storeAsURL( url, storeProps );
        }
        catch ( IOException ex ) {
	        throw new HybsSystemException( "ODSファイルへの変換時にエラーが発生しました。", ex );
        }
	}

	/**
	 * ExcelファイルをODS形式のファイルに変換します。
	 *
	 * 例外が発生した場合は、desktop#terminate()でデスクトップインスタンスをCloseするのではなく、
	 * 必ずプロセスをkillして下さい。desktop#terminate()を行うと、プロセスからの応答がなくなり、
	 * スレッドが停止する可能性があります。 
	 * 
	 * @param inputFile
	 * @param outputFile
	 */
	public final static void convert( final String inputFile, final String outputFile ) {
		SOfficeProcess soffice = null;
		try {			
			// プロセスの取得
			soffice = new SOfficeProcess( "conv" );
			soffice.bootstrap();
			XDesktop desktop = soffice.getDesktop();

			// 起動・変換
			XComponent calc = open( desktop, inputFile );
			ods( calc, outputFile );
			System.out.println( "Converting "+ inputFile + " to " + outputFile );

			// Calcを閉じる
			close( calc );
		}
		catch( Exception ex ) {
			ex.printStackTrace();
		}
		finally {
			soffice.close();
		}
	}
	
	/**
	 * ExcelファイルをODS形式のファイルに変換します。
	 * 
	 * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main( final String[] args ) throws Exception {
		if( args.length < 2 ) {
			System.out.println( "usage : OdsConverter [input] [outputdir]" );
			return;
		}

		File input = new File( args[0] );
		File output = new File( args[1] );

		// 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
		if( output.mkdirs() ) {
			System.err.println( args[1] + " の ディレクトリ作成に失敗しました。" );
		}

		if( input.isDirectory() ) {
			File[] inputFiles = input.listFiles();
			for( int i = 0; i<inputFiles.length; i++ ) {
				if( inputFiles[i].getAbsolutePath().endsWith( ".xls" ) ) {
					String inputFile = inputFiles[i].getAbsolutePath();
					String outputFile = output.getAbsolutePath() + File.separator + inputFiles[i].getName().replace( ".xls", ".ods" );
					convert( inputFile, outputFile );
				}
			}
		}
		else {
			if( input.getAbsolutePath().endsWith( ".xls" ) ) {
				String inputFile = input.getAbsolutePath();
				String outputFile = output.getAbsolutePath() + File.separator + input.getName().replace( ".xls", ".ods" );
				convert( inputFile, outputFile );
			}
		}
	}
}
