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 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.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.xml.sax.SAXException;

import blanco.db.conf.BlancoDbDatabaseConnectionSettingDef;
import blanco.db.exception.BlancoDbException;
import blanco.db.helper.BlancoDbDefaultGenerator;
import blanco.db.helper.BlancoDbExcel2Xml;
import blanco.db.helper.BlancoDbTableGateway2Xml;
import blanco.plugin.dbee.editors.BlancoDbEEPluginUtil;

public class BlancoDbEEGenerateAction implements IObjectActionDelegate {

    private ISelection selection;

    public BlancoDbEEGenerateAction() {
        super();
    }

    public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    }

    public void run(IAction action) {
        Shell shell = new Shell();

        if (selection instanceof StructuredSelection) {
            StructuredSelection look = (StructuredSelection) selection;
            Object objectLook = look.getFirstElement();
            if (objectLook instanceof IFile) {
                // まだ実装されていません。
            }
        }
    }

    public void selectionChanged(IAction action, ISelection selection) {
        this.selection = selection;
    }

    /**
     * blancoDbソースコード生成処理を実行します。
     * 
     * @param input
     * @return
     * @throws InvocationTargetException
     * @throws InterruptedException
     */
    public static final void processBlancoDb(final IFile ifile,
            final Shell shell, final IProgressMonitor workbenchMonitor,
            final String metaDir, final String jdbcDriver,
            final String jdbcUrl, final String jdbcUser,
            final String jdbcPassword, final String jdbcSchema,
            final String basePackage, final boolean isSimpleTableProcess,
            final boolean isSqlMetaProcess) 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("blancoDbソースコード生成", 10);

                                // 接続情報の構築
                                BlancoDbDatabaseConnectionSettingDef connDef = new BlancoDbDatabaseConnectionSettingDef();
                                connDef.setJdbcDriver(jdbcDriver);
                                connDef.setJdbcUrl(jdbcUrl);
                                connDef.setJdbcUser(jdbcUser);
                                connDef.setJdbcPassword(jdbcPassword);
                                if (jdbcSchema != null
                                        && jdbcSchema.length() > 0) {
                                    // スキーマ名を指定。
                                    // 指定がある場合にのみセットするのが作法です。
                                    connDef.setSchema(jdbcSchema);
                                }

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

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

                                // blancoフォルダが無ければ最初に作成します。
                                IFolder folderTargetDirectoryBlanco = ifile
                                        .getProject().getFolder("blanco");
                                if (ifile.getProject().getFolder(metaDir)
                                        .exists() == false) {
                                    folderTargetDirectoryBlanco.create(true,
                                            true, workbenchMonitor);
                                }
                                File blancoTargetDirectory = folderTargetDirectoryBlanco
                                        .getLocation().toFile();

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

                                if (isSimpleTableProcess) {
                                    processSimpleTable(ifile, shell,
                                            workbenchMonitor, basePackage,
                                            connDef, blancoTargetDirectory,
                                            monitor);
                                }

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

                                if (isSqlMetaProcess) {
                                    processSqlMeta(ifile, shell,
                                            workbenchMonitor, metaDir,
                                            basePackage, connDef,
                                            blancoTargetDirectory, monitor);
                                }

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

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

                                folderTargetDirectoryBlanco.refreshLocal(
                                        IResource.DEPTH_INFINITE,
                                        workbenchMonitor);

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

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

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

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

                                monitor.subTask("フォルダ更新: sqlフォルダをリフレッシュ");
                                // あくまでも保険として同期を取ります。
                                BlancoDbEEPluginUtil.refreshFolder(ifile,
                                        workbenchMonitor, metaDir);
                            } catch (CoreException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (SQLException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (BlancoDbException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (SAXException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (IOException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } catch (ParserConfigurationException e) {
                                e.printStackTrace();
                                throw new InvocationTargetException(e, e
                                        .toString());
                            } finally {
                                monitor.done();
                            }
                        }
                    });
        } catch (InvocationTargetException ex) {
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "例外が発生しました。処理を中断します。\n"
                            + ex.getTargetException().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();
        } catch (Error er) {
            MessageDialog.openWarning(shell, "ソースコード生成",
                    "予期せぬエラーが発生しました。処理を中断します。\n" + er.toString());
            er.printStackTrace();
        } finally {
        }
    }

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

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

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

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

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

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

        // TableGatewayのXMLファイルを元にR/Oマッピングを自動生成
        BlancoDbDefaultGenerator generatorTable = new BlancoDbDefaultGenerator() {
            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(connDef, blancoTempDirectoryTable, basePackage,
                blancoTargetDirectory.toString());

    }

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

        // SQL定義ファイルによる生成
        BlancoDbEEPluginUtil
                .deleteFolder(ifile, workbenchMonitor, "tmp/db/sql");
        BlancoDbEEPluginUtil
                .createFolder(ifile, workbenchMonitor, "tmp/db/sql");
        File blancoSqlDirectory = ifile.getProject().getFolder(metaDir)
                .getLocation().toFile();

        File blancoTempDirectorySql = ifile.getProject()
                .getFolder("tmp/db/sql").getLocation().toFile();

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

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

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

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

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

        // SQL定義ファイルのXMLファイルを元にR/Oマッピングを自動生成
        BlancoDbDefaultGenerator generatorSql = new BlancoDbDefaultGenerator() {
            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(connDef, blancoTempDirectorySql, basePackage,
                blancoTargetDirectory.toString());
    }
}