package blanco.plugin.strutsee.actions;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

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.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import blanco.plugin.strutsee.editors.BlancoStrutsEEEditor;
import blanco.plugin.strutsee.editors.BlancoStrutsEEPluginUtil;
import blanco.struts.BlancoStrutsMeta2Xml;
import blanco.struts.BlancoStrutsXml2JavaClass;

public class BlancoStrutsEEGenerateAction implements IObjectActionDelegate {

    private ISelection selection;

    /**
     * GUIデバッグ用の低速モード。通常はfalseで運用します。
     */
    private static final boolean IS_DEBUG_SLOW = false;

    public BlancoStrutsEEGenerateAction() {
        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) {
                try {
                    final StringBuffer metaDir = new StringBuffer();
                    final StringBuffer runtimePakage = new StringBuffer();
                    InputStream inStream = null;
                    try {
                        inStream = ((IFile) objectLook).getContents();

                        TransformerFactory tf = TransformerFactory
                                .newInstance();
                        Transformer transformer = tf.newTransformer();
                        transformer.transform(new StreamSource(inStream),
                                new SAXResult(new ContentHandler() {
                                    public void setDocumentLocator(Locator arg0) {
                                    }

                                    public void startDocument()
                                            throws SAXException {
                                    }

                                    public void endDocument()
                                            throws SAXException {
                                    }

                                    public void startPrefixMapping(String arg0,
                                            String arg1) throws SAXException {
                                    }

                                    public void endPrefixMapping(String arg0)
                                            throws SAXException {
                                    }

                                    public void startElement(String arg0,
                                            String arg1, String arg2,
                                            Attributes attributes)
                                            throws SAXException {
                                        if (arg1.equals("blancostruts")) {
                                            for (int index = 0; index < attributes
                                                    .getLength(); index++) {
                                                String localName = attributes
                                                        .getLocalName(index);
                                                String value = attributes
                                                        .getValue(index);
                                                if (localName.equals("metadir")) {
                                                    metaDir.append(value);
                                                } else if (localName
                                                        .equals("runtimepakage")) {
                                                    runtimePakage.append(value);
                                                }
                                            }
                                        }
                                    }

                                    public void endElement(String arg0,
                                            String arg1, String arg2)
                                            throws SAXException {
                                    }

                                    public void characters(char[] arg0,
                                            int arg1, int arg2)
                                            throws SAXException {
                                    }

                                    public void ignorableWhitespace(
                                            char[] arg0, int arg1, int arg2)
                                            throws SAXException {
                                    }

                                    public void processingInstruction(
                                            String arg0, String arg1)
                                            throws SAXException {
                                    }

                                    public void skippedEntity(String arg0)
                                            throws SAXException {
                                    }
                                }));
                    } catch (TransformerConfigurationException e) {
                        e.printStackTrace();
                    } catch (TransformerException e) {
                        // この読み込みエラーは無視します。
                    } catch (CoreException e) {
                        // TODO 自動生成された catch ブロック
                        e.printStackTrace();
                    } finally {
                        try {
                            if (inStream != null) {
                                inStream.close();
                            }
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    }

                    System.out.println("metaDir:" + metaDir.toString());
                    System.out.println("runtimePakage:"
                            + runtimePakage.toString());
                    if (metaDir.length() > 0 && runtimePakage.length() > 0) {
                        processBlancoStruts((IFile) objectLook, shell, null,
                                metaDir.toString(), runtimePakage.toString(),
                                false);
                        // とりあえず、ここはランタイムを作成しないでおく。
                    }
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

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

    /**
     * blancoStrutsソースコード生成処理を実行します。
     * 
     * @param input
     * @return
     * @throws InvocationTargetException
     * @throws InterruptedException
     */
    public static final void processBlancoStruts(final IFile ifile,
            final Shell shell, IProgressMonitor workbenchMonitor,
            final String metaDir, final String runtimePakage,
            final boolean isCreateRuntime) 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("blancoStrutsソースコード生成", 10);

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

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

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

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

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

                                final IFolder findFolder = BlancoStrutsEEPluginUtil
                                        .findFolder(ifile, monitor, metaDir);
                                IResource[] resourcesMeta = findFolder
                                        .members();
                                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(
                                                "xls") == false) {
                                            // System.out
                                            // .println("xls以外の拡張子のファイル["
                                            // + fileLook
                                            // + "]はスキップします。");
                                            continue;
                                        }

                                        IFile fileTarget = tempFolder
                                                .getFile(fileLook.getName()
                                                        + ".xml");
                                        monitor.subTask("メタファイル処理: ファイル["
                                                + fileLook.getName()
                                                + "]を処理します (" + (index + 1)
                                                + "/" + resourcesMeta.length
                                                + ")");
                                        InputStream inStream = null;
                                        OutputStream outStream = null;
                                        try {
                                            inStream = fileLook.getContents();
                                            outStream = new BufferedOutputStream(
                                                    new FileOutputStream(
                                                            fileTarget
                                                                    .getLocation()
                                                                    .toFile()));
                                            new BlancoStrutsMeta2Xml().process(
                                                    inStream, outStream);
                                            outStream.flush();
                                        } finally {
                                            if (inStream != null) {
                                                inStream.close();
                                            }
                                            if (outStream != null) {
                                                outStream.close();
                                            }
                                        }
                                        if (IS_DEBUG_SLOW) {
                                            Thread.sleep(1000);
                                        }
                                    }
                                }

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

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

                                tempFolder.refreshLocal(
                                        IResource.DEPTH_INFINITE, monitor);

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

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

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

                                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 {
                                            new BlancoStrutsXml2JavaClass()
                                                    .process(
                                                            fileLook
                                                                    .getLocation()
                                                                    .toFile(),
                                                            blancoTargetDirectory,
                                                            runtimePakage,
                                                            isCreateRuntime);
                                        } catch (IllegalArgumentException ex) {
                                            throw new IllegalArgumentException(

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

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

                                    }
                                }

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

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

                                folderTargetDirectory.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();
                            }
                        }
                    });
        } catch (InvocationTargetException 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();
        }
    }
}