/*
 * Copyright (c) 2006-2008 Maskat Project.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Maskat Project - initial API and implementation
 */
package org.maskat.ui.editors.layout;

import java.io.IOException;
import java.util.EventObject;
import java.util.Iterator;

import org.eclipse.core.resources.IFile;
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.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.SnapToGeometry;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.actions.AlignmentAction;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;

import org.maskat.core.layout.Component;
import org.maskat.core.layout.Layout;
import org.maskat.ui.MaskatNature;
import org.maskat.ui.MaskatUIPlugin;
import org.maskat.ui.Messages;
import org.maskat.ui.editors.layout.actions.CopyComponentAction;
import org.maskat.ui.editors.layout.actions.CutComponentAction;
import org.maskat.ui.editors.layout.actions.PasteComponentAction;
import org.maskat.ui.editors.layout.editparts.ChainedEditPartFactory;
import org.maskat.ui.editors.layout.outline.LayoutOutLinePage;
import org.maskat.ui.views.properties.tabbed.TabbedEventPropertySheetPage;

public class LayoutGraphicalEditor extends GraphicalEditorWithFlyoutPalette implements
		ITabbedPropertySheetPageContributor, IResourceChangeListener {

	/** The editor ID */
	public static final String EDITOR_ID = MaskatUIPlugin.PLUGIN_ID
			+ ".layoutGraphicalEditor"; //$NON-NLS-1$

	protected MaskatResources data;

	private LayoutOutLinePage outlinePage = null;

	private PluggablePaletteCustomizer customizer;
	
	private IProject project;

	private boolean saving = false;
	
	public LayoutGraphicalEditor() {
		super();
		setEditDomain(new DefaultEditDomain(this));
	}

	protected PaletteRoot getPaletteRoot() {
		if (customizer == null) {
			customizer = new PluggablePaletteCustomizer();
		}
		return customizer.getPaletteRoot();
	}

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

		MaskatNature nature = MaskatNature.getNature(project);
		
		if (nature != null) {
			boolean isGrid = nature.getGridSelection();
			boolean isToGrid = nature.getSnapToGridSelection();
			boolean isToObj = nature.getSnapToGeometrySelection();
			int gridXSize = nature.getGridSize();
			int gridYSize = gridXSize;
			
			viewer.setProperty(SnapToGrid.PROPERTY_GRID_SPACING,
				new Dimension(gridXSize, gridYSize));
			
			viewer.setProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED,
					Boolean.valueOf(isToObj));
			
			viewer.setProperty(SnapToGrid.PROPERTY_GRID_ENABLED,
					Boolean.valueOf(isToGrid));
			
			viewer.setProperty(SnapToGrid.PROPERTY_GRID_VISIBLE,
					Boolean.valueOf(isGrid));
			
			// 最上位のモデルの設定
			genContextMenu(isGrid, isToGrid, isToObj);
		} else {
			genContextMenu(false, false, false);
		}
		viewer.getEditDomain().getPaletteViewer().setCustomizer(customizer);	
		viewer.setContents(data.getLayout());
	}

	protected void genContextMenu(boolean isGrid, boolean isToGrid, boolean isToObj) {
		final GraphicalViewer viewer = getGraphicalViewer();
		MenuManager mm = viewer.getContextMenu();
		if (mm == null) {
			mm = new MenuManager();
			viewer.setContextMenu(mm);
		}
		mm.removeAll();

		IWorkbenchWindow window = getSite().getWorkbenchWindow();
		mm.add(ActionFactory.UNDO.create(window));
		mm.add(ActionFactory.REDO.create(window));	
		mm.add(new Separator());		
		mm.add(ActionFactory.CUT.create(window));
		mm.add(ActionFactory.COPY.create(window));
		mm.add(ActionFactory.PASTE.create(window));
		mm.add(new Separator());
		mm.add(ActionFactory.DELETE.create(window));
		mm.add(new Separator());
		
		MenuManager smm = new MenuManager(
				Messages.getString("layout.contextmenu.alignmenu")); //$NON-NLS-1$
		ActionRegistry registry = getActionRegistry();
		
		for (Iterator ite = registry.getActions(); ite.hasNext();) {
			Object action = ite.next();
			if (action instanceof AlignmentAction) {
				smm.add((IAction) action);
			}
		}
		mm.add(smm);
		//mm.add(new Separator());
		
		final boolean isSnapGrid = isToGrid;
		final IAction snapToGridAction = new Action(
				Messages.getString("layout.contextmenu.snaptogrid"), //$NON-NLS-1$
				IAction.AS_RADIO_BUTTON) {
			private boolean selected = isSnapGrid;
			public void run() {
				boolean checked = isChecked();
				if (selected && isChecked()) {
					checked = false;
					setChecked(checked);
				}
				selected = checked;
				viewer.setProperty(SnapToGrid.PROPERTY_GRID_ENABLED,
						Boolean.valueOf(checked));
				storeGridSelection("snapToGrid", isChecked()); //$NON-NLS-1$
			}
		};
		final boolean isSnapObj = isToObj; 
		final IAction snapToGeometryAction = new Action(
				Messages.getString("layout.contextmenu.snaptogeometry"), //$NON-NLS-1$
				IAction.AS_RADIO_BUTTON) {
			private boolean selected = isSnapObj;
			public void run() {
				boolean checked = isChecked();
				if (selected && isChecked()) {
					checked = false;
					setChecked(checked);
				}
				selected = checked;
				viewer.setProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED,
						Boolean.valueOf(isChecked()));
				storeGridSelection("snapToObj", isChecked()); //$NON-NLS-1$
			}
		};
		IAction viewGridAction = new Action(
				Messages.getString("layout.contextmenu.grid"), //$NON-NLS-1$
				IAction.AS_CHECK_BOX) {
			public void run() {
				snapToGridAction.setEnabled(isChecked());
				if (!isChecked()) {
					viewer.setProperty(SnapToGrid.PROPERTY_GRID_ENABLED, Boolean.FALSE);
				} else if (snapToGridAction.isChecked()) {
					viewer.setProperty(SnapToGrid.PROPERTY_GRID_ENABLED, Boolean.TRUE);
				}
				viewer.setProperty(SnapToGrid.PROPERTY_GRID_VISIBLE,
						Boolean.valueOf(isChecked()));
				storeGridSelection("gridView", isChecked()); //$NON-NLS-1$
			}
		};
		IAction upGridAction = new Action(Messages.getString(
				"layout.contextmenu.gridsize.up"), IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
			public void run() {
				Dimension d = (Dimension) viewer.getProperty(SnapToGrid.PROPERTY_GRID_SPACING);
				Dimension n = new Dimension(d.width + 5, d.height + 5);
				viewer.setProperty(SnapToGrid.PROPERTY_GRID_SPACING, n);
				storeGridSize(n.width);
			}
		};
		IAction downGridAction = new Action(Messages.getString(
				"layout.contextmenu.gridsize.down"), IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
			public void run() {
				Dimension d = (Dimension) viewer.getProperty(SnapToGrid.PROPERTY_GRID_SPACING);
				Dimension n = new Dimension(d.width > 0 ? d.width - 5 : 0,
						d.height > 0 ? d.height - 5 : 0);
				viewer.setProperty(SnapToGrid.PROPERTY_GRID_SPACING, n);
				storeGridSize(n.width);
			}
		};
		IAction baseGridAction = new Action(Messages.getString(
				"layout.contextmenu.gridsize.standard"), IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
			public void run() {
				viewer.setProperty(SnapToGrid.PROPERTY_GRID_SPACING,
					new Dimension(SnapToGrid.DEFAULT_GRID_SIZE, SnapToGrid.DEFAULT_GRID_SIZE));
				storeGridSize(SnapToGrid.DEFAULT_GRID_SIZE);
			}
		};
		snapToGridAction.setChecked(isToGrid);
		snapToGridAction.setEnabled(isGrid);
		snapToGeometryAction.setChecked(isToObj);
		viewGridAction.setChecked(isGrid);		

		
		MenuManager gridMenu = new MenuManager(
				Messages.getString("layout.contextmenu.gridmenu")); //$NON-NLS-1$
		gridMenu.add(viewGridAction);
		gridMenu.add(snapToGridAction);
		gridMenu.add(snapToGeometryAction);
		gridMenu.add(new Separator());
		
		MenuManager sizeMenu = new MenuManager(
				Messages.getString("layout.contextmenu.gridsize")); //$NON-NLS-1$
		sizeMenu.add(upGridAction);
		sizeMenu.add(downGridAction);
		sizeMenu.add(new Separator());
		sizeMenu.add(baseGridAction);
		gridMenu.add(sizeMenu);
		
		mm.add(gridMenu);
		mm.add(new Separator());

		IAction showPropertiesViewAction = new Action(Messages
				.getString("layout.contextmenu.showproperties")) {
			public void run() {
				try {
					getSite().getPage().showView(IPageLayout.ID_PROP_SHEET);
				} catch (PartInitException e) {
					MaskatUIPlugin.log(e.getStatus());
				}
			}
		};
		mm.add(showPropertiesViewAction);
		mm.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
		getSite().registerContextMenu("#layoutGraphicalEditor", mm,
				getSite().getSelectionProvider());
	}

	private void storeGridSelection(String propertyName, boolean value) {
		MaskatNature nature = MaskatNature.getNature(project);
		if ("gridView".equals(propertyName)) { //$NON-NLS-1$
			nature.setGridSelection(value);
		} else if ("snapToGrid".equals(propertyName)) { //$NON-NLS-1$
			if (value) {
				nature.setSnapToGeometrySelection(false);
			}
			nature.setSnapToGridSelection(value);
		} else if ("snapToObj".equals(propertyName)) { //$NON-NLS-1$
			if (value) {
				nature.setSnapToGridSelection(false);
			}
			nature.setSnapToGeometrySelection(value);
		}
		try {
			nature.getPreferenceStore().save();
		} catch (IOException e) {
			IStatus status = new Status(IStatus.ERROR, MaskatUIPlugin.PLUGIN_ID, 0, e
					.getMessage() == null ? "" : e.getMessage(), e); //$NON-NLS-1$
			MaskatUIPlugin.log(status);		
		}
	}	
	
	private void storeGridSize(int size) {
		MaskatNature nature = MaskatNature.getNature(project);
		nature.setGridSize(size);
		try {
			nature.getPreferenceStore().save();
		} catch (IOException e) {
			IStatus status = new Status(IStatus.ERROR, MaskatUIPlugin.PLUGIN_ID, 0, e
					.getMessage() == null ? "" : e.getMessage(), e); //$NON-NLS-1$
			MaskatUIPlugin.log(status);		
		}
	}		
	
	/*
	 * (非 Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void doSave(IProgressMonitor monitor) {
		try {
			saving = true;
			data.save();
			getCommandStack().markSaveLocation();
			firePropertyChange(IEditorPart.PROP_DIRTY);
		} catch (CoreException e) {
			ErrorDialog.openError(getSite().getShell(),
					Messages.getString("layout.editor.save.error.title"), //$NON-NLS-1$
					Messages.getString("layout.editor.save.msg.error"), e //$NON-NLS-1$
					.getStatus());
			MaskatUIPlugin.log(e.getStatus());
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, MaskatUIPlugin.PLUGIN_ID, 0, e
					.getMessage() == null ? "" : e.getMessage(), e);
			MessageDialog.openError(getSite().getShell(),
					Messages.getString("layout.editor.save.error.title"), //$NON-NLS-1$
					Messages.getString("layout.editor.save.msg.error")); //$NON-NLS-1$
			MaskatUIPlugin.log(status);
		} finally {
			saving = false;
		}
	}

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

	/*
	 * (非 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 ChainedEditPartFactory());
	}

	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		// Workaround for bug #226, #299:
		// All editors in a multi-page editor should have key binding service
		// to switch shortcut keys.
		site.getKeyBindingService();

		// 注意、super.initはinitLoadの後で呼び出すこと
		// initLoadにPartInitExceptionを投げられたら、super.initにselectionListenerすでに登録されてしまったので、まずい
		super.init(site, input);
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
	 */
	protected void setInput(IEditorInput input) {
		super.setInput(input);

		IFile layoutXmlFile = (IFile) input.getAdapter(IFile.class);
		if (layoutXmlFile != null) {
			setPartName(layoutXmlFile.getName());
			project = layoutXmlFile.getProject();
		}

		this.data = new MaskatResources(layoutXmlFile);
		try {
			data.load();
			registComponentName(data.getLayout());
		} catch (CoreException e) {
			MaskatUIPlugin.log(new Status(IStatus.ERROR, MaskatUIPlugin.PLUGIN_ID, 0,
					Messages.getString("layout.editor.load.msg.error"), e)); //$NON-NLS-1$
		} catch (Exception e) {
			IStatus status = new Status(IStatus.ERROR, MaskatUIPlugin.PLUGIN_ID, 0, e
					.getMessage() == null ? "" : e.getMessage(), e);
			MaskatUIPlugin.log(status);
		}
	}

	private void registComponentName(Component comp) {
		Layout layout = data.getLayout();
		layout.setElement(comp);
		for (Iterator ite = comp.getChildren(); ite != null && ite.hasNext();) {
			Object obj = ite.next();
			if (obj instanceof Component) {
				registComponentName((Component) obj);
			}
		}
	}

	public void selectionChanged(IWorkbenchPart part, ISelection selection) {
		if (this.equals(getSite().getPage().getActiveEditor()) ||
				this.equals(part)) {
			updateActions(getSelectionActions());
		}
	}

	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		super.dispose();
	}
	
	public Object getAdapter(Class type) {
		if (type == IPropertySheetPage.class) {
			return new TabbedEventPropertySheetPage(this);
		} else if (IContentOutlinePage.class.equals(type)) {
			outlinePage = new LayoutOutLinePage(this);
			getSelectionSynchronizer().addViewer(outlinePage.getViewer());
			return outlinePage;
		}
		return super.getAdapter(type);
	}

	public String getContributorId() {
		return EDITOR_ID;
	}

	protected void createActions() {
		super.createActions();
		ActionRegistry registry = getActionRegistry();

		// 水平方向の整列アクション
		IAction action = new AlignmentAction((IWorkbenchPart) this,
				PositionConstants.LEFT);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		action = new AlignmentAction((IWorkbenchPart) this, PositionConstants.CENTER);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());
		
		action = new AlignmentAction((IWorkbenchPart) this, PositionConstants.RIGHT);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		// 垂直方向の整列アクション
		action = new AlignmentAction((IWorkbenchPart) this, PositionConstants.TOP);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		action = new AlignmentAction((IWorkbenchPart) this, PositionConstants.MIDDLE);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		action = new AlignmentAction((IWorkbenchPart) this, PositionConstants.BOTTOM);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		action = new CopyComponentAction(this);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		action = new CutComponentAction(this);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());

		action = new PasteComponentAction(this);
		registry.registerAction(action);
		getSelectionActions().add(action.getId());
	}

	protected void firePropertyChange(int property) {
		super.firePropertyChange(property);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
	 */
	public void resourceChanged(IResourceChangeEvent event) {
		IResourceDelta delta = event.getDelta();
		if (delta != null) {
			delta = delta.findMember(data.getLayoutXMLFile().getFullPath());
		}

		if (delta != null && !saving) {
			switch (delta.getKind()) {
			case IResourceDelta.CHANGED:
				setInput(getEditorInput());
				getGraphicalViewer().setContents(data.getLayout());
				if (outlinePage != null) {
					outlinePage.update();
				}
				getCommandStack().flush();
				break;
			case IResourceDelta.REMOVED:
				getEditorSite().getPage().closeEditor(this, false);
				break;
			}
		}
	}

}
