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

import java.util.*;

import org.eclipse.core.resources.*;
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.Status;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.progress.UIJob;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.filesystems.FileSystemsHelper;
import org.jboss.tools.common.model.impl.XModelObjectImpl;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.util.EclipseResourceUtil;

public class XMarkerManager implements IResourceChangeListener {
	
	private static XMarkerManager instance;
	
	public static XMarkerManager getInstance() {
		if(instance == null) {
			instance = new XMarkerManager();
		}
		return instance;
	}

	private Map<IFile, Set<XMarker>> xmarkers = new HashMap<IFile, Set<XMarker>>();
	
	private Map<IFile, Set<XModelObject>> errorObjects = new HashMap<IFile, Set<XModelObject>>();
	private Map<IFile, Set<XModelObject>> warningObjects = new HashMap<IFile, Set<XModelObject>>();
	private Set<IFile> uptodate = new HashSet<IFile>();
	
	private XMarkerManager() {
		ModelPlugin.getWorkspace().addResourceChangeListener(this);
	}

	public void resourceChanged(IResourceChangeEvent event) {
		if((event.getType() == IResourceChangeEvent.PRE_DELETE
			|| event.getType() == IResourceChangeEvent.PRE_CLOSE)
				&& event.getResource() instanceof IProject) {
			IProject p = (IProject)event.getResource();
			clear(p.getFullPath());
			return;
		}
		IResourceDelta delta = event.getDelta();
		try {
			if(delta != null) {
				delta.accept(visitor);
			}
		} catch (CoreException e) {
			ModelPlugin.getDefault().logError(e);
		}
	}

	private synchronized void clear(IFile f) {
		uptodate.remove(f);
		errorObjects.remove(f);
		warningObjects.remove(f);
	}

	private synchronized void clear(IPath f) {
		Iterator<IFile> it = uptodate.iterator();
		while(it.hasNext()) {
			if(f.isPrefixOf(it.next().getFullPath())) {
				it.remove();
			}
		}
		it = errorObjects.keySet().iterator();
		while(it.hasNext()) {
			if(f.isPrefixOf(it.next().getFullPath())) {
				it.remove();
			}
		}
		it = warningObjects.keySet().iterator();
		while(it.hasNext()) {
			if(f.isPrefixOf(it.next().getFullPath())) {
				it.remove();
			}
		}
	}
	

	ResourceDeltaVisitor visitor = new ResourceDeltaVisitor();

	class ResourceDeltaVisitor implements IResourceDeltaVisitor {
		@Override
		public boolean visit(IResourceDelta delta) throws CoreException {
			IResource r = delta.getResource();
			if(delta.getKind() == IResourceDelta.REMOVED) {
				if(r instanceof IFile) {
					clear((IFile)r);
				} else {
					clear(r.getFullPath());
				}
				return false;
			} else if(delta.getKind() == IResourceDelta.ADDED) {
				return false;
			} else if(delta.getKind() == IResourceDelta.NO_CHANGE) {
				return true;
			} else {
				if(r instanceof IFile) {
					IFile f = (IFile)r;
					if(uptodate.contains(f)) {
						synchronized(this) {
							uptodate.remove(f);
						}
						if(f.exists()) {
							updateJob.add(f);
						}
					}
				}
			}
			return true;
		}		
	}


	UpdateJob updateJob = new UpdateJob();

	class UpdateJob extends UIJob {// implements XRunnable {		
		Set<IFile> fs = new HashSet<IFile>();
		boolean running = false;

		UpdateJob() {
			super(Display.getDefault(), "XMarkerManager");
		}

		public String getId() {
			return "XMarkerManager"; //$NON-NLS-1$
		}
//		public void run() {
		public IStatus runInUIThread(IProgressMonitor monitor) {
			synchronized (this) {
				running = true;
			}
//			try {
//				Thread.sleep(100);
//			} catch (InterruptedException e) {
//				return Status.OK_STATUS;
//			}
			try {
				IFile f = null;
				while((f = nextFile()) != null) {
					reload(f);
				}
			} finally {
				synchronized (this) {
					running = false;
				}
			}
			return Status.OK_STATUS;
		}

		synchronized IFile nextFile() {
			if(fs.isEmpty()) {
				return null;
			}
			IFile f = fs.iterator().next();
			fs.remove(f);
			return f;
		}
	
		public synchronized void add(IFile f) {
			if(fs.contains(f)) {
				return;
			}
			fs.add(f);
			if(!running) {
//				XJob.addRunnable(updateJob);
				running = true;
				schedule(100);
			}
		}		
	}

	void forceReload(IFile file) {
		synchronized (this) {
			uptodate.remove(file);
		}
		reload(file);
	}
	
	public void reload(IFile file) {
		synchronized (this) {
			if(uptodate.contains(file)) {
				return;
			} else {
				uptodate.add(file);
			}
		}
		IMarker[] ms = new IMarker[0];
		try {
			ms = (!file.isAccessible()) 
				? ModelPlugin.getWorkspace().getRoot().findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE)
				: file.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
		} catch (CoreException e) {
			ModelPlugin.getPluginLog().logError(e);
		}

		
		Set<XMarker> xms = new HashSet<XMarker>();
		synchronized (this) {
			if(file != null && file.isAccessible()) {
				Set<XMarker> xms1 = getMarkers(file);
				if(xms1 != null) xms.addAll(xms1);
			}
		}
		
		Set<XModelObject> os;

		synchronized (this) { 
			os = errorObjects.get(file);
			if(os == null) {
				os = new HashSet<XModelObject>();
				errorObjects.put(file, os);
			}
		}
		reload(ms, file, xms, os, IMarker.SEVERITY_ERROR);
		synchronized (this) { 
			os = warningObjects.get(file);
			if(os == null) {
				os = new HashSet<XModelObject>();
				warningObjects.put(file, os);
			}
		}
		xms.clear(); //xmarkers are all errors
		reload(ms, file, xms, os, IMarker.SEVERITY_WARNING);
	}
	
	void reload(IMarker[] ms, IFile file, Set<XMarker> xms, Set<XModelObject> objects, int severity) {
		Set<XModelObject> es = new HashSet<XModelObject>();
		for (int i = 0; i < ms.length; i++) {
			if(severity != ms[i].getAttribute(IMarker.SEVERITY, 0)) continue;
			IResource r = ms[i].getResource();
			XModelObject o = EclipseResourceUtil.getObjectByResource(r);
			if(o == null) o = EclipseResourceUtil.createObjectForResource(r);
			if(o == null) continue;
			String path = ms[i].getAttribute("path", null); //$NON-NLS-1$
			o = (path == null) ? o : o.getModel().getByPath(path);
			if(o == null) continue;
			es.add(o);
			String attr = ms[i].getAttribute("attribute", null); //$NON-NLS-1$
			if(attr != null && attr.length() > 0) {
				((XModelObjectImpl)o).addErrorAttributeDirty(attr);
			}
		}
		if(!xms.isEmpty()) {
			XModelObject o = EclipseResourceUtil.getObjectByResource(file);
			if(o == null) o = EclipseResourceUtil.createObjectForResource(file);
			if(o != null) {
				for (XMarker m: xms) {
					String path = m.getPath();
					XModelObject c = o.getModel().getByPath(path);
					if(c != null) {
						es.add(c);
						String attr = m.getAttribute();
						if(attr != null && attr.length() > 0) {
							((XModelObjectImpl)c).addErrorAttributeDirty(attr);
						}
					}
				}
			}			
		}
		Set<XModelObject> copy = new HashSet<XModelObject>();
		Set<XModelObject> toRemove = new HashSet<XModelObject>();
		Set<XModelObject> toAdd = new HashSet<XModelObject>();
		
		synchronized(this) {
			copy.addAll(objects);
		}
		
		Iterator<XModelObject> it = copy.iterator();
		while(it.hasNext()) {
			XModelObject o = it.next();
			if(!es.contains(o)) {
				if(o.getErrorState() == severity) {
					o.setErrorState(0);
				}
				toRemove.add(o);
			} else if(es.contains(o)) {
				if(severity > o.getErrorState()) {
					o.setErrorState(severity);
				} else {
					((XModelObjectImpl)o).commitErrorAttributes();
				}
				es.remove(o);
			}
		}
		it = es.iterator();
		while(it.hasNext()) {
			XModelObject o = (XModelObject)it.next();
			if(severity > o.getErrorState()) {
				o.setErrorState(severity);
			} else {
				((XModelObjectImpl)o).commitErrorAttributes();
			}					
			if(!objects.contains(o)) {
				toAdd.add(o);
			}
		}

		synchronized(this) {
			objects.removeAll(toRemove);
			objects.addAll(toAdd);
		}
		
	}

	void update(XModelObject object) {
		XModelObject fo = FileSystemsHelper.getFile(object);
		if(fo != null) {
			IFile f = (IFile)fo.getAdapter(IFile.class);
			if(f != null && f.exists()) {
				reload(f);
			}
		}
	}
	
	public int getErrorState(XModelObject object) {
		if(object == null) return 0;
		update(object);
		if(object.getErrorState() == IMarker.SEVERITY_ERROR || object.getErrorChildCount() > 0) return IMarker.SEVERITY_ERROR;
		if(object.getErrorState() == IMarker.SEVERITY_WARNING || object.getWarningChildCount() > 0) return IMarker.SEVERITY_WARNING;
		return 0;
	}
	
	public boolean hasErrors(XModelObject object) {
		update(object);
		return object != null && (object.getErrorState() == IMarker.SEVERITY_ERROR || object.getErrorChildCount() > 0);
	}
	
	public boolean hasWarnings(XModelObject object) {
		update(object);
		return object != null && (object.getErrorState() == IMarker.SEVERITY_WARNING || object.getWarningChildCount() > 0);
	}
	
	public boolean hasErrors(XModelObject object, String attribute) {
		update(object);
		if(attribute == null) return hasErrors(object);
		if(object.getErrorState() == 0) return false;
		return object.getAttributeErrorState(attribute);
	}

	public String getError(XModelObject object, String attribute) {
		XModelObject f = ((XModelObjectImpl)object).getResourceAncestor();
		IFile file = (f == null) ? null : (IFile)f.getAdapter(IFile.class);
		if(file == null) return null;
		IMarker[] ms = null;
		try {
			ms = file.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
		} catch (CoreException e) {
			//ignore no markers - no problem			
			return null;
		}
		if(ms == null) return null;
		String pathInFile = object.getPath().substring(f.getPath().length());
		for (int i = 0; i < ms.length; i++) {
			XModelObject o = object;
			String path = ms[i].getAttribute("path", null); //$NON-NLS-1$
			if(path != null && !path.endsWith(pathInFile)) {
				continue;
			}
			o = (path == null) ? o : o.getModel().getByPath(path);
			if(o == null) continue;
			String attr = ms[i].getAttribute("attribute", null); //$NON-NLS-1$
			if(attr != null && attr.equals(attribute)) {
				return ms[i].getAttribute(IMarker.MESSAGE, null);
			}
		}

		Set<XMarker> xms = getMarkers(file);
		if(xms != null) {
			for (XMarker m: xms) {
				XModelObject o = object;
				String path = m.getPath();
				if(path != null && !path.endsWith(pathInFile)) {
					continue;
				}
				o = (path == null) ? o : o.getModel().getByPath(path);
				if(o == null) continue;
				String attr = m.getAttribute();
				if(attr != null && attr.equals(attribute)) {
					return m.getMessage();
				}
			}
		}
		return null;
	}

	Set<XMarker> getMarkers(IFile file) {
		return xmarkers.get(file);
	}

	synchronized void clearXMarkers(IFile file, Set<XMarker> removed) {
		Set<XMarker> all = getMarkers(file);
		if(all != null) {
			all.removeAll(removed);
		}
	}

	synchronized void addXMarkers(IFile file, Set<XMarker> added) {
		Set<XMarker> all = getMarkers(file);
		if(all == null) {
			all = new HashSet<XMarker>();
			xmarkers.put(file, all);
		}
		all.addAll(added);
	}
}

/*local*/ class XMarker {
	String type;
	String message;
	String path;
	String attribute;

	public XMarker() {}

	public void setType(String s) {
		type = s;
	}

	public void setMessage(String s) {
		message = s;
	}

	public void setPath(String s) {
		path = s;
	}

	public void setAttribute(String s) {
		attribute = s;
	}

	public String getType() {
		return type;
	}

	public String getMessage() {
		return message;
	}

	public String getPath() {
		return path;
	}

	public String getAttribute() {
		return attribute;
	}
}
