package com.hybris.hyeclipse.platform;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;

public class PlatformHolder {
	
	private final ILog log;
	
	private final ClasspathChagedListener classpathChagedListener = new ClasspathChagedListener(this);
	private final WorkspaceChangedListener workspaceChangeListener = new WorkspaceChangedListener(this);
	
	private volatile Platform currentPlatform;
	
	PlatformHolder(ILog log) {
		this.log = log;
		final IWorkspace workspace = ResourcesPlugin.getWorkspace();
		for (final IProject project : workspace.getRoot().getProjects()) {
			if (Platform.isPlatformProject(project)) {
				platformProjectAdded(project);
			}
		}
		
		addResourceChangeListenerToWorkspace();
	}
	
	private void addResourceChangeListenerToWorkspace() {
		ResourcesPlugin.getWorkspace().addResourceChangeListener(workspaceChangeListener, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_CLOSE);
	}

	public Platform getCurrent() {
		return currentPlatform;
	}
	
	public boolean isPlatformAvailable() {
		return currentPlatform != null;
	}
	
	private void updatePlatform(final IJavaProject platformJavaProject) {
		try {
			currentPlatform = new Platform(platformJavaProject);
		}
		catch (CoreException e) {
			log.log(new Status(Status.ERROR, log.getBundle().getSymbolicName(), "Failed to update platform", e));
		}
	}
	
	private void platformProjectAdded(final IProject project) {
		assert !isPlatformAvailable();
		assert Platform.isPlatformProject(project);
		
		final IJavaProject platformJavaProject = JavaCore.create(project);
		
		updatePlatform(platformJavaProject);
		JavaCore.addElementChangedListener(classpathChagedListener, ElementChangedEvent.POST_CHANGE);
	}
	
	void clear() {
		JavaCore.removeElementChangedListener(classpathChagedListener);
		currentPlatform = null;
		addResourceChangeListenerToWorkspace();
	}
	
	void classPathChanged() {
		assert isPlatformAvailable();
		
		updatePlatform(currentPlatform.getPlatformJavaProject());
	}

	boolean isCurrentPlatformProject(IJavaProject javaProject) {
		return isPlatformAvailable() && currentPlatform.getPlatformJavaProject() == javaProject;
	}
	
	void projectAdded(final IProject project) {
		if (isPlatformAvailable() && Platform.isPlatformProject(project)) {
			log.log(new Status(Status.ERROR, log.getBundle().getSymbolicName(), "Platform project added while platform already available. There can only be ONE."));
		}
	}

	void workspaceChanged(IResourceChangeEvent event) {
		try {
			if (event.getType() == IResourceChangeEvent.PRE_DELETE || event.getType() == IResourceChangeEvent.PRE_CLOSE) {
				if (Platform.isPlatformProject((IProject)event.getResource())) {
					clear();
				}
			}
			else if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
				event.getDelta().accept(new IResourceDeltaVisitor() {

					@Override
					public boolean visit(IResourceDelta delta) throws CoreException {
						if (delta.getKind() == IResourceDelta.ADDED
								&& delta.getResource().getType() == IResource.PROJECT) {
							projectAdded((IProject) delta.getResource());
						}
						return delta.getResource().getType() == IResource.ROOT;
					}

				});
			}
		} catch (CoreException e) {
			log.log(new Status(Status.ERROR, log.getBundle().getSymbolicName(), "Error when handling IResourceChangedEvent", e));
		}
	}

}