/*******************************************************************************
 * 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.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.jboss.tools.common.model.XFilteredTree;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.filesystems.FileSystemsHelper;
import org.jboss.tools.common.model.project.IModelNature;
import org.jboss.tools.common.model.project.ModelNature;
import org.jboss.tools.common.model.ui.navigator.TreeViewerModelListenerImpl;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.model.util.XModelTreeListenerSWTASync;

public class NavigatorContentProvider implements ITreeContentProvider, IResourceChangeListener {
	protected FilteredTreesCache filteredTrees = FilteredTreesCache.getInstance();
	protected TreeViewer viewer = null;
	protected TreeViewerModelListenerImpl listener;
	protected XModelTreeListenerSWTASync syncListener;
	protected Set<String> projects = new HashSet<String>();
	protected int isLoading = 0;
	public NavigatorContentProvider() {}
	
	public void dispose() {
		if (viewer != null) {
			IWorkspace workspace = getWorkspace(viewer.getInput());
			if(workspace != null) {
				workspace.removeResourceChangeListener(this);
			}
		}
		if (projects != null) {
			projects.clear();
			projects = null;
		}
		if(filteredTrees != null && syncListener != null) {
			filteredTrees.removeListener(syncListener);
		}
		syncListener = null;
	}
	
	public void setListener(TreeViewerModelListenerImpl listener) {
		this.listener = listener;
		syncListener = new XModelTreeListenerSWTASync(listener);
	}
	
	public Object[] getChildren(Object parentElement) {
		if (parentElement instanceof XModelObject) {
			XFilteredTree tree = getFilteredTree(parentElement);
			return (tree == null) ? null : getFilteredTree(parentElement).getChildren((XModelObject)parentElement);
		}			
		return null;
	}
	
	public Object getParent(Object element)	{
		if (element instanceof XModelObject) {
			XFilteredTree tree = getFilteredTree(element);
			return (tree == null) ? null : tree.getParent((XModelObject)element);
		}
		return null;
	}
	
	public boolean hasChildren(Object element) {
		if (element instanceof XModelObject) {
		    XModelObject o = (XModelObject)element;
		    if(!o.isActive()) return false;
			XFilteredTree tree = getFilteredTree(element);
			return tree != null && tree.hasChildren((XModelObject)element);
		}
		return false;
	}
	
//	============================================ IStructuredContentProvider	
	public Object[] getElements(Object inputElement) {
		isLoading++;
		try {
		this.projects.clear();
		TreeMap<String,XModelObject> result = new TreeMap<String,XModelObject>(new C());
		if (inputElement instanceof IWorkspaceRoot) {
			IWorkspaceRoot workspaceRoot = (IWorkspaceRoot)inputElement;
			IProject[] projects = workspaceRoot.getProjects();
			for (int i = 0; i < projects.length; i++) {
				XModelObject root = addProject(projects[i]);
				if (root != null) result.put(projects[i].getName(), root);
			}
		} else if (inputElement instanceof IProject) {
			IProject p = (IProject)inputElement;
			XModelObject root = addProject(p);
			if (root != null) result.put(p.getName(), root);
		}
		return result.values().toArray();
		} finally {
			isLoading--;
		}
	}
	
//	============================================ IContentProvider	
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		this.viewer = (TreeViewer)viewer;
		IWorkspace oldWorkspace = getWorkspace(oldInput);
		IWorkspace newWorkspace = getWorkspace(newInput);

		if (oldWorkspace != newWorkspace) {
			if (oldWorkspace != null) 
				oldWorkspace.removeResourceChangeListener(this);
			if (newWorkspace != null)
				newWorkspace.addResourceChangeListener(this);
		}
	}
	
	private IWorkspace getWorkspace(Object input) {
		return (input instanceof IWorkspace) ? (IWorkspace)input
				: (input instanceof IContainer) ? ((IContainer)input).getWorkspace()
				: null;
	}
	
//	============================================ IResourceChangeListener
	public void resourceChanged(final IResourceChangeEvent event) {
		if(isLoading > 0) return;
		final IResourceDelta delta = event.getDelta();
		final IResource resource = event.getResource();
		Control ctrl = viewer.getControl();
		
		if (ctrl != null && !ctrl.isDisposed()) {
			// Do a sync exec, not an async exec, since the resource delta
			// must be traversed in this method.  It is destroyed
			// when this method returns.
			ctrl.getDisplay().syncExec(
				new Runnable() {
					public void run() {
						switch (event.getType()) {
							case IResourceChangeEvent.POST_CHANGE :
								handlePostChange(delta);
								break;
							case IResourceChangeEvent.PRE_CLOSE :
								handlePreClose(resource);
								break;
							case IResourceChangeEvent.PRE_DELETE :
								handlePreDelete(resource);
								break;
						}
						check();
					}
				}
			);
		}
	}
	
	private XModelObject addProject(IProject project)	{
		if (project == null || !project.isOpen()) return null;
		IModelNature nature = getModelNature(project);
		if(nature == null) return null;
		XModelObject root = nature.getModel().getRoot();
			//Does not work now that the check above excludes closed projects. 
			if(!project.isOpen()) {
				root = root.copy(0);
			}
		XFilteredTree tree = getFilteredTree(root);
		if(tree == null) return null;
		if(listener != null) {
			listener.setViewer(viewer);
			filteredTrees.addListener(syncListener, nature.getModel());
		}
		projects.add(project.getLocation().toString());
		return tree.getRoot();
	}
	
	protected IModelNature getModelNature(IProject project) {
		return EclipseResourceUtil.getModelNature(project);
	}

	protected String getFilteredTreeName(XModel model) {
		return getFilteredTreeName();
	}
	
	protected String getFilteredTreeName() {
		return "FileSystems";
	}
	
	XFilteredTree getFilteredTree(Object object) {
		if (object instanceof XModelObject)	{
			XModel model = ((XModelObject)object).getModel();
			if(FileSystemsHelper.getFileSystems(model) == null) return null;
			String name = getFilteredTreeName(model);
			return filteredTrees.getFilteredTree(name, model);
		}				
		return null;
	}
		
	private void handlePostChange(IResourceDelta delta) {
		Control ctrl = viewer.getControl();
		if (ctrl == null || ctrl.isDisposed()) return;

		IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.CHANGED);
		if (affectedChildren.length > 0) {
			ArrayList<XModelObject> affected = new ArrayList<XModelObject>();
			for (int i = 0; i < affectedChildren.length; i++) {
				IProject project = (IProject)affectedChildren[i].getResource().getAdapter(IProject.class);
				if (affectedChildren[i].getKind() == IResourceDelta.ADDED ||
					(affectedChildren[i].getKind() == IResourceDelta.CHANGED && 
					 ((affectedChildren[i].getFlags() & IResourceDelta.OPEN) != 0)) || 
					 !projects.contains(project.getLocation().toString())) {
					if(!ModelNature.checkModelNature(project)) {
						continue;
					}
					XModelObject root = addProject(project);
					if (root != null) affected.add(root);
				}
			}
			
			if (!affected.isEmpty()) {
				viewer.add(viewer.getInput(), affected.toArray());
				viewer.refresh();
			}
		}
	}

	private void handlePreClose(IResource resource)	{
		handlePreDelete(resource);		
	}

	private void handlePreDelete(IResource resource) {
		Control ctrl = viewer.getControl();
		if (ctrl == null || ctrl.isDisposed()) return;

		IProject project = (IProject)resource.getAdapter(IProject.class);
		if(project == null) return;
		XModelObject root = getRootByProject(project);
		projects.remove(project.getLocation().toString());
		if (root != null) {
			root.getModel().removeModelTreeListener(syncListener);
			filteredTrees.remove(root.getModel());
			viewer.remove(root);
		} 
	}

	protected XModelObject getRootByProject(IProject project) {
		if(project == null || !project.isOpen()) return null;
		IModelNature nature = EclipseResourceUtil.getModelNature(project);
		if(nature == null) return null;
		XFilteredTree filteredTree = getFilteredTree(nature.getModel().getRoot());
		return (filteredTree != null) ? filteredTree.getRoot() : null;
	}
	
	private void check() {
		if(viewer == null) return;
		Tree swtTree = viewer.getTree();
		if(swtTree == null || swtTree.isDisposed()) return;
		TreeItem[] is = swtTree.getItems();
		if(is == null || is.length == 0) return;
		for (int i = 0; i < is.length; i++) {
			XModelObject o = (XModelObject)is[i].getData();
			if(o == null) continue;
			IProject p = EclipseResourceUtil.getProject(o);
			IModelNature nature = EclipseResourceUtil.getModelNature(p);
			if(nature == null) {
				o.getModel().removeModelTreeListener(syncListener);
				viewer.remove(o);
				projects.remove(p.getLocation().toString());
			} else {
				String classname = o.getModel().getMetaData().getMapping("FilteredTrees").getValue(getFilteredTreeName(o.getModel()));
				XFilteredTree tree = getFilteredTree(o.getModel().getRoot());
				if(tree != null && !tree.getClass().getName().equals(classname)) {
					o.getModel().removeModelTreeListener(syncListener);
					projects.remove(p.getLocation().toString());
					viewer.remove(o);
					viewer.refresh();
				}
			}
		}
	}
	
	class C implements Comparator<Object> {
		public int compare(Object o1, Object o2) {
			return (o1 == null ? "" : o1.toString()).compareToIgnoreCase(o2 == null ? "" : o2.toString());
		}
		
	}
	
}
