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.utils;
  
  import static tern.utils.ExtensionUtils.JSON_EXTENSION;
  import static tern.utils.ExtensionUtils.JS_EXTENSION;
  import static tern.utils.ExtensionUtils.TERN_SUFFIX;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import com.eclipsesource.json.JsonObject;
  import com.eclipsesource.json.JsonValue;
  
  import tern.ITernProject;
  import tern.TernException;
  import tern.metadata.ModuleDependenciesComparator;
  import tern.metadata.TernModuleMetadata;
  import tern.metadata.TernModuleMetadataManager;
  import tern.repository.ITernRepository;
  import tern.server.BasicTernDef;
  import tern.server.BasicTernPlugin;
  import tern.server.ITernDef;
  import tern.server.ITernModule;
  import tern.server.ITernModuleConfigurable;
  import tern.server.ITernPlugin;
  import tern.server.ModuleType;
  import tern.server.TernModuleConfigurable;
  import tern.server.TernModuleInfo;
  
  /**
   * Helper for {@link ITernModule}.
   *
   */
  public class TernModuleHelper {
  
  	private TernModuleHelper() {
  	}
  
  	/**
  	 * Group the given list tern modules by {@link ITernModule#getType()}.
  	 * 
  	 * @param modules
  	 * @param groupedModules
  	 */
  	public static List<ITernModule> groupByType(List<ITernModule> modules) {
  		List<ITernModule> groupedModules = new ArrayList<ITernModule>();
  		Map<String, TernModuleConfigurable> wrappers = null;
  		for (ITernModule module : modules) {
  			if (!isConfigurableModule(module)) {
  				// module is not configurable, add it
  				groupedModules.add(module);
  			} else {
  				// module is configurable (version can be customized, or
  				// options), wrap it with TernModuleConfigurable
  				if (wrappers == null) {
  					wrappers = new HashMap<String, TernModuleConfigurable>();
  				}
  				TernModuleConfigurable wrapper = wrappers.get(module.getType());
  				if (wrapper == null) {
  					wrapper = new TernModuleConfigurable(module);
  					wrappers.put(module.getType(), wrapper);
  					groupedModules.add(wrapper);
  				} else {
  					wrapper.addModule(module);
  				}
  			}
  		}
  		return groupedModules;
  	}
  
  	/**
  	 * Returns true if the given module can be configured and false otherwise.
  	 * 
  	 * @param module
  	 * @return true if the given module can be configured and false otherwise.
  	 */
  	public static boolean isConfigurableModule(ITernModule module) {
  		TernModuleMetadata metadata = module.getMetadata();
  		return !StringUtils.isEmpty(module.getVersion())
  				|| (metadata != null && (metadata.hasOptions() || metadata.isLinter()));
  	}
  
  	/**
  	 * Update the given list of {@link ITernDef} or {@link ITernPlugin} by using
  	 * the given module.
  	 * 
  	 * @param module
  	 * @param defs
  	 *            to update.
  	 * @param plugins
  	 *            to update.
  	 */
  	public static void update(List<ITernDef> defs, List<ITernPlugin> plugins, ITernModule module) {
  		update(defs, plugins, null, module);
  	}
  
  	/**
  	 * Update the given list of {@link ITernDef} or {@link ITernPlugin} by using
  	 * the given module.
  	 * 
  	 * @param module
  	 * @param defs
  	 *            to update.
  	 * @param plugins
  	 *            to update.
  	 */
  	private static void update(List<ITernDef> defs, List<ITernPlugin> plugins, JsonValue options, ITernModule module) {
  		switch (module.getModuleType()) {
  		case Def:
  			defs.add((ITernDef) module);
  			break;
  		case Plugin:
  			plugins.add((ITernPlugin) module);
  			break;
  		case Configurable:
  			ITernModule wrappedModule = ((ITernModuleConfigurable) module).getWrappedModule();
  			JsonValue wrappedOptions = ((ITernModuleConfigurable) module).getOptions();
  			update(defs, plugins, wrappedOptions, wrappedModule);
  			break;
  		}
  	}
  
  	/**
  	 * Update the given tern project by using the given module.
  	 * 
  	 * @param module
  	 * @param ternProject
  	 */
  	public static void update(ITernModule module, ITernProject ternProject) {
  		update(module, null, ternProject);
  	}
  
  	/**
  	 * Update the given tern project by using the given module.
  	 * 
  	 * @param module
  	 * @param ternProject
  	 */
  	public static void update(ITernModule module, JsonValue options, ITernProject ternProject) {
  		switch (module.getModuleType()) {
  		case Def:
  			ternProject.addLib((ITernDef) module);
  			break;
  		case Plugin:
  			ternProject.addPlugin((ITernPlugin) module, options);
  			break;
  		case Configurable:
  			ITernModule wrappedModule = ((ITernModuleConfigurable) module).getWrappedModule();
  			JsonValue wrappedOptions = ((ITernModuleConfigurable) module).getOptions();
  			update(wrappedModule, wrappedOptions, ternProject);
  			break;
  		}
  	}
  
  	/**
  	 * Retrieve {@link ITernModuleConfigurable} for the given module inside the
  	 * list of modules.
  	 * 
  	 * @param module
  	 * @param options
  	 * @param allModules
  	 * @return
  	 * @throws TernException
  	 */
  	public static ITernModuleConfigurable findConfigurable(ITernModule module, JsonValue options,
  			List<ITernModule> allModules) throws TernException {
  		String version = module.getVersion();
  		ITernModuleConfigurable c;
  		for (ITernModule f : allModules) {
  			if (f.getModuleType() == ModuleType.Configurable && f.getType().equals(module.getType())) {
  				c = ((ITernModuleConfigurable) f);
  				if (!StringUtils.isEmpty(version)) {
  					c.setVersion(version);
  				}
  				if (options != null) {
  					if (options.isObject()) {
  						// set a copy of the options.
  						c.setOptions(new JsonObject((JsonObject) options));
  					} else if (options.isNull()) {
  						c.setOptions(options);
  					}
  				}
  				return (ITernModuleConfigurable) f;
  			}
  		}
  		return null;
  	}
  
  	/**
  	 * Returns true if the given module has options and false otherwise.
  	 * 
  	 * @param module
  	 * @return true if the given module has options and false otherwise.
  	 */
  	public static boolean hasOptions(ITernModule module) {
  		if (module == null || module.getMetadata() == null) {
  			return false;
  		}
  		return module.getMetadata().hasOptions();
  	}
  
  	public static ITernModule createModule(File file, ITernRepository repository, ITernRepository defaultRepository) {
  		String filename = file.getName();
  		if (file.isDirectory()) {
  			if (filename.startsWith(TERN_SUFFIX)) {
  				// the folder follows the syntax tern-{name} where name is the
  				// tern plugin name.
  				TernModuleInfo info = new TernModuleInfo(filename.substring(TERN_SUFFIX.length(), filename.length()));
  				// try to get local or repository metadata.
  				TernModuleMetadata metadata = getMetadata(info.getType(), file, repository, defaultRepository);
  				if (metadata != null && metadata.isDef()) {
  					return new BasicTernDef(info, metadata);
  				}
  				return new BasicTernPlugin(info, metadata);
  			}
  		} else if (file.isFile()) {
  			// the module is (local file), try and guess the module type (*.json
  			// or *.js)
  			return createModule(filename, repository, defaultRepository);
  		}
  		return null;
  	}
  
  	/**
  	 * Create tern module from the given file name and null if it's not a tern
  	 * module.
  	 * 
  	 * @param filename
  	 * @return tern module from the given file name and null if it's not a tern
  	 *         module.
  	 */
  	public static ITernModule createModule(String filename, ITernRepository repository,
  			ITernRepository defaultRepository) {
  		int index = filename.lastIndexOf('.');
  		if (index == -1) {
  			// the file has none file extension, it's not a tern plugin.
  			return null;
  		}
  		String fileExtension = filename.substring(index + 1, filename.length());
  		filename = filename.substring(0, index);
  		if (fileExtension.equals(JSON_EXTENSION)) {
  			// the file is JSON file, it's a tern JSON Type Definition
  			TernModuleInfo info = new TernModuleInfo(filename);
  			TernModuleMetadata metadata = getMetadata(info.getType(), null, repository, defaultRepository);
  			return new BasicTernDef(info, metadata);
  		} else if (fileExtension.equals(JS_EXTENSION)) {
  			// the file is JavaScript file, it's a tern plugin
  			TernModuleInfo info = new TernModuleInfo(filename);
  			TernModuleMetadata metadata = getMetadata(info.getType(), null, repository, defaultRepository);
  			return new BasicTernPlugin(info, metadata);
  		}
  		// it's not a tern module
  		return null;
  	}
  
  	/**
  	 * Retrieve module metadata from the given module type and null otherwise.
  	 * 
  	 * @param moduleName
  	 * @param moduleDir
  	 *            search file metadata/${moduleName}.metadata.json
  	 * @param repository
  	 *            otherwise search in the repository of the module
  	 * @param defaultRepository
  	 *            otherwise search in the default repository.
  	 * @return module metadata from the given module type and null otherwise.
  	 */
  	private static TernModuleMetadata getMetadata(String moduleName, File moduleDir, ITernRepository repository,
  			ITernRepository defaultRepository) {
  		TernModuleMetadata metadata = null;
  		if (moduleDir != null && moduleDir.isDirectory()) {
  			try {
  				metadata = TernModuleMetadataManager.loadMetadata(moduleDir, moduleName);
  			} catch (IOException e) {
  				e.printStackTrace();
  			}
  		}
  		if (metadata == null && repository != null) {
  			metadata = repository.getDefaultMetadata(moduleName);
  		}
  		if (metadata == null && defaultRepository != null) {
  			metadata = defaultRepository.getDefaultMetadata(moduleName);
  		}
  		return metadata;
  	}
  
  	/**
  	 * Returns the file path as string.
  	 * 
  	 * @param file
  	 * @return the file path as string.
  	 */
  	public static String getPath(File file) {
  		try {
  			return file.getCanonicalPath();
  		} catch (IOException e) {
  			return file.getPath();
  		}
  	}
  
  	/**
  	 * Returns the file name of the given tern module.
  	 * 
  	 * @param module
  	 * @return the file name of the given tern module.
  	 */
  	public static String getFileName(ITernModule module) {
  		switch (module.getModuleType()) {
  		case Def:
  			return new StringBuilder(module.getName()).append('.').append(JSON_EXTENSION).toString();
  		default:
  			return new StringBuilder(module.getName()).append('.').append(JS_EXTENSION).toString();
  		}
  	}
  
  	/**
  	 * Sort the given list of modules by dependencies.
  	 * 
  	 * @param modules
  	 */
  	public static void sort(List<ITernModule> modules) {
  		sort(modules, null);
  	}
  	
  	public static void sort(List<ITernModule> modules, TernModuleMetadataManager manager) {
  		new ModuleDependenciesComparator(modules, manager);
  	}
  
  	/**
  	 * Format list modules as string.
  	 * 
  	 * @return format list modules as string.
  	 */
  	public static String getModulesAsString(ITernModule... modules) {
  		StringBuilder s = new StringBuilder();
  		for (int i = 0; i < modules.length; i++) {
  			if (i > 0) {
  				s.append(",");
  			}
  			s.append(modules[i].getName());
  		}
  		return s.toString();
  	}
  
  	/**
  	 * Returns the label of the given module.
  	 * 
  	 * @param module
  	 * @return the label of the given module.
  	 */
  	public static String getLabel(ITernModule module) {
  		try {
  			TernModuleMetadata metadata = module.getMetadata();
  			if (metadata != null && !StringUtils.isEmpty(metadata.getLabel())) {
  				return metadata.getLabel();
  			}
  		} catch (Exception e) {
  			// Exception can be thrown if TernPlugin enum is used.
  		}
  		return module.getName();
  	}
  
  	
  }
  