content: /**
   *  Copyright (c) 2013-2016 Angelo ZERR.
   *  All rights reserved. This program and the accompanying materials
   *  are 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:
   *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
   */
  package tern.eclipse.ide.internal.core;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  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.IResourceDeltaVisitor;
  import org.eclipse.core.resources.ResourcesPlugin;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IPath;
  import org.eclipse.core.runtime.preferences.InstanceScope;
  
  import com.eclipsesource.json.JsonArray;
  import com.eclipsesource.json.JsonObject;
  import com.eclipsesource.json.JsonValue;
  
  import tern.TernException;
  import tern.eclipse.ide.core.IIDETernProject;
  import tern.eclipse.ide.core.IIDETernRepository;
  import tern.eclipse.ide.core.ITernRepositoryManager;
  import tern.eclipse.ide.core.TernCorePlugin;
  import tern.eclipse.ide.core.preferences.TernCorePreferenceConstants;
  import tern.eclipse.ide.internal.core.preferences.TernCorePreferencesSupport;
  import tern.eclipse.ide.internal.core.resources.IDETernProject;
  import tern.server.ITernModule;
  import tern.server.ITernPlugin;
  import tern.utils.StringUtils;
  import tern.utils.TernModuleHelper;
  
  /**
   * Manager of tern repository.
   *
   */
  public class TernRepositoryManager implements ITernRepositoryManager, IResourceChangeListener, IResourceDeltaVisitor {
  
  	private static final TernRepositoryManager INSTANCE = new TernRepositoryManager();
  	private static IIDETernRepository DEFAULT_REPOSITORY;
  
  	public static TernRepositoryManager getManager() {
  		return INSTANCE;
  	}
  
  	private final Map<String, IIDETernRepository> repositories;
  
  	public TernRepositoryManager() {
  		this.repositories = new HashMap<String, IIDETernRepository>();
  	}
  
  	public void initialize() {
  		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
  	}
  
  	public void dispose() {
  		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
  	}
  
  	@Override
  	public Collection<IIDETernRepository> getRepositories() {
  		loadRepositoriesIfNeeded();
  		return repositories.values();
  	}
  
  	/**
  	 * Load tern repositories if needed.
  	 */
  	private void loadRepositoriesIfNeeded() {
  		if (repositories.size() == 0) {
  			loadRepositories();
  		}
  	}
  
  	/**
  	 * Load tern repositories.
  	 */
  	private synchronized void loadRepositories() {
  		if (repositories.size() > 0) {
  			// already loaded.
  			return;
  		}
  		repositories.clear();
  		// default repositories
  		loadDefaultRepositories();
  		// custom repositories
  		loadCustomRepositories();
  		// install external modules
  		installExternalModules();
  	}
  
  	/**
  	 * Deploy module contributions declared with extension point
  	 * "tern.eclipse.ide.core.ternModuleContributions" in each tern
  	 * repositories.
  	 */
  	private void installExternalModules() {
  		TernModuleInstall[] modules = TernModuleInstallManager.getManager().getTernModuleInstalls();
  		for (TernModuleInstall module : modules) {
  			Collection<IIDETernRepository> reps = repositories.values();
  			for (IIDETernRepository repository : reps) {
  				try {
  					repository.install(module.getSrc());
  				} catch (Throwable e) {
  					Trace.trace(Trace.SEVERE, "Cannot install module" + module.getName(), e);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Load default tern repositories.
  	 */
  	private void loadDefaultRepositories() {
  		addRepository(getDefaultRepository(), repositories);
  	}
  
  	/**
  	 * Load custom tern repositories.
  	 */
  	private void loadCustomRepositories() {
  		String values = new InstanceScope().getNode(TernCorePlugin.getDefault().getBundle().getSymbolicName())
  				.get(TernCorePreferenceConstants.REPOSITORIES, null);
  		if (!StringUtils.isEmpty(values)) {
  			String[] s = values.split(REPOSITORY_SEPARATOR);
  			String name = null;
  			File baseDir = null;
  			for (int i = 0; i < s.length / 2; i++) {
  				name = s[i];
  				baseDir = new File(s[i + 1]);
  				addRepository(createRepository(name, baseDir), repositories);
  			}
  		}
  	}
  
  	@Override
  	public IIDETernRepository createRepository(String name, File baseDir) {
  		return createRepository(name, baseDir, false);
  	}
  
  	private IIDETernRepository createRepository(String name, File baseDir, boolean isDefault) {
  		return new IDETernRepository(name, baseDir, isDefault);
  	}
  
  	@Override
  	public IIDETernRepository getDefaultRepository() {
  		if (DEFAULT_REPOSITORY != null) {
  			return DEFAULT_REPOSITORY;
  		}
  		try {
  			DEFAULT_REPOSITORY = createDefaultRepository();
  		} catch (Throwable e) {
  			Trace.trace(Trace.SEVERE, "Cannot load the default tern repository.", e);
  		}
  		return DEFAULT_REPOSITORY;
  	}
  
  	private synchronized IIDETernRepository createDefaultRepository() throws TernException, IOException {
  		if (DEFAULT_REPOSITORY != null) {
  			return DEFAULT_REPOSITORY;
  		}
  		return createRepository(DEFAULT_REPOSITORY_NAME, TernCorePlugin.getTernRepositoryBaseDir(), true);
  	}
  
  	private void addRepository(IIDETernRepository repository, Map<String, IIDETernRepository> repositories) {
  		if (repository != null) {
  			repositories.put(repository.getName(), repository);
  		}
  	}
  
  	@Override
  	public IIDETernRepository getRepository(String name) {
  		loadRepositoriesIfNeeded();
  		return repositories.get(name);
  	}
  
  	private IIDETernRepository getRepository(IIDETernProject ternProject) {
  		return getRepository(ternProject != null ? ternProject.getProject() : null);
  	}
  
  	@Override
  	public IIDETernRepository getRepository(IProject project) {
  		loadRepositoriesIfNeeded();
  		String name = TernCorePreferencesSupport.getInstance().getUsedTernRepositoryName(project);
  		if (!StringUtils.isEmpty(name)) {
  			IIDETernRepository repository = getRepository(name);
  			if (repository != null) {
  				return repository;
  			}
  		}
  		return getDefaultRepository();
  	}
  
  	@Override
  	public void setRepositories(Collection<IIDETernRepository> repositories) {
  		StringBuilder value = new StringBuilder();
  		for (IIDETernRepository repository : repositories) {
  			if (!repository.isDefault()) {
  				if (value.length() > 0) {
  					value.append(REPOSITORY_SEPARATOR);
  				}
  				value.append(repository.getName());
  				value.append(REPOSITORY_SEPARATOR);
  				value.append(repository.getTernBaseDirAsString());
  			}
  		}
  		new InstanceScope().getNode(TernCorePlugin.getDefault().getBundle().getSymbolicName())
  				.put(TernCorePreferenceConstants.REPOSITORIES, value.toString());
  		loadRepositories();
  	}
  
  	@Override
  	public List<ITernModule> getCheckedModules(IIDETernProject ternProject, List<ITernModule> allModules) {
  		List<ITernModule> checkedModules = new ArrayList<ITernModule>();
  		// Tern Plugins
  		JsonValue options = null;
  		JsonObject plugins = ternProject.getPlugins();
  		for (String name : plugins.names()) {
  			options = plugins.get(name);
  			ITernModule plugin = findTernModule(name.toString(), ternProject);
  			updateCheckedModule(plugin, options, allModules, checkedModules);
  		}
  
  		// JSON Type Definitions
  		JsonArray defs = ternProject.getLibs();
  		for (JsonValue name : defs) {
  			ITernModule def = findTernModule(name.asString(), ternProject);
  			updateCheckedModule(def, null, allModules, checkedModules);
  		}
  		return checkedModules;
  	}
  
  	@Override
  	public List<ITernModule> getCheckedModules(String[] moduleNames, List<ITernModule> allModules,
  			List<ITernModule> groupedModules) {
  		List<ITernModule> checkedModules = new ArrayList<ITernModule>();
  		for (String name : moduleNames) {
  			ITernModule module = findTernModule(name, allModules);
  			updateCheckedModule(module, null, groupedModules, checkedModules);
  		}
  		return checkedModules;
  	}
  
  	/**
  	 * Update the checked modules with the given module.
  	 * 
  	 * @param module
  	 * @param options
  	 * @param allModules
  	 * @param checkedModules
  	 */
  	private void updateCheckedModule(ITernModule module, JsonValue options, List<ITernModule> allModules,
  			List<ITernModule> checkedModules) {
  		if (module != null) {
  			if (!TernModuleHelper.isConfigurableModule(module)) {
  				addModule(module, checkedModules);
  			} else {
  				try {
  					addModule(TernModuleHelper.findConfigurable(module, options, allModules), checkedModules);
  				} catch (TernException e) {
  					Trace.trace(Trace.SEVERE, "Error while finding configurable module.", e);
  				}
  			}
  		}
  	}
  
  	private void addModule(ITernModule module, List<ITernModule> checkedModules) {
  		if (!checkedModules.contains(module)) {
  			checkedModules.add(module);
  		}
  	}
  
  	@Override
  	public ITernModule findTernModule(String name, IIDETernProject ternProject) {
  		IIDETernRepository repository = getRepository(ternProject);
  		ITernModule m = repository.getModule(name);
  		if (m != null) {
  			return m;
  		}
  		if (ternProject != null) {
  			List<ITernModule> projectModules = ternProject.getProjectModules();
  			return findTernModule(name, projectModules);
  		}
  		return null;
  	}
  
  	private ITernModule findTernModule(String name, List<ITernModule> projectModules) {
  		if (projectModules != null) {
  			for (ITernModule module : projectModules) {
  				if (module.getName().equals(name)) {
  					return module;
  				}
  			}
  		}
  		return null;
  	}
  
  	@Override
  	public ITernModule[] getTernModules(String moduleNames, IDETernProject ternProject) {
  		ITernModule module = null;
  		List<ITernModule> modules = new ArrayList<ITernModule>();
  		String[] names = moduleNames.split(",");
  		for (int i = 0; i < names.length; i++) {
  			module = findTernModule(names[i], ternProject);
  			if (module != null) {
  				addModule(module, modules);
  			}
  		}
  		return modules.toArray(ITernPlugin.EMPTY_MODULE);
  	}
  
  	@Override
  	public void resourceChanged(IResourceChangeEvent event) {
  		try {
  			IResource resource = event.getResource();
  			switch (event.getType()) {
  			case IResourceChangeEvent.PRE_DELETE:
  				// called when project is deleted.
  			case IResourceChangeEvent.PRE_CLOSE:
  				// called when project is closed.
  				if (resource != null && resource.getType() == IResource.PROJECT) {
  					IProject project = (IProject) resource;
  					disconnectProject(project);
  				}
  				break;
  			case IResourceChangeEvent.POST_CHANGE:
  				IResourceDelta delta = event.getDelta();
  				if (delta != null) {
  					delta.accept(this);
  				}
  				break;
  			}
  		} catch (Throwable e) {
  			Trace.trace(Trace.SEVERE, "Error while tern repository synchronization", e);
  		}
  	}
  
  	private void connectProject(IProject project) {
  		IPath location = project.getLocation();
  		if (location != null) {
  			for (IIDETernRepository repository : repositories.values()) {
  				if (location.equals(repository.getLocation())) {
  					((IDETernRepository) repository).setProject(project);
  				}
  			}
  		}
  	}
  
  	private void disconnectProject(IProject project) {
  		for (IIDETernRepository repository : repositories.values()) {
  			if (project.equals(repository.getProject())) {
  				((IDETernRepository) repository).setProject(null);
  			}
  		}
  	}
  
  	@Override
  	public boolean visit(IResourceDelta delta) throws CoreException {
  		IResource resource = delta.getResource();
  		if (resource == null) {
  			return false;
  		}
  		switch (resource.getType()) {
  		case IResource.ROOT:
  			return true;
  		case IResource.PROJECT:
  			IProject project = (IProject) resource;
  			if (project.isOpen()
  					&& (delta.getKind() == IResourceDelta.CHANGED || delta.getKind() == IResourceDelta.ADDED)
  					&& (delta.getFlags() & IResourceDelta.OPEN) != 0) {
  				connectProject(project);
  			}
  			return false;
  		}
  
  		return false;
  	}
  
  }
  