package blanco.plugin.strutsee.editors;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;

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.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import blanco.plugin.strutsee.BlancoStrutsEEPlugin;
import blanco.plugin.strutsee.actions.BlancoStrutsEEGenerateAction;

/**
 * blancoStrutsエディタ <br>
 * 
 * @author iga
 */
public class BlancoStrutsEEEditor extends TextEditor {

    private Composite top = null;

    public static final String TARGET_FILENAME = "blancostruts.blancofw";

    public static final String BLANCO_META_FILE = "画面定義ファイル";

    public BlancoStrutsEEEditor() {
        // エンコーディングを操作するためにセットしています。
        setDocumentProvider(new BlancoStrutsEEDocumentProvider());
    }

    public void createPartControl(Composite parent) {
        top = parent;
        top.setSize(new org.eclipse.swt.graphics.Point(1000, 400));

        GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        top.setLayout(layout);

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        labelBlancoStrutsTitle = new Label(top, SWT.NONE);
        labelBlancoStrutsTitle.setText("BlancoStruts設定ファイル エディタ");

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        labelBlancoStrutsSettingId = new Label(top, SWT.NONE);
        labelBlancoStrutsSettingId.setText("設定ID");
        textBlancoStrutsSettingId = new Text(top, SWT.BORDER);
        textBlancoStrutsSettingId.setText("blancoStruts");
        textBlancoStrutsSettingId.setEditable(false);

        labelMetadir = new Label(top, SWT.NONE);
        labelMetadir.setText("メタディレクトリ");
        createComboMetadir();

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        labelMetadirComment = new Label(top, SWT.NONE);
        labelMetadirComment.setText("※メタディレクトリは通常変更せずに利用します。");
        labelMetadirComment.setSize(new org.eclipse.swt.graphics.Point(500,
                30 + 20));

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        buttonCreateMetafile = new Button(top, SWT.NONE);
        buttonCreateMetafile.setText(BLANCO_META_FILE + " 新規作成");
        buttonCreateMetafile
                .addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
                    public void widgetSelected(
                            org.eclipse.swt.events.SelectionEvent e) {
                        // メタファイル作成の際には、他の項目のチェックは行いません。
                        saveToTextEditor();

                        if (comboMetadir.getText().length() == 0) {
                            MessageDialog
                                    .openWarning(
                                            top.getShell(),
                                            BLANCO_META_FILE + " 新規作成",
                                            "メタディレクトリ名を指定してください。\n"
                                                    + "※メタディレクトリ名の一般的な設定値は 'meta/struts' です。");
                            return;
                        }

                        final IFileEditorInput input = (IFileEditorInput) getEditorInput();

                        try {
                            if (BlancoStrutsEEPluginUtil.findFolder(
                                    input.getFile(), getProgressMonitor(),
                                    comboMetadir.getText()).exists() == false) {
                                BlancoStrutsEEPluginUtil.createFolder(input
                                        .getFile(), getProgressMonitor(),
                                        comboMetadir.getText());

                                MessageDialog.openInformation(top.getShell(),
                                        "メタディレクトリの作成", "メタディレクトリ ["
                                                + comboMetadir.getText()
                                                + "] を新規に作成しました。\n"
                                                + "※初回にのみ必要な動作です。");
                            }
                        } catch (CoreException e3) {
                            MessageDialog.openWarning(top.getShell(),
                                    BLANCO_META_FILE + " 新規作成",
                                    "メタディレクトリ作成に失敗しました。処理中断します。:"
                                            + e3.toString());
                            e3.printStackTrace();
                            return;
                        }

                        try {
                            URL localURL = getPluginRoot();

                            InputDialog inputDialog = new InputDialog(top
                                    .getShell(), BLANCO_META_FILE + " 新規作成",
                                    BLANCO_META_FILE + "(メタファイル)をメタディレクトリ["
                                            + comboMetadir.getText()
                                            + "]に作成します。" + BLANCO_META_FILE
                                            + "の新規ファイル名を入力してください。",
                                    "blancoStrutsMeta.xls",
                                    new IInputValidator() {
                                        public String isValid(String newText) {
                                            if (newText.endsWith(".xls") == false) {
                                                return "ファイル名は .xlsで終了するように指定してください。";
                                            }
                                            if (newText.indexOf('/') >= 0
                                                    || newText.indexOf('\\') >= 0
                                                    || newText.indexOf(':') >= 0
                                                    || newText.indexOf(';') >= 0
                                                    || newText.indexOf('*') >= 0
                                                    || newText.indexOf('?') >= 0
                                                    || newText.indexOf('\"') >= 0
                                                    || newText.indexOf('>') >= 0
                                                    || newText.indexOf('<') >= 0
                                                    || newText.indexOf('|') >= 0) {
                                                return "ファイル名には / \\ : ; * ? \" > < | は利用しないでください。";
                                            }
                                            // 既にそのファイルが存在しないかどうかをチェック
                                            IFile newFileMeta = input.getFile()
                                                    .getProject().getFolder(
                                                            comboMetadir
                                                                    .getText())
                                                    .getFile(newText);
                                            if (newFileMeta.exists()) {
                                                return "指定のファイルは既に存在しています。"
                                                        + "別のファイル名を指定してください。";
                                            }
                                            return null;
                                        }
                                    });
                            if (inputDialog.open() == InputDialog.CANCEL) {
                                inputDialog.close();
                                return;
                            }
                            final String newMetaFilename = inputDialog
                                    .getValue();
                            inputDialog.close();

                            // プラグインのローカルからテンプレートを読み込みます。
                            InputStream inStream = new FileInputStream(localURL
                                    .getPath()
                                    + "/meta/template/blancoStrutsTemplate.xls");
                            IFile newFileMeta = input.getFile().getProject()
                                    .getFolder(comboMetadir.getText()).getFile(
                                            newMetaFilename);
                            newFileMeta.create(inStream, true,
                                    getProgressMonitor());
                            inStream.close();
                            MessageDialog.openInformation(top.getShell(),
                                    BLANCO_META_FILE + " 新規作成",
                                    BLANCO_META_FILE + "(メタファイル ["
                                            + newMetaFilename + "] をメタディレクトリ["
                                            + comboMetadir.getText()
                                            + "]に新規作成しました。\n"
                                            + "このファイルを編集して、画面定義関連情報を設定してください。");
                        } catch (CoreException e1) {
                            e1.printStackTrace();
                            return;
                        } catch (IOException e2) {
                            MessageDialog.openWarning(top.getShell(), "メタファイル",
                                    "メタファイルのコピーに失敗しました。:" + e2.toString());
                            e2.printStackTrace();
                        }

                    }
                });

        labelRuntimePakage = new Label(top, SWT.NONE);
        labelRuntimePakage.setText("ランタイムパッケージ");

        createComboRuntimePakage();

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        labelRuntimePakageComment = new Label(top, SWT.NONE);
        labelRuntimePakageComment.setText("※ランタイムソースのパッケージを指定して下さい。");
        labelRuntimePakageComment.setSize(new org.eclipse.swt.graphics.Point(
                500, 20));

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        checkCreateRuntime = new Button(top, SWT.CHECK);
        checkCreateRuntime.setText("ランタイムソースコードを出力する。");

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        labelRuntimePakageComment2 = new Label(top, SWT.NONE);
        labelRuntimePakageComment2.setText("※一度はランタイムソースを生成してください。");
        labelRuntimePakageComment2.setSize(new org.eclipse.swt.graphics.Point(
                500, 20));

        // スペーサー
        new Label(top, SWT.NONE).setText("");
        new Label(top, SWT.NONE).setText("");

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        buttonExecute = new Button(top, SWT.NONE);
        buttonExecute.setText("ソースコード生成");
        buttonExecute
                .addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
                    public void widgetSelected(
                            org.eclipse.swt.events.SelectionEvent e) {
                        if (checkInputField() == false) {
                            return;
                        }

                        try {
                            if (checkInputField() == false) {
                                return;
                            }

                            final IFileEditorInput input = (IFileEditorInput) getEditorInput();
                            if (input.getFile().getProject().getFolder(
                                    comboMetadir.getText()).exists() == false) {
                                MessageDialog.openWarning(top.getShell(),
                                        "ソースコード生成", "メタディレクトリ ("
                                                + comboMetadir.getText()
                                                + ") が存在しません。処理を中断します。");
                                return;
                            }

                            showBusy(true);
                            // 再入を防止するために必要です。
                            buttonExecute.setEnabled(false);

                            BlancoStrutsEEGenerateAction.processBlancoStruts(
                                    input.getFile(), top.getShell(),
                                    getProgressMonitor(), comboMetadir
                                            .getText(), comboRuntimePakage
                                            .getText(), checkCreateRuntime
                                            .getSelection());

                            // blancoフォルダを同期
                            BlancoStrutsEEPluginUtil.refreshFolder(input
                                    .getFile(), getProgressMonitor(), "blanco");

                            // tmpフォルダを同期
                            BlancoStrutsEEPluginUtil.refreshFolder(input
                                    .getFile(), getProgressMonitor(), "tmp");

                            // strutsフォルダを同期
                            // あくまでも保険として同期を取ります。
                            BlancoStrutsEEPluginUtil.refreshFolder(input
                                    .getFile(), getProgressMonitor(),
                                    comboMetadir.getText());
                        } catch (Exception ex) {
                            ex.printStackTrace();
                            StringWriter writer = new StringWriter();
                            ex.printStackTrace(new PrintWriter(writer));
                            writer.flush();
                            MessageDialog.openError(top.getShell(), "ソースコード生成",
                                    "回復不能なエラーが発生.:" + ex.toString() + ": "
                                            + writer.toString());
                        } finally {
                            showBusy(false);
                            // 再入を防止するために必要です。
                            buttonExecute.setEnabled(true);
                        }
                    }

                });

        // スペーサー
        new Label(top, SWT.NONE).setText("");

        labelSettingComment = new Label(top, SWT.NONE);
        labelSettingComment.setText("※各々の設定値は " + TARGET_FILENAME
                + "ファイルに平文で格納されます。");
        labelSettingComment
                .setSize(new org.eclipse.swt.graphics.Point(500, 20));

        loadFromTextEditor();

        setFocusLostHandler(comboMetadir);
        setFocusLostHandler(comboRuntimePakage);
    }

    /**
     * プラグインのルートディレクトリを取得します。
     * 
     * @return ルートディレクトリのURL これをgetPath()するとフルパスになります。
     * @throws IOException
     */
    private URL getPluginRoot() throws IOException {
        URL relativeURL = BlancoStrutsEEPlugin.getDefault().getBundle()
                .getEntry("/");
        URL localURL = Platform.asLocalURL(relativeURL);
        return localURL;
    }

    private void setFocusLostHandler(Control target) {
        target.addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent e) {
            }

            public void focusLost(FocusEvent e) {
                saveToTextEditor();
            }

        });
    }

    public void setFocus() {
    }

    /**
     * 読込処理を行います <br>
     * このメソッドは勝手に命名して作成しました。 <br>
     */
    public void loadFromTextEditor() {
        try {
            String editorText = getDocumentProvider().getDocument(
                    getEditorInput()).get();

            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(
                    new StreamSource(new StringReader(editorText)),
                    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 {
                            // System.out.println("startElement:" + arg1);
                            if (arg1.equals("blancostruts")) {
                                // System.out.println("blancostrutsエンティティを発見");
                                for (int index = 0; index < attributes
                                        .getLength(); index++) {
                                    // System.out.println(attributes
                                    // .getLocalName(index));
                                    String localName = attributes
                                            .getLocalName(index);
                                    String value = attributes.getValue(index);
                                    if (localName.equals("metadir")) {
                                        comboMetadir.setText(value);
                                    } else if (localName
                                            .equals("runtimepakage")) {
                                        comboRuntimePakage.setText(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) {
            // この読み込みエラーは無視します。
        }
    }

    /**
     * 保存処理を行います。
     */
    public void saveToTextEditor() {
        StringWriter writer = new StringWriter();
        try {
            TransformerFactory tf = TransformerFactory.newInstance();
            SAXTransformerFactory saxTf = (SAXTransformerFactory) tf;
            TransformerHandler saxHandler;
            saxHandler = saxTf.newTransformerHandler();
            saxHandler.setResult(new StreamResult(writer));

            saxHandler.startDocument();
            AttributesImpl attributes = new AttributesImpl();
            attributes.addAttribute("", "version", "version", "CDATA", "0.1.0");
            saxHandler.startElement("", "blanco", "blanco", attributes);
            attributes = new AttributesImpl();
            attributes.addAttribute("", "name", "name", "CDATA",
                    textBlancoStrutsSettingId.getText());
            saxHandler.startElement("", "target", "target", attributes);
            attributes = new AttributesImpl();

            attributes.addAttribute("", "metadir", "metadir", "CDATA",
                    comboMetadir.getText());
            attributes.addAttribute("", "runtimepakage", "runtimepakage",
                    "CDATA", comboRuntimePakage.getText());

            saxHandler.startElement("", "blancostruts", "blancostruts",
                    attributes);

            saxHandler.endElement("", "blancostruts", "blancostruts");
            saxHandler.endElement("", "target", "target");
            saxHandler.endElement("", "blanco", "blanco");

            saxHandler.endDocument();
        } catch (TransformerConfigurationException e1) {
            e1.printStackTrace();
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        } catch (SAXException ex) {
            ex.printStackTrace();
        }

        String pastEditorText = getDocumentProvider().getDocument(
                getEditorInput()).get();
        if (pastEditorText.equals(writer.toString()) == false) {
            // 内容が異なる場合にのみ書き込みを行います。
            getDocumentProvider().getDocument(getEditorInput()).set(
                    writer.toString());
        }
    }

    public boolean isSaveAsAllowed() {
        return false;
    }

    public boolean isSaveOnCloseNeeded() {
        return true;
    }

    /**
     * 入力項目のチェックを実施します。 <br>
     * エラーダイアログはこのメソッドで表示します。
     * 
     * @return
     */
    private final boolean checkInputField() {
        boolean isError = false;
        if (comboMetadir.getText().length() == 0) {
            comboMetadir.setBackground(top.getDisplay().getSystemColor(
                    SWT.COLOR_YELLOW));
            isError = true;
        } else {
            comboMetadir.setBackground(top.getDisplay().getSystemColor(
                    SWT.COLOR_INFO_BACKGROUND));
        }

        if (comboRuntimePakage.getText().length() == 0) {
            comboRuntimePakage.setBackground(top.getDisplay().getSystemColor(
                    SWT.COLOR_YELLOW));
            isError = true;
        } else {
            comboRuntimePakage.setBackground(top.getDisplay().getSystemColor(
                    SWT.COLOR_INFO_BACKGROUND));
        }

        if (isError) {
            MessageDialog.openWarning(top.getShell(), "必須項目への入力が足りません",
                    "必須項目への入力が足りません。\nハイライト表示されている必須項目に値を入力してください");
        }

        return !isError;
    }

    // ビジュアル領域
    private Label labelBlancoStrutsTitle = null;

    private Label labelBlancoStrutsSettingId = null;

    private Text textBlancoStrutsSettingId = null;

    private Label labelMetadir = null;

    private Combo comboMetadir = null;

    private Label labelMetadirComment = null;

    private Label labelSettingComment = null;

    private Button buttonExecute = null;

    private Button buttonCreateMetafile = null;

    private Label labelRuntimePakage = null;

    private Label labelRuntimePakageComment = null;

    private Label labelRuntimePakageComment2 = null;

    private Combo comboRuntimePakage = null;

    private Button checkCreateRuntime = null;

    /**
     * This method initializes comboMetadir
     * 
     */
    private void createComboMetadir() {
        comboMetadir = new Combo(top, SWT.NONE);
        comboMetadir.add("meta/struts");
        comboMetadir.select(0);
    }

    private void createComboRuntimePakage() {
        comboRuntimePakage = new Combo(top, SWT.NONE);
        comboRuntimePakage.add("root.pkg");
        comboRuntimePakage.select(0);
    }
}