/*
 * blanco Framework
 * Copyright (c) 2004-2008 NTT DATA BUSINESS BRAINS Corp.
 * 
 * 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.soap.actions;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
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.commons.io.File2StreamWrapper;
import blanco.commons.util.BlancoFileUtil;
import blanco.commons.util.BlancoNameUtil;
import blanco.plugin.soap.BlancoSOAPPlugin;
import blanco.plugin.soap.editors.BlancoSOAPPluginUtil;
import blanco.soap.terasoluna.rich.ParseTelegramProcessDefInfo;
import blanco.soap.terasoluna.rich.TerasolunaBLogicWsdl2BLogicInterface;
import blanco.soap.terasoluna.rich.TerasolunaBLogicWsdl2Controller;
import blanco.soap.terasoluna.rich.TerasolunaBLogicWsdl2NamespacesProperties;
import blanco.valueobject.BlancoValueObjectXml2JavaClass;
import blanco.xsd.BlancoXsdXsd2Castor;
import blanco.xsd.BlancoXsdXsd2CastorTest;
import blanco.xsd.BlancoXsdXsd2TemplateXml;
import blanco.xsd.BlancoXsdXsd2ValueObject;
import blanco.xsd.BlancoXsdXsdValidator;
import blanco.xsd.parser.ComplexTypeStructure;

/**
 * XSD生成を駆動するアクション
 * 
 * @author IGA Tosiki
 */
public class BlancoXsd2TerasolunaSourceGenerateAction {
    /**
     * GUIデバッグ用の低速モード。通常はfalseで運用します。
     */
    private static final boolean IS_DEBUG_SLOW = false;

    /**
     * blancoSOAPソースコード生成処理を実行します。
     * 
     * @param input
     * @return
     * @throws InvocationTargetException
     * @throws InterruptedException
     */
    public static final void process(final IFile ifile, final Shell shell,
            IProgressMonitor workbenchMonitor, final String metaDir)
            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 {
            // PlatformUI.getWorkbench().getProgressService()を有益と判断。
            PlatformUI.getWorkbench().getProgressService().busyCursorWhile(
                    new IRunnableWithProgress() {
                        public void run(IProgressMonitor monitor)
                                throws InvocationTargetException,
                                InterruptedException {
                            try {
                                monitor.beginTask("blancoXsdソースコード生成", 10);

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

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

                                final IFolder folderDirBlanco = ifile
                                        .getProject().getFolder("blanco");
                                folderDirBlanco.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);
                                if (folderDirBlanco.exists() == false) {
                                    folderDirBlanco.create(true, true, monitor);
                                }

                                monitor.subTask("フォルダ作成: blanco/mainフォルダ");
                                final IFolder folderDirBlancoMain = folderDirBlanco
                                        .getFolder("main");
                                // 更新は不要
                                if (folderDirBlancoMain.exists() == false) {
                                    folderDirBlancoMain.create(true, true,
                                            monitor);
                                }

                                monitor.subTask("フォルダ作成: blanco/testフォルダ");
                                final IFolder folderDirBlancoTest = folderDirBlanco
                                        .getFolder("test");
                                // 更新は不要
                                if (folderDirBlancoTest.exists() == false) {
                                    folderDirBlancoTest.create(true, true,
                                            monitor);
                                }

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

                                // テンポラリフォルダを一旦削除します。
                                BlancoSOAPPluginUtil.deleteFolder(ifile,
                                        monitor, "tmp/soap/xsd");
                                BlancoSOAPPluginUtil.createFolder(ifile,
                                        monitor, "tmp/soap/xsd");

                                final IFolder folderDirSoapXsd = BlancoSOAPPluginUtil
                                        .findFolder(ifile, monitor,
                                                "tmp/soap/xsd");
                                folderDirSoapXsd.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);

                                final IFolder folderDirMetaDir = BlancoSOAPPluginUtil
                                        .findFolder(ifile, monitor, metaDir);
                                folderDirMetaDir.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);
                                final IResource[] resourcesMetaFiles = folderDirMetaDir
                                        .members();
                                for (int index = 0; index < resourcesMetaFiles.length; index++) {
                                    if (monitor.isCanceled()) {
                                        return;
                                    }
                                    if (resourcesMetaFiles[index] instanceof IFile) {
                                        final IFile fileLookMetaFile = (IFile) resourcesMetaFiles[index];
                                        if (fileLookMetaFile.getFileExtension()
                                                .equals("xsd") == false) {
                                            // 拡張子がxsd以外のファイルはスキップします。
                                            continue;
                                        }

                                        monitor.subTask("Javaソースコード作成: ファイル["
                                                + fileLookMetaFile.getName()
                                                + "]を処理します (" + (index + 1)
                                                + "/"
                                                + resourcesMetaFiles.length
                                                + ")");
                                        final String fileName = BlancoNameUtil
                                                .trimFileExtension(fileLookMetaFile
                                                        .getName());
                                        if (fileName == null) {
                                            throw new IllegalArgumentException(
                                                    "処理の過程で加工後のファイル名がnullになってしまいました。加工前のファイル名["
                                                            + fileName + "]");
                                        }
                                        try {
                                            // XSDファイルの妥当性検証を行います。
                                            new File2StreamWrapper(
                                                    fileLookMetaFile
                                                            .getLocation()
                                                            .toFile(), null) {
                                                protected void process(
                                                        InputStream inStream,
                                                        OutputStream outStreamIgnore)
                                                        throws Exception {
                                                    new BlancoXsdXsdValidator()
                                                            .process(inStream,
                                                                    fileName);
                                                }
                                            }.run();

                                            // ValueObject定義ファイルを生成します。
                                            new BlancoXsdXsd2ValueObject()
                                                    .process(
                                                            fileLookMetaFile
                                                                    .getLocation()
                                                                    .toFile(),
                                                            folderDirSoapXsd
                                                                    .getLocation()
                                                                    .toFile());
                                        } catch (IllegalArgumentException ex) {
                                            BlancoSOAPPlugin.log(ex);
                                            throw new IllegalArgumentException(

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

                                        try {
                                            final ComplexTypeStructure[] result = new ComplexTypeStructure[1];

                                            // Castor用の設定ファイルを生成します。
                                            final File fileCastorTarget = new File(
                                                    folderDirSoapXsd
                                                            .getLocation()
                                                            .toFile()
                                                            .getAbsolutePath()
                                                            + "/"
                                                            + fileName
                                                            + ".castorxml");
                                            final InputStream inStream = fileLookMetaFile
                                                    .getContents();
                                            try {
                                                new File2StreamWrapper(null,
                                                        fileCastorTarget) {
                                                    protected void process(
                                                            InputStream inStreamIgnore,
                                                            OutputStream outStream)
                                                            throws Exception {
                                                        result[0] = new BlancoXsdXsd2Castor()
                                                                .process(
                                                                        inStream,
                                                                        fileName,
                                                                        outStream);
                                                    }
                                                }.run();
                                            } finally {
                                                // 外で作ったストリームは外で閉じます。
                                                inStream.close();
                                            }

                                            // 外部オプション01を利用している場合には、blancoフォルダにcastor用設定ファイルおよびXSDをコピー
                                            final String targetFilenameWithoutExt = folderDirBlancoMain
                                                    .getLocation().toFile()
                                                    .getAbsolutePath()
                                                    + "/"
                                                    + result[0].getTypeOfJava()
                                                            .replace('.', '/');
                                            BlancoFileUtil.copy(
                                                    fileCastorTarget, new File(
                                                            targetFilenameWithoutExt
                                                                    + ".xml"));
                                            BlancoFileUtil.copy(
                                                    fileLookMetaFile
                                                            .getLocation()
                                                            .toFile(),
                                                    new File(
                                                            targetFilenameWithoutExt
                                                                    + ".xsd"));

                                        } catch (IllegalArgumentException ex) {
                                            BlancoSOAPPlugin.log(ex);
                                            throw new IllegalArgumentException(
                                                    "ファイル["
                                                            + fileLookMetaFile
                                                                    .getName()
                                                            + "]のCastor処理中にエラーが発生しました。処理を中断します。\n\n"
                                                            + ex.toString());
                                        }

                                        try {
                                            // XSDから典型的なXMLデータを生成します。
                                            new BlancoXsdXsd2TemplateXml()
                                                    .process(
                                                            fileName,
                                                            fileName,
                                                            fileLookMetaFile
                                                                    .getLocation()
                                                                    .toFile(),
                                                            new File(
                                                                    folderDirSoapXsd
                                                                            .getLocation()
                                                                            .toFile()
                                                                            .getAbsolutePath()
                                                                            + "/"
                                                                            + fileName
                                                                            + ".templatexml"));
                                        } catch (IllegalArgumentException ex) {
                                            BlancoSOAPPlugin.log(ex);
                                            throw new IllegalArgumentException(
                                                    "ファイル["
                                                            + fileLookMetaFile
                                                                    .getName()
                                                            + "]の処理中にエラーが発生しました。処理を中断します。\n\n"
                                                            + ex.toString());
                                        }

                                        try {
                                            // XSDから典型的なCastor書き込みJUnitを生成します。
                                            new BlancoXsdXsd2CastorTest()
                                                    .process(
                                                            new File(
                                                                    folderDirSoapXsd
                                                                            .getLocation()
                                                                            .toFile()
                                                                            .getAbsolutePath()
                                                                            + "/"
                                                                            + fileName
                                                                            + ".templatexml"),
                                                            fileLookMetaFile
                                                                    .getLocation()
                                                                    .toFile(),
                                                            folderDirBlanco
                                                                    .getLocation()
                                                                    .toFile()
                                                                    .getAbsolutePath());
                                        } catch (IllegalArgumentException ex) {
                                            BlancoSOAPPlugin.log(ex);
                                            throw new IllegalArgumentException(
                                                    "ファイル["
                                                            + fileLookMetaFile
                                                                    .getName()
                                                            + "]の処理中にエラーが発生しました。処理を中断します。\n\n"
                                                            + ex.toString());
                                        }

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

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

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

                                        monitor.subTask("Javaソースコード作成: ファイル["
                                                + fileLookValueObjectMeta
                                                        .getName()
                                                + "]を処理します (" + (index + 1)
                                                + "/"
                                                + resourcesMetaFiles.length
                                                + ")");
                                        try {
                                            new BlancoValueObjectXml2JavaClass()
                                                    .process(
                                                            fileLookValueObjectMeta
                                                                    .getLocation()
                                                                    .toFile(),
                                                            folderDirBlanco
                                                                    .getLocation()
                                                                    .toFile());
                                        } catch (IllegalArgumentException ex) {
                                            BlancoSOAPPlugin.log(ex);
                                            throw new IllegalArgumentException(
                                                    "ファイル["
                                                            + fileLookValueObjectMeta
                                                                    .getName()
                                                            + "]の処理中にエラーが発生しました。処理を中断します。\n\n"
                                                            + ex.toString());
                                        }

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

                                // 外部オプション01のキック
                                processExternalProgram(monitor,
                                        resourcesMetaFiles);

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

                                folderDirBlanco.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);

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

                                folderDirSoapXsd.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);

                                if (IS_DEBUG_SLOW) {
                                    Thread.sleep(1000);
                                }
                            } 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();
                            }
                        }

                        /**
                         * 外部オプション01の実行を行います。
                         * 
                         * @param monitor
                         * @param resourcesMeta
                         * @param blancoTargetDirectory
                         * @throws Exception
                         */
                        private void processExternalProgram(
                                final IProgressMonitor monitor,
                                final IResource[] resourcesMeta)
                                throws Exception {
                            // blancoフォルダが無ければ最初に作成します。
                            final IFolder folderTargetDirectoryBlanco = ifile
                                    .getProject().getFolder("blanco");
                            if (ifile.getProject().getFolder(metaDir).exists() == false) {
                                folderTargetDirectoryBlanco.create(true, true,
                                        monitor);
                            }
                            final File blancoTargetDirectory = folderTargetDirectoryBlanco
                                    .getLocation().toFile();
                            final File tempDirectory = ifile.getProject()
                                    .getFolder("tmp").getLocation().toFile();

                            // 【外部オプション01】
                            // 外部オプション01のキック:『初期化処理』
                            final TerasolunaBLogicWsdl2NamespacesProperties namespaceProperties = new TerasolunaBLogicWsdl2NamespacesProperties();

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

                                    monitor.subTask("Javaソースコード作成: ファイル["
                                            + fileLook.getName() + "]を処理します ("
                                            + (index + 1) + "/"
                                            + resourcesMeta.length + ")");
                                    try {
                                        // 外部オプション01のキック:『ファイルごとの処理』
                                        File fileLook1 = fileLook.getLocation()
                                                .toFile();
                                        // 電文処理定義のマップを取得。
                                        final HashMap mapTelegramProcessDef = new ParseTelegramProcessDefInfo()
                                                .process(tempDirectory);

                                        // コントローラの作成。
                                        new TerasolunaBLogicWsdl2Controller()
                                                .process(fileLook1,
                                                        mapTelegramProcessDef,
                                                        blancoTargetDirectory);

                                        // BLogicインタフェースの作成。
                                        new TerasolunaBLogicWsdl2BLogicInterface()
                                                .process(fileLook1,
                                                        mapTelegramProcessDef,
                                                        blancoTargetDirectory);

                                        // プロパティファイル生成のための解析を実施。
                                        namespaceProperties.parse(fileLook1);

                                    } catch (IllegalArgumentException ex) {
                                        BlancoSOAPPlugin.log(ex);
                                        throw new IllegalArgumentException(

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

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

                            // 【外部オプション01】
                            // 外部オプション01のキック:『終了処理』
                            // closeが呼び出されたのを契機にプロパティファイルを生成する。
                            new File2StreamWrapper(
                                    null,
                                    new File(
                                            folderTargetDirectoryBlanco
                                                    .getLocation().toFile()
                                                    .getAbsolutePath()
                                                    + "/main/"
                                                    + TerasolunaBLogicWsdl2NamespacesProperties.FILE_NAME)) {
                                protected void process(
                                        InputStream inStreamIgnore,
                                        OutputStream outStream)
                                        throws Exception {
                                    namespaceProperties.generate(outStream);
                                }
                            }.run();
                        }
                    });
        } catch (InvocationTargetException ex) {
            BlancoSOAPPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "例外が発生しました。処理を中断します。\n" + ex.getCause().toString());
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "割り込み中断例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Exception ex) {
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬ例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        }
    }
}