/*
 * blancoDb Enterprise Edition
 * 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.dbee.actions;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;

import javax.xml.parsers.ParserConfigurationException;
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 org.xml.sax.SAXException;

import blanco.db.BlancoDbConstants;
import blanco.db.BlancoDbXml2JavaClass;
import blanco.db.common.BlancoDbMeta2Xml;
import blanco.db.common.BlancoDbTableMeta2Xml;
import blanco.db.common.valueobject.BlancoDbSetting;
import blanco.db.resourcebundle.BlancoDbResourceBundle;
import blanco.doclisting.BlancoDocListingXml2CombinedXmlDb;
import blanco.plugin.dbee.BlancoDbEEPlugin;
import blanco.plugin.dbee.editors.BlancoDbEEPluginUtil;

public class BlancoDbEEGenerateAction {
    private static final BlancoDbResourceBundle fBundle = new BlancoDbResourceBundle();

    /**
     * blancoDbソースコード生成処理を実行します。
     * 
     * @param ifile
     * @param shell
     * @param workbenchMonitor
     * @param metaDir
     * @param dbSetting
     * @param isSimpleTableProcess
     * @param isSqlMetaProcess
     * @throws InvocationTargetException
     * @throws InterruptedException
     */
    public static final void processBlancoDb(final IFile ifile,
            final Shell shell, IProgressMonitor workbenchMonitor,
            final String metaDir, final BlancoDbSetting dbSetting,
            final boolean isSimpleTableProcess, final boolean isSqlMetaProcess)
            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);
            dbSetting.setEncoding(encoding);

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

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

                                monitor.subTask("ディレクトリ: blancoフォルダの作成");
                                monitor.worked(1);

                                // 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();

                                // pluginでは相対フォルダに出力します。
                                dbSetting.setTargetDir(blancoTargetDirectory
                                        .getAbsolutePath());

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

                                if (isSimpleTableProcess) {
                                    processSimpleTable(ifile, shell, monitor,
                                            dbSetting, monitor);
                                }

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

                                if (isSqlMetaProcess) {
                                    processSqlMeta(ifile, shell, monitor,
                                            metaDir, dbSetting, monitor);
                                }

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

                                monitor.subTask("フォルダ更新: blancoフォルダをリフレッシュ");
                                monitor.worked(1);

                                folderTargetDirectoryBlanco.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);

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

                                monitor.subTask("フォルダ更新: blancoフォルダをリフレッシュ");

                                // tmpフォルダを同期
                                BlancoDbEEPluginUtil.refreshFolder(ifile,
                                        monitor, "tmp");

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

                                monitor.subTask("フォルダ更新: sqlフォルダをリフレッシュ");
                                // あくまでも保険として同期を取ります。
                                BlancoDbEEPluginUtil.refreshFolder(ifile,
                                        monitor, metaDir);
                            } catch (Exception e) {
                                // 最終防衛ライン
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .getMessage());
                            } catch (Error e) {
                                // 最終防衛ライン
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } finally {
                                monitor.done();
                            }
                        }
                    });
        } catch (InvocationTargetException ex) {
            BlancoDbEEPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "例外が発生しました。処理を中断します。\n"
                            + ex.getTargetException().getMessage());
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            BlancoDbEEPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "割り込み中断例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Exception ex) {
            BlancoDbEEPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬ例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Error er) {
            BlancoDbEEPlugin.log(er);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬエラーが発生しました。処理を中断します。\n" + er.toString());
            er.printStackTrace();
        }
    }

    /**
     * 単一表を処理します。
     * 
     * @param ifile
     * @param shell
     * @param workbenchMonitor
     * @param metaDir
     * @param basePackage
     * @param runtimePackage
     * @param connDef
     * @param blancoTargetDirectory
     * @param monitor
     * @throws InvocationTargetException
     * @throws InterruptedException
     * @throws CoreException
     * @throws SQLException
     * @throws BlancoDbException
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws ClassNotFoundException
     * @throws TransformerException
     */
    private static void processSimpleTable(final IFile ifile,
            final Shell shell, final IProgressMonitor workbenchMonitor,
            final BlancoDbSetting dbSetting, final IProgressMonitor monitor)
            throws InvocationTargetException, InterruptedException,
            CoreException, SQLException, SAXException, IOException,
            ParserConfigurationException, ClassNotFoundException,
            TransformerException {
        // 単一表用のテンポラリフォルダは一旦削除します。
        monitor.subTask("単一表: テンポラリフォルダを整理します。");
        BlancoDbEEPluginUtil.deleteFolder(ifile, workbenchMonitor, "tmp"
                + BlancoDbConstants.TARGET_SUBDIRECTORY + "/table");
        BlancoDbEEPluginUtil.createFolder(ifile, workbenchMonitor, "tmp"
                + BlancoDbConstants.TARGET_SUBDIRECTORY + "/table");
        final File blancoTempDirectoryTable = ifile.getProject().getFolder(
                "tmp" + BlancoDbConstants.TARGET_SUBDIRECTORY + "/table")
                .getLocation().toFile();

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

        monitor.subTask("単一表: データベース上の単一表メタ情報を取得します。");
        monitor.worked(1);

        final BlancoDbTableMeta2Xml tableMeta2Xml = new BlancoDbTableMeta2Xml() {
            public boolean progress(int progressCurrent, int progressTotal,
                    String progressItem) {
                if (monitor.isCanceled()) {
                    return false;
                }
                monitor.subTask("単一表 (" + progressCurrent + "/" + progressTotal
                        + "): 表[" + progressItem + "]を抽出");
                return true;
            }
        };
        tableMeta2Xml.process(dbSetting, blancoTempDirectoryTable);

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

        monitor.subTask("単一表: メタ情報をもとに R/Oマッピングソースコードを生成します。");
        monitor.worked(1);

        // 単一表のXMLファイルを元にR/Oマッピングを自動生成
        final BlancoDbXml2JavaClass generatorTable = new BlancoDbXml2JavaClass() {
            public boolean progress(int progressCurrent, int progressTotal,
                    String progressItem) {
                if (monitor.isCanceled()) {
                    return false;
                }
                monitor.subTask("単一表 (" + progressCurrent + "/" + progressTotal
                        + "): ファイル[" + progressItem + "]からR/Oマッピング生成");
                return true;
            }
        };

        generatorTable.process(dbSetting, blancoTempDirectoryTable);
    }

    /**
     * SQL定義書ファイルを処理します。
     * 
     * @param ifile
     * @param shell
     * @param workbenchMonitor
     * @param metaDir
     * @param basePackage
     * @param runtimePackage
     * @param connDef
     * @param blancoTargetDirectory
     * @param monitor
     * @throws InvocationTargetException
     * @throws InterruptedException
     * @throws CoreException
     * @throws SQLException
     * @throws BlancoDbException
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws TransformerException
     * @throws ClassNotFoundException
     */
    private static void processSqlMeta(final IFile ifile, final Shell shell,
            final IProgressMonitor workbenchMonitor, final String metaDir,
            final BlancoDbSetting dbSetting, final IProgressMonitor monitor)
            throws InvocationTargetException, InterruptedException,
            CoreException, SQLException, SAXException, IOException,
            ParserConfigurationException, TransformerException,
            ClassNotFoundException {
        monitor
                .subTask(fBundle.getMetafileDisplayname()
                        + ": テンポラリフォルダを整理します。");
        monitor.worked(1);

        // SQL定義書ファイルによる生成
        BlancoDbEEPluginUtil.deleteFolder(ifile, workbenchMonitor, "tmp"
                + BlancoDbConstants.TARGET_SUBDIRECTORY + "/sql");
        BlancoDbEEPluginUtil.createFolder(ifile, workbenchMonitor, "tmp"
                + BlancoDbConstants.TARGET_SUBDIRECTORY + "/sql");
        final IFolder searchMetaDir = ifile.getProject().getFolder(metaDir);

        final File blancoTempDirectorySql = ifile.getProject().getFolder(
                "tmp" + BlancoDbConstants.TARGET_SUBDIRECTORY + "/sql")
                .getLocation().toFile();

        final File blancoTempDirectoryDocListing = ifile.getProject()
                .getFolder("tmp/doclisting").getLocation().toFile();

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

        monitor.subTask(fBundle.getMetafileDisplayname() + ": "
                + fBundle.getMetafileDisplayname()
                + "の情報をもとに中間ファイル(XML)を生成します。");
        monitor.worked(1);

        searchMetaDir.refreshLocal(IResource.DEPTH_INFINITE, workbenchMonitor);

        // ExcelファイルからXMLファイルを自動生成します。
        new BlancoDbMeta2Xml() {
            public boolean progress(int progressCurrent, int progressTotal,
                    String progressItem) {
                if (monitor.isCanceled()) {
                    return false;
                }
                monitor.subTask(fBundle.getMetafileDisplayname() + "("
                        + progressCurrent + "/" + progressTotal + "): ファイル["
                        + progressItem + "]を抽出");
                return true;
            }
        }.processDirectory(searchMetaDir.getLocation().toFile(),
                blancoTempDirectorySql.getAbsolutePath());

        monitor.subTask(fBundle.getMetafileDisplayname()
                + ": 情報をもとに R/Oマッピングソースコードを生成します。");
        monitor.worked(1);

        // SQL定義書ファイルのXMLファイルを元にR/Oマッピングを自動生成
        final BlancoDbXml2JavaClass generatorSql = new BlancoDbXml2JavaClass() {
            public boolean progress(int progressCurrent, int progressTotal,
                    String progressItem) {
                if (monitor.isCanceled()) {
                    return false;
                }
                monitor.subTask(fBundle.getMetafileDisplayname() + "("
                        + progressCurrent + "/" + progressTotal + "): ファイル["
                        + progressItem + "]からR/Oマッピング生成");
                return true;
            }

        };

        generatorSql.process(dbSetting, blancoTempDirectorySql);

        // SQL定義書の一覧のための中間ファイルを作成。
        final BlancoDocListingXml2CombinedXmlDb doclistingDb = new BlancoDocListingXml2CombinedXmlDb();
        doclistingDb.setBasePackage(dbSetting.getBasePackage());
        doclistingDb.setRuntimePackage(dbSetting.getRuntimePackage());
        doclistingDb.process(blancoTempDirectorySql,
                blancoTempDirectoryDocListing);
    }
}