package org.objectstyle.wolips.eomodeler.editors.openEntity;

import java.util.Collections;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.StyledString;
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.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.OpenFileAction;
import org.eclipse.ui.actions.OpenWithMenu;
import org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.objectstyle.wolips.eomodeler.Activator;

/**
 * Shows a list of resources to the user with a text entry field for a string
 * pattern used to filter the list of resources.
 * 
 * @since 2.1
 */
public class OpenEntityDialog extends FilteredResourcesSelectionDialog {

	private Button openWithButton;

	/**
	 * Creates a new instance of the class.
	 * 
	 * @param parentShell
	 *            the parent shell
	 * @param container
	 *            the container
	 * @param typesMask
	 *            the types mask
	 */
	public OpenEntityDialog(Shell parentShell, IContainer container, boolean showSelectionHistory) {
		super(parentShell, true, container, IResource.FILE | IResource.FOLDER);
		setTitle("Open Model");
		PlatformUI.getWorkbench().getHelpSystem().setHelp(parentShell, IIDEHelpContextIds.OPEN_RESOURCE_DIALOG);

		setListLabelProvider(new EntityLabelProvider());
		setDetailsLabelProvider(new EntityDetailsLabelProvider());
		if (!showSelectionHistory) {
			setSelectionHistory(null);
		}
	}

	@Override
	protected ItemsFilter createFilter() {
		return new EntityFilter(ResourcesPlugin.getWorkspace().getRoot(), false, IResource.FILE | IResource.FOLDER);
	}

	protected class EntityFilter extends ResourceFilter {
		public EntityFilter(IContainer container, boolean showDerived, int typeMask) {
			super(container, showDerived, typeMask);
		}

		@Override
		public boolean matchItem(Object item) {
			boolean matches = super.matchItem(item);
			if (matches) {
				matches = false;
				IResource resource = (IResource) item;
				if ("plist".equals(resource.getFileExtension()) || "storedProcedure".equals(resource.getFileExtension())) {
					if ("eomodeld".equals(resource.getParent().getFileExtension())) {
						matches = true;
					}
				}
				else if ("eomodeld".equals(resource.getFileExtension()) && resource instanceof IContainer) {
					matches = true;
				}
			}
			return matches;
		}
	}

	protected void fillContextMenu(IMenuManager menuManager) {
		super.fillContextMenu(menuManager);

		IStructuredSelection selectedItems = getSelectedItems();
		if (selectedItems.isEmpty()) {
			return;
		}

		IWorkbenchPage activePage = getActivePage();
		if (activePage == null) {
			return;
		}

		// Add 'Open' menu item
		OpenFileAction openFileAction = new OpenFileAction(activePage) {
			@SuppressWarnings("synthetic-access")
			public void run() {
				okPressed();
			}
		};
		openFileAction.selectionChanged(selectedItems);
		if (!openFileAction.isEnabled()) {
			return;
		}
		menuManager.add(new Separator());
		menuManager.add(openFileAction);

		IAdaptable selectedAdaptable = getSelectedAdaptable();
		if (selectedAdaptable == null) {
			return;
		}

		// Add 'Open With...' sub-menu
		MenuManager subMenu = new MenuManager(IDEWorkbenchMessages.OpenResourceDialog_openWithMenu_label);
		OpenWithMenu openWithMenu = new OpenWithMenu(activePage, selectedAdaptable) {
			@SuppressWarnings("synthetic-access")
			protected void openEditor(IEditorDescriptor editorDescriptor, boolean openUsingDescriptor) {
				computeResult();
				setResult(Collections.EMPTY_LIST);
				close();
				super.openEditor(editorDescriptor, openUsingDescriptor);
			}
		};
		subMenu.add(openWithMenu);
		menuManager.add(subMenu);
	}

	protected void createButtonsForButtonBar(final Composite parent) {
		// increment the number of columns in the button bar
		GridLayout parentLayout = (GridLayout) parent.getLayout();
		parentLayout.numColumns++;
		parentLayout.makeColumnsEqualWidth = false;

		final Composite openComposite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.horizontalSpacing = 0;
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		openComposite.setLayout(layout);

		Button okButton = createButton(openComposite, IDialogConstants.OK_ID, IDEWorkbenchMessages.OpenResourceDialog_openButton_text, true);

		// Arrow down button for Open With menu
		((GridLayout) openComposite.getLayout()).numColumns++;
		openWithButton = new Button(openComposite, SWT.PUSH);
		openWithButton.setToolTipText(IDEWorkbenchMessages.OpenResourceDialog_openWithButton_text);
		openWithButton.setImage(WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_LCL_BUTTON_MENU));

		GridData data = new GridData(SWT.CENTER, SWT.FILL, false, true);
		openWithButton.setLayoutData(data);

		openWithButton.addMouseListener(new MouseAdapter() {
			@SuppressWarnings("synthetic-access")
			public void mouseDown(MouseEvent e) {
				showOpenWithMenu(openComposite);
			}
		});
		openWithButton.addSelectionListener(new SelectionAdapter() {
			@SuppressWarnings("synthetic-access")
			public void widgetSelected(SelectionEvent e) {
				showOpenWithMenu(openComposite);
			}
		});

		Button cancelButton = createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);

		GridData cancelLayoutData = (GridData) cancelButton.getLayoutData();
		GridData okLayoutData = (GridData) okButton.getLayoutData();
		int buttonWidth = Math.max(cancelLayoutData.widthHint, okLayoutData.widthHint);
		cancelLayoutData.widthHint = buttonWidth;
		okLayoutData.widthHint = buttonWidth;

		if (openComposite.getDisplay().getDismissalAlignment() == SWT.RIGHT) {
			// Make the default button the right-most button.
			// See also special code in
			// org.eclipse.jface.dialogs.Dialog#initializeBounds()
			openComposite.moveBelow(null);
			if (Util.isCarbon()) {
				okLayoutData.horizontalIndent = -10;
			}
		}
	}

	protected void initializeBounds() {
		super.initializeBounds();
		if (openWithButton.getDisplay().getDismissalAlignment() == SWT.RIGHT) {
			// Move the menu button back to the right of the default button.
			if (!Util.isMac()) {
				// On the Mac, the round buttons and the big padding would
				// destroy the visual coherence of the split button.
				openWithButton.moveBelow(null);
				openWithButton.getParent().layout();
			}
		}
	}

	protected void updateButtonsEnableState(IStatus status) {
		super.updateButtonsEnableState(status);
		if (openWithButton != null && !openWithButton.isDisposed()) {
			openWithButton.setEnabled(!status.matches(IStatus.ERROR) && getSelectedItems().size() == 1);
		}
	}

	private IAdaptable getSelectedAdaptable() {
		IStructuredSelection s = getSelectedItems();
		if (s.size() != 1) {
			return null;
		}
		Object selectedElement = s.getFirstElement();
		if (selectedElement instanceof IAdaptable) {
			return (IAdaptable) selectedElement;
		}
		return null;
	}

	private IWorkbenchPage getActivePage() {
		IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (activeWorkbenchWindow == null) {
			return null;
		}
		return activeWorkbenchWindow.getActivePage();
	}

	private void showOpenWithMenu(final Composite openComposite) {
		IWorkbenchPage activePage = getActivePage();
		if (activePage == null) {
			return;
		}
		IAdaptable selectedAdaptable = getSelectedAdaptable();
		if (selectedAdaptable == null) {
			return;
		}

		OpenWithMenu openWithMenu = new OpenWithMenu(activePage, selectedAdaptable) {
			@SuppressWarnings("synthetic-access")
			protected void openEditor(IEditorDescriptor editorDescriptor, boolean openUsingDescriptor) {
				computeResult();
				setResult(Collections.EMPTY_LIST);
				close();
				super.openEditor(editorDescriptor, openUsingDescriptor);
			}
		};

		Menu menu = new Menu(openComposite.getParent());
		Control c = openComposite;
		Point p = c.getLocation();
		p.y = p.y + c.getSize().y;
		p = c.getParent().toDisplay(p);

		menu.setLocation(p);
		openWithMenu.fill(menu, -1);
		menu.setVisible(true);
	}

	/**
	 * A label provider for ResourceDecorator objects. It creates labels with a
	 * resource full path for duplicates. It uses the Platform UI label
	 * decorator for providing extra resource info.
	 */
	protected class EntityLabelProvider extends LabelProvider implements ILabelProviderListener, IStyledLabelProvider {

		// Need to keep our own list of listeners
		private ListenerList listeners = new ListenerList();

		private WorkbenchLabelProvider _provider = new WorkbenchLabelProvider();

		/**
		 * Creates a new instance of the class
		 */
		public EntityLabelProvider() {
			super();
			_provider.addListener(this);
		}
		
		protected String removeExtension(String str) {
			String finalStr = str;
			int dotIndex = finalStr.lastIndexOf('.');
			if (dotIndex != -1) {
				finalStr = finalStr.substring(0, dotIndex);
			}
			return finalStr;
		}

		public Image getImage(Object element) {
			if (!(element instanceof IResource)) {
				return super.getImage(element);
			}

			Image image;
			IResource res = (IResource) element;
			String extension = res.getFileExtension();
			if ("eomodeld".equals(extension)) {
				image = Activator.getDefault().getImageRegistry().get(Activator.EOMODEL_ICON);
			} else if ("plist".equals(extension)) {
				image = Activator.getDefault().getImageRegistry().get(Activator.EOENTITY_ICON);
			} else if ("fspec".equals(extension)) {
				image = Activator.getDefault().getImageRegistry().get(Activator.EOFETCHSPEC_ICON);
			} else if ("storedProcedure".equals(extension)) {
				image = Activator.getDefault().getImageRegistry().get(Activator.EOSTOREDPROCEDURE_ICON);
			} else {
				image = _provider.getImage(element);
			}
			return image;
		}

		public String getText(Object element) {
			if (!(element instanceof IResource)) {
				return super.getText(element);
			}

			IResource res = (IResource) element;

			String str = res.getName();
			str = removeExtension(str);

			// extra info for duplicates
			if (isDuplicateElement(element)) {
				str = str + " - " + removeExtension(res.getParent().getFullPath().makeRelative().toString()); //$NON-NLS-1$
			}

			return str;
		}

		public StyledString getStyledText(Object element) {
			if (!(element instanceof IResource)) {
				return new StyledString(super.getText(element));
			}

			IResource res = (IResource) element;

			StyledString str = new StyledString(removeExtension(res.getName()));

			// extra info for duplicates
			if (isDuplicateElement(element)) {
				str.append(" - ", StyledString.QUALIFIER_STYLER); //$NON-NLS-1$
				str.append(removeExtension(res.getParent().getFullPath().makeRelative().toString()), StyledString.QUALIFIER_STYLER);
			}

			// Debugging:
			// int pathDistance = pathDistance(res.getParent());
			// if (pathDistance != Integer.MAX_VALUE / 2) {
			// if (pathDistance > Integer.MAX_VALUE / 4)
			// str.append(" (" + (pathDistance - Integer.MAX_VALUE / 4) +
			// " folders up from current selection)",
			// StyledString.QUALIFIER_STYLER);
			// else
			// str.append(" (" + pathDistance +
			// " folders down from current selection)",
			// StyledString.QUALIFIER_STYLER);
			// }

			return str;
		}

		public void dispose() {
			_provider.removeListener(this);
			_provider.dispose();

			super.dispose();
		}

		public void addListener(ILabelProviderListener listener) {
			listeners.add(listener);
		}

		public void removeListener(ILabelProviderListener listener) {
			listeners.remove(listener);
		}

		public void labelProviderChanged(LabelProviderChangedEvent event) {
			Object[] l = listeners.getListeners();
			for (int i = 0; i < listeners.size(); i++) {
				((ILabelProviderListener) l[i]).labelProviderChanged(event);
			}
		}

	}

	/**
	 * A label provider for details of ResourceItem objects.
	 */
	protected class EntityDetailsLabelProvider extends EntityLabelProvider {
		public Image getImage(Object element) {
			if (!(element instanceof IResource)) {
				return super.getImage(element);
			}

			IResource parent = ((IResource) element).getParent();
			return super.getImage(parent);
		}

		public String getText(Object element) {
			if (!(element instanceof IResource)) {
				return super.getText(element);
			}

			IResource parent = ((IResource) element).getParent();

			if (parent.getType() == IResource.ROOT) {
				// Get readable name for workspace root ("Workspace"), without
				// duplicating language-specific string here.
				return null;
			}
			
			return removeExtension(parent.getFullPath().makeRelative().toString());
		}

		public void labelProviderChanged(LabelProviderChangedEvent event) {
			Object[] l = super.listeners.getListeners();
			for (int i = 0; i < super.listeners.size(); i++) {
				((ILabelProviderListener) l[i]).labelProviderChanged(event);
			}
		}
	}

}
