/*
 * blanco Framework
 * Copyright (C) 2004-2006 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.valueobjectdotnet.actions;

import java.io.File;
import java.io.IOException;
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.doclisting.BlancoDocListingXml2CombinedXmlValueObject;
import blanco.plugin.valueobjectdotnet.BlancoValueObjectDotNetPlugin;
import blanco.plugin.valueobjectdotnet.editors.BlancoValueObjectDotNetEditor;
import blanco.plugin.valueobjectdotnet.editors.BlancoValueObjectDotNetPluginUtil;
import blanco.valueobjectdotnet.BlancoValueObjectDotNetMeta2Xml;
import blanco.valueobjectdotnet.BlancoValueObjectDotNetXml2SourceFile;

/**
 * 生成アクションの処理です。
 * 
 * TODO 共通化候補：抽象化クラスの自動生成を検討してください。
 * 
 * @author IGA Tosiki
 */
public class BlancoValueObjectDotNetGenerateAction {
    /**
     * blancoValueObject Javaソースコード生成処理を実行します。
     * 
     * @param ifile
     *            処理対象のファイル。
     * @param shell
     *            親のシェル。
     * @param workbenchMonitor
     *            ワークベンチモニタ。
     * @param metaDir
     *            メタディレクトリ。
     * @param isNameAdjust
     *            名前変形を行うかどうか。
     * @throws InvocationTargetException
     *             実行時の例外。
     * @throws InterruptedException
     *             割り込み例外。
     */
    public final void process(final IFile ifile, final Shell shell,
            IProgressMonitor workbenchMonitor, final String metaDir,
            final boolean isNameAdjust) throws InvocationTargetException,
            InterruptedException {
        if (ifile.getProject().getFolder(metaDir).exists() == false) {
            MessageDialog.openWarning(shell, "ソースコード生成", "メタディレクトリ (" + metaDir
                    + ") が存在しません。処理を中断します。");
            return;
        }

        // Eclipse 3.3 対策: begin
        // workbenchMonitorを利用することを停止しました。
        // org.eclipse.swt.SWTException: Invalid thread access が発生してしまいます。
        // Eclipse 3.3 対策: end

        try {
            // Eclipse プラグインの所定のルールに従って自動生成ソースコードの文字エンコーディングを決定。
            final String encoding = ifile.getProject().getDefaultCharset(true);

            PlatformUI.getWorkbench().getProgressService().busyCursorWhile(
                    new IRunnableWithProgress() {
                        public void run(final IProgressMonitor monitor)
                                throws InvocationTargetException,
                                InterruptedException {
                            try {
                                processInternal(ifile, monitor, metaDir,
                                        isNameAdjust, monitor, encoding);
                                return;
                            } catch (Exception e) {
                                // 最終防衛ライン
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (Error e) {
                                // 最終防衛ライン
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } finally {
                                // 最後の最後では done扱いとします。
                                monitor.done();
                            }
                        }
                    });
        } catch (InvocationTargetException ex) {
            BlancoValueObjectDotNetPlugin.log(ex);
            // 例外の中から本当の原因を取り出すことがポイントです。
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "例外が発生しました。処理を中断します。\n" + ex.getCause().toString());
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            BlancoValueObjectDotNetPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "割り込み中断例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Exception ex) {
            BlancoValueObjectDotNetPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬ例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Error er) {
            BlancoValueObjectDotNetPlugin.log(er);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬエラーが発生しました。処理を中断します。\n" + er.toString());
            er.printStackTrace();
        }
    }

    /**
     * 実際のアクション処理内容。
     * 
     * @param ifile
     * @param workbenchMonitor
     * @param metaDir
     * @param isNameAdjust
     * @param monitor
     * @throws CoreException
     * @throws IOException
     * @throws TransformerException
     */
    protected void processInternal(final IFile ifile,
            final IProgressMonitor workbenchMonitor, final String metaDir,
            final boolean isNameAdjust, final IProgressMonitor monitor,
            final String argEncoding) throws CoreException, IOException,
            TransformerException {
        monitor.beginTask("blancoValueObjectDotNetソースコード生成", 10);

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

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

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

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

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

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

        final IFolder findFolder = BlancoValueObjectDotNetPluginUtil
                .findFolder(ifile, workbenchMonitor, metaDir);
        findFolder.refreshLocal(IResource.DEPTH_INFINITE, workbenchMonitor);

        new BlancoValueObjectDotNetMeta2Xml() {
            protected boolean progress(final int progressCurrent,
                    final int progressTotal, final String progressItem) {
                if (monitor.isCanceled()) {
                    // 処理中断します。
                    return false;
                }

                monitor.subTask("メタファイル処理: ファイル[" + progressItem + "]を処理します ("
                        + progressCurrent + "/" + progressTotal + ")");

                return true;
            }
        }.processDirectory(findFolder.getLocation().toFile(), tempFolder
                .getLocation().toFile().getAbsolutePath());

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

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

        tempFolder.refreshLocal(IResource.DEPTH_INFINITE, workbenchMonitor);

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

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

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

        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("xml") == false) {
                    // 拡張子がxml以外のファイルはスキップします。
                    continue;
                }

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

                try {
                    final BlancoValueObjectDotNetXml2SourceFile xml2JavaClass = new BlancoValueObjectDotNetXml2SourceFile();
                    xml2JavaClass.setEncoding(argEncoding);
                    xml2JavaClass.setNameAdjust(isNameAdjust);
                    xml2JavaClass.process(fileLook.getLocation().toFile(),
                            blancoTargetDirectory);
                } catch (IllegalArgumentException ex) {
                    throw new IllegalArgumentException(

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

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

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

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

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

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

        folderTargetDirectory.refreshLocal(IResource.DEPTH_INFINITE,
                workbenchMonitor);
        return;
    }
}