/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.ui.views.navigator;

import java.util.Properties;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.BuildAction;
import org.eclipse.ui.actions.CloseResourceAction;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.ide.IDEActionFactory;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.ISetSelectionTarget;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.views.framelist.FrameList;
import org.eclipse.ui.views.navigator.IResourceNavigator;
import org.eclipse.ui.views.navigator.ResourceComparator;
import org.eclipse.ui.views.navigator.ResourceNavigatorActionGroup;
import org.eclipse.ui.views.navigator.ResourcePatternFilter;
import org.eclipse.ui.views.navigator.ResourceSorter;
import org.jboss.tools.common.meta.action.XActionInvoker;
import org.jboss.tools.common.model.XFilteredTree;
import org.jboss.tools.common.model.XJob;
import org.jboss.tools.common.model.XModelException;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.XJob.XRunnable;
import org.jboss.tools.common.model.event.XModelTreeEvent;
import org.jboss.tools.common.model.filesystems.XFileObject;
import org.jboss.tools.common.model.impl.trees.FileSystemsTree;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.project.IModelNature;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.model.ui.action.ModelContributionManager;
import org.jboss.tools.common.model.ui.dnd.ControlDragDrop;
import org.jboss.tools.common.model.ui.editor.IModelObjectEditorInput;
import org.jboss.tools.common.model.ui.navigator.NavigatorLabelProvider;
import org.jboss.tools.common.model.ui.navigator.NavigatorStatusLineProvider;
import org.jboss.tools.common.model.ui.navigator.TreeViewerDragDropProvider;
import org.jboss.tools.common.model.ui.navigator.TreeViewerMenuInvoker;
import org.jboss.tools.common.model.ui.navigator.TreeViewerModelListenerImpl;
import org.jboss.tools.common.model.ui.select.XModelObjectSelectionProvider;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.model.util.ModelFeatureFactory;

public class NavigatorViewPart extends ViewPart implements ISaveablePart, ISetSelectionTarget, IResourceNavigator , IShowInTarget //, ISelectionProvider 
{
	public static final String VIEW_ID = "org.jboss.tools.common.model.ui.navigator.NavigatorViewPart"; //$NON-NLS-1$
	private TreeViewer viewer;
	private XModelObjectSelectionProvider selectionProvider = new XModelObjectSelectionProvider();
	private TreeViewerDragDropProvider dndProvider = new TreeViewerDragDropProvider(); 
	private ControlDragDrop dnd = new ControlDragDrop();
	private CloseResourceAction closeProjectAction;
	private BuildAction buildProjectAction;
	private BuildAction rebuildProjectAction;
	protected NavigatorContentProvider contentProvider;
	private IMemento memento = null;
	private NavigatorStatusLineProvider statusLineProvider = new NavigatorStatusLineProvider();
	private ActivationListener fActivationListener= new ActivationListener();
	
	boolean useModelMenu = true;
	
	public NavigatorViewPart() {
		selectionProvider.addSelectionChangedListener(new SL());
	}
	
	public void setFocus() {
		getViewer().getTree().setFocus();
	}

	public TreeViewer getViewer() {
		return viewer;
	}
	
	public void createPartControl(Composite parent)	{
		createViewer(parent);
		viewer.setInput(getInitialInput());
		selectionProvider.addHost("viewer", viewer, true); //$NON-NLS-1$
		getSite().setSelectionProvider(selectionProvider);
		if (memento != null) {
			restoreLinkingEnabled();
		}
		initActionBars();
		dndProvider.setViewer(viewer);
		dnd.setProvider(dndProvider);
		dnd.enable();
		initContextMenu();

		activateF3();

		getSite().getPage().addPartListener(partListener);

		if (memento != null)
			restoreState(memento);
		memento = null;
	}
	
	private void activateF3() {
		IHandlerService h = (IHandlerService)getViewSite().getActionBars().getServiceLocator().getService(IHandlerService.class);
		IAction action = new Action(){
			public boolean isEnabled() {
				return true;
			}
			public void run() {
				ISelection s = viewer.getSelection();
				if(s.isEmpty() || !(s instanceof StructuredSelection)) return;
				StructuredSelection ss = (StructuredSelection)s;
				Object o = ss.getFirstElement();
				if(!(o instanceof XModelObject)) return;
				XModelObject object = (XModelObject)o;
				if(XActionInvoker.getAction("Open", object) == null) return; //$NON-NLS-1$
				XActionInvoker.invoke("Open", object, null);					 //$NON-NLS-1$
			}
		};
		h.activateHandler("org.eclipse.jdt.ui.edit.text.java.open.editor", new ActionHandler(action)); //$NON-NLS-1$
	}
	
	private IAction createActionInstance(String name) {
		return (IAction)ModelFeatureFactory.getInstance().createFeatureInstance(name);
	}
	
	protected String[] getActionClasses() {
		String[] actions = new String[]{
		};
		return actions;
	}

	protected void initActionBars() {
		IActionBars bars = getViewSite().getActionBars();
        String[] actions = getActionClasses();
        for (int i = 0; i < actions.length; i++) {
			IAction action = createActionInstance(actions[i]);
        	if(action != null) bars.getToolBarManager().add(action);
        }
		closeProjectAction = new CloseResourceAction(getViewer().getTree().getShell());
		getSite().getSelectionProvider().addSelectionChangedListener(closeProjectAction);
		getViewSite().getActionBars().setGlobalActionHandler(
				IDEActionFactory.CLOSE_PROJECT.getId(),
			closeProjectAction
		);
		buildProjectAction = new BuildAction(getViewer().getTree().getShell(), IncrementalProjectBuilder.INCREMENTAL_BUILD);
		getSite().getSelectionProvider().addSelectionChangedListener(buildProjectAction);
		getViewSite().getActionBars().setGlobalActionHandler(
				IDEActionFactory.BUILD_PROJECT.getId(),
			buildProjectAction
		);
		rebuildProjectAction = new BuildAction(getViewer().getTree().getShell(), IncrementalProjectBuilder.FULL_BUILD);
		getSite().getSelectionProvider().addSelectionChangedListener(rebuildProjectAction);
		getViewSite().getActionBars().setGlobalActionHandler(
				IDEActionFactory.BUILD_PROJECT.getId(),
			rebuildProjectAction
		);
		makeActions();
		getActionGroup().fillActionBars(getViewSite().getActionBars());
		updateActionBars((IStructuredSelection) viewer.getSelection());
	}
	
	public void dispose() {
		if (getSite()!=null&&getSite().getSelectionProvider()!=null) {
			getSite().getSelectionProvider().removeSelectionChangedListener(closeProjectAction);	
			getSite().getSelectionProvider().removeSelectionChangedListener(buildProjectAction);	
			getSite().getSelectionProvider().removeSelectionChangedListener(rebuildProjectAction);
		}
		if(getSite()!=null) {
		    getSite().getWorkbenchWindow().getShell().removeShellListener(fActivationListener);
		}
		super.dispose();
	}

	protected void createViewer(Composite parent) {
		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setUseHashlookup(true);
		initContentProvider(viewer);
		initLabelProvider(viewer);
//		initFilters(viewer);
		initListeners(viewer);
	}
	
	protected void initLabelProvider(TreeViewer treeViewer)	{
		treeViewer.setLabelProvider(new NavigatorLabelProvider());		
	}
	
	protected void initContentProvider(TreeViewer treeViewer) {
		NavigatorContentProvider c = new NavigatorContentProvider();
		ML listener = new ML();
		listener.setViewer(treeViewer);
		c.setListener(listener);
		contentProvider = c;
		treeViewer.setContentProvider(contentProvider);
	}

	protected void initListeners(TreeViewer treeViewer) {
		treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				handleSelectionChanged(event);
			}
		});
//@S_CHECK@
  		listener = createMenuInvoker();
		listener.setViewer(viewer);
		if(useModelMenu) {
			viewer.getTree().addMouseListener(listener);
		}
	}
	
	protected TreeViewerMenuInvoker createMenuInvoker() {
		return new NavigatorMenuInvoker();
	}

	public XModelObject getSelectedModelObject() {
		if(getViewer() == null || getViewer().getTree() == null || getViewer().getTree().isDisposed()) return null;
		TreeItem[] ti = getViewer().getTree().getSelection();
		if(ti == null || ti.length == 0) return null;
		return getModelObjectForWidget(ti[0]);
	}
	
	public XModelObject getModelObjectForWidget(Widget widget) {
		if(!(widget instanceof TreeItem)) return null;
		TreeItem item = (TreeItem)widget;
		Object itemData = item.getData();
		return (!(itemData instanceof XModelObject)) ? null : (XModelObject)itemData;
	}
	
	protected IAdaptable getInitialInput() {
		IAdaptable input = getSite().getPage().getInput();
		if (input != null) {
			IResource resource = null;
			if (input instanceof IResource) {
				resource = (IResource) input;
			} else {
				resource = (IResource) input.getAdapter(IResource.class);
			}
			if (resource != null) {
				switch (resource.getType()) {
					case IResource.FILE :
						return resource.getParent();
					case IResource.FOLDER :
					case IResource.PROJECT :
					case IResource.ROOT :
						return (IContainer) resource;
					default :
						// Unknown resource type.  Fall through.
						break;
				}
			}
		}
		return ModelUIPlugin.getWorkspace().getRoot();
	}
	
	public Object getAdapter(Class adapter)
	{
		//ModelUIPlugin.log("getAdapter(" + adapter.getName() + ")");
		return super.getAdapter(adapter);
	}

	public void doSave(IProgressMonitor monitor) {
		XModelObject o = getSelectedModelObject();
		if(o == null) return;
		while(o != null && o.getFileType() == XFileObject.NONE) o = o.getParent();
		if(o == null || o.getFileType() != XFileObject.FILE) return;
		XActionInvoker.invoke("SaveActions.Save", o, null);		 //$NON-NLS-1$
		firePropertyChange(IEditorPart.PROP_DIRTY);
	}

	public void doSaveAs() {}

	public boolean isDirty() {
		XModelObject o = getSelectedModelObject();
		if(o == null) return false;
		while(o != null && o.getFileType() == XFileObject.NONE) o = o.getParent();
		if(o == null || o.getFileType() != XFileObject.FILE) return false;
		return o.isModified();
	}

	public boolean isSaveAsAllowed() {
		return false;
	}

	public boolean isSaveOnCloseNeeded() {
		return false;
	}
	
	class SL implements ISelectionChangedListener {
		public void selectionChanged(SelectionChangedEvent event) {
			firePropertyChange(IEditorPart.PROP_DIRTY);
		}
	}
	
	class ML extends TreeViewerModelListenerImpl {

		public void nodeChanged(XModelTreeEvent event) {
			super.nodeChanged(event);
			firePropertyChange(IEditorPart.PROP_DIRTY);
		}
	}

	public void selectReveal(ISelection selection)
	{
		if (selection instanceof IStructuredSelection)
		{
			Object object = ((IStructuredSelection)selection).getFirstElement();
			if (object instanceof IProject)
			{
				XModelObject root = contentProvider.getRootByProject((IProject)object);
				if(root != null) getSite().getSelectionProvider().setSelection(
					new StructuredSelection(root)
				);
			}
		}
	}

	public void init(IViewSite site, IMemento _memento) throws PartInitException {
		init(site);
		this.memento = _memento;
		site.getWorkbenchWindow().getShell().addShellListener(fActivationListener);
	}
		
	public void saveState(IMemento _memento) {
		XModelObject o = getSelectedModelObject();
		if(o != null) _memento.putString("selection", getNodePath(o)); //$NON-NLS-1$
		saveLinkingEnabled(_memento);
	}

	public void restoreState(IMemento _memento) {
		String selection = _memento.getString("selection"); //$NON-NLS-1$
		XModelObject o = findModelObject(selection);
		if(o != null) viewer.setSelection(new StructuredSelection(o));
		restoreLinkingEnabled(); 
	}
	
	private String getNodePath(XModelObject o) {
		IProject p = (IProject)o.getModel().getProperties().get("project"); //$NON-NLS-1$
		return p.getName() + ":" + o.getPath(); //$NON-NLS-1$
	}
	
	private XModelObject findModelObject(String nodepath) {
		if(nodepath == null || nodepath.length() == 0) return null;
		int i = nodepath.indexOf(":"); //$NON-NLS-1$
		IProject p = ModelUIPlugin.getWorkspace().getRoot().getProject(nodepath.substring(0, i));
		return EclipseResourceUtil.getObjectByPath(p, nodepath.substring(i + 1));
	}

	// see ResourceNavigator	
	protected void handleSelectionChanged(SelectionChangedEvent event) {
		IStructuredSelection sel = (IStructuredSelection) event.getSelection();
		updateStatusLine(sel);
		updateActionBars(sel);
		linkToEditor(sel);
	}

	protected void updateStatusLine(IStructuredSelection selection) {
		String msg = statusLineProvider.getStatusLineMessage(selection);
		getViewSite().getActionBars().getStatusLineManager().setMessage(msg);
	}
	
	//// IResourceNavigator

	public ResourcePatternFilter getPatternFilter() {
		return new ResourcePatternFilter();
	}
	
	IWorkingSet workingSet = null;

	public IWorkingSet getWorkingSet() {
		return workingSet;
	}

	public void setWorkingSet(IWorkingSet workingSet) {
		this.workingSet = workingSet;
	}
	
	protected ResourceSorter resourceSorter = null;
	public ResourceSorter getSorter() {
		return resourceSorter;
	}

	public void setSorter(ResourceSorter sorter) {
		resourceSorter = sorter;
	}
	
	String[] filtersPreference = new String[0];

	public void setFiltersPreference(String[] patterns) {
		filtersPreference = patterns;
	}

	public FrameList getFrameList() {
		return null;
	}

	protected boolean isLinkingEnabled = false;

	public boolean isLinkingEnabled() {
		return isLinkingEnabled;
	}

	public void setLinkingEnabled(boolean enabled) {
		isLinkingEnabled = enabled;
		if (enabled) {
			IEditorPart editor = getSite().getPage().getActiveEditor();
			if (editor != null && editor != this) {
				editorActivated(editor);
			}
		}
	}
	
	protected void makeActions() {
		setActionGroup(new ModelNavigatorActionGroup(this));
	}
	
	ResourceNavigatorActionGroup actionGroup;

	protected ResourceNavigatorActionGroup getActionGroup() {
		return actionGroup;
	}

	protected void setActionGroup(ResourceNavigatorActionGroup actionGroup) {
		this.actionGroup = actionGroup;
	}

	protected void updateActionBars(IStructuredSelection selection) {
		ResourceNavigatorActionGroup group = getActionGroup();
		if (group != null) {
			group.setContext(new ActionContext(selection));
			group.updateActionBars();
		}
	}
	
	// MenuManager
	
	ModelContributionManager menuMgr;
	TreeViewerMenuInvoker listener;
	
	protected void initContextMenu() {
		menuMgr = new ModelContributionManager(getSite().getShell(), listener);
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				fillContextMenu(manager);
			}
		});
		TreeViewer treeViewer = getViewer();
		Menu menu = menuMgr.createContextMenu(treeViewer.getTree());
		treeViewer.getTree().setMenu(menu);
		getSite().registerContextMenu(menuMgr, treeViewer);
		listener.setStandardInvoker(menuMgr);

	}
	
	public void callMenu() {
		fillContextMenu(menuMgr);
		Menu menu = menuMgr.createContextMenu(viewer.getTree());
		menu.setVisible(true);
	}

	protected void fillContextMenu(IMenuManager menu) {
//		IStructuredSelection selection =
//			(IStructuredSelection) getViewer().getSelection();
//		if(menu instanceof ModelContributionManager) {
//			((ModelContributionManager)menu).setSelection(selection);
//		}
		menu.update(true);
	}
	
	// linking
	
	public TreeViewer getTreeViewer() {
		return (TreeViewer)getViewer();
	}
	
	private IPartListener partListener = new IPartListener() {
		public void partActivated(IWorkbenchPart part) {
			if (part instanceof IEditorPart && part != NavigatorViewPart.this)
				editorActivated((IEditorPart) part);
		}
		public void partBroughtToTop(IWorkbenchPart part) {
		}
		public void partClosed(IWorkbenchPart part) {
		}
		public void partDeactivated(IWorkbenchPart part) {
		}
		public void partOpened(IWorkbenchPart part) {
		}
	};
	
	private void saveLinkingEnabled(IMemento _memento) {
		_memento.putInteger("LinkModelProjectsToEditor", isLinkingEnabled ? 1 : 0); //$NON-NLS-1$
	}
	
	private void restoreLinkingEnabled() {
		Integer val = memento.getInteger("LinkModelProjectsToEditor");//IWorkbenchPreferenceConstants.LINK_NAVIGATOR_TO_EDITOR); //$NON-NLS-1$
		if (val != null) {
			isLinkingEnabled = val.intValue() != 0;
		}
	}
	
	protected void editorActivated(IEditorPart editor) {
		if (!isLinkingEnabled()) {
			return;
		}
		IEditorInput input = editor.getEditorInput();
		XModelObject o = null;
		if(input instanceof IModelObjectEditorInput) {
			o = ((IModelObjectEditorInput)input).getXModelObject();
		} else if (input instanceof IFileEditorInput) {
			IFileEditorInput fileInput = (IFileEditorInput) input;
			IFile file = fileInput.getFile();
			if(file == null) return;
			o = getObjectByResource(file);
		} 
		if(o == null) return;
		ISelection newSelection = new StructuredSelection(o);
		if (!getTreeViewer().getSelection().equals(o)) {
			getTreeViewer().setSelection(newSelection);
		}
	}
	
	/**
	 * Returns model object associated with file in tree. 
	 * Navigator may display model object that is not
	 * the model object representing the file. E.g. see
	 * bean objects in jsf 
	 * @param file
	 * @return
	 */	
	protected XModelObject getObjectByResource(IFile file) {
		XModelObject o = EclipseResourceUtil.getObjectByResource(file);
		if(o == null || contentProvider == null) return o;
		XFilteredTree t = contentProvider.getFilteredTree(o);
		if(!(t instanceof FileSystemsTree)) return o;
		FileSystemsTree fst = (FileSystemsTree)t;
		return fst.getRepresentation(o);
	}

	protected void linkToEditor(IStructuredSelection selection) {
		if (!isLinkingEnabled()) {
			return;
		}
		Object obj = selection.getFirstElement();
		if (obj instanceof IAdaptable && selection.size() == 1) {
			IFile file = (IFile) ((IAdaptable)obj).getAdapter(IFile.class);
			if(file == null && obj instanceof XModelObject) {
				XModelObject o = (XModelObject)obj;
				Properties p = new Properties();
				p.setProperty("onlySelectIfOpen", "true"); //$NON-NLS-1$ //$NON-NLS-2$
				if(XActionInvoker.getAction("Open", o) != null) { //$NON-NLS-1$
					XActionInvoker.invoke("Open", o, p); //$NON-NLS-1$
				}
				return;
			}
			IWorkbenchPage page = getSite().getPage();
			//Using internal WorkbenchPage. Must change.
			IEditorPart editor = page.findEditor(new FileEditorInput(file));
			if(editor != null) {
				page.bringToTop(editor);
				return;
			}
		}
	}
	
	boolean isHandlingActivation = false;

	class ActivationListener extends ShellAdapter {
		
		public void shellActivated(ShellEvent event) {
			if(isHandlingActivation) return;
			event.widget.getDisplay().asyncExec(new Runnable() {
				public void run() {
					XJob.addRunnable(new ActivationXRunnable());
				}
			});
		}
		
	};
	private void handleActivation() throws XModelException {
		if(isHandlingActivation) return;
		isHandlingActivation = true;
		IProject[] ps = null;
		ps = ModelUIPlugin.getWorkspace().getRoot().getProjects();
		if(ps == null) return;
		for (int i = 0; i < ps.length; i++) {
			IModelNature n = EclipseResourceUtil.getModelNature(ps[i]);
			if(n != null) n.getModel().update();
		}
		isHandlingActivation = false;
	}

	class ActivationXRunnable implements XRunnable {

		public String getId() {
			return "Activation"; //$NON-NLS-1$
		}

		public void run() {
			try {
				handleActivation();
			} catch (XModelException e) {
				ModelPlugin.getPluginLog().logError(e);
			}
		}
		
	}
	
	public ResourceComparator getComparator() {
		return null; // TODO-3.3: does the contract allow you to not obey the get/set of comparator ?!
	}

	public void setComparator(ResourceComparator comparator) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.part.IShowInTarget#show(org.eclipse.ui.part.ShowInContext)
	 */
	public boolean show(ShowInContext context) {

		Object input = context.getInput();
		XModelObject o = null;
		if (input instanceof IModelObjectEditorInput) {
			o = ((IModelObjectEditorInput) input).getXModelObject();
		} else if (input instanceof IFileEditorInput) {
			IFileEditorInput fileInput = (IFileEditorInput) input;
			IFile file = fileInput.getFile();
			if (file == null)
				return false;
			o = getObjectByResource(file);
		}
		if (o == null)
			return false;
		ISelection newSelection = new StructuredSelection(o);
		if (!getTreeViewer().getSelection().equals(o)) {
			getTreeViewer().setSelection(newSelection);
		}

		return true;

	}

}
