/*
 * Copyright 2006 Mask@ Project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.maskat.ide.shell;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.apache.xmlbeans.SchemaTypeLoader;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.maskat.framework.eventdef.xml.EventDefParser;
import org.maskat.ide.MaskatIDEPlugin;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class EventDefCheckShell {
	private CheckShellStarter starter;

	private Shell parent;

	private static List teleList;

	private static Text fileName, resultTxt;

	private static NodeList teleNodeList;

	private static Button fileBtn, checkBtn;

	private static Label label, listLbl;

	// XXX static いいのか？
	private static Shell shell = null;

	private void initShell() {
		shell = new Shell(parent, SWT.APPLICATION_MODAL | SWT.RESIZE
				| SWT.DIALOG_TRIM);

		shell.addShellListener(new ShellAdapter() {
			public void shellClosed(ShellEvent e) {
				// 閉じされたら、表示しないようにさせる、disposeはしない
				if (!PlatformUI.isWorkbenchRunning())
					return;

				e.doit = false;
				shell.setVisible(false);
			}
		});
		shell.setText("イベント定義XMLチェック");
		createComponent(shell);

	}

	public Shell getShell() {
		if (shell == null)
			initShell();
		return shell;
	}

	public EventDefCheckShell(CheckShellStarter starter, Shell parent) {
		this.starter = starter;
		this.parent = parent;
	}

	public void showShell() {
		getShell();

		InputStream xmlStream = null;
		try {
			xmlStream = starter.getContents();
		} catch (CoreException e) {
			ErrorDialog.openError(shell, "ファイル開く失敗", // タイトル
					starter.getName() + "ファイルを開く操作に失敗しました。", // エラーメッセージ
					e.getStatus());
			return;
		}
		// スキーマファイルをxsltの転換でxmlインスタンスに変換

		NodeList teleNodeList = null;
		try {
			teleNodeList = EventDefParser.genTeleInstanceFromEventDef(
					xmlStream, EventDefParser.EVENTDEF_XSLT_FOR_INSTANCE);
			if (teleNodeList == null || teleNodeList.getLength() == 0) {
				MessageDialog.openError(getShell(), "イベント定義XML処理",
						"有効な電文定義が見つかりませんでした。" + "\n"
								+ "イベント定義XMLであることそして電文定義があることを確認してください。");
				return;
			}
		} catch (Exception e) {
			// イベントファイル処理に失敗。
			e.printStackTrace();
			handleException(e, "イベント定義XML処理", "イベント定義XML処理に失敗しました。");
			return;
		}

		resetShellData();

		addDataToShell(teleNodeList);

		shell.open();
	}

	private void handleException(Throwable e, String title, String message) {
		IStatus status = new Status(IStatus.ERROR, // ステータス種別: エラー
				MaskatIDEPlugin.PLUGIN_ID, // プラグインID
				0, // エラーコード
				e.getMessage(), // エラーメッセージ
				e); // 例外オブジェクト
		ErrorDialog.openError(shell, title, message, status);
	}

	private void resetShellData() {
		teleList.removeAll();
		teleNodeList = null;
		fileName.setText("");
		resultTxt.setText("");
	}

	private Group group;

	private void createComponent(Shell shell) {
		GridLayout layout = new GridLayout(1, false);
		shell.setLayout(layout);

		group = new Group(shell, SWT.NONE);
		GridData data = new GridData(GridData.FILL_BOTH);
		group.setLayoutData(data);
		layout = new GridLayout(5, false);
		group.setLayout(layout);

		listLbl = new Label(group, SWT.NONE);
		listLbl.setText("イベント定義XMLに定義されていた電文：");

		label = new Label(group, SWT.NONE);
		label.setText("電文スキーマファイル：");

		fileName = new Text(group, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
		data = new GridData(GridData.FILL_HORIZONTAL);
		data.grabExcessHorizontalSpace = true;
		fileName.setLayoutData(data);

		fileBtn = new Button(group, SWT.PUSH);
		fileBtn.setText("...");
		fileBtn.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				FileDialog dialog = new FileDialog(EventDefCheckShell.this
						.getShell());
				String fileName = dialog.open();
				if (fileName != null)
					EventDefCheckShell.fileName.setText(fileName);
			}
		});

		checkBtn = new Button(group, SWT.PUSH);
		checkBtn.setText("○");
		checkBtn.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				EventDefCheckShell.this.doSchemaCheck();
			}
		});

		teleList = new List(group, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL
				| SWT.H_SCROLL);
		data = new GridData(GridData.FILL_BOTH);
		data.grabExcessVerticalSpace = true;
		teleList.setLayoutData(data);
		teleList.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent e) {
				EventDefCheckShell.this.doSchemaCheck();
			}
		});

		resultTxt = new Text(group, SWT.BORDER | SWT.MULTI | SWT.WRAP
				| SWT.READ_ONLY | SWT.V_SCROLL);
		data = new GridData(GridData.FILL_BOTH);
		data.grabExcessHorizontalSpace = true;
		data.grabExcessVerticalSpace = true;
		data.horizontalSpan = 4;
		resultTxt.setLayoutData(data);
	}

	private void addDataToShell(NodeList teleNodeList) {
		// データ表示
		EventDefCheckShell.teleNodeList = teleNodeList;
		for (int i = 0; i < teleNodeList.getLength(); i++) {
			Node node = teleNodeList.item(i);
			String nodeName = node.getNodeName();
			String componentName = node.getAttributes().getNamedItem(
					"component").getNodeValue();
			String eventID = node.getAttributes().getNamedItem("event")
					.getNodeValue();
			teleList.add(componentName + "." + eventID + "." + nodeName);
		}
	}

	private void doSchemaCheck() {
		int selectIdx = teleList.getSelectionIndex();
		if (selectIdx == -1) {
			MessageDialog.openInformation(getShell(), "電文", "リストに電文を選択してください。");
			return;
		}
		String teleID = teleList.getItem(selectIdx);
		Node teleDefNode = teleNodeList.item(selectIdx);
		if (teleDefNode == null) {
			ErrorDialog.openError(this.getShell(), "内部データエラー", "電文ノード見つかりません。",
					null);
			return;
		}
		Node teleNode = teleDefNode.getFirstChild();
		if (teleNode == null) {
			resultTxt.setText(teleID + "は空白電文です。");
			return;
		}

		String schemaFileName = fileName.getText();
		if ("".equals(schemaFileName)) {
			MessageDialog.openInformation(getShell(), "スキーマファイル",
					"スキーマファイルを選択してください。");
			return;
		}

		XmlObject schemaXmlObj;
		try {
			schemaXmlObj = XmlObject.Factory.parse(new File(schemaFileName));
		} catch (XmlException e) {
			handleException(e, "スキーマパース処理", "スキーマパース処理に失敗しました。");
			return;
		} catch (IOException e) {
			handleException(e, "スキーマファイルIO", "スキーマファイルIO処理に失敗しました。");
			return;
		}
		SchemaTypeLoader sLoader = null;
		Collection compErrors = new ArrayList();
		XmlOptions schemaOptions = new XmlOptions();
		schemaOptions.setErrorListener(compErrors);
		try {
			sLoader = XmlBeans.compileXsd(new XmlObject[] { schemaXmlObj },
					sLoader, schemaOptions);
		} catch (XmlException e) {
			StringBuffer sb = new StringBuffer();
			sb.append("スキーマファイルコンパイルエラー。\n\n");
			for (Iterator i = compErrors.iterator(); i != null && i.hasNext();)
				sb.append(i.next() + "\n");
			resultTxt.setText(sb.toString());
			return;
		}

		XmlObject xmlObj;
		try {
			XmlOptions opt = (new XmlOptions())
					.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT);
			xmlObj = sLoader.parse(teleNode, null, opt);
		} catch (XmlException e) {
			handleException(e, "スキーマバリデーション処理", "スキーマバリデーション処理に失敗しました。");
			return;
		}
		Collection errors = new ArrayList();
		if (xmlObj.schemaType() == XmlObject.type) {
			resultTxt.setText(teleID + " NOT valid.  Document type not found.");
		} else if (xmlObj.validate(new XmlOptions().setErrorListener(errors)))
			resultTxt.setText(teleID + " 検証成功.");
		else {
			StringBuffer sb = new StringBuffer();
			sb.append(teleID + " 検証失敗.\n");
			for (Iterator i = errors.iterator(); i != null && i.hasNext();)
				sb.append(i.next() + "\n");
			resultTxt.setText(sb.toString());
		}
	}
}
