/*
 * blanco Framework
 * Copyright (C) 2004-2005 IGA Tosiki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.plugin.resourcebundle.actions;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;

import javax.xml.transform.TransformerException;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

import blanco.constants.BlancoConstantsXml2JavaClass;
import blanco.doclisting.BlancoDocListingXml2CombinedXmlResourceBundle;
import blanco.plugin.resourcebundle.BlancoResourceBundlePlugin;
import blanco.plugin.resourcebundle.editors.BlancoResourceBundlePluginUtil;
import blanco.resourcebundle.BlancoResourceBundleConstants;
import blanco.resourcebundle.BlancoResourceBundleMeta2Xml;
import blanco.resourcebundle.BlancoResourceBundleXml2CombinedXml;
import blanco.resourcebundle.BlancoResourceBundleXml2ConstantsXml;
import blanco.resourcebundle.BlancoResourceBundleXml2JavaClass;
import blanco.resourcebundle.BlancoResourceBundleXml2Properties;
import blanco.resourcebundle.BlancoResourceBundleXmlValidator;
import blanco.resourcebundle.resourcebundle.BlancoResourceBundleResourceBundle;

/**
 * リソースバンドル定義書からプロパティファイルおよびアクセサJavaソースコードを生成する Eclipseアクションです。
 * 
 * このソースコードはblancoResourceBundleの一部です。<br>
 * 
 * @author IGA Tosiki
 */
public class BlancoResourceBundleGenerateAction {
    /**
     * リソースバンドルアクセサのインスタンス。
     */
    private static final BlancoResourceBundleResourceBundle fBundle = new BlancoResourceBundleResourceBundle();

    /**
     * GUIデバッグ用の低速モード。通常はfalseで運用します。
     */
    private static final boolean IS_DEBUG_SLOW = false;

    /**
     * リソースバンドル定義書からプロパティファイルおよびアクセサJavaソースコードを生成する Eclipseアクションです。
     * 
     * @param ifile
     *            処理対象のファイルオブジェクト。
     * @param shell
     *            親シェル。
     * @param workbenchMonitor
     *            ワークベンチモニター。
     * @param metaDir
     *            メタファイルが格納されているディレクトリ。
     * @param isFailOnMessageFormatError
     *            リソースバンドル文字列をMessageFormatによるパースを行った際に、例外が発生したら処理を中断するかどうかのフラグ。
     * @throws InvocationTargetException
     *             実行時例外が発生した場合。
     * @throws InterruptedException
     *             割り込み例外が発生した場合。
     */
    public static final void processBlancoResourceBundle(final IFile ifile,
            final Shell shell, final IProgressMonitor workbenchMonitor,
            final String metaDir, final boolean isFailOnMessageFormatError)
            throws InvocationTargetException, InterruptedException {
        if (ifile.getProject().getFolder(metaDir).exists() == false) {
            MessageDialog.openWarning(shell, "ソースコード生成", "メタディレクトリ (" + metaDir
                    + ") が存在しません。処理を中断します。");
            return;
        }

        try {
            // PlatformUI.getWorkbench().getProgressService()を有益と判断。
            PlatformUI.getWorkbench().getProgressService().busyCursorWhile(
                    new IRunnableWithProgress() {
                        public void run(IProgressMonitor monitor)
                                throws InvocationTargetException,
                                InterruptedException {
                            try {
                                monitor
                                        .beginTask(
                                                BlancoResourceBundleConstants.PRODUCT_NAME
                                                        + "ソースコード生成", 10);

                                if (monitor.isCanceled()) {
                                    return;
                                }

                                monitor.subTask("ディレクトリ: テンポラリフォルダの調整。");

                                // テンポラリフォルダを一旦削除します。
                                BlancoResourceBundlePluginUtil
                                        .deleteFolder(
                                                ifile,
                                                workbenchMonitor,
                                                "tmp"
                                                        + BlancoResourceBundleConstants.TARGET_SUBDIRECTORY);
                                BlancoResourceBundlePluginUtil
                                        .createFolder(
                                                ifile,
                                                workbenchMonitor,
                                                "tmp"
                                                        + BlancoResourceBundleConstants.TARGET_SUBDIRECTORY);
                                final IFolder tempFolder = BlancoResourceBundlePluginUtil
                                        .findFolder(
                                                ifile,
                                                workbenchMonitor,
                                                "tmp"
                                                        + BlancoResourceBundleConstants.TARGET_SUBDIRECTORY);

                                // ドキュメント一覧のための中間ファイル用ディレクトリ。
                                final File blancoTempDirectoryDocListing = ifile
                                        .getProject().getFolder(
                                                "tmp/doclisting").getLocation()
                                        .toFile();
                                blancoTempDirectoryDocListing.mkdirs();

                                if (monitor.isCanceled()) {
                                    return;
                                }

                                monitor.subTask(fBundle
                                        .getMetafileDisplayname()
                                        + " (*.xls)を処理します");
                                monitor.worked(1);

                                final IFolder findFolder = BlancoResourceBundlePluginUtil
                                        .findFolder(ifile, workbenchMonitor,
                                                metaDir);
                                findFolder.refreshLocal(
                                        IResource.DEPTH_INFINITE,
                                        workbenchMonitor);
                                IResource[] resourcesMeta = findFolder
                                        .members();
                                for (int index = 0; index < resourcesMeta.length; index++) {
                                    if (monitor.isCanceled()) {
                                        return;
                                    }

                                    if (resourcesMeta[index] instanceof IFile) {
                                        IFile fileLook = (IFile) resourcesMeta[index];
                                        if (fileLook.getFileExtension().equals(
                                                "xls") == false) {
                                            // xls以外の拡張子のファイルはスキップ
                                            continue;
                                        }

                                        IFile fileTarget = tempFolder
                                                .getFile(fileLook.getName()
                                                        + ".xml");
                                        IFile fileTargetCombined = tempFolder
                                                .getFile(fileLook.getName()
                                                        + ".combinedxml");
                                        monitor.subTask("メタファイル処理: ファイル["
                                                + fileLook.getName()
                                                + "]を処理します (" + (index + 1)
                                                + "/" + resourcesMeta.length
                                                + ")");
                                        InputStream inStream = null;
                                        OutputStream outStream = null;
                                        try {
                                            inStream = fileLook.getContents();
                                            outStream = new FileOutputStream(
                                                    fileTarget.getLocation()
                                                            .toFile());
                                            new BlancoResourceBundleMeta2Xml()
                                                    .process(inStream,
                                                            outStream);
                                            outStream.flush();
                                        } finally {
                                            if (inStream != null) {
                                                inStream.close();
                                            }
                                            if (outStream != null) {
                                                outStream.close();
                                            }
                                        }

                                        fileTarget.refreshLocal(
                                                IResource.DEPTH_INFINITE,
                                                workbenchMonitor);
                                        // 生成したXMLファイルを組み替えます。
                                        new BlancoResourceBundleXml2CombinedXml()
                                                .process(
                                                        fileTarget
                                                                .getLocation()
                                                                .toFile(),
                                                        fileTargetCombined
                                                                .getLocation()
                                                                .toFile());

                                        if (IS_DEBUG_SLOW) {
                                            Thread.sleep(1000);
                                        }
                                    }
                                }

                                if (monitor.isCanceled()) {
                                    return;
                                }

                                monitor.subTask("フォルダ更新: tmpフォルダ");
                                monitor.worked(1);

                                tempFolder.refreshLocal(
                                        IResource.DEPTH_INFINITE,
                                        workbenchMonitor);

                                monitor.subTask("フォルダ作成: blancoフォルダ");

                                IFolder folderTargetDirectory = ifile
                                        .getProject().getFolder("blanco");
                                if (ifile.getProject().getFolder(metaDir)
                                        .exists() == false) {
                                    folderTargetDirectory.create(true, true,
                                            workbenchMonitor);
                                }

                                monitor.subTask("フォルダ作成: blanco/mainフォルダ");
                                final IFolder folderTargetDirectoryWithMain = folderTargetDirectory
                                        .getFolder("main");
                                if (ifile.getProject().getFolder(metaDir)
                                        .exists() == false) {
                                    folderTargetDirectoryWithMain.create(true,
                                            true, workbenchMonitor);
                                }

                                final File blancoTargetDirectory = folderTargetDirectory
                                        .getLocation().toFile();

                                monitor.subTask("Javaソースコード作成");
                                monitor.worked(1);

                                IResource[] resourcesXml2 = tempFolder
                                        .members();
                                for (int index = 0; index < resourcesXml2.length; index++) {
                                    if (monitor.isCanceled()) {
                                        return;
                                    }
                                    if (resourcesXml2[index] instanceof IFile) {
                                        IFile fileLook = (IFile) resourcesXml2[index];
                                        if (fileLook.getFileExtension().equals(
                                                "combinedxml") == false) {
                                            // 拡張子がcombinedxml以外のファイルはスキップします。
                                            continue;
                                        }

                                        monitor.subTask("Javaソースコード作成: ファイル["
                                                + fileLook.getName()
                                                + "]を処理します (" + (index + 1)
                                                + "/" + resourcesXml2.length
                                                + ")");

                                        try {
                                            // 最初に妥当性チェックを行います。
                                            final BlancoResourceBundleXmlValidator xmlValidator = new BlancoResourceBundleXmlValidator();
                                            xmlValidator
                                                    .setFailOnMessageFormatError(isFailOnMessageFormatError);
                                            xmlValidator.process(fileLook
                                                    .getLocation().toFile(),
                                                    blancoTargetDirectory);

                                            if ("true".equals(fBundle
                                                    .getGenerateBundleSource())) {
                                                final BlancoResourceBundleXml2JavaClass xml2javaclass = new BlancoResourceBundleXml2JavaClass();
                                                xml2javaclass
                                                        .setFailOnMessageFormatError(isFailOnMessageFormatError);
                                                xml2javaclass.process(
                                                        fileLook.getLocation()
                                                                .toFile(),
                                                        blancoTargetDirectory);
                                            }

                                            if ("true"
                                                    .equals(fBundle
                                                            .getGenerateConstantsSource())) {
                                                new BlancoResourceBundleXml2ConstantsXml()
                                                        .process(
                                                                fileLook
                                                                        .getLocation()
                                                                        .toFile(),
                                                                tempFolder
                                                                        .getLocation()
                                                                        .toFile());
                                            }
                                        } catch (IllegalArgumentException ex) {
                                            throw new IllegalArgumentException(

                                                    "ファイル["
                                                            + fileLook
                                                                    .getName()
                                                            + "]の処理中にエラーが発生しました。処理を中断します。\n\n"
                                                            + ex.getMessage());
                                        }

                                        if (IS_DEBUG_SLOW) {
                                            Thread.sleep(1000);
                                        }

                                    }
                                }

                                if (fBundle.getGenerateConstantsSource()
                                        .equals("true")) {
                                    monitor.subTask("定数クラス作成");
                                    monitor.worked(1);

                                    final File[] filesConstantsXml = new File(
                                            tempFolder.getLocation().toFile()
                                                    .getAbsolutePath()
                                                    + "/constants").listFiles();
                                    if (filesConstantsXml != null) {
                                        for (int index = 0; index < filesConstantsXml.length; index++) {
                                            if (filesConstantsXml[index]
                                                    .getName().endsWith(
                                                            ".constantsxml")) {
                                                new BlancoConstantsXml2JavaClass()
                                                        .process(
                                                                filesConstantsXml[index],
                                                                folderTargetDirectory
                                                                        .getLocation()
                                                                        .toFile());
                                            }
                                        }
                                    }
                                }

                                monitor.subTask("プロパティファイル作成");
                                monitor.worked(1);

                                IResource[] resourcesXml = tempFolder.members();
                                for (int index = 0; index < resourcesXml.length; index++) {
                                    if (monitor.isCanceled()) {
                                        return;
                                    }
                                    if (resourcesXml[index] instanceof IFile) {
                                        IFile fileLook = (IFile) resourcesXml[index];
                                        if (fileLook.getFileExtension().equals(
                                                "xml") == false) {
                                            // 拡張子がxml以外のファイルはスキップします。
                                            continue;
                                        }

                                        monitor.subTask("プロパティファイル作成: ファイル["
                                                + fileLook.getName()
                                                + "]を処理します (" + (index + 1)
                                                + "/" + resourcesXml.length
                                                + ")");

                                        // blanco/mainあてに出力します。
                                        new BlancoResourceBundleXml2Properties()
                                                .process(fileLook.getLocation()
                                                        .toFile(),
                                                        folderTargetDirectoryWithMain
                                                                .getLocation()
                                                                .toFile());
                                    }

                                    if (IS_DEBUG_SLOW) {
                                        Thread.sleep(1000);
                                    }
                                }

                                if (monitor.isCanceled()) {
                                    return;
                                }

                                monitor.subTask("ドキュメント一覧のための中間ファイルを作成。");
                                monitor.worked(1);

                                // ドキュメント一覧のための中間ファイルを作成。
                                final BlancoDocListingXml2CombinedXmlResourceBundle doclistingResourceBundle = new BlancoDocListingXml2CombinedXmlResourceBundle();
                                doclistingResourceBundle.process(tempFolder
                                        .getLocation().toFile(),
                                        blancoTempDirectoryDocListing);

                                monitor.subTask("フォルダ更新: blancoフォルダを更新");
                                monitor.worked(1);

                                if (monitor.isCanceled()) {
                                    return;
                                }

                                folderTargetDirectory.refreshLocal(
                                        IResource.DEPTH_INFINITE,
                                        workbenchMonitor);

                                if (IS_DEBUG_SLOW) {
                                    Thread.sleep(1000);
                                }
                            } catch (CoreException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (IOException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (TransformerException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } finally {
                                // 最後の最後では done扱いとします。
                                monitor.done();
                            }
                        }
                    });
        } catch (InvocationTargetException ex) {
            BlancoResourceBundlePlugin.log(ex);
            // 例外の中から本当の原因を取り出すことがポイントです。
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "例外が発生しました。処理を中断します。\n" + ex.getCause().toString());
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            BlancoResourceBundlePlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "割り込み中断例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Exception ex) {
            BlancoResourceBundlePlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬ例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Error er) {
            BlancoResourceBundlePlugin.log(er);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬエラーが発生しました。処理を中断します。\n" + er.toString());
            er.printStackTrace();
        }
    }
}