/*
 * 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.editors;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.ui.parts.GraphicalEditor;
import org.eclipse.gef.ui.properties.UndoablePropertySheetEntry;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.eclipse.ui.views.properties.PropertySheetEntry;
import org.eclipse.ui.views.properties.PropertySheetPage;
import org.maskat.framework.eventdef.IEventDefSource;
import org.maskat.framework.eventdef.xml.TelegramSchemaGenerator;
import org.maskat.framework.screendef.IComponentDef;
import org.maskat.framework.screendef.LayoutDef;
import org.maskat.framework.screendef.MaskatDef;
import org.maskat.framework.screendef.RadioGroupDef;
import org.maskat.framework.screendef.xml.LayoutDefParser;
import org.maskat.framework.screendef.xml.LayoutDefXmlSerializer;
import org.maskat.ide.MaskatIDEPlugin;
import org.maskat.ide.editors.linkeventsrc.LinkEventDefSource;
import org.maskat.ide.gef.editparts.ComponentEditPart;
import org.maskat.ide.gef.editparts.GridHeaderEditPart;
import org.maskat.ide.gef.editparts.MaskatEditPartFactory;
import org.maskat.ide.gef.editparts.SplitEditPart;
import org.maskat.ide.gef.editparts.SplitPageEditPart;
import org.maskat.ide.gef.editparts.TabEditPart;
import org.maskat.ide.gef.editparts.TabFolderEditPart;
import org.maskat.ide.property.LayoutProperty;
import org.maskat.xml.DefXmlSerializer;

public class MaskatEditor extends GraphicalEditor {

	public final static String ID = "org.maskat.ide.gef.editor1";

	public boolean gettingFocus = false;

	// protected LayoutDef layoutDef = null;

	protected MaskatDef maskatDef = null;

	/** The index of the layoutDef currently showed */
	protected int layoutIdx = 0;

	protected LayoutProperty layout = null;

	public void setFocus() {
		super.setFocus();
		gettingFocus = true;
	}

	public final static int SCREEN_EDIT_MODE = 1;

	public final static int SOURCE_EDIT_MODE = 2;

	public final static int TARGET_EDIT_MODE = 4;

	public int editMode = SCREEN_EDIT_MODE;

	private PaletteSupport paletteSupport;

	public MaskatEditor() {
		DefaultEditDomain ed = new DefaultEditDomain(this);
		paletteSupport = new PaletteSupport(ed);
		setEditDomain(ed);
	}

	Composite splitter;

	public void createPartControl(Composite parent) {
		splitter = paletteSupport.getEditorComposite(parent, getSite().getPage());
		super.createPartControl(splitter);
		paletteSupport.setGraphicalControl(getGraphicalViewer().getControl());
	}

	/**
	 * エディターを表示するコントロール
	 * 
	 * @return
	 */
	protected Composite getControl() {
		return splitter;
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#initializeGraphicalViewer()
	 */
	protected void initializeGraphicalViewer() {
		GraphicalViewer viewer = getGraphicalViewer();

		// 最上位のモデルの設定
		genLayoutMenu();
		viewer.setContents(getCurrentLayoutDef());
		createSourceListener();

		// EditorMenuProvider provider = new EditorMenuProvider();
		// EditPart root = getGraphicalViewer().getRootEditPart().getContents();
		// if (root instanceof AbstractGraphicalEditPart)
		// getGraphicalViewer().setContextMenu(
		// provider.genMenuManager(((AbstractGraphicalEditPart) root)
		// .getFigure()));
	}

	protected void genLayoutMenu() {
		final GraphicalViewer viewer = getGraphicalViewer();
		MenuManager mm = viewer.getContextMenu();
		if (mm == null) {
			mm = new MenuManager();
			viewer.setContextMenu(mm);
		}
		mm.removeAll();

		MenuManager subMenu = new MenuManager("layouts");

		Iterator it = maskatDef.getTypedChildren(LayoutDef.class);
		int currentIdx = 0;
		while (it.hasNext()) {
			LayoutDef layoutDef = (LayoutDef) it.next();
			final int theIdx = currentIdx;
			Action action = new Action(layoutDef.getName() == null ? "anonymous" + theIdx
					: layoutDef.getName(), Action.AS_RADIO_BUTTON) {
				public void run() {
					switchLayout(theIdx);
				}
			};
			action.setId(String.valueOf(theIdx));
			if (currentIdx == 0)
				action.setChecked(true);
			else
				action.setChecked(false);
			subMenu.add(action);
			currentIdx++;
		}
		mm.add(subMenu);
	}

	protected void switchLayout(int theIdx) {
		layoutIdx = theIdx;
		connectLayoutEvent();
		getGraphicalViewer().setContents(getCurrentLayoutDef());
	}

	private class ResourceDeltaVisitor implements IResourceDeltaVisitor {

		private IPath targetResourcePath;

		private boolean hit = false;

		public boolean isHit() {
			return hit;
		}

		public ResourceDeltaVisitor(IPath targetResourcePath) {
			this.targetResourcePath = targetResourcePath;
		}

		public boolean visit(IResourceDelta delta) throws CoreException {
			switch (delta.getKind()) {
			case IResourceDelta.ADDED:
				break;
			case IResourceDelta.REMOVED:
				if (delta.getFullPath().equals(targetResourcePath)) {
					hit = true;
				}
				break;
			case IResourceDelta.CHANGED:
				if (delta.getFullPath().equals(targetResourcePath)) {
					hit = true;
				}
				break;
			}
			return true;
		}

	}

	protected LayoutDef getCurrentLayoutDef() {
		return (LayoutDef) maskatDef.getChildByTypeIdx(LayoutDef.class, this.layoutIdx);
	}

	// XXX
	private boolean isThisEditorSaving = false;

	private IResourceChangeListener listener;

	private void createSourceListener() {
		// エディタークローズされたら、このリスナーが削除される
		listener = new IResourceChangeListener() {
			public void resourceChanged(IResourceChangeEvent event) {
				if (MaskatEditor.this.isThisEditorSaving)
					return;

				IEditorInput editorInput = MaskatEditor.this.getEditorInput();
				IFile iFile = (IFile) editorInput.getAdapter(IFile.class);
				ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(iFile
						.getFullPath());
				try {
					event.getDelta().accept(visitor);
				} catch (CoreException e1) {
					e1.printStackTrace();
				}
				if (visitor.isHit()) {
					Display.getDefault().syncExec(new Runnable() {
						public void run() {
							try {
								// TODO レイアウト定義XMLとイベント定義XML別々で。。。
								MaskatEditor.this.initLoad(MaskatEditor.this
										.getEditorInput());
								connectLayoutEvent();
								genLayoutMenu();
								MaskatEditor.this.getGraphicalViewer().setContents(
										getCurrentLayoutDef());
								MaskatEditor.this.getCommandStack().flush();
							} catch (PartInitException e) {
								MaskatIDEPlugin.getDefault().getLog().log(
										new Status(IStatus.ERROR,
												MaskatIDEPlugin.PLUGIN_ID, IStatus.ERROR,
												e.getMessage(), e));
							}

						}
					});
				}
			}
		};
		MaskatIDEPlugin.getWorkspace().addResourceChangeListener(listener);
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void doSave(IProgressMonitor monitor) {
		try {
			isThisEditorSaving = true;
			// XXX パフォーマンス
			StringWriter strWriter = new StringWriter(1024);
			DefXmlSerializer serializer = new LayoutDefXmlSerializer(maskatDef, strWriter);
			serializer.write();

			Iterator it = maskatDef.getTypedChildren(LayoutDef.class);
			while (it.hasNext()) {
				((LayoutDef) it.next()).removeAllByType(RadioGroupDef.class);
			}
			// RadioGroupDef should not be remained as a direct child of
			// layoutDef

			byte[] bytes = strWriter.toString().getBytes("UTF-8");
			layoutXmlFile.setContents(new ByteArrayInputStream(bytes), false, false,
					monitor);

			// イベント定義XMLセーブ
			String[] results = eventDefSource.save();

			boolean confirm = MessageDialog.openConfirm(this.getSite().getShell(),
					"スキーマ生成", "スキーマ自動生成します、よろしいですか？");
			if (confirm) {
				for (int i = 0; i < results.length; i++) {
					String result = results[i];
					IPath path = layoutXmlFile.getProjectRelativePath();
					IProject project = this.layoutXmlFile.getProject();
					TelegramSchemaGenerator.generate(new ByteArrayInputStream(result
							.getBytes("UTF-8")), project, path.removeLastSegments(1));
				}
			}

			getCommandStack().markSaveLocation();

			firePropertyChange(IEditorPart.PROP_DIRTY);
		} catch (CoreException e) {
			ErrorDialog.openError(this.getSite().getShell(), "保存", // タイトル
					"保存処理に失敗しました。", // エラーメッセージ
					e.getStatus());
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, // ステータス種別: エラー
					MaskatIDEPlugin.PLUGIN_ID, // プラグインID
					0, // エラーコード
					e.getMessage() == null ? "" : e.getMessage(), // エラーメッセージ
					e); // 例外オブジェクト
			ErrorDialog.openError(this.getSite().getShell(), "保存", // タイトル
					"保存処理に失敗しました。", // エラーメッセージ
					status);
		} finally {
			isThisEditorSaving = false;
		}

	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
	 */
	public void doSaveAs() {

	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#gotoMarker(org.eclipse.core.resources.IMarker)
	 */
	public void gotoMarker(IMarker marker) {

	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#isDirty()
	 */
	public boolean isDirty() {
		return getCommandStack().isDirty();
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
	 */
	public boolean isSaveAsAllowed() {
		return false;
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#commandStackChanged(java.util.EventObject)
	 */
	public void commandStackChanged(EventObject event) {
		super.commandStackChanged(event);
		// エディターの変更マークを更新
		firePropertyChange(IEditorPart.PROP_DIRTY);
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
	 */
	protected void configureGraphicalViewer() {
		super.configureGraphicalViewer();

		GraphicalViewer viewer = getGraphicalViewer();
		// EditPartFactoryの作成と設定
		viewer.setEditPartFactory(new MaskatEditPartFactory());
	}

	// private IFile eventXmlFile;

	private IFile layoutXmlFile;

	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		initLoad(input);
		// 注意、super.initはinitLoadの後で呼び出すこと
		// initLoadにPartInitExceptionを投げられたら、super.initにselectionListenerすでに登録されてしまったので、まずい
		super.init(site, input);
	}

	private void initLoad(IEditorInput input) throws PartInitException {
		layoutXmlFile = (IFile) input.getAdapter(IFile.class);

		// レイアウトXMLと対応するイベント定義XMLファイルを取得
		// IProject project = layoutXmlFile.getProject();
		// IPath path = layoutXmlFile.getProjectRelativePath();
		//
		// String lastSegment = path.lastSegment();
		// int index = lastSegment.lastIndexOf("xml") - 1;
		// path = path.removeLastSegments(1).append(
		// lastSegment.substring(0, index) + "_e.xml");

		// eventXmlFile = project.getFile(path);// イベント定義XMLファイル取得

		if (layoutXmlFile != null) {
			setPartName(layoutXmlFile.getName());
		}

		try {
			loadLayout();
			loadEvent();
		} catch (CoreException e) {
			throw new PartInitException(e.getStatus());
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, // ステータス種別: エラー
					MaskatIDEPlugin.PLUGIN_ID, // プラグインID
					0, // エラーコード
					e.getMessage() == null ? "" : e.getMessage(), // エラーメッセージ
					e); // 例外オブジェクト
			throw new PartInitException(status);
		}
		connectLayoutEvent();
	}

	private void loadLayout() throws Exception {
		InputStream inputS = null;
		try {
			layoutXmlFile.refreshLocal(1, null);

			if (layoutXmlFile.exists()) {
				inputS = layoutXmlFile.getContents();// contentsからデータを読み込む
				maskatDef = LayoutDefParser.parse(inputS);
			}
		} finally {
			try {
				if (inputS != null)
					inputS.close();
			} catch (IOException e) {
			}
		}
	}

	IEventDefSource eventDefSource;

	private void loadEvent() throws Exception {
		eventDefSource = new LinkEventDefSource(this.layoutXmlFile);
		eventDefSource.load();
	}

	private void connectLayoutEvent() {
		LayoutDef layoutDef = getCurrentLayoutDef();
		// layout.setLayoutDef(layoutDef);
		layoutDef.setLayoutEvtDefSrc(eventDefSource);
	}

	public void selectionChanged(IWorkbenchPart part, ISelection selection) {
		super.selectionChanged(part, selection);
		if (selection instanceof TextSelection) {
			TextSelection txtSel = (TextSelection) selection;
			List targets = new ArrayList();
			if (txtSel.getText() == null)// || getGraphicalViewer() == null)
				return;
			searchForNamedComponent(getGraphicalViewer().getRootEditPart().getContents(),
					txtSel.getText(), targets);
			getGraphicalViewer().deselectAll();// selectionを消去
			for (int i = 0; i < targets.size(); i++) {
				EditPart target = (EditPart) targets.get(i);
				getGraphicalViewer().appendSelection(target);
				if (i == 0) {
					getGraphicalViewer().reveal(target);
				}
			}
		}
	}

	/**
	 * EditPartのツリーから、ある名前のeditPartを洗い出す
	 * 
	 * @param root
	 * @param name
	 * @return
	 */
	public void searchForNamedComponent(EditPart root, String name, List list) {
		if (root == null || root.getChildren() == null) {
			return;
		}
		for (int i = 0; i < root.getChildren().size(); i++) {
			EditPart editPart = (EditPart) root.getChildren().get(i);
			if (editPart.getModel() instanceof IComponentDef) {
				if (name.equals(((IComponentDef) editPart.getModel()).getName())) {
					list.add(editPart);
				}
			}
			searchForNamedComponent(editPart, name, list);
		}
	}

	public void dispose() {
		MaskatIDEPlugin.getWorkspace().removeResourceChangeListener(listener);
		super.dispose();
	}

	public Object getAdapter(Class type) {
		if (type == org.eclipse.ui.views.properties.IPropertySheetPage.class) {
			PropertySheetPage page = new PropertySheetPage();

			PropertySheetEntry entry = new UndoablePropertySheetEntry(getCommandStack());
			entry.setPropertySourceProvider(new IPropertySourceProvider() {
				public IPropertySource getPropertySource(Object object) {
					if (object instanceof ComponentEditPart) {
						// MaskatのEditPartであれば、直接object.getAdapter(IPropertySource.class)を呼べば
						// nullが返却されるから、modelをIPropertySource.classへadapter
						AbstractGraphicalEditPart part = (AbstractGraphicalEditPart) object;
						return (IPropertySource) Platform.getAdapterManager().getAdapter(
								part.getModel(), IPropertySource.class);
					}
					if (object instanceof GridHeaderEditPart) {
						GridHeaderEditPart part = (GridHeaderEditPart) object;
						return (IPropertySource) Platform.getAdapterManager().getAdapter(
								part.getModel(), IPropertySource.class);
					}
					if (object instanceof SplitPageEditPart
							|| object instanceof SplitEditPart) {
						AbstractGraphicalEditPart part = (AbstractGraphicalEditPart) object;
						return (IPropertySource) Platform.getAdapterManager().getAdapter(
								part.getModel(), IPropertySource.class);
					}
					if (object instanceof TabFolderEditPart
							|| object instanceof TabEditPart) {
						AbstractGraphicalEditPart part = (AbstractGraphicalEditPart) object;
						return (IPropertySource) Platform.getAdapterManager().getAdapter(
								part.getModel(), IPropertySource.class);
					}
					if (object instanceof IPropertySource) {
						return (IPropertySource) object;
					}
					return null;
				}
			});
			page.setRootEntry(entry);
			return page;
		}
		Object result = paletteSupport.getAdapter(type);
		if (result != null)
			return result;

		return super.getAdapter(type);
	}

}
