/*
 * 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.dbdotnet.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.commons.util.BlancoStringUtil;
import blanco.db.BlancoDbDotNetConstants;
import blanco.db.BlancoDbMeta2XmlDotNet;
import blanco.db.BlancoDbXml2CsClassDotNet;
import blanco.db.common.BlancoDbTableMeta2Xml;
import blanco.db.common.valueobject.BlancoDbSetting;
import blanco.dbmetadata.valueobject.BlancoDbMetaDataColumnStructure;
import blanco.doclisting.BlancoDocListingXml2CombinedXmlDb;
import blanco.plugin.dbdotnet.BlancoDbDotNetPlugin;
import blanco.plugin.dbdotnet.editors.BlancoDbDotNetPluginUtil;
import blanco.plugin.dbdotnet.resourcebundle.BlancoDbDotNetPluginResourceBundle;

public class BlancoDbDotNetGenerateAction {
    /**
     * プラグインのリソースバンドル。
     */
    private static final BlancoDbDotNetPluginResourceBundle fBundlePlugin = new BlancoDbDotNetPluginResourceBundle();

    /**
     * blancoDbソースコード生成処理を実行します。
     * 
     * @param input
     * @return
     * @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,
            final int dataAccessType) 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(
                                        BlancoDbDotNetConstants.PRODUCT_NAME
                                                + "ソースコード生成", 10);

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

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

                                // blancoフォルダが無ければ最初に作成します。
                                final IFolder folderTargetDirectoryBlanco = ifile
                                        .getProject().getFolder("blanco.cs");
                                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, dataAccessType, monitor);
                                }

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

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

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

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

                                folderTargetDirectoryBlanco.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);

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

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

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

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

                                monitor.subTask("フォルダ更新: sqlフォルダをリフレッシュ");
                                // あくまでも保険として同期を取ります。
                                BlancoDbDotNetPluginUtil.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) {
            BlancoDbDotNetPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "例外が発生しました。処理を中断します。\n"
                            + ex.getTargetException().getMessage());
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            BlancoDbDotNetPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "割り込み中断例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Exception ex) {
            BlancoDbDotNetPlugin.log(ex);
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬ例外が発生しました。処理を中断します。\n" + ex.toString());
            ex.printStackTrace();
        } catch (Error er) {
            BlancoDbDotNetPlugin.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 int dataAccessType,
            final IProgressMonitor monitor) throws InvocationTargetException,
            InterruptedException, CoreException, SQLException, SAXException,
            IOException, ParserConfigurationException, ClassNotFoundException,
            TransformerException {
        if (fBundlePlugin.getClearTmpFolder().equals("true")) {
            // フラグが立っている場合にのみ、単一表のためのテンポラリフォルダを一旦削除します。
            monitor.subTask("単一表: テンポラリフォルダを整理します。");
            BlancoDbDotNetPluginUtil.deleteFolder(ifile, workbenchMonitor,
                    "tmp" + BlancoDbDotNetConstants.TARGET_SUBDIRECTORY
                            + "/table");
        }

        BlancoDbDotNetPluginUtil.createFolder(ifile, workbenchMonitor, "tmp"
                + BlancoDbDotNetConstants.TARGET_SUBDIRECTORY + "/table");
        final File blancoTempDirectoryTable = ifile.getProject().getFolder(
                "tmp" + BlancoDbDotNetConstants.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;
            }

            protected boolean isSkipTypeForSimpleTable(
                    final BlancoDbMetaDataColumnStructure columnStructure) {
                // DotNet版ではバイナリ型やlongvarcharを通常の表アクセスと同時に扱うことができます。
                return false;
            }
        };

        tableMeta2Xml.setFormatSql(true);
        tableMeta2Xml.process(dbSetting, blancoTempDirectoryTable);

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

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

        // TableGatewayのXMLファイルを元にR/Oマッピングを自動生成
        final BlancoDbXml2CsClassDotNet generatorTable = new BlancoDbXml2CsClassDotNet() {
            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,
                dataAccessType);

    }

    /**
     * SQL定義書ファイルを処理します。
     * 
     * SQL定義書をきっかけとしたR/Oマッピング自動生成を行います。
     * 
     * @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 int dataAccessType,
            final IProgressMonitor monitor) throws InvocationTargetException,
            InterruptedException, CoreException, SQLException, SAXException,
            IOException, ParserConfigurationException, TransformerException,
            ClassNotFoundException {
        monitor.worked(1);
        if (fBundlePlugin.getClearTmpFolder().equals("true")) {
            // フラグが立っている場合にのみ、SQL定義書のためのテンポラリフォルダを一旦削除します。
            monitor.subTask("SQL定義書: テンポラリフォルダを整理します。");
            BlancoDbDotNetPluginUtil.deleteFolder(ifile, workbenchMonitor,
                    "tmp" + BlancoDbDotNetConstants.TARGET_SUBDIRECTORY
                            + "/sql");
        }

        BlancoDbDotNetPluginUtil.createFolder(ifile, workbenchMonitor, "tmp"
                + BlancoDbDotNetConstants.TARGET_SUBDIRECTORY + "/sql");
        final IFolder searchMetaDir = ifile.getProject().getFolder(metaDir);

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

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

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

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

        searchMetaDir.refreshLocal(IResource.DEPTH_INFINITE, workbenchMonitor);

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

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

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

        };
        generatorSql.process(dbSetting, blancoTempDirectorySql, dataAccessType);

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